diff --git a/.editorconfig b/.editorconfig index c425d68b0..b8fb7609b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,11 +5,12 @@ root = true charset = utf-8 end_of_line = lf indent_size = 4 -indent_style = tab +indent_style = space quote_type = double insert_final_newline = true tab_width = 4 trim_trailing_whitespace = true +max_line_length = 120 [*.py] spaces_around_brackets = none diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..ef40a1e26 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +tests/samples/* filter=lfs diff=lfs merge=lfs -text +UnityPy/resources/* filter=lfs diff=lfs merge=lfs -text diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..bec4e38f8 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: K0lb3 diff --git a/.github/ISSUE_TEMPLATE/bug---error-report.md b/.github/ISSUE_TEMPLATE/bug---error-report.md new file mode 100644 index 000000000..dd64a8b9a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug---error-report.md @@ -0,0 +1,23 @@ +--- +name: Bug & Error report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Code** +A snippet of the code section that cases the bug or error. + +**Error** +The error message that is produced by python. + +**Bug** +A description of what you expect to happen/see and what actually happens. + +**To Reproduce** +- a copy of the file that causes the problem +- following data: + - Python version + - UnityPy version diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 000000000..700306b52 --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,97 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL Advanced" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '19 5 * * 0' + +jobs: + analyze: + name: Analyze (${{ matrix.language }}) + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners (GitHub.com only) + # Consider using larger runners or machines with greater resources for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + permissions: + # required for all workflows + security-events: write + + # required to fetch internal or private CodeQL packs + packages: read + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + include: + - language: c-cpp + build-mode: autobuild + - language: python + build-mode: none + # CodeQL supports the following values keywords for 'language': 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' + # Use `c-cpp` to analyze code written in C, C++ or both + # Use 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, + # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. + # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how + # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + submodules: recursive + lfs: true + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + # If the analyze step fails for one of the languages you are analyzing with + # "We were unable to automatically build your code", modify the matrix above + # to set the build mode to "manual" for that language. Then modify this step + # to build your code. + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + - if: matrix.build-mode == 'manual' + shell: bash + run: | + echo 'If you are using a "manual" build mode for one or more of the' \ + 'languages you are analyzing, replace this with the commands to build' \ + 'your code, for example:' + echo ' make bootstrap' + echo ' make release' + exit 1 + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml deleted file mode 100644 index 999ffdc2b..000000000 --- a/.github/workflows/python-package.yml +++ /dev/null @@ -1,57 +0,0 @@ -name: Test and Publish - -on: [push, pull_request] - -jobs: - deploy: - - strategy: - fail-fast: false - matrix: - os: [ - "ubuntu-latest", - "macOS-latest", - "windows-latest" - ] - python-version: [ - "3.8", - "3.7", - "3.6", - ] - architecture: ["x86", "x64"] - - timeout-minutes: 30 - - runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} ${{ matrix.architecture }} - ${{ matrix.python-version }} - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v1 - with: - python-version: ${{ matrix.python-version }} - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install --upgrade pip wheel setuptools twine - pip install pytest lz4 brotli Pillow fsb5 texture2ddecoder - - - name: Install module - run: | - python setup.py install - - - name: Test with pytest - run: | - pytest -v -s - - - name: Build and publish - if: success() && runner.os != 'Linux' && github.event_name == 'push' - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* --skip-existing diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..cd917a79c --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,147 @@ +name: Build & Publish wheels +on: + workflow_dispatch + + +jobs: + build_sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + lfs: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Build sdist + run: pipx run build --sdist + + - name: Install sdist + run: pip install dist/*.tar.gz + + - uses: actions/upload-artifact@v4 + with: + name: "sdist" + path: dist/*.tar.gz + + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + needs: [build_sdist] + strategy: + fail-fast: true + matrix: + os: [windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + lfs: true + + - name: Build wheels + uses: joerick/cibuildwheel@v3.3 + env: + CIBW_TEST_SKIP: "*" + CIBW_SKIP: "pp*" + + - uses: actions/upload-artifact@v4 + with: + name: "${{ matrix.os }}" + path: ./wheelhouse/*.whl + retention-days: 1 + + build_manylinux_wheels_ubuntu: + name: Build manylinux wheels on ubuntu-latest + runs-on: ubuntu-latest + needs: [build_sdist] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + lfs: true + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Build wheels + uses: joerick/cibuildwheel@v3.3 + env: + CIBW_TEST_SKIP: "*" + CIBW_SKIP: "pp* *-musllinux*" + + - uses: actions/upload-artifact@v4 + with: + name: "manylinux" + path: ./wheelhouse/*.whl + retention-days: 1 + + build_musllinux_wheels_ubuntu: + name: Build musllinux wheels on ubuntu-latest + runs-on: ubuntu-latest + needs: [build_sdist] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + lfs: true + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: all + + - name: Build wheels + uses: joerick/cibuildwheel@v3.3 + env: + CIBW_TEST_SKIP: "*" + CIBW_SKIP: "pp* *-manylinux*" + # fmod requires: + # default via musl: -exclude flag + # libdl.so.2 => /lib/ld-musl-x86_64.so.1 (0x7faeb127d000) + # librt.so.1 => /lib/ld-musl-x86_64.so.1 (0x7faeb127d000) + # libm.so.6 => /lib/ld-musl-x86_64.so.1 (0x7faeb127d000) + # libpthread.so.0 => /lib/ld-musl-x86_64.so.1 (0x7faeb127d000) + # libc.so.6 => /lib/ld-musl-x86_64.so.1 (0x7faeb127d000) + # deps: + # libgcc + # libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x7faeb1253000) + # libstdc++ + # libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x7faeb0a00000) + CIBW_BEFORE_ALL: "apk add libgcc libstdc++" + CIBW_REPAIR_WHEEL_COMMAND: "auditwheel repair -w {dest_dir} {wheel} --exclude libdl.so.2 --exclude librt.so.1 --exclude libm.so.6 --exclude libpthread.so.0 --exclude libc.so.6" + + + - uses: actions/upload-artifact@v4 + with: + name: "musllinux" + path: ./wheelhouse/*.whl + retention-days: 1 + + + upload_pypi: + name: Publish to PyPI + needs: [build_sdist, build_wheels, build_manylinux_wheels_ubuntu, build_musllinux_wheels_ubuntu] + runs-on: ubuntu-latest + + permissions: + id-token: write + steps: + - uses: actions/download-artifact@v4 + with: + path: dist + merge-multiple: true + + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..d322f25de --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,40 @@ +name: Test +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build_wheels: + name: Build wheels on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + lfs: true + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.9' + + - name: Install + run: pip install .[tests] + + - uses: astral-sh/ruff-action@v3 + + - name: Check code (ruff check) + run: ruff check + + - name: Check code style (ruff format) + run: ruff format --check + + - name: Run tests + run: python -m pytest -vs ./tests diff --git a/.gitignore b/.gitignore index f88deb469..85f418724 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,14 @@ +# test files and others +test.py +AssetStudio/ +.vscode/ +uv.lock + # Byte-compiled / optimized / DLL files __pycache__/ -.idea/ -*.py[cod] +*.py[codz] *$py.class -test.py -AssetStudio/ + # C extensions *.so @@ -22,14 +26,15 @@ parts/ sdist/ var/ wheels/ +share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller -# Usually these files are written by a python script from a template -# before PyInstaller builds the exe, so as to inject date/other infos into it. +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec @@ -40,14 +45,18 @@ pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ +.nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover +*.py.cover +*.lcov .hypothesis/ .pytest_cache/ +cover/ # Translations *.mo @@ -57,6 +66,7 @@ coverage.xml *.log local_settings.py db.sqlite3 +db.sqlite3-journal # Flask stuff: instance/ @@ -69,22 +79,85 @@ instance/ docs/_build/ # PyBuilder +.pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints -# pyenv -.python-version +# IPython +profile_default/ +ipython_config.py -# celery beat schedule file -celerybeat-schedule +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi/* +!.pixi/config.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule* +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ # SageMath parsed files *.sage.py # Environments .env +.envrc .venv env/ venv/ @@ -104,3 +177,50 @@ venv.bak/ # mypy .mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ +# Temporary file for partial code execution +tempCodeRunnerFile.py + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 6a8766a7f..000000000 --- a/.travis.yml +++ /dev/null @@ -1,25 +0,0 @@ -language: python # this works for Linux but is an error on macOS or Windows -jobs: - include: - - name: "Python 3.8.0 on Xenial Linux" - python: 3.8 # this works for Linux but is ignored on macOS or Windows - - name: "Python 3.7.4 on macOS" - os: osx - osx_image: xcode11.2 # Python 3.7.4 running on macOS 10.14.4 - language: shell # 'language: python' is an error on Travis CI macOS - - name: "Python 3.8.0 on Windows" - os: windows # Windows 10.0.17134 N/A Build 17134 - language: shell # 'language: python' is an error on Travis CI Windows - before_install: - - choco install python --version 3.8.0 - - python -m pip install --upgrade pip - env: PATH=/c/Python38:/c/Python38/Scripts:$PATH -install: - - pip3 install --upgrade pip # all three OSes agree about 'pip3' - - pip3 install pytest - - pip3 install lz4 brotli Pillow fsb5 texture2ddecoder -# 'python' points to Python 2.7 on macOS but points to Python 3.8 on Linux and Windows -# 'python3' is a 'command not found' error on Windows but 'py' works on Windows only -script: - - python3 setup.py install || python setup.py install - - pytest -v diff --git a/AssetBatchConverter.py b/AssetBatchConverter.py deleted file mode 100644 index e8be2d9ec..000000000 --- a/AssetBatchConverter.py +++ /dev/null @@ -1,109 +0,0 @@ -import os -from UnityPy import AssetsManager -from collections import Counter -import zipfile - -TYPES = ['Sprite', 'Texture2D', 'TextAsset'] - -ROOT = os.path.dirname(os.path.realpath(__file__)) - -# source folder -ASSETS = os.path.join(ROOT, 'assets') -# destination folder -DST = os.path.join(ROOT, 'extracted') -# number of dirs to ignore -# e.g. IGNOR_DIR_COUNT = 2 will reduce -# 'assets/assetbundles/images/story_picture/small/15.png' -# to -# 'images/story_picture/small/15.png' - -IGNOR_DIR_COUNT = 2 - -os.makedirs(DST, exist_ok=True) - - -def main(): - for root, dirs, files in os.walk(ASSETS, topdown=False): - if '.git' in root: - continue - for f in files: - print(f) - extension = os.path.splitext(f)[1] - src = os.path.realpath(os.path.join(root, f)) - - if extension == ".zip": - archive = zipfile.ZipFile(src, 'r') - for zf in archive.namelist(): - extract_assets(archive.open(zf)) - else: - extract_assets(src) - - -def extract_assets(src): - # load source - am = AssetsManager(src) - - # iterate over assets - for asset in am.assets.values(): - # assets without container / internal path will be ignored for now - if not asset.container: - continue - - # check which mode we will have to use - num_cont = sum(1 for obj in asset.container.values() if obj.type in TYPES) - num_objs = sum(1 for obj in asset.objects.values() if obj.type in TYPES) - - # check if container contains all important assets, if yes, just ignore the container - if num_objs <= num_cont * 2: - for asset_path, obj in asset.container.items(): - fp = os.path.join(DST, *asset_path.split('/')[IGNOR_DIR_COUNT:]) - export_obj(obj, fp) - - # otherwise use the container to generate a path for the normal objects - else: - extracted = [] - # find the most common path - occurence_count = Counter(os.path.splitext(asset_path)[0] for asset_path in asset.container.keys()) - local_path = os.path.join(DST, *occurence_count.most_common(1)[0][0].split('/')[IGNOR_DIR_COUNT:]) - - for obj in asset.objects.values(): - if obj.path_id not in extracted: - extracted.extend(export_obj(obj, local_path, append_name=True)) - - -def export_obj(obj, fp: str, append_name: bool = False) -> list: - if obj.type not in TYPES: - return [] - data = obj.read() - if append_name: - fp = os.path.join(fp, data.name) - - fp, extension = os.path.splitext(fp) - os.makedirs(os.path.dirname(fp), exist_ok=True) - - if obj.type == 'TextAsset': - if not extension: - extension = '.txt' - with open(f"{fp}{extension}", 'wb') as f: - f.write(data.script) - - elif obj.type == "Sprite": - extension = ".png" - data.image.save(f"{fp}{extension}") - - return [obj.path_id, data.m_RD.texture.path_id, getattr(data.m_RD.alphaTexture, 'path_id', None)] - - elif obj.type == "Texture2D": - extension = ".png" - fp = f"{fp}{extension}" - if not os.path.exists(fp): - try: - data.image.save(fp) - except EOFError: - pass - - return [obj.path_id] - - -if __name__ == '__main__': - main() diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..e55066ae7 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,16 @@ +# Change Log + +## 1.20 + +- overall type-hint improvements +- **UnityPy.classes** +  - replace hard-coded UnityPy.classes with generated class stubs +    - UnityPy.classes.legacy_patch to provide backward compatibility +    - classes are all parsed and dumped/stored using typetrees now +- **TypeTree** +  - use a hierarchical instead of a flat structure +    - list to Node.m_Children +  - rewrite the typetree read and write functions to reflect this +  - **remove map typetree type type due to its multi-dict nature** +- **Exporter** +  - extended Sprite-mesh support (solves some dicings automatically) diff --git a/LICENSE b/LICENSE index ddb366974..bf224f947 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2020 K0lb3 +Copyright (c) 2019-2026 K0lb3 Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 40794bdcf..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1 +0,0 @@ -include UnityPy/* \ No newline at end of file diff --git a/README.md b/README.md index a3a15c305..baf92789d 100644 --- a/README.md +++ b/README.md @@ -1,180 +1,536 @@ # UnityPy + [![Discord server invite](https://discordapp.com/api/guilds/603359898507673630/embed.png)](https://discord.gg/C6txv7M) [![PyPI supported Python versions](https://img.shields.io/pypi/pyversions/UnityPy.svg)](https://pypi.python.org/pypi/UnityPy) [![Win/Mac/Linux](https://img.shields.io/badge/platform-windows%20%7C%20macos%20%7C%20linux-informational)]() -[![MIT](https://img.shields.io/pypi/l/UnityPy.svg)](https://github.com/K0lb3/UnityPy/blob/master/LICENSE) -[![Build Status](https://travis-ci.com/K0lb3/UnityPy.svg?branch=master)](https://travis-ci.com/K0lb3/UnityPy) +[![MIT](https://img.shields.io/github/license/K0lb3/UnityPy)](https://github.com/K0lb3/UnityPy/blob/master/LICENSE) +![Test](https://github.com/K0lb3/UnityPy/workflows/Test/badge.svg) A Unity asset extractor for Python based on [AssetStudio](https://github.com/Perfare/AssetStudio). -1. [Installation](https://github.com/K0lb3/UnityPy#installation) -2. [Example](https://github.com/K0lb3/UnityPy#example) -3. [Important Classes](https://github.com/K0lb3/UnityPy#important-classes) -4. [Important Object Types](https://github.com/K0lb3/UnityPy#important-object-types) -5. [Goals](https://github.com/K0lb3/UnityPy#goals) -6. [Motivation](https://github.com/K0lb3/UnityPy#motivation) -7. [Community](https://github.com/K0lb3/UnityPy#community) +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). + +If you're using UnityPy for 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.** +These 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. + +```python +if UnityPy.__version__ != '1.9.6': + raise ImportError("Invalid UnityPy version detected. Please use version 1.9.6") +``` + +1. [Installation](#installation) +2. [Example](#example) +3. [Important Classes](#important-classes) +4. [Important Object Types](#important-object-types) +5. [Configurations](#configurations) +6. [Credits](#credits) ## Installation -**Python 3.6.0 or higher is required** +**Python 3.8 or higher is required.** + +Install via PyPI: -```cmd +```bash pip install UnityPy ``` -or download/clone the git and use +Install from source code: -```cmd -python setup.py install +```bash +git clone https://github.com/K0lb3/UnityPy.git +cd UnityPy +python -m pip install . ``` +### Notes + +#### Windows + +Visual C++ Redistributable is required for the brotli dependency. +In case a new(ish) Python version is used, it can happen that the C-dependencies of UnityPy might not be precompiled for this version. +In such cases the user either has to report this as issue or follow the steps of [this issue](https://github.com/K0lb3/UnityPy/issues/223) to compile it oneself. +Another option for the user is downgrading Python to the latest version supported by UnityPy. For this see the Python version badge at the top of the README. + +#### 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. Read [this section](#disable-typetree-c-implementation) for more details. + ## Example The following is a simple example. ```python import os -from UnityPy import AssetsManager +import UnityPy -def unpack_all_assets(source_folder : str, destination_folder : str): +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 AssetsManager - am = AssetsManager(file_path) - - # iterate over all assets and named objects - for asset in am.assets.values(): - for obj in asset.objects.values(): - # only process specific object types - if obj.type 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) + # 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) ``` -You probably have to read [Important Classes](https://github.com/K0lb3/UnityPy#important-classes) -and [Important Object Types](https://github.com/K0lb3/UnityPy#important-object-types) to understand how it works. - -People who have slightly advanced python skills should take a look at [AssetBatchConverter.py](https://github.com/K0lb3/UnityPy/blob/master/AssetBatchConverter.py) for a more advanced example. +You probably have to read [Important Classes](#important-classes) +and [Important Object Types](#important-object-types) to understand how it works. +Users with slightly advanced Python skills should look at [UnityPy/tools/extractor.py](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 -### [AssetsManager](https://github.com/K0lb3/UnityPy/blob/master/UnityPy/AssetsManager.py) +### Environment -AssetsManager loads and parses the files that are given to it. +[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, filestream,... -* a bytes object - will be loaded into a stream +- 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 itself if the file is a WebFile, BundleFile, Asset or APK itself. +UnityPy can detect if the file is a WebFile, BundleFile, Asset, or APK. -The unpacked assets will be loaded into ``.assets``, which is a dict consisting of ``asset-name : asset``. +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. ```python -from UnityPy import AssetsManager -am = AssetsManager(src) +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: + ... -for asset_name, asset in am.assets.items(): - pass +# 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]((https://github.com/K0lb3/UnityPy/blob/master/UnityPy/files/SerializedFile.py)) +### Asset -Assets are a container that contains multiple objects. +Assets \([SerializedFile class](UnityPy/files/SerializedFile.py)\) 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}``. +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 \([ObjectReader class](UnityPy/files/ObjectReader.py)\) contain the _actual_ files, e.g., textures, text files, meshes, settings, ... -The objects which have a file path can be found in the ``.container`` dict - ``{path : object}``. +To acquire the actual data of an object it has to be parsed first. +This happens via the parse functions mentioned below. +This isn't done automatically to save time as only a small part of the objects are usually of interest. +Serialized objects can be set with raw data using `.set_raw_data(data)` or modified with `.save()` function, if supported. -### [Object]((https://github.com/K0lb3/UnityPy/blob/master/UnityPy/ObjectReader.py)) +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. -Objects contain the *actual* files which, e.g. textures, text files, meshes, settings, ... +There are two general parsing functions, ``.parse_as_object()`` and ``.parse_as_dict()``. +``parse_as_dict`` parses the object data into a dict. +``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. +Some special classes, namely those below, have additional handlers added to their class for easier interaction with them. + +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. + +#### Example + +```py +for obj in env.objects: + if obj.type.name == "the type you want": + if obj.peek_name() != "the specific object you want": + continue + # parsing + instance = obj.parse_as_object() + dic = obj.parse_as_dict() + + # modifying + instance.m_Name = "new name" + dic["m_Name"] = "new name" + + # saving + obj.patch(instance) + obj.patch(dic) +``` + +#### Legacy + +Following functions are legacy functions that will be removed in the future when major version 2 hits. +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 | -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. ## Important Object Types -All object types can be found in [UnityPy/classes](https://github.com/K0lb3/UnityPy/tree/master/UnityPy/classes). +Now UnityPy uses [auto generated classes](UnityPy/classes/generated.py) with some useful extension methods and properties defined in [legacy_patch](UnityPy/classes/legacy_patch/). You can search for a specific classes in the module `UnityPy.classes` with your IDE's autocompletion. -### [Texture2D](https://github.com/K0lb3/UnityPy/blob/master/UnityPy/classes/Texture2D.py) +### Texture2D -* ``.name`` -* ``.image`` converts the texture into a ``PIL.Image`` -* ``.m_Width`` - texture width (int) -* ``.m_Height`` - texture height (int) +- `.m_Name` +- `.image` converts the texture into a `PIL.Image` +- `.m_Width` - texture width (int) +- `.m_Height` - texture height (int) -### [Sprite](https://github.com/K0lb3/UnityPy/blob/master/UnityPy/classes/Sprite.py) +**Export** + +```python +from PIL import Image +for obj in env.objects: + if obj.type.name == "Texture2D": + # export texture + tex = obj.parse_as_object() + path = os.path.join(export_dir, f"{tex.m_Name}.png") + tex.image.save(path) + # edit texture + fp = os.path.join(replace_dir, f"{tex.m_Name}.png") + pil_img = Image.open(fp) + tex.image = pil_img + tex.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. +Unlike most other extractors (including AssetStudio), UnityPy merges those two images by itself. + +- `.m_Name` +- `.image` - converts the merged texture part into a `PIL.Image` +- `.m_Width` - sprite width (int) +- `.m_Height` - sprite height (int) -* ``.name`` -* ``.image`` - converts the merged texture part into a ``PIL.Image`` -* ``.m_Width`` - sprite width (int) -* ``.m_Height`` - sprite height (int) +**Export** -### [TextAsset](https://github.com/K0lb3/UnityPy/blob/master/UnityPy/classes/TextAsset.py) +```python +for obj in env.objects: + if obj.type.name == "Sprite": + sprite = obj.parse_as_object() + path = os.path.join(export_dir, f"{sprite.m_Name}.png") + sprite.image.save(path) +``` + +### TextAsset TextAssets are usually normal text files. -* ``.name`` -* ``.script`` - binary data (bytes) -* ``.text`` - script decoded via UTF8 (str) +- `.m_Name` +- `.m_Script` - str + +Some games save binary data as TextAssets. +As ``m_Script`` gets handled as str by default, +use ``m_Script.encode("utf-8", "surrogateescape")`` to retrieve the original binary data. + +**Export** + +```python +for obj in env.objects: + if obj.type.name == "TextAsset": + # export asset + txt = obj.parse_as_object() + path = os.path.join(export_dir, f"{txt.m_Name}.txt") + with open(path, "wb") as f: + f.write(txt.m_Script.encode("utf-8", "surrogateescape")) + # edit asset + fp = os.path.join(replace_dir, f"{txt.m_Name}.txt") + with open(fp, "rb") as f: + txt.m_Script = f.read().decode("utf-8", "surrogateescape") + txt.save() +``` + +### MonoBehaviour -Some games save binary data as TextFile, so it's usually better to use ``.script``. +MonoBehaviour assets are usually used to save the class instances with their values. +The structure/typetree for these classes might not be contained in the asset files. +In such cases see the 2nd example (TypeTreeGenerator) below. -### [MonoBehaviour](https://github.com/K0lb3/UnityPy/blob/master/UnityPy/classes/MonoBehaviour.py) +- `.m_Name` +- `.m_Script` +- custom data -MonoBehaviour assets are usually binary data that has to be decoded. -e.g. via msgpack, protobuf +**Export** -* ``.name`` -* ``.script``- binary data (bytes) +```python +import json + +for obj in env.objects: + if obj.type.name == "MonoBehaviour": + # export + # save decoded data + tree = obj.parse_as_dict() + 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) + + # edit + tree = obj.parse_as_dict() + # apply modifications to the data within the tree + obj.patch(tree) +``` -### [AudioClip](https://github.com/K0lb3/UnityPy/blob/master/UnityPy/classes/AudioClip.py) +**TypeTreeGenerator** -* ``.samples`` - ``{sample-name : sample-data}`` dict +UnityPy can generate the typetrees of MonoBehaviours from the game assemblies using an optional package, ``TypeTreeGeneratorAPI``, which has to be installed via pip. +UnityPy will automatically try to generate the typetree of MonoBehaviours if the typetree is missing in the assets and ``env.typetree_generator`` is set. + +```python +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() +``` + + +### AudioClip + +- `.samples` - `{sample-name : sample-data}` + +The samples are converted into the .wav format. +The sample data is a .wav file in bytes. + +```python +clip: AudioClip +for name, data in clip.samples.items(): + with open(name, "wb") as f: + f.write(data) +``` + +### Font + +**Export** + +```python +if obj.type.name == "Font": + font: Font = obj.parse_as_object() + if font.m_FontData: + extension = ".ttf" + if font.m_FontData[0:4] == b"OTTO": + extension = ".otf" + + with open(os.path.join(path, font.m_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. + +```python +mesh: Mesh +with open(f"{mesh.m_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. + +```python +mesh_renderer: Renderer +export_dir: str + +if mesh_renderer.m_GameObject: + # get the name of the model + game_obj_reader = mesh_renderer.m_GameObject.deref() + game_obj_name = game_obj_reader.peek_name() + export_dir = os.path.join(export_dir, game_obj_name) +mesh_renderer.export(export_dir) +``` + +### Texture2DArray + +WARNING - not well tested + +- `.m_Name` +- `.image` converts the texture2darray into a `PIL.Image` +- `.m_Width` - texture width (int) +- `.m_Height` - texture height (int) + +**Export** + +```python +import os +from PIL import Image +for obj in env.objects: + if obj.type.name == "Texture2DArray": + # export texture + tex_arr = obj.parse_as_object() + for i, image in enumerate(tex_arr.images): + image.save(os.path.join(path, f"{tex_arr.m_Name}_{i}.png")) + # editing isn't supported yet! +``` + +## Configurations + +There're several configurations and interfaces that provide the customizability to UnityPy. + +### Unity CN Decryption + +The Chinese version of Unity has its own builtin option to encrypt AssetBundles/BundleFiles. As it's a feature of Unity itself, and not a game specific protection, it is included in UnityPy as well. +To enable encryption simply use the code as follow, with `key` being the value that the game that loads the bundles passes to `AssetBundle.SetAssetBundleDecryptKey`. + +```python +import UnityPy +UnityPy.set_assetbundle_decrypt_key(key) +``` + +### Unity Fallback Version + +In case UnityPy failed to detect the Unity version of the game assets, you can set a fallback version. e.g. + +```python +import UnityPy.config +UnityPy.config.FALLBACK_UNITY_VERSION = "2.5.0f5" +``` + +### Disable Typetree C-Implementation + +The [C-implementation](UnityPyBoost/) of typetree reader can boost the parsing of typetree by a lot. If you want to disable it and use pure Python reader, you can put the following 2 lines in your main file. + +```python +from UnityPy.helpers import TypeTreeHelper +TypeTreeHelper.read_typetree_boost = False +``` + +### Custom Block (De)compression + +Some game assets have non-standard compression/decompression algorithm applied on the block data. If you wants to customize the compression/decompression function, you can modify the corresponding function mapping. e.g. + +```python +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 +``` -## Goals +- `custom_compress(data: bytes) -> bytes` (where bytes can also be bytearray or memoryview) +- `custom_decompress(data: bytes, uncompressed_size: int) -> bytes` -### WIP +### Custom Filesystem -* a documentation -* the ability to edit assets (like in UABE) +UnityPy uses [fsspec](https://github.com/fsspec/filesystem_spec) under the hood to manage all filesystem interactions. +This allows using various different types of filesystems without having to change UnityPy's code. +It also means that you can use your own custom filesystem to e.g. handle indirection via catalog files, load assets on demand from a server, or decrypt files. -### planned +Following methods of the filesystem have to be implemented for using it in UnityPy. -* support for more object types -* code optimization -* speed-ups via C-extensions -* multiprocessing +- `sep` (not a function, just the separator as character) +- `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` ("rb" mode required, "wt" required for ModelExporter) +- `makedirs(self, path: str, exist_ok: bool = False) -> bool` -## Motivation +## Credits -I'm an active data-miner and noticed that unitypack has problems with new unity assets. -The problem in unitypack isn't that easy to fix and the undocumented code is a bit hard to understand. -That's why I tried other tools like UABE and AssetStudio. Sadly none of these tools can be used like unitypack. -That's why I started this project. +First of all, +thanks a lot to all contributors of UnityPy and all of its users. -## Community +Also, many thanks to: -[Discord](https://discord.gg/C6txv7M) +- [Perfare](https://github.com/Perfare) for creating and maintaining and every contributor of [AssetStudio](https://github.com/Perfare/AssetStudio) +- [ds5678](https://github.com/ds5678) for the [TypeTreeDumps](https://github.com/AssetRipper/TypeTreeDumps) and the [custom minimal Tpk format](https://github.com/AssetRipper/Tpk) +- [Razmoth](https://github.com/Razmoth) for figuring out and sharing Unity CN's AssetBundle decryption ([src](https://github.com/Razmoth/PGRStudio)). +- [nesrak1](https://github.com/nesrak1) for figuring out the [Switch texture swizzling](https://github.com/nesrak1/UABEA/blob/master/TexturePlugin/Texture2DSwitchDeswizzler.cs) +- xiop_13690 (discord) for figuring out unsolved issues of the ManagedReferencesRegistry diff --git a/UnityPy/AssetsManager.py b/UnityPy/AssetsManager.py deleted file mode 100644 index 1adc511a7..000000000 --- a/UnityPy/AssetsManager.py +++ /dev/null @@ -1,231 +0,0 @@ -import io -import logging -import os -from zipfile import ZipFile - -from .EndianBinaryReader import EndianBinaryReader -from .Progress import Progress -from .files import BundleFile, SerializedFile, WebFile -from .helpers import ImportHelper - -FileType = ImportHelper.FileType - -# create logger -logger = logging.getLogger("UnityPy") -logger.setLevel(logging.DEBUG) - -# create console handler and set level to debug -ch = logging.StreamHandler() -ch.setLevel(logging.DEBUG) - -# create formatter -formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") - -# add formatter to ch -ch.setFormatter(formatter) - -# add ch to logger -logger.addHandler(ch) - - -class AssetsManager: - assets: dict - resource_file_readers: dict - import_files: dict - Progress: Progress - - def __init__(self, *args): - self.assets = {} - self.resource_file_readers = {} - self.import_files = {} - - self.Progress = Progress() - - if args: - for arg in args: - if isinstance(arg, str): - if os.path.isfile(arg): - if os.path.splitext(arg)[-1] in [".apk", ".zip"]: - self.load_zip_file(arg) - else: - self.load_file(arg) - elif os.path.isdir(arg): - self.load_folder(arg) - else: - self.load_file(arg) - - def load_files(self, files: list): - """Loads all files (list) into the AssetsManager and merges .split files for common usage.""" - path = os.path.dirname(files[0]) - ImportHelper.merge_split_assets(path) - to_read_file = ImportHelper.processing_split_files(files) - self.load(to_read_file) - - def load_folder(self, path: str): - """Loads all files in the given path and its subdirs into the AssetsManager.""" - ImportHelper.merge_split_assets(path, True) - files = ImportHelper.list_all_files(path) - to_read_file = ImportHelper.processing_split_files(files) - self.load(to_read_file) - - def load(self, files: list): - """Loads all files into the AssetsManager.""" - for f in files: - self.import_files[os.path.basename(f)] = f - - self.Progress.reset() - # use a for loop because list size can change - for i, f in enumerate(self.import_files.values()): - self.load_file(f) - self.Progress.report(i + 1, len(self.import_files)) - - def load_file(self, full_name: str = "", data=None): - typ, reader = ImportHelper.check_file_type(data if data else full_name) - if not full_name: - full_name = str(data)[:256] - if typ == FileType.AssetsFile: - self.load_assets_file(full_name, reader) - elif typ == FileType.BundleFile: - self.load_bundle_file(full_name, reader) - elif typ == FileType.WebFile: - self.load_web_file(full_name, reader) - elif typ == FileType.ZIP: - self.load_zip_file(reader.stream) - - def load_zip_file(self, value): - buffer = None - if isinstance(value, str) and os.path.exists(value): - buffer = open(value, "rb") - elif isinstance(value, (bytes, bytearray)): - buffer = ZipFile(io.BytesIO(value)) - elif isinstance(value, (io.BufferedReader, io.BufferedIOBase)): - buffer = value - - z = ZipFile(buffer) - - for path in z.namelist(): - self.load_file(path, z.open(path).read()) - - def load_assets_file(self, full_name: str, reader: EndianBinaryReader): - file_name = os.path.basename(full_name) - if file_name not in self.assets: - logging.info(f"Loading {full_name}") - try: - assets_file = SerializedFile(self, full_name, reader) - self.assets[assets_file.name] = assets_file - - for sharedFile in assets_file._externals: - shared_file_path = os.path.join( - os.path.dirname(full_name), sharedFile.name - ) - shared_file_name = sharedFile.name - - if shared_file_name not in self.import_files: - if not os.path.exists(shared_file_path): - find_files = [ - f - for f in ImportHelper.list_all_files( - os.path.dirname(full_name) - ) - if shared_file_name in f - ] - if find_files: - shared_file_path = find_files[0] - - if os.path.exists(shared_file_path): - self.import_files[shared_file_name] = shared_file_path - - return assets_file - - except Exception as e: - reader.dispose() - logging.error(f"Unable to load assets file {file_name}", e) - else: - reader.dispose() - - def load_assets_from_memory( - self, - full_name: str, - reader: EndianBinaryReader, - original_path: str, - unity_version=None, - ): - file_name = os.path.basename(full_name) - if file_name.endswith((".resS", ".resource", ".config", ".xml", ".dat")): - self.resource_file_readers[file_name] = reader - return False - elif file_name not in self.assets: - try: - assets_file = SerializedFile(self, full_name, reader) - assets_file.original_path = original_path - if assets_file.header.version < 7: - assets_file.set_version(unity_version) - self.assets[file_name] = assets_file - except Exception as e: - logging.error( - f"Unable to load assets file {file_name} from {original_path}", e - ) - self.resource_file_readers[file_name] = reader - return False - return True - - def load_bundle_file( - self, full_name: str, reader: EndianBinaryReader, parent_path=None - ): - file_name = os.path.basename(full_name) - logging.info(f"Loading {full_name}") - bundle_file = None - try: - bundle_file = BundleFile(reader, full_name) - for f in bundle_file.files: - dummy_path = os.path.join(os.path.dirname(full_name), f.name) - self.load_assets_from_memory( - dummy_path, - EndianBinaryReader(f.stream), - full_name if parent_path else bundle_file.version_engine, - ) - except Exception as e: - string = f"Unable to load bundle file {file_name}" - if parent_path: - string += f" from {os.path.basename(parent_path)}" - string += "\n" + str(e) - logging.error(string, e) - finally: - reader.dispose() - return bundle_file - - def load_web_file(self, full_name: str, reader: EndianBinaryReader): - file_name = os.path.basename(full_name) - logging.info(f"Loading {full_name}") - dispose = True - web_file = None - try: - web_file = WebFile(reader) - for f in web_file.files: - dummy_path = os.path.join(os.path.dirname(full_name), f.name) - typ, reader = ImportHelper.check_file_type(f.stream) - if typ == FileType.AssetsFile: - dispose = self.load_assets_from_memory( - dummy_path, reader, full_name - ) - elif typ == FileType.BundleFile: - self.load_bundle_file(dummy_path, reader, full_name) - elif typ == FileType.WebFile: - self.load_web_file(dummy_path, reader) - except Exception as e: - logging.error(f"Unable to load web file {file_name}", e) - finally: - if dispose: - reader.dispose() - return web_file - - def clear(self): - for assetsFile in self.assets: - assetsFile.Objects = [] - assetsFile.reader.Close() - self.assets = {} - - for resourceFileReader in self.resource_file_readers: - resourceFileReader.Value.Close() - - self.resource_file_readers = {} diff --git a/UnityPy/CommonString.py b/UnityPy/CommonString.py deleted file mode 100644 index 788dcd606..000000000 --- a/UnityPy/CommonString.py +++ /dev/null @@ -1,110 +0,0 @@ -COMMON_STRING = { - 0: "AABB", - 5: "AnimationClip", - 19: "AnimationCurve", - 34: "AnimationState", - 49: "Array", - 55: "Base", - 60: "BitField", - 69: "bitset", - 76: "bool", - 81: "char", - 86: "ColorRGBA", - 96: "Component", - 106: "data", - 111: "deque", - 117: "double", - 124: "dynamic_array", - 138: "FastPropertyName", - 155: "first", - 161: "float", - 167: "Font", - 172: "GameObject", - 183: "Generic Mono", - 196: "GradientNEW", - 208: "GUID", - 213: "GUIStyle", - 222: "int", - 226: "list", - 231: "long long", - 241: "map", - 245: "Matrix4x4f", - 256: "MdFour", - 263: "MonoBehaviour", - 277: "MonoScript", - 288: "byte_size", - 299: "m_Curve", - 307: "m_EditorClassIdentifier", - 331: "m_EditorHideFlags", - 349: "m_Enabled", - 359: "m_ExtensionPtr", - 374: "m_GameObject", - 387: "index", - 395: "is_array", - 405: "m_IsStatic", - 416: "meta_flag", - 427: "name", - 434: "_object_hide_flags", - 452: "m_PrefabInternal", - 469: "m_PrefabParentObject", - 490: "m_Script", - 499: "m_StaticEditorFlags", - 519: "type", - 526: "version", - 536: "Object", - 543: "pair", - 548: "PPtr", - 564: "PPtr", - 581: "PPtr", - 596: "PPtr", - 616: "PPtr", - 633: "PPtr", - 646: "PPtr", - 659: "PPtr", - 672: "PPtr", - 688: "PPtr", - 702: "PPtr", - 718: "PPtr", - 734: "Prefab", - 741: "Quaternionf", - 753: "Rectf", - 759: "RectInt", - 767: "RectOffset", - 778: "second", - 785: "set", - 789: "short", - 795: "size", - 800: "SInt16", - 807: "SInt32", - 814: "SInt64", - 821: "SInt8", - 827: "staticvector", - 840: "string", - 847: "TextAsset", - 857: "TextMesh", - 866: "Texture", - 874: "Texture2D", - 884: "Transform", - 894: "TypelessData", - 907: "UInt16", - 914: "UInt32", - 921: "UInt64", - 928: "UInt8", - 934: "unsigned int", - 947: "unsigned long long", - 966: "unsigned short", - 981: "vector", - 988: "Vector2f", - 997: "Vector3f", - 1006: "Vector4f", - 1015: "m_ScriptingClassIdentifier", - 1042: "Gradient", - 1051: "Type*", - 1057: "int2_storage", - 1070: "int3_storage", - 1083: "BoundsInt", - 1093: "m_CorrespondingSourceObject", - 1121: "m_PrefabInstance", - 1138: "m_PrefabAsset", - 1152: "FileSize", -} diff --git a/UnityPy/ObjectReader.py b/UnityPy/ObjectReader.py deleted file mode 100644 index d387d1423..000000000 --- a/UnityPy/ObjectReader.py +++ /dev/null @@ -1,37 +0,0 @@ -from . import classes - - -class ObjectReader: - def __init__(self, reader, assets_file, object_info): - self.assets_file = assets_file - self.path_id = object_info.path_id - self.byte_start = object_info.byte_start - self.byte_size = object_info.byte_size - self.serialized_type = object_info.serialized_type - self.platform = assets_file.target_platform - self.version2 = assets_file.header.version - self.type = object_info.class_id - self.version = assets_file.version - self.build_type = assets_file.build_type - self.reader = reader - - @property - def container(self): - return ( - self.assets_file._container[self.path_id] - if self.path_id in self.assets_file._container - else None - ) - - def reset(self): - self.reader.Position = self.byte_start - - def read(self): - return getattr(classes, self.type.name, classes.Object)(self) - - def __getattr__(self, item: str): - if hasattr(self.reader, item): - return getattr(self.reader, item) - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.type.name) diff --git a/UnityPy/Progress.py b/UnityPy/Progress.py deleted file mode 100644 index 48a0dea18..000000000 --- a/UnityPy/Progress.py +++ /dev/null @@ -1,16 +0,0 @@ -class Progress: - pre_value: int - - def reset(self): - self.pre_value = 0 - self.report(0) - - def report(self, current: int, total: int = None): - if total: - value = current * 100 // total - else: - value = current - - if value > self.pre_value: - self.pre_value = value - print(f"{value}%") diff --git a/UnityPy/ResourceReader.py b/UnityPy/ResourceReader.py deleted file mode 100644 index ac0749c9a..000000000 --- a/UnityPy/ResourceReader.py +++ /dev/null @@ -1,67 +0,0 @@ -import os - -from .helpers import ImportHelper - - -class ResourceReader: - need_search: bool - path: str - assets_file: None - offset: int - size: int - - def __init__(self, *args): - """ - Input: - Option 1: - path - file path - assets_file - SerializedFile - offset - - size - - Option 2: - reader - EndianBinaryReader - offset - - size - - """ - if len(args) == 4: - self.need_search = True - self.path = args[0] - self.assets_file = args[1] - elif len(args) == 3: - self.need_search = False - self.reader = args[0] - self.offset = args[-2] - self.size = args[-1] - - def get_data(self): - if self.need_search: - resource_file_name = os.path.basename(self.path) - - reader = self.assets_file.assets_manager.resource_file_readers.get( - resource_file_name - ) - if reader: - reader.Position = self.offset - return reader.read_bytes(self.size) - - current_directory = os.path.dirname(self.assets_file.full_name) - resource_file_path = os.path.join(current_directory, resource_file_name) - if not os.path.isfile(resource_file_path): - find_files = ImportHelper.find_all_files( - current_directory, resource_file_name - ) - if find_files: - resource_file_path = find_files[0] - - if os.path.isfile(resource_file_path): - with open(resource_file_path, "rb") as f: - f.seek(self.offset) - return f.read(self.size) - else: - raise FileNotFoundError( - f"Can't find the resource file {resource_file_name}" - ) - - else: - self.reader.Position = self.offset - return self.reader.read_bytes(self.size) diff --git a/UnityPy/UnityPyBoost.pyi b/UnityPy/UnityPyBoost.pyi new file mode 100644 index 000000000..34ac0b86c --- /dev/null +++ b/UnityPy/UnityPyBoost.pyi @@ -0,0 +1,47 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, List, Literal, Optional, Tuple, Union + +if TYPE_CHECKING: + from .classes import Object + from .files.SerializedFile import SerializedFile + +def unpack_vertexdata( + data: Union[bytes, bytearray], + component_byte_size: int, + vertex_count: int, + stream_offset: int, + stream_stride: int, + channel_offset: int, + channel_dimension: int, + swap: bool, +) -> bytes: ... +def read_typetree( + data: Union[bytes, bytearray], + node: TypeTreeNode, + endian: Literal["<", ">"], + as_dict: bool, + assetsfile: SerializedFile, + classes: dict, +) -> Tuple[Union[dict[str, Any], Object], int]: ... + +class TypeTreeNode: + m_Level: int + m_Type: str + m_Name: str + m_ByteSize: int + m_Version: int + m_Children: List[TypeTreeNode] + m_TypeFlags: Optional[int] = None + m_VariableCount: Optional[int] = None + m_Index: Optional[int] = None + m_MetaFlag: Optional[int] = None + m_RefTypeHash: Optional[int] = None + _clean_name: str + +def decrypt_block( + index_bytes: Union[bytes, bytearray], + substitute_bytes: Union[bytes, bytearray], + data: Union[bytes, bytearray], + index: int, +) -> bytes: ... diff --git a/UnityPy/__init__.py b/UnityPy/__init__.py index 0215425f7..44828896e 100644 --- a/UnityPy/__init__.py +++ b/UnityPy/__init__.py @@ -1,11 +1,11 @@ -__version__ = "1.4.6.3" +__version__ = "1.25.3" -from .environment import Environment - - -def load(*args): - return Environment(*args) +from .environment import Environment as Environment +from .helpers.ArchiveStorageManager import ( + set_assetbundle_decrypt_key as set_assetbundle_decrypt_key, +) +load = Environment # backward compatibility AssetsManager = Environment diff --git a/UnityPy/__main__.py b/UnityPy/__main__.py new file mode 100644 index 000000000..b7ac88bfa --- /dev/null +++ b/UnityPy/__main__.py @@ -0,0 +1,4 @@ +if __name__ == "__main__": + from UnityPy.cli import main + + main() diff --git a/UnityPy/classes/Animation.py b/UnityPy/classes/Animation.py deleted file mode 100644 index d9bc05eb6..000000000 --- a/UnityPy/classes/Animation.py +++ /dev/null @@ -1,10 +0,0 @@ -from .Behaviour import Behaviour -from .PPtr import PPtr - - -class Animation(Behaviour): - def __init__(self, reader): - super().__init__(reader=reader) - self.animation = PPtr(reader) - num_animations = reader.read_int() - self.animations = [PPtr(reader) for i in range(num_animations)] diff --git a/UnityPy/classes/AnimationClip.py b/UnityPy/classes/AnimationClip.py deleted file mode 100644 index 8a5e37cc3..000000000 --- a/UnityPy/classes/AnimationClip.py +++ /dev/null @@ -1,569 +0,0 @@ -import math -from enum import IntEnum - -from .NamedObject import NamedObject -from .PPtr import PPtr -from ..enums import ClassIDType -from ..math import Quaternion, Vector3 -from ..streams import EndianBinaryReader - - -def uint(num): - if num < 0 or num > 4294967295: - return num % 4294967296 - return num - - -class Keyframe: - def __init__(self, reader, readerFunc): - self.time = reader.read_float() - self.value = readerFunc() - self.inSlope = readerFunc() - self.outSlope = readerFunc() - if reader.version >= (2018,): # 2018 and up - self.weightedMode = reader.read_int() - self.inWeight = readerFunc() - self.outWeight = readerFunc() - - -class AnimationCurve: - def __init__(self, reader, readerFunc): - version = reader.version - numCurves = reader.read_int() - self.m_Curve = [Keyframe(reader, readerFunc) for _ in range(numCurves)] - - self.m_PreInfinity = reader.read_int() - self.m_PostInfinity = reader.read_int() - if version >= (5, 3): # 5.3 and up - self.m_RotationOrder = reader.read_int() - - -class QuaternionCurve: - def __init__(self, reader): - self.curve = AnimationCurve(reader, reader.read_quaternion) # - self.path = reader.read_aligned_string() - - -class PackedFloatVector: - def __init__(self, reader): - self.m_NumItems = reader.read_u_int() - self.m_Range = reader.read_float() - self.m_Start = reader.read_float() - numData = reader.read_int() - self.m_Data = reader.read_bytes(numData) - reader.align_stream() - self.m_BitSize = reader.read_byte() - reader.align_stream() - - def UnpackFloats( - self, - itemCountInChunk: int, - chunkStride: int, - start: int = 0, - numChunks: int = -1, - ): - bitPos: int = self.m_BitSize * start - indexPos: int = bitPos // 8 - bitPos %= 8 - - scale: float = 1.0 / self.m_Range - if numChunks == -1: - numChunks = self.m_NumItems // itemCountInChunk - end = chunkStride * numChunks / 4 - data = [] - for index in (0, end, chunkStride // 4): - for i in range(itemCountInChunk): - x = 0 # uint - bits = 0 - while bits < self.m_BitSize: - x |= uint( - (self.m_Data[indexPos] >> bitPos) << bits - ) # (uint)((m_Data[indexPos] >> bitPos) << bits) - num = min(self.m_BitSize - bits, 8 - bitPos) - bitPos += num - bits += num - if bitPos == 8: # - indexPos += 1 - bitPos = 0 - - x &= uint((1 << self.m_BitSize) - 1) # (uint)(1 << m_BitSize) - 1u - data.append(x / (scale * ((1 << self.m_BitSize) - 1)) + self.m_Start) - - return data - - -class PackedIntVector: - def __init__(self, reader): - self.m_NumItems = reader.read_u_int() - numData = reader.read_int() - self.m_Data = reader.read_bytes(numData) - reader.align_stream() - - self.m_BitSize = reader.read_byte() - reader.align_stream() - - def UnpackInts(self): - data = [] - indexPos = 0 - bitPos = 0 - m_BitSize = self.m_BitSize - - for i in range(self.m_NumItems): - bits = 0 - entry = 0 - while bits << m_BitSize: - entry |= (self.m_Data[indexPos] >> bitPos) << bits - num = min(m_BitSize - bits, 8 - bitPos) - bitPos += num - bits += num - if bitPos == 8: # - indexPos += 1 - bitPos = 0 - data.append(entry & (1 << m_BitSize) - 1) - return data - - -class PackedQuatVector: - def __init__(self, reader): - self.m_NumItems = reader.read_u_int() - numData = reader.read_int() - self.m_Data = reader.read_bytes(numData) - reader.align_stream() - - def UnpackQuats(self): - m_Data = self.m_Data - data = [] - indexPos = 0 - bitPos = 0 - - for i in range(self.m_NumItems): - flags = 0 - bits = 0 - while bits < 3: - flags |= (m_Data[indexPos] >> bitPos) << bits # unit - num = min(3 - bits, 8 - bitPos) - bitPos += num - bits += num - if bitPos == 8: # - indexPos += 1 - bitPos = 0 - flags &= 7 - - q = Quaternion() - sum = 0 - for j in range(4): - if (flags & 3) != j: # - bitSize = 9 if ((flags & 3) + 1) % 4 == j else 10 - x = 0 - - bits = 0 - while bits < bitSize: - x |= (m_Data[indexPos] >> bitPos) << bits # uint - num = min(bitSize - bits, 8 - bitPos) - bitPos += num - bits += num - if bitPos == 8: # - indexPos += 1 - bitPos = 0 - x &= (1 << bitSize) - 1 # unit - q[j] = x / (0.5 * ((1 << bitSize) - 1)) - 1 - sum += q[j] * q[j] - - lastComponent = flags & 3 # int - q[lastComponent] = math.sqrt(1 - sum) # float - if (flags & 4) != 0: # 0u - q[lastComponent] = -q[lastComponent] - data.append(q) - - return data - - -class CompressedAnimationCurve: - def __init__(self, reader): - self.m_Path = reader.read_aligned_string() - self.m_Times = PackedIntVector(reader) - self.m_Values = PackedQuatVector(reader) - self.m_Slopes = PackedFloatVector(reader) - self.m_PreInfinity = reader.read_int() - self.m_PostInfinity = reader.read_int() - - -class Vector3Curve: - def __init__(self, reader): - self.curve = AnimationCurve(reader, reader.read_vector3) # Vector3 - self.path = reader.read_aligned_string() - - -class FloatCurve: - def __init__(self, reader): - self.curve = AnimationCurve(reader, reader.read_float) # Float - self.attribute = reader.read_aligned_string() - self.path = reader.read_aligned_string() - self.classID = ClassIDType(reader.read_int()) - self.script = PPtr(reader) # MonoScript - - -class PPtrKeyframe: - def __init__(self, reader): - self.time = reader.read_float() - self.value = PPtr(reader) # Object - - -class PPtrCurve: - def __init__(self, reader): - numCurves = reader.read_int() - self.curve = [PPtrKeyframe(reader) for _ in range(numCurves)] - - self.attribute = reader.read_aligned_string() - self.path = reader.read_aligned_string() - self.classID = reader.read_int() - self.script = PPtr(reader) # MonoScript - - -class AABB: - def __init__(self, reader): - self.m_Center = reader.read_vector3() - self.m_Extent = reader.read_vector3() - - -class xform: - def __init__(self, reader): - version = reader.version - self.t = ( - reader.read_vector3() - if version >= (5, 4) - else Vector3(reader.read_vector4()) - ) # 5.4 and up - self.q = reader.read_quaternion() - self.s = ( - reader.read_vector3() - if version >= (5, 4) - else Vector3(reader.read_vector4()) - ) # 5.4 and up - - -class HandPose: - def __init__(self, reader): - self.m_GrabX = xform(reader) - self.m_DoFArray = reader.read_float_array() - self.m_Override = reader.read_float() - self.m_CloseOpen = reader.read_float() - self.m_InOut = reader.read_float() - self.m_Grab = reader.read_float() - - -class HumanGoal: - def __init__(self, reader): - version = reader.version - self.m_X = xform(reader) - self.m_WeightT = reader.read_float() - self.m_WeightR = reader.read_float() - if version >= (5,): # 5.0 and up - self.m_HintT = ( - reader.read_vector3() - if version >= (5, 4) - else Vector3(reader.read_vector4()) - ) # 5.4 and up - self.m_HintWeightT = reader.read_float() - - -class HumanPose: - def __init__(self, reader): - version = reader.version - self.m_RootX = xform(reader) - self.m_LookAtPosition = ( - reader.read_vector3() - if version >= (5, 4) - else Vector3(reader.read_vector4()) - ) # 5.4 and up - self.m_LookAtWeight = reader.read_vector4() - - numGoals = reader.read_int() - self.m_GoalArray = [HumanGoal(reader) for _ in range(numGoals)] - - self.m_LeftHandPose = HandPose(reader) - self.m_RightHandPose = HandPose(reader) - - self.m_DoFArray = reader.read_float_array() - - if version >= (5, 2): # 5.2 and up - numTDof = reader.read_int() - self.m_TDoFArray = [ - reader.read_vector3() - if version >= (5, 4) - else Vector3(reader.read_vector4()) # 5.4 and up - for _ in range(numTDof) - ] - - -class StreamedCurveKey: - def __init__(self, reader): - self.index = reader.read_int() - self.coeff = reader.read_float_array(4) - - self.outSlope = self.coeff[2] - self.value = self.coeff[3] - - def CalculateNextInSlope(self, dx: float, rhs): - """ - :param dx: float - :param rhs: StreamedCurvedKey - :return: - """ - # Stepped - if self.coeff[0] == 0 and self.coeff[1] == 0 and self.coeff[2] == 0: - return float.PositiveInfinity - - dx = max(dx, 0.0001) - dy = rhs.value - self.value - length = 1.0 / (dx * dx) - d1 = self.outSlope * dx - d2 = dy + dy + dy - d1 - d1 - self.coeff[1] / length - return d2 / dx - - -class StreamedFrame: - def __init__(self, reader): - self.time = reader.read_float() - numKeys = reader.read_int() - self.keyList = [StreamedCurveKey(reader) for _ in range(numKeys)] - - -class StreamedClip: - def __init__(self, reader): - self.data = reader.read_u_int_array() - self.curveCount = reader.read_u_int() - - def ReadData(self): - frameList = [] - buffer = self.data[0 : len(self.data) * 4] - reader = EndianBinaryReader(buffer) - while reader.Position < reader.Length: - frameList.append(StreamedFrame(reader)) - - for frameIndex in range(2, len(frameList) - 1): - frame = frameList[frameIndex] - for curveKey in frame.keyList: - i = frameIndex - 1 - while i >= 0: - preFrame = frameList[i] - try: - preCurveKey = [ - x for x in preFrame.keyList if x.index == curveKey.index - ][0] - curveKey.inSlope = preCurveKey.CalculateNextInSlope( - frame.time - preFrame.time, curveKey - ) - break - except IndexError: - pass - i -= 1 - return frameList - - -class DenseClip: - def __init__(self, reader): - self.m_FrameCount = reader.read_int() - self.m_CurveCount = reader.read_u_int() - self.m_SampleRate = reader.read_float() - self.m_BeginTime = reader.read_float() - self.m_SampleArray = reader.read_float_array() - - -class ConstantClip: - def __init__(self, reader): - self.data = reader.read_float_array() - - -class ValueConstant: - def __init__(self, reader): - version = reader.version - self.m_ID = reader.read_u_int() - if version < (5, 5): # 5.5 down - self.m_TypeID = reader.read_u_int() - self.m_Type = reader.read_u_int() - self.m_Index = reader.read_u_int() - - -class ValueArrayConstant: - def __init__(self, reader): - numVals = reader.read_int() - self.m_ValueArray = [ValueConstant(reader) for _ in range(numVals)] - - -class Clip: - def __init__(self, reader): - version = reader.version - self.m_StreamedClip = StreamedClip(reader) - self.m_DenseClip = DenseClip(reader) - if version >= (4, 3): # 4.3 and up - self.m_ConstantClip = ConstantClip(reader) - if version < (2018, 3): # 2018.3 down - self.m_Binding = ValueArrayConstant(reader) - - -class ValueDelta: - def __init__(self, reader): - self.m_Start = reader.read_float() - self.m_Stop = reader.read_float() - - -class ClipMuscleConstant: - def __init__(self, reader): - version = reader.version - self.m_DeltaPose = HumanPose(reader) - self.m_StartX = xform(reader) - if version >= (5, 5): # 5.5 and up - self.m_StopX = xform(reader) - self.m_LeftFootStartX = xform(reader) - self.m_RightFootStartX = xform(reader) - if version < (5,): # 5.0 down - self.m_MotionStartX = xform(reader) - self.m_MotionStopX = xform(reader) - self.m_AverageSpeed = ( - reader.read_vector3() - if version >= (5, 4) - else Vector3(reader.read_vector4()) - ) # 5.4 and up - self.m_Clip = Clip(reader) - self.m_StartTime = reader.read_float() - self.m_StopTime = reader.read_float() - self.m_OrientationOffsetY = reader.read_float() - self.m_Level = reader.read_float() - self.m_CycleOffset = reader.read_float() - self.m_AverageAngularSpeed = reader.read_float() - - self.m_IndexArray = reader.read_int_array() - if version < (4, 3): # 4.3 down - self.m_AdditionalCurveIndexArray = reader.read_int_array() - numDeltas = reader.read_int() - self.m_ValueArrayDelta = [ValueDelta(reader) for _ in range(numDeltas)] - if version >= (5, 3): # 5.3 and up - self.m_ValueArrayReferencePose = reader.read_float_array() - - self.m_Mirror = reader.read_boolean() - if version >= (4, 3): # 4.3 and up - self.m_LoopTime = reader.read_boolean() - self.m_LoopBlend = reader.read_boolean() - self.m_LoopBlendOrientation = reader.read_boolean() - self.m_LoopBlendPositionY = reader.read_boolean() - self.m_LoopBlendPositionXZ = reader.read_boolean() - if version >= (5, 5): # 5.5 and up - self.m_StartAtOrigin = reader.read_boolean() - self.m_KeepOriginalOrientation = reader.read_boolean() - self.m_KeepOriginalPositionY = reader.read_boolean() - self.m_KeepOriginalPositionXZ = reader.read_boolean() - self.m_HeightFromFeet = reader.read_boolean() - reader.align_stream() - - -class GenericBinding: - def __init__(self, reader): - version = reader.version - self.path = reader.read_u_int() - self.attribute = reader.read_u_int() - self.script = PPtr(reader) # Object - if version >= (5, 6): # 5.6 and up - self.typeID = ClassIDType(reader.read_int()) - else: - self.typeID = ClassIDType(reader.read_u_short()) - self.customType = reader.read_byte() - self.isPPtrCurve = reader.read_byte() - reader.align_stream() - - -class AnimationClipBindingConstant: - def __init__(self, reader): - numBindings = reader.read_int() - self.genericBindings = [GenericBinding(reader) for _ in range(numBindings)] - - numMappings = reader.read_int() - self.pptrCurveMapping = [PPtr(reader) for _ in range(numMappings)] # Object - - def FindBinding(self, index): - curves = 0 - for b in self.genericBindings: - if b.typeID == ClassIDType.Transform: # - switch = b.attribute - - if switch in [1, 3, 4]: - # case 1: #kBindTransformPosition - # case 3: #kBindTransformScale - # case 4: #kBindTransformEuler - curves += 3 - elif switch == 2: # kBindTransformRotation - curves += 4 - else: - curves += 1 - else: - curves += 1 - if curves > index: - return b - return None - - -class AnimationType(IntEnum): - kLegacy = (1,) - kGeneric = (2,) - kHumanoid = 3 - - -class AnimationClip(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - version = reader.version - if version >= (5,): # 5.0 and up - self.m_Legacy = reader.read_boolean() - elif version >= (4,): # 4.0 and up - self.m_AnimationType = AnimationType(reader.read_int()) - if self.m_AnimationType == AnimationType.kLegacy: # - self.m_Legacy = True - else: - self.m_Legacy = True - - self.m_Compressed = reader.read_boolean() - if version >= (4, 3): # 4.3 and up - self.m_UseHighQualityCurve = reader.read_boolean() - reader.align_stream() - numRCurves = reader.read_int() - self.m_RotationCurves = [QuaternionCurve(reader) for _ in range(numRCurves)] - - numCRCurves = reader.read_int() - self.m_CompressedRotationCurves = [ - CompressedAnimationCurve(reader) for _ in range(numCRCurves) - ] - - if version >= (5, 3): # 5.3 and up - numEulerCurves = reader.read_int() - self.m_EulerCurves = [Vector3Curve(reader) for _ in range(numEulerCurves)] - - numPCurves = reader.read_int() - self.m_PositionCurves = [Vector3Curve(reader) for _ in range(numPCurves)] - - numSCurves = reader.read_int() - self.m_ScaleCurves = [Vector3Curve(reader) for _ in range(numSCurves)] - - numFCurves = reader.read_int() - self.m_FloatCurves = [FloatCurve(reader) for _ in range(numFCurves)] - if version >= (4, 3): # 4.3 and up - numPtrCurves = reader.read_int() - self.m_PPtrCurves = [PPtrCurve(reader) for _ in range(numPtrCurves)] - - self.m_SampleRate = reader.read_float() - self.m_WrapMode = reader.read_int() - if version >= (3, 4): # 3.4 and up - self.m_Bounds = AABB(reader) - if version >= (4,): # 4.0 and up - self.m_MuscleClipSize = reader.read_u_int() - self.m_MuscleClip = ClipMuscleConstant(reader) - if version >= (4, 3): # 4.3 and up - self.m_ClipBindingConstant = AnimationClipBindingConstant(reader) - - -# m_HasGenericRootTransform 2018.3 -# m_HasMotionFloatCurves 2018.3 -# numEvents = reader.read_int() -# self.m_Events = [ -# AnimationEvent(reader) -# for _ in range(numEvents) -# ] diff --git a/UnityPy/classes/Animator.py b/UnityPy/classes/Animator.py deleted file mode 100644 index 7fd0f0064..000000000 --- a/UnityPy/classes/Animator.py +++ /dev/null @@ -1,39 +0,0 @@ -from .Behaviour import Behaviour -from .PPtr import PPtr - - -class Animator(Behaviour): - def __init__(self, reader): - super().__init__(reader=reader) - - self.avatar = PPtr(reader) # Avatar - self.controller = PPtr(reader) # RuntimeAnimatorController - self.culling_mode = reader.read_int() - version = self.version - - if version[:2] > (4, 4): - self.update_mode = reader.read_int() - - self.m_ApplyRootMotion = reader.read_boolean() - if version >= (4, 5): # 4.5 and up - 5.0 down - reader.align_stream() - - if version >= (5,): # 5.0 and up - self.linear_velocity_blending = reader.read_boolean() - reader.align_stream() - - if version < (4, 5): # 4.5 down - self.animate_physics = reader.read_boolean() - - if version >= (4, 3): # 4.3 and up - self.has_transform_hierarchy = reader.read_boolean() - - if version >= (4, 5): # 4.5 and up - self.allow_constant_clip_sampling_optimization = reader.read_boolean() - - if (4,) < version[:1] < (2018,): # 5.0 and up - 2018 down - reader.align_stream() - - if version >= (2018,): # 2018 and up - self.m_KeepAnimatorControllerStateOnDisable = reader.read_boolean() - reader.align_stream() diff --git a/UnityPy/classes/AnimatorController.py b/UnityPy/classes/AnimatorController.py deleted file mode 100644 index edfa303b1..000000000 --- a/UnityPy/classes/AnimatorController.py +++ /dev/null @@ -1,321 +0,0 @@ -from .AnimationClip import ValueArrayConstant -from .PPtr import PPtr -from .RuntimeAnimatorController import RuntimeAnimatorController -from ..math import Vector3 - - -class AnimatorController(RuntimeAnimatorController): - def __init__(self, reader): - super().__init__(reader=reader) - self.m_ControllerSize = reader.read_u_int() - self.m_Controller = ControllerConstant(reader) - tosSize = reader.read_int() - self.m_TOS = {} - for _ in range(tosSize): - key = reader.read_u_int() - self.m_TOS[key] = reader.read_aligned_string() - - numClips = reader.read_int() - self.m_AnimationClips = [PPtr(reader) for _ in range(numClips)] - - -class HumanPoseMask: - def __init__(self, reader): - version = reader.version - self.word0 = reader.read_u_int() - self.word1 = reader.read_u_int() - if version >= (5, 2): # 5.2 and up - self.word2 = reader.read_u_int() - - -class SkeletonMaskElement: - def __init__(self, reader): - self.m_PathHash = reader.read_u_int() - self.m_Weight = reader.read_float() - - -class SkeletonMask: - def __init__(self, reader): - numElements = reader.read_int() - self.m_Data = [SkeletonMaskElement(reader) for _ in range(numElements)] - - -class LayerConstant: - def __init__(self, reader): - version = reader.version - self.m_StateMachineIndex = reader.read_u_int() - self.m_StateMachineMotionSetIndex = reader.read_u_int() - self.m_BodyMask = HumanPoseMask(reader) - self.m_SkeletonMask = SkeletonMask(reader) - self.m_Binding = reader.read_u_int() - self.m_LayerBlendingMode = reader.read_int() - if version >= (4, 2): # 4.2 and up - self.m_DefaultWeight = reader.read_float() - self.m_IKPass = reader.read_boolean() - if version >= (4, 2): # 4.2 and up - self.m_SyncedLayerAffectsTiming = reader.read_boolean() - reader.align_stream() - - -class ConditionConstant: - def __init__(self, reader): - self.m_ConditionMode = reader.read_u_int() - self.m_EventID = reader.read_u_int() - self.m_EventThreshold = reader.read_float() - self.m_ExitTime = reader.read_float() - - -class TransitionConstant: - def __init__(self, reader): - version = reader.version - - numConditions = reader.read_int() - self.m_ConditionConstantArray = [ - ConditionConstant(reader) for _ in range(numConditions) - ] - - self.m_DestinationState = reader.read_u_int() - if version >= (5,): # 5.0 and up - self.m_FullPathID = reader.read_u_int() - - self.m_ID = reader.read_u_int() - self.m_UserID = reader.read_u_int() - self.m_TransitionDuration = reader.read_float() - self.m_TransitionOffset = reader.read_float() - if version >= (5,): # 5.0 and up - self.m_ExitTime = reader.read_float() - self.m_HasExitTime = reader.read_boolean() - self.m_HasFixedDuration = reader.read_boolean() - reader.align_stream() - self.m_InterruptionSource = reader.read_int() - self.m_OrderedInterruption = reader.read_boolean() - else: - self.m_Atomic = reader.read_boolean() - - if version >= (4, 5): # 4.5 and up - self.m_CanTransitionToSelf = reader.read_boolean() - - reader.align_stream() - - -class LeafInfoConstant: - def __init__(self, reader): - self.m_IDArray = reader.read_u_int_array() - self.m_IndexOffset = reader.read_u_int() - - -class MotionNeighborList: - def __init__(self, reader): - self.m_NeighborArray = reader.read_u_int_array() - - -class Blend2dDataConstant: - def __init__(self, reader): - self.m_ChildPositionArray = reader.read_vector2_array() - self.m_ChildMagnitudeArray = reader.read_float_array() - self.m_ChildPairVectorArray = reader.read_vector2_array() - self.m_ChildPairAvgMagInvArray = reader.read_float_array() - - numNeighbours = reader.read_int() - self.m_ChildNeighborListArray = [ - MotionNeighborList(reader) for _ in range(numNeighbours) - ] - - -class Blend1dDataConstant: # wrong labeled: - def __init__(self, reader): - self.m_ChildThresholdArray = reader.read_float_array() - - -class BlendDirectDataConstant: - def __init__(self, reader): - self.m_ChildBlendEventIDArray = reader.read_u_int_array() - self.m_NormalizedBlendValues = reader.read_boolean() - reader.align_stream() - - -class BlendTreeNodeConstant: - def __init__(self, reader): - version = reader.version - - if version >= (4, 1): # 4.1 and up - self.m_BlendType = reader.read_u_int() - self.m_BlendEventID = reader.read_u_int() - if version >= (4, 1): # 4.1 and up - self.m_BlendEventYID = reader.read_u_int() - self.m_ChildIndices = reader.read_u_int_array() - if version < (4, 1): # 4.1 down - self.m_ChildThresholdArray = reader.read_float_array() - - if version >= (4, 1): # 4.1 and up - self.m_Blend1dData = Blend1dDataConstant(reader) - self.m_Blend2dData = Blend2dDataConstant(reader) - - if version >= (5,): # 5.0 and up - self.m_BlendDirectData = BlendDirectDataConstant(reader) - - self.m_ClipID = reader.read_u_int() - if (4, 5) <= version[:2] < (5, 0): # 4.5 - 5.0 - self.m_ClipIndex = reader.read_u_int() - - self.m_Duration = reader.read_float() - - if version >= (4, 1, 3): # 4.1.3 and up - self.m_CycleOffset = reader.read_float() - self.m_Mirror = reader.read_boolean() - reader.align_stream() - - -class BlendTreeConstant: - def __init__(self, reader): - version = reader.version - - numNodes = reader.read_int() - self.m_NodeArray = [BlendTreeNodeConstant(reader) for _ in range(numNodes)] - - if version < (4, 5): # 4.5 down - self.m_BlendEventArrayConstant = ValueArrayConstant(reader) - - -class StateConstant: - def __init__(self, reader): - version = reader.version - - numTransistions = reader.read_int() - self.m_TransitionConstantArray = [ - TransitionConstant(reader) for _ in range(numTransistions) - ] - - self.m_BlendTreeConstantIndexArray = reader.read_int_array() - - if version < (5, 2): # 5.2 down - numInfos = reader.read_int() - self.m_LeafInfoArray = [LeafInfoConstant(reader) for _ in range(numInfos)] - - numBlends = reader.read_int() - self.m_BlendTreeConstantArray = [ - BlendTreeConstant(reader) for _ in range(numBlends) - ] - - self.m_NameID = reader.read_u_int() - if version >= (4, 3): # 4.3 and up - self.m_PathID = reader.read_u_int() - if version >= (5,): # 5.0 and up - self.m_FullPathID = reader.read_u_int() - - self.m_TagID = reader.read_u_int() - if version >= (5, 1): # 5.1 and up - self.m_SpeedParamID = reader.read_u_int() - self.m_MirrorParamID = reader.read_u_int() - self.m_CycleOffsetParamID = reader.read_u_int() - - if version >= (2017, 2): # 2017.2 and up - self.m_TimeParamID = reader.read_u_int() - - self.m_Speed = reader.read_float() - if version >= (4, 1): # 4.1 and up - self.m_CycleOffset = reader.read_float() - self.m_IKOnFeet = reader.read_boolean() - if version >= (5,): # 5.0 and up - self.m_WriteDefaultValues = reader.read_boolean() - - self.m_Loop = reader.read_boolean() - if version >= (4, 1): # 4.1 and up - self.m_Mirror = reader.read_boolean() - - reader.align_stream() - - -class SelectorTransitionConstant: - def __init__(self, reader): - self.m_Destination = reader.read_u_int() - - numConditions = reader.read_int() - self.m_ConditionConstantArray = [ - ConditionConstant(reader) for _ in range(numConditions) - ] - - -class SelectorStateConstant: - def __init__(self, reader): - numTransitions = reader.read_int() - self.m_TransitionConstantArray = [ - SelectorTransitionConstant(reader) for _ in range(numTransitions) - ] - self.m_FullPathID = reader.read_u_int() - self.m_isEntry = reader.read_boolean() - reader.align_stream() - - -class StateMachineConstant: - def __init__(self, reader): - version = reader.version - - numStates = reader.read_int() - self.m_StateConstantArray = [StateConstant(reader) for _ in range(numStates)] - - numAnyStates = reader.read_int() - self.m_AnyStateTransitionConstantArray = [ - TransitionConstant(reader) for _ in range(numAnyStates) - ] - - if version >= (5,): # 5.0 and up - numSelectors = reader.read_int() - self.m_SelectorStateConstantArray = [ - SelectorStateConstant(reader) for _ in range(numSelectors) - ] - - self.m_DefaultState = reader.read_u_int() - self.m_MotionSetCount = reader.read_u_int() - - -class ValueArray: - def __init__(self, reader): - version = reader.version - - if version < (5, 5): # 5.5 down - self.m_BoolValues = reader.read_boolean_array() - reader.align_stream() - self.m_IntValues = reader.read_int_array() - self.m_FloatValues = reader.read_float_array() - - if version < (4, 3): # 4.3 down - self.m_VectorValues = reader.read_vector4_array() - else: - numPosValues = reader.read_int() - self.m_PositionValues = [ - reader.read_vector3() - if version >= (5, 4) - else Vector3(reader.read_vector4()) # 5.4 and up - for _ in range(numPosValues) - ] - - self.m_QuaternionValues = reader.read_vector4_array() - - numScaleValues = reader.read_int() - self.m_ScaleValues = [ - reader.read_vector3() - if version >= (5, 4) - else Vector3(reader.read_vector4()) # 5.4 and up - for _ in range(numScaleValues) - ] - - if version >= (5, 5): # 5.5 and up - self.m_FloatValues = reader.read_float_array() - self.m_IntValues = reader.read_int_array() - self.m_BoolValues = reader.read_boolean_array() - reader.align_stream() - - -class ControllerConstant: - def __init__(self, reader): - numLayers = reader.read_int() - self.m_LayerArray = [LayerConstant(reader) for _ in range(numLayers)] - - numStates = reader.read_int() - self.m_StateMachineArray = [ - StateMachineConstant(reader) for _ in range(numStates) - ] - - self.m_Values = ValueArrayConstant(reader) - self.m_DefaultValues = ValueArray(reader) diff --git a/UnityPy/classes/AnimatorOverrideController.py b/UnityPy/classes/AnimatorOverrideController.py deleted file mode 100644 index 8274be254..000000000 --- a/UnityPy/classes/AnimatorOverrideController.py +++ /dev/null @@ -1,16 +0,0 @@ -from .PPtr import PPtr -from .RuntimeAnimatorController import RuntimeAnimatorController - - -class AnimationClipOverride: - def __init__(self, reader): - self.original_clip = PPtr(reader) - self.override_clip = PPtr(reader) - - -class AnimatorOverrideController(RuntimeAnimatorController): - def __init__(self, reader): - super().__init__(reader=reader) - self.controller = PPtr(reader) - num_overrides = reader.read_int() - self.clips = [AnimationClipOverride(reader) for i in range(num_overrides)] diff --git a/UnityPy/classes/AssetBundle.py b/UnityPy/classes/AssetBundle.py deleted file mode 100644 index f0d178dc4..000000000 --- a/UnityPy/classes/AssetBundle.py +++ /dev/null @@ -1,21 +0,0 @@ -from .NamedObject import NamedObject -from .PPtr import PPtr - - -class AssetInfo: - def __init__(self, reader): - self.preload_index = reader.read_int() - self.preload_size = reader.read_int() - self.asset = PPtr(reader) - - -class AssetBundle(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - preload_table_size = reader.read_int() - self.preload_table = [PPtr(reader) for _ in range(preload_table_size)] - container_size = reader.read_int() - self.container = {} - for i in range(container_size): - key = reader.read_aligned_string() - self.container[key] = AssetInfo(reader) diff --git a/UnityPy/classes/AudioClip.py b/UnityPy/classes/AudioClip.py deleted file mode 100644 index 369839012..000000000 --- a/UnityPy/classes/AudioClip.py +++ /dev/null @@ -1,61 +0,0 @@ -from .NamedObject import NamedObject -from ..enums import AudioType, AudioCompressionFormat, AUDIO_TYPE_EXTEMSION -from ..export import AudioClipConverter -from ..helpers.ResourceReader import get_resource_data - - -class AudioClip(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - self.m_Source = "" - version = self.version - if version < (5,): # 5.0 down - self.m_Format = reader.read_int() - self.m_Type = AudioType(reader.read_int()) - self.m_3D = reader.read_boolean() - self.m_UseHardware = reader.read_boolean() - reader.align_stream() - - if version >= (3, 2): # and version <= (5,): # 3.2.0 to 5 - self.m_Stream = reader.read_int() - m_Size = reader.read_int() - tsize = m_Size + 4 - m_Size % 4 if (m_Size % 4 != 0) else m_Size - if reader.byte_size + reader.byte_start - reader.Position != tsize: - m_Offset = reader.read_u_int() - self.m_Source = self.assets_file.full_name + ".resS" - else: - m_Size = reader.read_int() - - self.extension = AUDIO_TYPE_EXTEMSION.get(self.m_Type, ".audioclip") - else: - self.m_LoadType = reader.read_int() - self.m_Channels = reader.read_int() - self.m_Frequency = reader.read_int() - self.m_BitsPerSample = reader.read_int() - self.m_Length = reader.read_float() - self.m_IsTrackerFormat = reader.read_boolean() - reader.align_stream() - self.m_SubsoundIndex = reader.read_int() - self.m_PreloadAudioData = reader.read_boolean() - self.m_LoadInBackground = reader.read_boolean() - self.m_Legacy3D = reader.read_boolean() - reader.align_stream() - self.m_Source = reader.read_aligned_string() - m_Offset = reader.read_u_long() - m_Size = reader.read_long() - self.m_CompressionFormat = AudioCompressionFormat(reader.read_int()) - - self.extension = AUDIO_TYPE_EXTEMSION.get( - self.m_CompressionFormat, ".audioclip" - ) - - if self.m_Source: - self.m_AudioData = get_resource_data( - self.m_Source, self.assets_file, m_Offset, m_Size - ) - else: - self.m_AudioData = reader.read_bytes(m_Size) - - @property - def samples(self) -> dict: - return AudioClipConverter.extract_audioclip_samples(self) diff --git a/UnityPy/classes/Avatar.py b/UnityPy/classes/Avatar.py deleted file mode 100644 index c32789a48..000000000 --- a/UnityPy/classes/Avatar.py +++ /dev/null @@ -1,155 +0,0 @@ -from .AnimationClip import xform -from .NamedObject import NamedObject - - -class Avatar(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - self.m_AvatarSize = reader.read_u_int() - self.m_Avatar = AvatarConstant(reader) - - numTOS = reader.read_int() - self.m_TOS = {} - for _ in range(numTOS): - key = reader.read_u_int() - self.m_TOS[key] = reader.read_aligned_string() - - # HumanDescription m_HumanDescription 2019 and up - - def FindBonePath(self, hash): - return self.m_TOS[hash] - - -class Node: - def __init__(self, reader): - self.m_ParentId = reader.read_int() - self.m_AxesId = reader.read_int() - - -class Limit: - def __init__(self, reader): - version = reader.version - if version >= (5, 4): # 5.4 and up - self.m_Min = reader.read_vector3() - self.m_Max = reader.read_vector3() - else: - self.m_Min = reader.read_vector4() - self.m_Max = reader.read_vector4() - - -class Axes: - def __init__(self, reader): - version = reader.version - self.m_PreQ = reader.read_vector4() - self.m_PostQ = reader.read_vector4() - if version >= (5, 4): # 5.4 and up - self.m_Sgn = reader.read_vector3() - else: - self.m_Sgn = reader.read_vector4() - self.m_Limit = Limit(reader) - self.m_Length = reader.read_float() - self.m_Type = reader.read_u_int() - - -class Skeleton: - def __init__(self, reader): - numNodes = reader.read_int() - self.m_Node = [Node(reader) for _ in range(numNodes)] - - self.m_ID = reader.read_u_int_array() - - numAxes = reader.read_int() - self.m_AxesArray = [Axes(reader) for _ in range(numAxes)] - - -class SkeletonPose: - def __init__(self, reader): - numXforms = reader.read_int() - self.m_X = [xform(reader) for _ in range(numXforms)] - - -class Hand: - def __init__(self, reader): - self.m_HandBoneIndex = reader.read_int_array() - - -class Handle: - def __init__(self, reader): - self.m_X = xform(reader) - self.m_ParentHumanIndex = reader.read_u_int() - self.m_ID = reader.read_u_int() - - -class Collider: - def __init__(self, reader): - self.m_X = xform(reader) - self.m_Type = reader.read_u_int() - self.m_XMotionType = reader.read_u_int() - self.m_YMotionType = reader.read_u_int() - self.m_ZMotionType = reader.read_u_int() - self.m_MinLimitX = reader.read_float() - self.m_MaxLimitX = reader.read_float() - self.m_MaxLimitY = reader.read_float() - self.m_MaxLimitZ = reader.read_float() - - -class Human: - def __init__(self, reader): - version = reader.version - self.m_RootX = xform(reader) - self.m_Skeleton = Skeleton(reader) - self.m_SkeletonPose = SkeletonPose(reader) - self.m_LeftHand = Hand(reader) - self.m_RightHand = Hand(reader) - - if version < (2018, 2): # 2018.2 down - numHandles = reader.read_int() - self.m_Handles = [Handle(reader) for _ in range(numHandles)] - numColliders = reader.read_int() - self.m_ColliderArray = [Collider(reader) for _ in range(numColliders)] - self.m_HumanBoneIndex = reader.read_int_array() - self.m_HumanBoneMass = reader.read_float_array() - - if version < (2018, 2): # 2018.2 down - self.m_ColliderIndex = reader.read_int_array() - - self.m_Scale = reader.read_float() - self.m_ArmTwist = reader.read_float() - self.m_ForeArmTwist = reader.read_float() - self.m_UpperLegTwist = reader.read_float() - self.m_LegTwist = reader.read_float() - self.m_ArmStretch = reader.read_float() - self.m_LegStretch = reader.read_float() - self.m_FeetSpacing = reader.read_float() - self.m_HasLeftHand = reader.read_boolean() - self.m_HasRightHand = reader.read_boolean() - if version >= (5, 2): # 5.2 and up - self.m_HasTDoF = reader.read_boolean() - reader.align_stream() - - -class AvatarConstant: - def __init__(self, reader): - version = reader.version - self.m_AvatarSkeleton = Skeleton(reader) - self.m_AvatarSkeletonPose = SkeletonPose(reader) - - if version >= (4, 3): # 4.3 and up - self.m_DefaultPose = SkeletonPose(reader) - self.m_SkeletonNameIDArray = reader.read_u_int_array() - - self.m_Human = Human(reader) - - self.m_HumanSkeletonIndexArray = reader.read_int_array() - - if version >= (4, 3): # 4.3 and up - self.m_HumanSkeletonReverseIndexArray = reader.read_int_array() - - self.m_RootMotionBoneIndex = reader.read_int() - self.m_RootMotionBoneX = xform(reader) - - if version >= (4, 3): # 4.3 and up - self.m_RootMotionSkeleton = Skeleton(reader) - self.m_RootMotionSkeletonPose = SkeletonPose(reader) - - self.m_RootMotionSkeletonIndexArray = reader.read_int_array() diff --git a/UnityPy/classes/Behaviour.py b/UnityPy/classes/Behaviour.py deleted file mode 100644 index 1a7ec5e55..000000000 --- a/UnityPy/classes/Behaviour.py +++ /dev/null @@ -1,8 +0,0 @@ -from .Component import Component - - -class Behaviour(Component): - def __init__(self, reader): - super().__init__(reader=reader) - self.enabled = reader.read_byte() - self._dummy = reader.align_stream() diff --git a/UnityPy/classes/BuildSettings.py b/UnityPy/classes/BuildSettings.py deleted file mode 100644 index 41d7545b4..000000000 --- a/UnityPy/classes/BuildSettings.py +++ /dev/null @@ -1,12 +0,0 @@ -from .Object import Object - - -class BuildSettings(Object): - def __init__(self, reader): - super().__init__(reader=reader) - self.levels = reader.read_string_array() - self.has_render_texture = reader.read_boolean() - self.has_pro_version = reader.read_boolean() - self.has_publishing_rights = reader.read_boolean() - self.has_shadows = reader.read_boolean() - self.version = reader.read_aligned_string() diff --git a/UnityPy/classes/ClassIDTypeToClassMap.py b/UnityPy/classes/ClassIDTypeToClassMap.py new file mode 100644 index 000000000..39759d19c --- /dev/null +++ b/UnityPy/classes/ClassIDTypeToClassMap.py @@ -0,0 +1,1090 @@ +from typing import Dict, Union + +from ..enums.ClassIDType import ClassIDType as CIT +from . import ( + AimConstraint, + AnchoredJoint2D, + Animation, + AnimationClip, + Animator, + AnimatorController, + AnimatorOverrideController, + AnimatorState, + AnimatorStateMachine, + AnimatorStateTransition, + AnimatorTransition, + AnimatorTransitionBase, + AnnotationManager, + AreaEffector2D, + ArticulationBody, + AssemblyDefinitionAsset, + AssemblyDefinitionImporter, + AssemblyDefinitionReferenceAsset, + AssemblyDefinitionReferenceImporter, + AssetBundle, + AssetBundleManifest, + AssetDatabaseV1, + AssetImporter, + AssetImporterLog, + AssetImportInProgressProxy, + AssetMetaData, + AssetServerCache, + ASTCImporter, + AudioBehaviour, + AudioBuildInfo, + AudioChorusFilter, + AudioClip, + AudioDistortionFilter, + AudioEchoFilter, + AudioFilter, + AudioHighPassFilter, + AudioImporter, + AudioListener, + AudioLowPassFilter, + AudioManager, + AudioMixer, + AudioMixerController, + AudioMixerEffectController, + AudioMixerGroup, + AudioMixerGroupController, + AudioMixerLiveUpdateBool, + AudioMixerLiveUpdateFloat, + AudioMixerSnapshot, + AudioMixerSnapshotController, + AudioReverbFilter, + AudioReverbZone, + AudioSource, + Avatar, + AvatarMask, + BaseAnimationTrack, + BaseVideoTexture, + Behaviour, + BillboardAsset, + BillboardRenderer, + BlendTree, + BoxCollider, + BoxCollider2D, + BuildReport, + BuildSettings, + BuiltAssetBundleInfoSet, + BuoyancyEffector2D, + CachedSpriteAtlas, + CachedSpriteAtlasRuntimeData, + Camera, + Canvas, + CanvasGroup, + CanvasRenderer, + CapsuleCollider, + CapsuleCollider2D, + CGProgram, + CharacterController, + CharacterJoint, + CircleCollider2D, + Cloth, + ClothRenderer, + CloudWebServicesManager, + ClusterInputManager, + Collider, + Collider2D, + Collision, + Collision2D, + Component, + CompositeCollider2D, + ComputeShader, + ComputeShaderImporter, + ConfigurableJoint, + ConstantForce, + ConstantForce2D, + CrashReportManager, + Cubemap, + CubemapArray, + CustomRenderTexture, + DDSImporter, + DefaultAsset, + DefaultImporter, + DelayedCallManager, + Derived, + DistanceJoint2D, + EdgeCollider2D, + EditorBuildSettings, + EditorExtension, + EditorExtensionImpl, + EditorProjectAccess, + EditorSettings, + EditorUserBuildSettings, + EditorUserSettings, + Effector2D, + EllipsoidParticleEmitter, + EmptyObject, + FakeComponent, + FBXImporter, + FixedJoint, + FixedJoint2D, + Flare, + FlareLayer, + Font, + FrictionJoint2D, + GameManager, + GameObject, + GameObjectRecorder, + GlobalGameManager, + GraphicsSettings, + Grid, + GridLayout, + GUIDSerializer, + GUIElement, + GUILayer, + GUIText, + GUITexture, + Halo, + HaloLayer, + HaloManager, + HierarchyState, + HingeJoint, + HingeJoint2D, + HumanTemplate, + IConstraint, + IHVImageFormatImporter, + InputManager, + InspectorExpandedState, + InteractiveCloth, + Joint, + Joint2D, + KTXImporter, + LensFlare, + LevelGameManager, + LibraryAssetImporter, + Light, + LightingDataAsset, + LightingDataAssetParent, + LightingSettings, + LightmapParameters, + LightmapSettings, + LightProbeGroup, + LightProbeProxyVolume, + LightProbes, + LineRenderer, + LocalizationAsset, + LocalizationImporter, + LODGroup, + LookAtConstraint, + LowerResBlitTexture, + MasterServerInterface, + Material, + Mesh, + Mesh3DSImporter, + MeshCollider, + MeshFilter, + MeshParticleEmitter, + MeshRenderer, + ModelImporter, + MonoBehaviour, + MonoImporter, + MonoManager, + MonoObject, + MonoScript, + Motion, + MovieImporter, + MovieTexture, + MultiArtifactTestImporter, + NamedObject, + NativeFormatImporter, + NativeObjectType, + NavMeshAgent, + NavMeshData, + NavMeshObsolete, + NavMeshObstacle, + NavMeshProjectSettings, + NavMeshSettings, + NetworkManager, + NetworkView, + NewAnimationTrack, + NScreenBridge, + Object, + OcclusionArea, + OcclusionCullingData, + OcclusionCullingSettings, + OcclusionPortal, + OffMeshLink, + PackageManifest, + PackageManifestImporter, + PackedAssets, + ParentConstraint, + ParticleAnimator, + ParticleEmitter, + ParticleRenderer, + ParticleSystem, + ParticleSystemForceField, + ParticleSystemRenderer, + PerformanceReportingManager, + PhysicMaterial, + Physics2DSettings, + PhysicsManager, + PhysicsMaterial2D, + PhysicsUpdateBehaviour2D, + Pipeline, + PlatformEffector2D, + PlatformModuleSetup, + PlayableDirector, + PlayerSettings, + PluginBuildInfo, + PluginImporter, + PointEffector2D, + Polygon2D, + PolygonCollider2D, + PositionConstraint, + PrefabImporter, + PrefabInstance, + PreloadData, + Preset, + PresetManager, + PreviewAnimationClip, + ProceduralMaterial, + ProceduralTexture, + Projector, + PropertyModificationsTargetTestObject, + PVRImporter, + QualitySettings, + RaycastCollider, + RayTracingShader, + RayTracingShaderImporter, + RectTransform, + ReferencesArtifactGenerator, + ReflectionProbe, + RelativeJoint2D, + Renderer, + RendererFake, + RenderPassAttachment, + RenderSettings, + RenderTexture, + ResourceManager, + Rigidbody, + Rigidbody2D, + RootMotionData, + RotationConstraint, + RuntimeAnimatorController, + RuntimeInitializeOnLoadManager, + SampleClip, + ScaleConstraint, + SceneAsset, + ScenesUsingAssets, + SceneVisibilityState, + ScriptableCamera, + ScriptedImporter, + ScriptMapper, + SerializableManagedHost, + SerializableManagedRefTestClass, + Shader, + ShaderImporter, + ShaderVariantCollection, + SiblingDerived, + SketchUpImporter, + SkinnedCloth, + SkinnedMeshRenderer, + Skybox, + SliderJoint2D, + SortingGroup, + SparseTexture, + SpeedTreeImporter, + SpeedTreeWindAsset, + SphereCollider, + SpringJoint, + SpringJoint2D, + Sprite, + SpriteAtlas, + SpriteAtlasAsset, + SpriteAtlasDatabase, + SpriteAtlasImporter, + SpriteMask, + SpriteRenderer, + SpriteShapeRenderer, + StreamingController, + StreamingManager, + SubDerived, + SubstanceArchive, + SubstanceImporter, + SurfaceEffector2D, + TagManager, + TargetJoint2D, + Terrain, + TerrainCollider, + TerrainData, + TerrainLayer, + TestObjectVectorPairStringBool, + TestObjectWithSerializedAnimationCurve, + TestObjectWithSerializedArray, + TestObjectWithSerializedMapStringBool, + TestObjectWithSerializedMapStringNonAlignedStruct, + TestObjectWithSpecialLayoutOne, + TestObjectWithSpecialLayoutTwo, + TextAsset, + TextMesh, + TextScriptImporter, + Texture, + Texture2D, + Texture2DArray, + Texture3D, + TextureImporter, + Tilemap, + TilemapCollider2D, + TilemapRenderer, + TimeManager, + TrailRenderer, + Transform, + Tree, + TrueTypeFontImporter, + UnityAdsManager, + UnityAnalyticsManager, + UnityConnectSettings, + Vector3f, + VersionControlSettings, + VFXManager, + VFXRenderer, + VideoClip, + VideoClipImporter, + VideoPlayer, + VisualEffect, + VisualEffectAsset, + VisualEffectImporter, + VisualEffectObject, + VisualEffectResource, + VisualEffectSubgraph, + VisualEffectSubgraphBlock, + VisualEffectSubgraphOperator, + WebCamTexture, + WheelCollider, + WheelJoint2D, + WindZone, + WorldAnchor, + WorldParticleCollider, +) + +ClassIDTypeToClassMapValueType = Union[ + AimConstraint, + AnchoredJoint2D, + Animation, + AnimationClip, + Animator, + AnimatorController, + AnimatorOverrideController, + AnimatorState, + AnimatorStateMachine, + AnimatorStateTransition, + AnimatorTransition, + AnimatorTransitionBase, + AnnotationManager, + AreaEffector2D, + ArticulationBody, + AssemblyDefinitionAsset, + AssemblyDefinitionImporter, + AssemblyDefinitionReferenceAsset, + AssemblyDefinitionReferenceImporter, + AssetBundle, + AssetBundleManifest, + AssetDatabaseV1, + AssetImporter, + AssetImporterLog, + AssetImportInProgressProxy, + AssetMetaData, + AssetServerCache, + ASTCImporter, + AudioBehaviour, + AudioBuildInfo, + AudioChorusFilter, + AudioClip, + AudioDistortionFilter, + AudioEchoFilter, + AudioFilter, + AudioHighPassFilter, + AudioImporter, + AudioListener, + AudioLowPassFilter, + AudioManager, + AudioMixer, + AudioMixerController, + AudioMixerEffectController, + AudioMixerGroup, + AudioMixerGroupController, + AudioMixerLiveUpdateBool, + AudioMixerLiveUpdateFloat, + AudioMixerSnapshot, + AudioMixerSnapshotController, + AudioReverbFilter, + AudioReverbZone, + AudioSource, + Avatar, + AvatarMask, + BaseAnimationTrack, + BaseVideoTexture, + Behaviour, + BillboardAsset, + BillboardRenderer, + BlendTree, + BoxCollider, + BoxCollider2D, + BuildReport, + BuildSettings, + BuiltAssetBundleInfoSet, + BuoyancyEffector2D, + CachedSpriteAtlas, + CachedSpriteAtlasRuntimeData, + Camera, + Canvas, + CanvasGroup, + CanvasRenderer, + CapsuleCollider, + CapsuleCollider2D, + CGProgram, + CharacterController, + CharacterJoint, + CircleCollider2D, + Cloth, + ClothRenderer, + CloudWebServicesManager, + ClusterInputManager, + Collider, + Collider2D, + Collision, + Collision2D, + Component, + CompositeCollider2D, + ComputeShader, + ComputeShaderImporter, + ConfigurableJoint, + ConstantForce, + ConstantForce2D, + CrashReportManager, + Cubemap, + CubemapArray, + CustomRenderTexture, + DDSImporter, + DefaultAsset, + DefaultImporter, + DelayedCallManager, + Derived, + DistanceJoint2D, + EdgeCollider2D, + EditorBuildSettings, + EditorExtension, + EditorExtensionImpl, + EditorProjectAccess, + EditorSettings, + EditorUserBuildSettings, + EditorUserSettings, + Effector2D, + EllipsoidParticleEmitter, + EmptyObject, + FakeComponent, + FBXImporter, + FixedJoint, + FixedJoint2D, + Flare, + FlareLayer, + Font, + FrictionJoint2D, + GameManager, + GameObject, + GameObjectRecorder, + GlobalGameManager, + GraphicsSettings, + Grid, + GridLayout, + GUIDSerializer, + GUIElement, + GUILayer, + GUIText, + GUITexture, + Halo, + HaloLayer, + HaloManager, + HierarchyState, + HingeJoint, + HingeJoint2D, + HumanTemplate, + IConstraint, + IHVImageFormatImporter, + InputManager, + InspectorExpandedState, + InteractiveCloth, + Joint, + Joint2D, + KTXImporter, + LensFlare, + LevelGameManager, + LibraryAssetImporter, + Light, + LightingDataAsset, + LightingDataAssetParent, + LightingSettings, + LightmapParameters, + LightmapSettings, + LightProbeGroup, + LightProbeProxyVolume, + LightProbes, + LineRenderer, + LocalizationAsset, + LocalizationImporter, + LODGroup, + LookAtConstraint, + LowerResBlitTexture, + MasterServerInterface, + Material, + Mesh, + Mesh3DSImporter, + MeshCollider, + MeshFilter, + MeshParticleEmitter, + MeshRenderer, + ModelImporter, + MonoBehaviour, + MonoImporter, + MonoManager, + MonoObject, + MonoScript, + Motion, + MovieImporter, + MovieTexture, + MultiArtifactTestImporter, + NamedObject, + NativeFormatImporter, + NativeObjectType, + NavMeshAgent, + NavMeshData, + NavMeshObsolete, + NavMeshObstacle, + NavMeshProjectSettings, + NavMeshSettings, + NetworkManager, + NetworkView, + NewAnimationTrack, + NScreenBridge, + Object, + OcclusionArea, + OcclusionCullingData, + OcclusionCullingSettings, + OcclusionPortal, + OffMeshLink, + PackageManifest, + PackageManifestImporter, + PackedAssets, + ParentConstraint, + ParticleAnimator, + ParticleEmitter, + ParticleRenderer, + ParticleSystem, + ParticleSystemForceField, + ParticleSystemRenderer, + PerformanceReportingManager, + PhysicMaterial, + Physics2DSettings, + PhysicsManager, + PhysicsMaterial2D, + PhysicsUpdateBehaviour2D, + Pipeline, + PlatformEffector2D, + PlatformModuleSetup, + PlayableDirector, + PlayerSettings, + PluginBuildInfo, + PluginImporter, + PointEffector2D, + Polygon2D, + PolygonCollider2D, + PositionConstraint, + PrefabImporter, + PrefabInstance, + PreloadData, + Preset, + PresetManager, + PreviewAnimationClip, + ProceduralMaterial, + ProceduralTexture, + Projector, + PropertyModificationsTargetTestObject, + PVRImporter, + QualitySettings, + RaycastCollider, + RayTracingShader, + RayTracingShaderImporter, + RectTransform, + ReferencesArtifactGenerator, + ReflectionProbe, + RelativeJoint2D, + Renderer, + RendererFake, + RenderPassAttachment, + RenderSettings, + RenderTexture, + ResourceManager, + Rigidbody, + Rigidbody2D, + RootMotionData, + RotationConstraint, + RuntimeAnimatorController, + RuntimeInitializeOnLoadManager, + SampleClip, + ScaleConstraint, + SceneAsset, + ScenesUsingAssets, + SceneVisibilityState, + ScriptableCamera, + ScriptedImporter, + ScriptMapper, + SerializableManagedHost, + SerializableManagedRefTestClass, + Shader, + ShaderImporter, + ShaderVariantCollection, + SiblingDerived, + SketchUpImporter, + SkinnedCloth, + SkinnedMeshRenderer, + Skybox, + SliderJoint2D, + SortingGroup, + SparseTexture, + SpeedTreeImporter, + SpeedTreeWindAsset, + SphereCollider, + SpringJoint, + SpringJoint2D, + Sprite, + SpriteAtlas, + SpriteAtlasAsset, + SpriteAtlasDatabase, + SpriteAtlasImporter, + SpriteMask, + SpriteRenderer, + SpriteShapeRenderer, + StreamingController, + StreamingManager, + SubDerived, + SubstanceArchive, + SubstanceImporter, + SurfaceEffector2D, + TagManager, + TargetJoint2D, + Terrain, + TerrainCollider, + TerrainData, + TerrainLayer, + TestObjectVectorPairStringBool, + TestObjectWithSerializedAnimationCurve, + TestObjectWithSerializedArray, + TestObjectWithSerializedMapStringBool, + TestObjectWithSerializedMapStringNonAlignedStruct, + TestObjectWithSpecialLayoutOne, + TestObjectWithSpecialLayoutTwo, + TextAsset, + TextMesh, + TextScriptImporter, + Texture, + Texture2D, + Texture2DArray, + Texture3D, + TextureImporter, + Tilemap, + TilemapCollider2D, + TilemapRenderer, + TimeManager, + TrailRenderer, + Transform, + Tree, + TrueTypeFontImporter, + UnityAdsManager, + UnityAnalyticsManager, + UnityConnectSettings, + Vector3f, + VersionControlSettings, + VFXManager, + VFXRenderer, + VideoClip, + VideoClipImporter, + VideoPlayer, + VisualEffect, + VisualEffectAsset, + VisualEffectImporter, + VisualEffectObject, + VisualEffectResource, + VisualEffectSubgraph, + VisualEffectSubgraphBlock, + VisualEffectSubgraphOperator, + WebCamTexture, + WheelCollider, + WheelJoint2D, + WindZone, + WorldAnchor, + WorldParticleCollider, +] + +ClassIDTypeToClassMap: Dict[CIT, ClassIDTypeToClassMapValueType] = { + CIT.Object: Object, + CIT.GameObject: GameObject, + CIT.Component: Component, + CIT.LevelGameManager: LevelGameManager, + CIT.Transform: Transform, + CIT.TimeManager: TimeManager, + CIT.GlobalGameManager: GlobalGameManager, + CIT.Behaviour: Behaviour, + CIT.GameManager: GameManager, + CIT.AudioManager: AudioManager, + CIT.ParticleAnimator: ParticleAnimator, + CIT.InputManager: InputManager, + CIT.EllipsoidParticleEmitter: EllipsoidParticleEmitter, + CIT.Pipeline: Pipeline, + CIT.EditorExtension: EditorExtension, + CIT.Physics2DSettings: Physics2DSettings, + CIT.Camera: Camera, + CIT.Material: Material, + CIT.MeshRenderer: MeshRenderer, + CIT.Renderer: Renderer, + CIT.ParticleRenderer: ParticleRenderer, + CIT.Texture: Texture, + CIT.Texture2D: Texture2D, + CIT.OcclusionCullingSettings: OcclusionCullingSettings, + CIT.GraphicsSettings: GraphicsSettings, + CIT.MeshFilter: MeshFilter, + CIT.OcclusionPortal: OcclusionPortal, + CIT.Mesh: Mesh, + CIT.Skybox: Skybox, + CIT.QualitySettings: QualitySettings, + CIT.Shader: Shader, + CIT.TextAsset: TextAsset, + CIT.Rigidbody2D: Rigidbody2D, + CIT.Physics2DManager: Physics2DSettings, + CIT.Collider2D: Collider2D, + CIT.Rigidbody: Rigidbody, + CIT.PhysicsManager: PhysicsManager, + CIT.Collider: Collider, + CIT.Joint: Joint, + CIT.CircleCollider2D: CircleCollider2D, + CIT.HingeJoint: HingeJoint, + CIT.PolygonCollider2D: PolygonCollider2D, + CIT.BoxCollider2D: BoxCollider2D, + CIT.PhysicsMaterial2D: PhysicsMaterial2D, + CIT.MeshCollider: MeshCollider, + CIT.BoxCollider: BoxCollider, + CIT.CompositeCollider2D: CompositeCollider2D, + CIT.EdgeCollider2D: EdgeCollider2D, + CIT.CapsuleCollider2D: CapsuleCollider2D, + CIT.ComputeShader: ComputeShader, + CIT.AnimationClip: AnimationClip, + CIT.ConstantForce: ConstantForce, + CIT.WorldParticleCollider: WorldParticleCollider, + CIT.TagManager: TagManager, + CIT.AudioListener: AudioListener, + CIT.AudioSource: AudioSource, + CIT.AudioClip: AudioClip, + CIT.RenderTexture: RenderTexture, + CIT.CustomRenderTexture: CustomRenderTexture, + CIT.MeshParticleEmitter: MeshParticleEmitter, + CIT.ParticleEmitter: ParticleEmitter, + CIT.Cubemap: Cubemap, + CIT.Avatar: Avatar, + CIT.AnimatorController: AnimatorController, + CIT.GUILayer: GUILayer, + CIT.RuntimeAnimatorController: RuntimeAnimatorController, + CIT.ScriptMapper: ScriptMapper, + CIT.Animator: Animator, + CIT.TrailRenderer: TrailRenderer, + CIT.DelayedCallManager: DelayedCallManager, + CIT.TextMesh: TextMesh, + CIT.RenderSettings: RenderSettings, + CIT.Light: Light, + CIT.CGProgram: CGProgram, + CIT.BaseAnimationTrack: BaseAnimationTrack, + CIT.Animation: Animation, + CIT.MonoBehaviour: MonoBehaviour, + CIT.MonoScript: MonoScript, + CIT.MonoManager: MonoManager, + CIT.Texture3D: Texture3D, + CIT.NewAnimationTrack: NewAnimationTrack, + CIT.Projector: Projector, + CIT.LineRenderer: LineRenderer, + CIT.Flare: Flare, + CIT.Halo: Halo, + CIT.LensFlare: LensFlare, + CIT.FlareLayer: FlareLayer, + CIT.HaloLayer: HaloLayer, + CIT.NavMeshProjectSettings: NavMeshProjectSettings, + CIT.HaloManager: HaloManager, + CIT.Font: Font, + CIT.PlayerSettings: PlayerSettings, + CIT.NamedObject: NamedObject, + CIT.GUITexture: GUITexture, + CIT.GUIText: GUIText, + CIT.GUIElement: GUIElement, + CIT.PhysicMaterial: PhysicMaterial, + CIT.SphereCollider: SphereCollider, + CIT.CapsuleCollider: CapsuleCollider, + CIT.SkinnedMeshRenderer: SkinnedMeshRenderer, + CIT.FixedJoint: FixedJoint, + CIT.RaycastCollider: RaycastCollider, + CIT.BuildSettings: BuildSettings, + CIT.AssetBundle: AssetBundle, + CIT.CharacterController: CharacterController, + CIT.CharacterJoint: CharacterJoint, + CIT.SpringJoint: SpringJoint, + CIT.WheelCollider: WheelCollider, + CIT.ResourceManager: ResourceManager, + CIT.NetworkView: NetworkView, + CIT.NetworkManager: NetworkManager, + CIT.PreloadData: PreloadData, + CIT.MovieTexture: MovieTexture, + CIT.ConfigurableJoint: ConfigurableJoint, + CIT.TerrainCollider: TerrainCollider, + CIT.MasterServerInterface: MasterServerInterface, + CIT.TerrainData: TerrainData, + CIT.LightmapSettings: LightmapSettings, + CIT.WebCamTexture: WebCamTexture, + CIT.EditorSettings: EditorSettings, + CIT.InteractiveCloth: InteractiveCloth, + CIT.ClothRenderer: ClothRenderer, + CIT.EditorUserSettings: EditorUserSettings, + CIT.SkinnedCloth: SkinnedCloth, + CIT.AudioReverbFilter: AudioReverbFilter, + CIT.AudioHighPassFilter: AudioHighPassFilter, + CIT.AudioChorusFilter: AudioChorusFilter, + CIT.AudioReverbZone: AudioReverbZone, + CIT.AudioEchoFilter: AudioEchoFilter, + CIT.AudioLowPassFilter: AudioLowPassFilter, + CIT.AudioDistortionFilter: AudioDistortionFilter, + CIT.SparseTexture: SparseTexture, + CIT.AudioBehaviour: AudioBehaviour, + CIT.AudioFilter: AudioFilter, + CIT.WindZone: WindZone, + CIT.Cloth: Cloth, + CIT.SubstanceArchive: SubstanceArchive, + CIT.ProceduralMaterial: ProceduralMaterial, + CIT.ProceduralTexture: ProceduralTexture, + CIT.Texture2DArray: Texture2DArray, + CIT.CubemapArray: CubemapArray, + CIT.OffMeshLink: OffMeshLink, + CIT.OcclusionArea: OcclusionArea, + CIT.Tree: Tree, + CIT.NavMeshObsolete: NavMeshObsolete, + CIT.NavMeshAgent: NavMeshAgent, + CIT.NavMeshSettings: NavMeshSettings, + CIT.LightProbesLegacy: LightProbes, + CIT.ParticleSystem: ParticleSystem, + CIT.ParticleSystemRenderer: ParticleSystemRenderer, + CIT.ShaderVariantCollection: ShaderVariantCollection, + CIT.LODGroup: LODGroup, + CIT.BlendTree: BlendTree, + CIT.Motion: Motion, + CIT.NavMeshObstacle: NavMeshObstacle, + CIT.SortingGroup: SortingGroup, + CIT.SpriteRenderer: SpriteRenderer, + CIT.Sprite: Sprite, + CIT.CachedSpriteAtlas: CachedSpriteAtlas, + CIT.ReflectionProbe: ReflectionProbe, + CIT.ReflectionProbes: ReflectionProbe, + CIT.Terrain: Terrain, + CIT.LightProbeGroup: LightProbeGroup, + CIT.AnimatorOverrideController: AnimatorOverrideController, + CIT.CanvasRenderer: CanvasRenderer, + CIT.Canvas: Canvas, + CIT.RectTransform: RectTransform, + CIT.CanvasGroup: CanvasGroup, + CIT.BillboardAsset: BillboardAsset, + CIT.BillboardRenderer: BillboardRenderer, + CIT.SpeedTreeWindAsset: SpeedTreeWindAsset, + CIT.AnchoredJoint2D: AnchoredJoint2D, + CIT.Joint2D: Joint2D, + CIT.SpringJoint2D: SpringJoint2D, + CIT.DistanceJoint2D: DistanceJoint2D, + CIT.HingeJoint2D: HingeJoint2D, + CIT.SliderJoint2D: SliderJoint2D, + CIT.WheelJoint2D: WheelJoint2D, + CIT.ClusterInputManager: ClusterInputManager, + CIT.BaseVideoTexture: BaseVideoTexture, + CIT.NavMeshData: NavMeshData, + CIT.AudioMixer: AudioMixer, + CIT.AudioMixerController: AudioMixerController, + CIT.AudioMixerGroupController: AudioMixerGroupController, + CIT.AudioMixerEffectController: AudioMixerEffectController, + CIT.AudioMixerSnapshotController: AudioMixerSnapshotController, + CIT.PhysicsUpdateBehaviour2D: PhysicsUpdateBehaviour2D, + CIT.ConstantForce2D: ConstantForce2D, + CIT.Effector2D: Effector2D, + CIT.AreaEffector2D: AreaEffector2D, + CIT.PointEffector2D: PointEffector2D, + CIT.PlatformEffector2D: PlatformEffector2D, + CIT.SurfaceEffector2D: SurfaceEffector2D, + CIT.BuoyancyEffector2D: BuoyancyEffector2D, + CIT.RelativeJoint2D: RelativeJoint2D, + CIT.FixedJoint2D: FixedJoint2D, + CIT.FrictionJoint2D: FrictionJoint2D, + CIT.TargetJoint2D: TargetJoint2D, + CIT.LightProbes: LightProbes, + CIT.LightProbeProxyVolume: LightProbeProxyVolume, + CIT.SampleClip: SampleClip, + CIT.AudioMixerSnapshot: AudioMixerSnapshot, + CIT.AudioMixerGroup: AudioMixerGroup, + CIT.NScreenBridge: NScreenBridge, + CIT.AssetBundleManifest: AssetBundleManifest, + CIT.UnityAdsManager: UnityAdsManager, + CIT.RuntimeInitializeOnLoadManager: RuntimeInitializeOnLoadManager, + CIT.CloudWebServicesManager: CloudWebServicesManager, + CIT.UnityAnalyticsManager: UnityAnalyticsManager, + CIT.CrashReportManager: CrashReportManager, + CIT.PerformanceReportingManager: PerformanceReportingManager, + CIT.UnityConnectSettings: UnityConnectSettings, + CIT.AvatarMask: AvatarMask, + CIT.PlayableDirector: PlayableDirector, + CIT.VideoPlayer: VideoPlayer, + CIT.VideoClip: VideoClip, + CIT.ParticleSystemForceField: ParticleSystemForceField, + CIT.SpriteMask: SpriteMask, + CIT.WorldAnchor: WorldAnchor, + CIT.OcclusionCullingData: OcclusionCullingData, + # CIT.SmallestEditorClassID: SmallestEditorClassID, + CIT.PrefabInstance: PrefabInstance, + CIT.EditorExtensionImpl: EditorExtensionImpl, + CIT.AssetImporter: AssetImporter, + CIT.AssetDatabaseV1: AssetDatabaseV1, + CIT.Mesh3DSImporter: Mesh3DSImporter, + CIT.TextureImporter: TextureImporter, + CIT.ShaderImporter: ShaderImporter, + CIT.ComputeShaderImporter: ComputeShaderImporter, + CIT.AudioImporter: AudioImporter, + CIT.HierarchyState: HierarchyState, + CIT.GUIDSerializer: GUIDSerializer, + CIT.AssetMetaData: AssetMetaData, + CIT.DefaultAsset: DefaultAsset, + CIT.DefaultImporter: DefaultImporter, + CIT.TextScriptImporter: TextScriptImporter, + CIT.SceneAsset: SceneAsset, + CIT.NativeFormatImporter: NativeFormatImporter, + CIT.MonoImporter: MonoImporter, + CIT.AssetServerCache: AssetServerCache, + CIT.LibraryAssetImporter: LibraryAssetImporter, + CIT.ModelImporter: ModelImporter, + CIT.FBXImporter: FBXImporter, + CIT.TrueTypeFontImporter: TrueTypeFontImporter, + CIT.MovieImporter: MovieImporter, + CIT.EditorBuildSettings: EditorBuildSettings, + CIT.DDSImporter: DDSImporter, + CIT.InspectorExpandedState: InspectorExpandedState, + CIT.AnnotationManager: AnnotationManager, + CIT.PluginImporter: PluginImporter, + CIT.EditorUserBuildSettings: EditorUserBuildSettings, + CIT.PVRImporter: PVRImporter, + CIT.ASTCImporter: ASTCImporter, + CIT.KTXImporter: KTXImporter, + CIT.IHVImageFormatImporter: IHVImageFormatImporter, + CIT.AnimatorStateTransition: AnimatorStateTransition, + CIT.AnimatorState: AnimatorState, + CIT.HumanTemplate: HumanTemplate, + CIT.AnimatorStateMachine: AnimatorStateMachine, + CIT.PreviewAnimationClip: PreviewAnimationClip, + CIT.AnimatorTransition: AnimatorTransition, + CIT.SpeedTreeImporter: SpeedTreeImporter, + CIT.AnimatorTransitionBase: AnimatorTransitionBase, + CIT.SubstanceImporter: SubstanceImporter, + CIT.LightmapParameters: LightmapParameters, + CIT.LightingDataAsset: LightingDataAsset, + # CIT.GISRaster: GISRaster, + # CIT.GISRasterImporter: GISRasterImporter, + # CIT.CadImporter: CadImporter, + CIT.SketchUpImporter: SketchUpImporter, + CIT.BuildReport: BuildReport, + CIT.PackedAssets: PackedAssets, + CIT.VideoClipImporter: VideoClipImporter, + # CIT.ActivationLogComponent: ActivationLogComponent, + # CIT.int: int, + # CIT.bool: bool, + # CIT.float: float, + CIT.MonoObject: MonoObject, + CIT.Collision: Collision, + CIT.Vector3f: Vector3f, + CIT.RootMotionData: RootMotionData, + CIT.Collision2D: Collision2D, + CIT.AudioMixerLiveUpdateFloat: AudioMixerLiveUpdateFloat, + CIT.AudioMixerLiveUpdateBool: AudioMixerLiveUpdateBool, + CIT.Polygon2D: Polygon2D, + # CIT.void: void, + CIT.TilemapCollider2D: TilemapCollider2D, + CIT.AssetImporterLog: AssetImporterLog, + CIT.VFXRenderer: VFXRenderer, + CIT.SerializableManagedRefTestClass: SerializableManagedRefTestClass, + CIT.Grid: Grid, + CIT.ScenesUsingAssets: ScenesUsingAssets, + CIT.ArticulationBody: ArticulationBody, + CIT.Preset: Preset, + CIT.EmptyObject: EmptyObject, + CIT.IConstraint: IConstraint, + CIT.TestObjectWithSpecialLayoutOne: TestObjectWithSpecialLayoutOne, + CIT.AssemblyDefinitionReferenceImporter: AssemblyDefinitionReferenceImporter, + CIT.SiblingDerived: SiblingDerived, + CIT.TestObjectWithSerializedMapStringNonAlignedStruct: TestObjectWithSerializedMapStringNonAlignedStruct, + CIT.SubDerived: SubDerived, + CIT.AssetImportInProgressProxy: AssetImportInProgressProxy, + CIT.PluginBuildInfo: PluginBuildInfo, + CIT.EditorProjectAccess: EditorProjectAccess, + CIT.PrefabImporter: PrefabImporter, + CIT.TestObjectWithSerializedArray: TestObjectWithSerializedArray, + CIT.TestObjectWithSerializedAnimationCurve: TestObjectWithSerializedAnimationCurve, + CIT.TilemapRenderer: TilemapRenderer, + CIT.ScriptableCamera: ScriptableCamera, + CIT.SpriteAtlasAsset: SpriteAtlasAsset, + CIT.SpriteAtlasDatabase: SpriteAtlasDatabase, + CIT.AudioBuildInfo: AudioBuildInfo, + CIT.CachedSpriteAtlasRuntimeData: CachedSpriteAtlasRuntimeData, + CIT.RendererFake: RendererFake, + CIT.AssemblyDefinitionReferenceAsset: AssemblyDefinitionReferenceAsset, + CIT.BuiltAssetBundleInfoSet: BuiltAssetBundleInfoSet, + CIT.SpriteAtlas: SpriteAtlas, + CIT.RayTracingShaderImporter: RayTracingShaderImporter, + CIT.RayTracingShader: RayTracingShader, + CIT.LightingSettings: LightingSettings, + CIT.PlatformModuleSetup: PlatformModuleSetup, + CIT.VersionControlSettings: VersionControlSettings, + CIT.AimConstraint: AimConstraint, + CIT.VFXManager: VFXManager, + CIT.VisualEffectSubgraph: VisualEffectSubgraph, + CIT.VisualEffectSubgraphOperator: VisualEffectSubgraphOperator, + CIT.VisualEffectSubgraphBlock: VisualEffectSubgraphBlock, + CIT.LocalizationImporter: LocalizationImporter, + CIT.Derived: Derived, + CIT.PropertyModificationsTargetTestObject: PropertyModificationsTargetTestObject, + CIT.ReferencesArtifactGenerator: ReferencesArtifactGenerator, + CIT.AssemblyDefinitionAsset: AssemblyDefinitionAsset, + CIT.SceneVisibilityState: SceneVisibilityState, + CIT.LookAtConstraint: LookAtConstraint, + CIT.SpriteAtlasImporter: SpriteAtlasImporter, + CIT.MultiArtifactTestImporter: MultiArtifactTestImporter, + CIT.GameObjectRecorder: GameObjectRecorder, + CIT.LightingDataAssetParent: LightingDataAssetParent, + CIT.PresetManager: PresetManager, + CIT.TestObjectWithSpecialLayoutTwo: TestObjectWithSpecialLayoutTwo, + CIT.StreamingManager: StreamingManager, + CIT.LowerResBlitTexture: LowerResBlitTexture, + CIT.StreamingController: StreamingController, + CIT.RenderPassAttachment: RenderPassAttachment, + CIT.TestObjectVectorPairStringBool: TestObjectVectorPairStringBool, + CIT.GridLayout: GridLayout, + CIT.AssemblyDefinitionImporter: AssemblyDefinitionImporter, + CIT.ParentConstraint: ParentConstraint, + CIT.FakeComponent: FakeComponent, + CIT.PositionConstraint: PositionConstraint, + CIT.RotationConstraint: RotationConstraint, + CIT.ScaleConstraint: ScaleConstraint, + CIT.Tilemap: Tilemap, + CIT.PackageManifest: PackageManifest, + CIT.PackageManifestImporter: PackageManifestImporter, + CIT.TerrainLayer: TerrainLayer, + CIT.SpriteShapeRenderer: SpriteShapeRenderer, + CIT.NativeObjectType: NativeObjectType, + CIT.TestObjectWithSerializedMapStringBool: TestObjectWithSerializedMapStringBool, + CIT.SerializableManagedHost: SerializableManagedHost, + CIT.VisualEffectAsset: VisualEffectAsset, + CIT.VisualEffectImporter: VisualEffectImporter, + CIT.VisualEffectResource: VisualEffectResource, + CIT.VisualEffectObject: VisualEffectObject, + CIT.VisualEffect: VisualEffect, + CIT.LocalizationAsset: LocalizationAsset, + CIT.ScriptedImporter: ScriptedImporter, +} + +__all__ = ["ClassIDTypeToClassMap"] diff --git a/UnityPy/classes/Component.py b/UnityPy/classes/Component.py deleted file mode 100644 index cd6c1c105..000000000 --- a/UnityPy/classes/Component.py +++ /dev/null @@ -1,8 +0,0 @@ -from .EditorExtension import EditorExtension -from .PPtr import PPtr - - -class Component(EditorExtension): - def __init__(self, reader): - super().__init__(reader=reader) - self.GameObject = PPtr(reader) # GameObject diff --git a/UnityPy/classes/EditorExtension.py b/UnityPy/classes/EditorExtension.py deleted file mode 100644 index d374a4849..000000000 --- a/UnityPy/classes/EditorExtension.py +++ /dev/null @@ -1,18 +0,0 @@ -from .Object import Object -from .PPtr import PPtr, save_ptr -from ..enums import BuildTarget -from ..streams import EndianBinaryWriter, EndianBinaryReader - - -class EditorExtension(Object): - def __init__(self, reader: EndianBinaryReader): - super().__init__(reader=reader) - if self.platform == BuildTarget.NoTarget: - self.prefab_parent_object = PPtr(reader) - self.prefab_internal = PPtr(reader) - - def save(self, writer: EndianBinaryWriter): - super().save(writer, intern_call=True) - if self.platform == BuildTarget.NoTarget: - save_ptr(self.prefab_parent_object) - save_ptr(self.prefab_internal) diff --git a/UnityPy/classes/Font.py b/UnityPy/classes/Font.py deleted file mode 100644 index a4ba5dcb1..000000000 --- a/UnityPy/classes/Font.py +++ /dev/null @@ -1,85 +0,0 @@ -from .NamedObject import NamedObject -from .PPtr import PPtr - - -class Font(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - version = self.version - if version >= (5, 5): # 5.5 and up: - self.LineSpacing = reader.read_float() - self.DefaultMaterial = PPtr(reader) - self.FontSize = reader.read_float() - self.Texture = PPtr(reader) - self.AsciiStartOffset = reader.read_int() - self.Tracking = reader.read_float() - self.CharacterSpacing = reader.read_int() - self.CharacterPadding = reader.read_int() - self.ConvertCase = reader.read_int() - CharacterRects_size = reader.read_int() - for i in range(CharacterRects_size): - reader.Position += 44 # CharacterInfo data 41 - KerningValues_size = reader.read_int() - for i in range(KerningValues_size): - reader.Position += 8 - self.PixelScale = reader.read_float() - FontData_size = reader.read_int() - if FontData_size > 0: - self.FontData = reader.read_bytes(FontData_size) - else: - self.AsciiStartOffset = reader.read_int() - - if version[:1] <= (3,): - self.FontCountX = reader.read_int() - self.FontCountY = reader.read_int() - - self.Kerning = reader.read_float() - self.LineSpacing = reader.read_float() - - if version[:1] <= (3,): - PerCharacterKerning_size = reader.read_int() - for i in range(PerCharacterKerning_size): - first = reader.read_int() - second = reader.read_float() - else: - self.CharacterSpacing = reader.read_int() - self.CharacterPadding = reader.read_int() - - self.ConvertCase = reader.read_int() - self.DefaultMaterial = PPtr(reader) - - CharacterRects_size = reader.read_int() - for i in range(CharacterRects_size): - index = reader.read_int() - # Rectf uv - uvx = reader.read_float() - uvy = reader.read_float() - uvwidth = reader.read_float() - uvheight = reader.read_float() - # Rectf vert - vertx = reader.read_float() - verty = reader.read_float() - vertwidth = reader.read_float() - vertheight = reader.read_float() - width = reader.read_float() - - if version >= (4,): - flipped = reader.read_boolean() - reader.align_stream() - - self.Texture = PPtr(reader) - - KerningValues_size = reader.read_int() - for i in range(KerningValues_size): - pairfirst = reader.read_short() - pairsecond = reader.read_short() - second = reader.read_float() - - if version[:1] <= (3,): - self.GridFont = reader.read_boolean() - reader.align_stream() - else: - self.PixelScale = reader.read_float() - FontData_size = reader.read_int() - if FontData_size > 0: - self.FontData = reader.read_bytes(FontData_size) diff --git a/UnityPy/classes/GameObject.py b/UnityPy/classes/GameObject.py deleted file mode 100644 index c53f12e6b..000000000 --- a/UnityPy/classes/GameObject.py +++ /dev/null @@ -1,26 +0,0 @@ -from .EditorExtension import EditorExtension -from .PPtr import PPtr - - -class GameObject(EditorExtension): - """ - public PPtr[] m_Components; - public string m_Name; - public Transform m_Transform; - public MeshRenderer m_MeshRenderer; - public MeshFilter m_MeshFilter; - public SkinnedMeshRenderer m_SkinnedMeshRenderer; - public Animator m_Animator; - public Animation m_Animation; - """ - - def __init__(self, reader): - super().__init__(reader=reader) - component_size = reader.read_int() - self.components = [] - for i in range(component_size): - if self.version < (5, 5): - first = reader.read_int() - self.components.append(PPtr(reader)) - self.layer = reader.read_int() - self.name = reader.read_aligned_string() diff --git a/UnityPy/classes/Material.py b/UnityPy/classes/Material.py deleted file mode 100644 index 5b5d796b3..000000000 --- a/UnityPy/classes/Material.py +++ /dev/null @@ -1,65 +0,0 @@ -from .NamedObject import NamedObject -from .PPtr import PPtr - - -class Material(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - version = self.version - self.m_Shader = PPtr(reader) # Shader - - if version >= (5,): # 5.0 and up - self.m_ShaderKeywords = reader.read_aligned_string() - self.m_LightmapFlags = reader.read_u_int() - - elif version >= (4, 1): # 4.x - self.m_ShaderKeywords = reader.read_string_array() - - if version >= (5, 6): # 5.6 and up - self.m_EnableInstancingVariants = reader.read_boolean() - # var m_DoubleSidedGI = a_Stream.read_boolean() //2017 and up - reader.align_stream() - - if version >= (4, 3): # 4.3 and up - self.m_CustomRenderQueue = reader.read_int() - - if version >= (5, 1): # 5.1 and up - stringTagMapSize = reader.read_int() - self.stringTagMap = {} - for _ in range(stringTagMapSize): - first = reader.read_aligned_string() - second = reader.read_aligned_string() - self.stringTagMap[first] = second - - if version >= (5, 6): # 5.6 and up - self.disabledShaderPasses = reader.read_string_array() - - self.m_SavedProperties = UnityPropertySheet(reader) - - -class UnityTexEnv: - def __init__(self, reader): - self.m_Texture = PPtr(reader) # Texture - self.m_Scale = reader.read_vector2() - self.m_Offset = reader.read_vector2() - - -class UnityPropertySheet: - def __init__(self, reader): - m_TexEnvsSize = reader.read_int() - self.m_TexEnvs = {} - for i in range(m_TexEnvsSize): - key = reader.read_aligned_string() - self.m_TexEnvs[key] = UnityTexEnv(reader) - - m_FloatsSize = reader.read_int() - self.m_Floats = {} - for i in range(m_FloatsSize): - key = reader.read_aligned_string() - self.m_Floats[key] = reader.read_float() - - m_ColorsSize = reader.read_int() - self.m_Colors = {} - for i in range(m_ColorsSize): - key = reader.read_aligned_string() - self.m_Colors[key] = reader.read_color4() diff --git a/UnityPy/classes/Mesh.py b/UnityPy/classes/Mesh.py deleted file mode 100644 index 8e4b98b53..000000000 --- a/UnityPy/classes/Mesh.py +++ /dev/null @@ -1,754 +0,0 @@ -import math - -from .AnimationClip import AABB, PackedFloatVector, PackedIntVector -from .NamedObject import NamedObject -from .Texture2D import StreamingInfo -from ..helpers.ResourceReader import get_resource_data -from ..math import Matrix4x4, Vector3 - - -class MinMaxAABB: - def __init__(self, reader): - self.m_Min = reader.read_vector3() - self.m_Max = reader.read_vector3() - - -class CompressedMesh: - def __init__(self, reader): - version = reader.version - self.m_Vertices = PackedFloatVector(reader) - self.m_UV = PackedFloatVector(reader) - if version < (5,): # 5 down - self.m_BindPoses = PackedFloatVector(reader) - self.m_Normals = PackedFloatVector(reader) - self.m_Tangents = PackedFloatVector(reader) - self.m_Weights = PackedIntVector(reader) - self.m_NormalSigns = PackedIntVector(reader) - self.m_TangentSigns = PackedIntVector(reader) - if version >= (5,): # 5 and up - self.m_FloatColors = PackedFloatVector(reader) - self.m_BoneIndices = PackedIntVector(reader) - self.m_Triangles = PackedIntVector(reader) - if version >= (3, 5): # 3.5 and up - if version < (5,): # 5 down - self.m_Colors = PackedIntVector(reader) - else: - self.m_UVInfo = reader.read_u_int() - - -class StreamInfo: - def __init__(self, **kwargs): - if "reader" in kwargs: - reader = kwargs["reader"] - version = reader.version - self.channelMask = reader.read_u_int() - self.offset = reader.read_u_int() - - if version < (4,): # 4.0 down - self.stride = reader.read_u_int() - self.align = reader.read_u_int() - else: - self.stride = reader.read_byte() - self.dividerOp = reader.read_byte() - self.frequency = reader.read_u_short() - else: - self.__dict__ = kwargs - - -class ChannelInfo: - def __init__(self, reader): - self.stream = reader.read_byte() - self.offset = reader.read_byte() - self.format = reader.read_byte() - self.dimension = reader.read_byte() - - -class VertexData: - def __init__(self, reader): - self.reader = reader - version = reader.version - - if version < (2018,): # 2018 down - self.m_CurrentChannels = reader.read_u_int() - - self.m_VertexCount = reader.read_u_int() - - if version >= (4,): # 4.0 and up - m_ChannelsSize = reader.read_int() - self.m_Channels = [ChannelInfo(reader) for _ in range(m_ChannelsSize)] - - if version < (5,): # 5.0 down - if version < (4,): # 4.0 down - m_StreamsSize = 4 - else: - m_StreamsSize = reader.read_int() - - self.m_Streams = [StreamInfo(reader=reader) for _ in range(m_StreamsSize)] - - if version < (4,): # 4.0 down - self.GetChannels() - else: # 5.0 and up - self.GetStreams() - - self.m_DataSize = reader.read_bytes(reader.read_int()) - reader.align_stream() - - def GetStreams(self): - streamCount = 1 + ( - 0 if not self.m_Channels else max([x.stream for x in self.m_Channels]) - ) - self.m_Streams = {} - offset = 0 - for s in range(streamCount): - chnMask = 0 - stride = 0 - for chn, m_Channel in enumerate(self.m_Channels): - if m_Channel.stream == s: - if m_Channel.dimension > 0: - chnMask |= 1 << chn # Shift 1UInt << chn - stride += m_Channel.dimension * MeshHelper.GetChannelFormatSize( - m_Channel.format - ) - self.m_Streams[s] = StreamInfo( - channelMask=chnMask, - offset=offset, - stride=stride, - dividerOp=0, - frequency=0, - ) - offset += self.m_VertexCount * stride - # static size_t align_streamSize (size_t size) { return (size + (kVertexStreamAlign-1)) & ~(kVertexStreamAlign-1) - offset = (offset + (16 - 1)) & ~( - 16 - 1 - ) # (offset + (16u - 1u)) & ~(16u - 1u); - - def GetChannels(self): - self.m_Channels = [] # ChannelInfo[6] - for i in range(6): - self.m_Channels.append(ChannelInfo(self.reader)) - for s, m_Stream in enumerate(self.m_Streams): - channelMask = bytearray(m_Stream.channelMask) # BitArray - offset = 0 - for i in range(6): - if channelMask[i]: - m_Channel = self.m_Channels[i] - m_Channel.stream = s - m_Channel.offset = offset - if i in [0, 1]: - # 0 - kShaderChannelVertex - # 1 - kShaderChannelNormal - m_Channel.format = 0 # kChannelFormatFloat - m_Channel.dimension = 3 - elif i == 2: # kShaderChannelColor - m_Channel.format = 2 # kChannelFormatColor - m_Channel.dimension = 4 - elif i in [3, 4]: - # 3 - kShaderChannelTexCoord0 - # 4 - kShaderChannelTexCoord1 - m_Channel.format = 0 # kChannelFormatFloat - m_Channel.dimension = 2 - elif i == 5: # kShaderChannelTangent - m_Channel.format = 0 # kChannelFormatFloat - m_Channel.dimension = 4 - offset += m_Channel.dimension * MeshHelper.GetChannelFormatSize( - m_Channel.format - ) - - def FixChannel(self): - if any( - [x.dimension > 4 for x in self.m_Channels] - ): # m_Channels.FirstOrDefault(x => x.dimension > 4) != null: # - fixStream = max( - [x.stream for x in self.m_Channels] - ) # m_Channels.Max(x => x.stream) - fixChannels = [ - x for x in self.m_Channels if x.dimension > 0 and x.stream == fixStream - ] - stride = 0 - for i, curChannel in enumerate(fixChannels): - preChannel = fixChannels[i - 1] - offset = curChannel.offset - preChannel.offset - preChannel.dimension = offset / MeshHelper.GetChannelFormatSize( - preChannel.format - ) - stride += offset - # Fix Last - m_Channel = fixChannels[-1] - streamSize = len(self.m_DataSize) - self.m_Streams[fixStream].offset - totalStride = streamSize / self.m_VertexCount - channelStride = totalStride - stride - m_Channel.dimension = channelStride / MeshHelper.GetChannelFormatSize( - m_Channel.format - ) - self.GetStreams() - - -class BoneWeights4: - def __init__(self, reader=None): - if reader: - self.weight = reader.read_float_array(4) - self.boneIndex = reader.read_int_array(4) - else: - self.weight = [0.0] * 4 - self.boneIndex = [0] * 4 - - -class BlendShapeVertex: - def __init__(self, reader): - self.vertex = reader.read_vector3() - self.normal = reader.read_vector3() - self.tangent = reader.read_vector3() - self.index = reader.read_u_int() - - -class MeshBlendShape: - def __init__(self, reader): - version = reader.version - - if version < (4, 3): # 4.3 down - self.name = reader.read_aligned_string() - self.firstVertex = reader.read_u_int() - self.vertexCount = reader.read_u_int() - if version < (4, 3): # 4.3 down - self.aabbMinDelta = reader.read_vector3() - self.aabbMaxDelta = reader.read_vector3() - self.hasNormals = reader.read_boolean() - self.hasTangents = reader.read_boolean() - if version >= (4, 3): # 4.3 and up - reader.align_stream() - - -class MeshBlendShapeChannel: - def __init__(self, reader): - self.name = reader.read_aligned_string() - self.nameHash = reader.read_u_int() - self.frameIndex = reader.read_int() - self.frameCount = reader.read_int() - - -class BlendShapeData: - def __init__(self, reader): - version = reader.version - - if version >= (4, 3): # 4.3 and up - numVerts = reader.read_int() - self.vertices = [BlendShapeVertex(reader) for _ in range(numVerts)] - - numShapes = reader.read_int() - self.shapes = [MeshBlendShape(reader) for _ in range(numShapes)] - - numChannels = reader.read_int() - self.channels = [MeshBlendShapeChannel(reader) for _ in range(numChannels)] - self.fullWeights = reader.read_float_array() - else: - m_ShapesSize = reader.read_int() - self.m_Shapes = [MeshBlendShape(reader) for _ in range(m_ShapesSize)] - reader.align_stream() - m_ShapeVerticesSize = reader.read_int() - self.m_ShapeVertices = [ - BlendShapeVertex(reader) for _ in range(m_ShapeVerticesSize) - ] - - -class SubMesh: - def __init__(self, reader): - version = reader.version - - self.firstByte = reader.read_u_int() - self.indexCount = reader.read_u_int() - self.topology = reader.read_int() - - if version < (4,): # 4.0 down - self.triangleCount = reader.read_u_int() - - if version >= (2017, 3): # 2017.3 and up - self.baseVertex = reader.read_u_int() - - if version >= (3,): # 3.0 and up - self.firstVertex = reader.read_u_int() - self.vertexCount = reader.read_u_int() - self.localAABB = AABB(reader) - - -class Mesh(NamedObject): - m_StreamData: None - - def __init__(self, reader): - super().__init__(reader=reader) - version = reader.version - - self.m_Use16BitIndices = True - self.m_Indices = [] - - if version < (3, 5): # 3.5 down - self.m_Use16BitIndices = reader.read_int() > 0 - - if version[:2] <= (2, 5): # 2.5 and down - m_IndexBuffer_size = reader.read_int() - - if self.m_Use16BitIndices: # - self.m_IndexBuffer = [ - reader.read_u_short() - for _ in range(math.ceil(m_IndexBuffer_size / 2)) - ] - reader.align_stream() - else: - self.m_IndexBuffer = reader.read_u_int_array(m_IndexBuffer_size // 4) - - m_SubMeshesSize = reader.read_int() - self.m_SubMeshes = [SubMesh(reader) for _ in range(m_SubMeshesSize)] - - if version >= (4, 1): # 4.1 and up - self.m_Shapes = BlendShapeData(reader) - - if version >= (4, 3): # 4.3 and up - self.m_BindPose = reader.read_matrix_array() - self.m_BoneNameHashes = reader.read_u_int_array() - self.m_RootBoneNameHash = reader.read_u_int() - - if version >= (2, 6): # 2.6.0 and up - if version >= (2019,): # 2019 and up - m_BonesAABBSize = reader.read_int() - self.m_BonesAABB = [MinMaxAABB(reader) for _ in range(m_BonesAABBSize)] - self.m_VariableBoneCountWeights = reader.read_u_int_array() - - self.m_MeshCompression = reader.read_byte() - if version >= (4,): # - if version < (5,): # - self.m_StreamCompression = reader.read_byte() - self.m_IsReadable = reader.read_boolean() - self.m_KeepVertices = reader.read_boolean() - self.m_KeepIndices = reader.read_boolean() - reader.align_stream() - - # Unity fixed it in 2017.3.1p1 and later versions - if ( - version >= (2017, 4) # 2017.4 - or version[:3] == (2017, 3, 1) and self.build_type.IsPatch # fixed after 2017.3.1px - or version[:2] == (2017, 3) and self.m_MeshCompression == 0 # 2017.3.xfx with no compression - ): - self.m_IndexFormat = reader.read_int() - - m_IndexBuffer_size = reader.read_int() - if self.m_Use16BitIndices: - self.m_IndexBuffer = [ - reader.read_u_short() for _ in range(m_IndexBuffer_size // 2) - ] - reader.align_stream() - else: - self.m_IndexBuffer = reader.read_u_int_array(m_IndexBuffer_size // 4) - - if version < (3, 5): # 3.4.2 and earlier - m_VertexCount = reader.read_int() - self.m_Vertices = reader.read_float_array(m_VertexCount * 3) # Vector3 - - m_SkinSize = reader.read_int() - self.m_Skin = [BoneWeights4(reader) for _ in range(m_SkinSize)] - - self.m_BindPose = reader.read_matrix_array() - self.m_UV0 = reader.read_float_array(reader.read_int() * 2) # Vector2 - self.m_UV1 = reader.read_float_array(reader.read_int() * 2) # Vector2 - - if version[:2] <= (2, 5): # 2.5 and down - m_TangentSpace_size = reader.read_int() - self.m_Normals = [0] * (m_TangentSpace_size * 3) - self.m_Tangets = [0] * (m_TangentSpace_size * 4) - for v in range(m_TangentSpace_size): - self.m_Normals[v * 3] = reader.read_float() - self.m_Normals[v * 3 + 1] = reader.read_float() - self.m_Normals[v * 3 + 2] = reader.read_float() - self.m_Tangents[v * 3] = reader.read_float() - self.m_Tangents[v * 3 + 1] = reader.read_float() - self.m_Tangents[v * 3 + 2] = reader.read_float() - self.m_Tangents[v * 3 + 3] = reader.read_float() # handedness - else: # 2.6.0 and later - self.m_Tangents = reader.read_float_array( - reader.read_int() * 4 - ) # Vector4 - self.m_Normals = reader.read_float_array( - reader.read_int() * 3 - ) # Vector3 - else: - if version < (2018, 2): # 2018.2 down - m_SkinSize = reader.read_int() - self.m_Skin = [BoneWeights4(reader) for _ in range(m_SkinSize)] - - if version[:2] <= (4, 2): # 4.2 and down - self.m_BindPose = reader.read_matrix_array() - - self.m_VertexData = VertexData(reader) - - if version >= (2, 6): # 2.6.0 and later - self.m_CompressedMesh = CompressedMesh(reader) - - self.m_LocalAABB = AABB(reader) - - if version[:2] <= (3, 4): # 3.4.2 and earlier - m_Colors_size = reader.read_int() - self.mColors = [ - reader.read_byte() # (float)reader.read_byte() / 0xFF - for _ in range(m_Colors_size * 4) - ] - - m_CollisionTriangles_size = reader.read_int() - reader.Position += m_CollisionTriangles_size * 4 # UInt32 indices - m_CollisionVertexCount = reader.read_int() - - self.m_MeshUsageFlags = reader.read_int() - - if version >= (5,): # 5.0 and up - self.m_BakedConvexCollisionMesh = reader.read_bytes(reader.read_int()) - reader.align_stream() - self.m_BakedTriangleCollisionMesh = reader.read_bytes(reader.read_int()) - reader.align_stream() - - if version >= (2018, 2): # 2018.2 and up - self.m_MeshMetrics = [reader.read_float(), reader.read_float()] - - if version >= (2018, 3): # 2018.3 and up - reader.align_stream() - self.m_StreamData = StreamingInfo(reader) - - # try: - # self.ProcessData() - # except: - # pass - - def ProcessData(self): - if self.m_StreamData and self.m_StreamData.path: - if self.m_VertexData.m_VertexCount > 0: # - self.m_VertexData.m_DataSize = get_resource_data( - self.m_StreamData.path, - self.assets_file, - self.m_StreamData.offset, - self.m_StreamData.size, - ) - # Fix channel after 2018.3 - version = self.version - if version >= (2018, 3): # - self.m_VertexData.FixChannel() - if version >= (3, 5): # 3.5 and up - self.ReadVertexData() - - if version >= (2, 6): # 2.6.0 and later - self.DecompressCompressedMesh() - - self.BuildFaces() - - def ReadVertexData(self): - m_VertexCount = self.m_VertexData.m_VertexCount - version = self.version - for chn, m_Channel in enumerate(self.m_VertexData.m_Channels): - if m_Channel.dimension > 0: - m_Stream = self.m_VertexData.m_Streams[m_Channel.stream] - channelMask = bytearray(m_Stream.channelMask) - if channelMask[chn]: - if version < (2018,) and chn == 2 and m_Channel.format == 2: # 2018.0 down - m_Channel.dimension = 4 - - componentByteSize = MeshHelper.GetChannelFormatSize( - m_Channel.format - ) - componentBytes = bytearray( - m_VertexCount * m_Channel.dimension * componentByteSize - ) - for v in range(m_VertexCount): - vertexOffset = ( - m_Stream.offset + m_Channel.offset + m_Stream.stride * v - ) - for d in range(m_Channel.dimension): - componentOffset = vertexOffset + componentByteSize * d - dstOffset = componentByteSize * ( - v * m_Channel.dimension + d - ) - componentBytes[ - dstOffset : dstOffset + componentByteSize - ] += self.m_VertexData.m_DataSize[ - componentOffset : componentOffset + componentByteSize - ] - - if ( - self.reader.endian == ">" and componentByteSize > 1 - ): # if big endian, swap bytes - for i in range(0, len(componentBytes), componentByteSize): - componentBytes[i : i + componentByteSize] = componentBytes[ - i : i + componentByteSize - ][::-1] - - componentsIntArray = [] - componentsFloatArray = [] - if m_Channel.format == 10 or m_Channel.format == 11: # - componentsIntArray = MeshHelper.BytesToIntArray(componentBytes) - else: - componentsFloatArray = MeshHelper.BytesToFloatArray( - componentBytes, componentByteSize - ) - - if version >= (2018,): # 2018.0 and up - if chn == 0: # kShaderChannelVertex - self.m_Vertices = componentsFloatArray - elif chn == 1: # kShaderChannelNormal - self.m_Normals = componentsFloatArray - elif chn == 2: # kShaderChannelTangent - self.m_Tangents = componentsFloatArray - elif chn == 3: # kShaderChannelColor - self.m_Colors = componentsFloatArray - elif chn == 4: # kShaderChannelTexCoord0 - self.m_UV0 = componentsFloatArray - elif chn == 5: # kShaderChannelTexCoord1 - self.m_UV1 = componentsFloatArray - elif chn == 6: # kShaderChannelTexCoord2 - self.m_UV2 = componentsFloatArray - elif chn == 7: # kShaderChannelTexCoord3 - self.m_UV3 = componentsFloatArray - # kShaderChannelTexCoord4 8 - # kShaderChannelTexCoord5 9 - # kShaderChannelTexCoord6 10 - # kShaderChannelTexCoord7 11 - # 2018.2 and up - elif chn == 12: # kShaderChannelBlendWeight - if getattr(self, "m_Skin", None): - self.InitMSkin() - for i in range(m_VertexCount): - for j in range(m_Channel.dimension): - self.m_Skin[i].weight[j] = componentsFloatArray[ - i * m_Channel.dimension + j - ] - elif chn == 13: # kShaderChannelBlendIndices - if getattr(self, "m_Skin", None): - self.InitMSkin() - for i in range(m_VertexCount): - for j in range(m_Channel.dimension): - self.m_Skin[i].boneIndex[j] = componentsIntArray[ - i * m_Channel.dimension + j - ] - - else: - if chn == 0: # kShaderChannelVertex - self.m_Vertices = componentsFloatArray - elif chn == 1: # kShaderChannelNormal - self.m_Normals = componentsFloatArray - elif chn == 2: # kShaderChannelColor - self.m_Colors = componentsFloatArray - elif chn == 3: # kShaderChannelTexCoord0 - self.m_UV0 = componentsFloatArray - elif chn == 4: # kShaderChannelTexCoord1 - self.m_UV1 = componentsFloatArray - elif chn == 5: - if version >= (5,): # kShaderChannelTexCoord2 - self.m_UV2 = componentsFloatArray - else: # kShaderChannelTangent - self.m_Tangents = componentsFloatArray - elif chn == 6: # kShaderChannelTexCoord3 - self.m_UV3 = componentsFloatArray - elif chn == 7: # kShaderChannelTangent - self.m_Tangents = componentsFloatArray - - def DecompressCompressedMesh(self): - # Vertex - version = self.version - m_CompressedMesh = self.m_CompressedMesh - if m_CompressedMesh.m_Vertices.m_NumItems > 0: - m_VertexCount = m_CompressedMesh.m_Vertices.m_NumItems / 3 - m_Vertices = m_CompressedMesh.m_Vertices.UnpackFloats(3, 4) - # UV - if m_CompressedMesh.m_UV.m_NumItems > 0: # - m_UV0 = m_CompressedMesh.m_UV.UnpackFloats(2, 4, 0, m_VertexCount) - if m_CompressedMesh.m_UV.m_NumItems >= m_VertexCount * 4: # - m_UV1 = m_CompressedMesh.m_UV.UnpackFloats( - 2, 4, m_VertexCount * 2, m_VertexCount - ) - if m_CompressedMesh.m_UV.m_NumItems >= m_VertexCount * 6: # - m_UV2 = m_CompressedMesh.m_UV.UnpackFloats( - 2, 4, m_VertexCount * 4, m_VertexCount - ) - if m_CompressedMesh.m_UV.m_NumItems >= m_VertexCount * 8: # - m_UV3 = m_CompressedMesh.m_UV.UnpackFloats( - 2, 4, m_VertexCount * 6, m_VertexCount - ) - # BindPose - if version < (5,): # 5.0 down - if m_CompressedMesh.m_BindPoses.m_NumItems > 0: # - m_BindPoses_Unpacked = m_CompressedMesh.m_BindPoses.UnpackFloats( - 16, 4 * 16 - ) - self.m_BindPose = [ - Matrix4x4(m_BindPoses_Unpacked[i : i + 16]) - for i in range(0, m_CompressedMesh.m_BindPoses.m_NumItems, 16) - ] - - # Normal - if m_CompressedMesh.m_Normals.m_NumItems > 0: - normalData = m_CompressedMesh.m_Normals.UnpackFloats(2, 4 * 2) - signs = m_CompressedMesh.m_NormalSigns.UnpackInts() - self.m_Normals = [] # float[m_CompressedMesh.m_Normals.m_NumItems / 2 * 3] - for i in range(0, math.ceil(m_CompressedMesh.m_Normals.m_NumItems / 2)): - x = normalData[i + 0] - y = normalData[i + 1] - zsqr = 1 - x * x - y * y - if zsqr >= 0: - z = math.sqrt(zsqr) - else: - z = 0 - normal = Vector3(x, y, z) - normal.normalize() - x = normal.X - y = normal.Y - z = normal.Z - if signs[i] == 0: - z = -z - self.m_Normals.extend([x, y, z]) - # Tangent - if m_CompressedMesh.m_Tangents.m_NumItems > 0: - tangentData = m_CompressedMesh.m_Tangents.UnpackFloats(2, 4 * 2) - signs = m_CompressedMesh.m_TangentSigns.UnpackInts() - self.m_Tangents = ( - [] - ) # float[m_CompressedMesh.m_Tangents.m_NumItems / 2 * 4] - for i in range(0, math.ceil(m_CompressedMesh.m_Tangents.m_NumItems / 2)): - x = tangentData[i * 2 + 0] - y = tangentData[i * 2 + 1] - zsqr = 1 - x * x - y * y - if zsqr >= 0: - z = math.sqrt(zsqr) - else: - z = 0 - vector3f = Vector3(x, y, z) - vector3f.Normalize() - x = vector3f.X - y = vector3f.Y - z = vector3f.Z - if signs[i * 2 + 0] == 0: # - z = -z - w = 1.0 if signs[i * 2 + 1] > 0 else -1.0 - self.m_Tangents.extend([x, y, z, w]) - - # FloatColor - if version >= (5,): # 5.0 and up - if m_CompressedMesh.m_FloatColors.m_NumItems > 0: # - self.m_Colors = m_CompressedMesh.m_FloatColors.UnpackFloats(1, 4) - # Skin - if m_CompressedMesh.m_Weights.m_NumItems > 0: - weights = m_CompressedMesh.m_Weights.UnpackInts() - boneIndices = m_CompressedMesh.m_BoneIndices.UnpackInts() - - self.InitMSkin() - - bonePos = 0 - boneIndexPos = 0 - j = 0 - sum = 0 - - for i in range(m_CompressedMesh.m_Weights.m_NumItems): - # read bone index and weight. - self.m_Skin[bonePos].weight[j] = weights[i] / 31.0 - self.m_Skin[bonePos].boneIndex[j] = boneIndices[boneIndexPos] - boneIndexPos += 1 - j += 1 - sum += weights[i] - - # the weights add up to one. fill the rest for this vertex with zero, and continue with next one. - if sum >= 31: # - while j < 4: - self.m_Skin[bonePos].weight[j] = 0 - self.m_Skin[bonePos].boneIndex[j] = 0 - bonePos += 1 - j += 1 - j = 0 - sum = 0 - # we read three weights, but they don't add up to one. calculate the fourth one, and read - # missing bone index. continue with next vertex. - elif j == 3: # - self.m_Skin[bonePos].weight[j] = (31 - sum) / 31.0 - self.m_Skin[bonePos].boneIndex[j] = boneIndices[boneIndexPos] - boneIndexPos += 1 - bonePos += 1 - j = 0 - sum = 0 - - # IndexBuffer - if m_CompressedMesh.m_Triangles.m_NumItems > 0: # - self.m_IndexBuffer = m_CompressedMesh.m_Triangles.UnpackUInts() - # Color - if ( - hasattr(m_CompressedMesh, "m_Colors") - and m_CompressedMesh.m_Colors.m_NumItems > 0 - ): - m_CompressedMesh.m_Colors.m_NumItems *= 4 - m_CompressedMesh.m_Colors.m_BitSize /= 4 - tempColors = m_CompressedMesh.m_Colors.UnpackInts() - self.m_Colors = [color / 255 for color in tempColors] - - def BuildFaces(self): - for m_SubMesh in self.m_SubMeshes: - firstIndex = m_SubMesh.firstByte // 2 - if not self.m_Use16BitIndices: - firstIndex /= 2 - - if m_SubMesh.topology == 0: - for i in range(0, m_SubMesh.indexCount, 3): - self.m_Indices.append(self.m_IndexBuffer[firstIndex + i]) - self.m_Indices.append(self.m_IndexBuffer[firstIndex + i + 1]) - self.m_Indices.append(self.m_IndexBuffer[firstIndex + i + 2]) - else: - j = 0 - for i in range(0, m_SubMesh.indexCount - 2): - fa = self.m_IndexBuffer[firstIndex + i] - fb = self.m_IndexBuffer[firstIndex + i + 1] - fc = self.m_IndexBuffer[firstIndex + i + 2] - - if (fa != fb) and (fa != fc) and (fc != fb): - self.m_Indices.append(fa) - self.m_Indices.extend([fc, fb] if i % 2 else [fb, fc]) - j += 1 - # fix indexCount - m_SubMesh.indexCount = j * 3 - - def InitMSkin(self): - self.m_Skin = [BoneWeights4() for _ in range(self.m_VertexCount)] - - -class MeshHelper: - @staticmethod - def GetChannelFormatSize(format): - if format == 0: # kChannelFormatFloat - return 4 - elif format == 1: # kChannelFormatFloat16 - return 2 - elif format == 2: # kChannelFormatColor - return 1 - elif format == 3: # kChannelFormatByte - return 1 - elif format == 4: # kChannelFormatUInt32 - return 4 - elif format == 10: # kChannelFormatInt32 - return 4 - elif format == 11: # kChannelFormatInt32 - return 4 - else: - return 0 - - @staticmethod - def BytesToFloatArray(inputBytes, size): - result = [] - for i in range(math.ceil(len(inputBytes) / size)): - value = 0 - if size == 1: - value = inputBytes[i] / 255.0 - elif size == 2: - value = float.from_bytes( - inputBytes[i * 2 : i * 2 + 2], byteorder="big", signed=True - ) # return Half.ToHalf((ushort)BitConverter.ToInt16(value, startIndex)); - elif size == 4: - value = float.from_bytes( - inputBytes[i * 4 : i * 4 + 4], byteorder="big", signed=True - ) - result[i] = value - return result - - @staticmethod - def BytesToIntArray(inputBytes): - return [ - int.from_bytes(inputBytes[i : i + 4], byteorder="big", signed=True) - for i in range(0, len(inputBytes), 4) - ] diff --git a/UnityPy/classes/MeshFilter.py b/UnityPy/classes/MeshFilter.py deleted file mode 100644 index ea66311e5..000000000 --- a/UnityPy/classes/MeshFilter.py +++ /dev/null @@ -1,8 +0,0 @@ -from .Component import Component -from .PPtr import PPtr - - -class MeshFilter(Component): - def __init__(self, reader): - super().__init__(reader=reader) - self.Mesh = PPtr(reader) diff --git a/UnityPy/classes/MeshRenderer.py b/UnityPy/classes/MeshRenderer.py deleted file mode 100644 index ada714ac9..000000000 --- a/UnityPy/classes/MeshRenderer.py +++ /dev/null @@ -1,6 +0,0 @@ -from .Renderer import Renderer - - -class MeshRenderer(Renderer): - def __init__(self, reader): - super().__init__(reader=reader) diff --git a/UnityPy/classes/MonoBehaviour.py b/UnityPy/classes/MonoBehaviour.py deleted file mode 100644 index e98399ea3..000000000 --- a/UnityPy/classes/MonoBehaviour.py +++ /dev/null @@ -1,11 +0,0 @@ -from .Behaviour import Behaviour -from .PPtr import PPtr - - -class MonoBehaviour(Behaviour): - def __init__(self, reader): - super().__init__(reader=reader) - self.script = PPtr(reader) - self.name = reader.read_aligned_string() - - self.read_type_tree() diff --git a/UnityPy/classes/MonoScript.py b/UnityPy/classes/MonoScript.py deleted file mode 100644 index c5c043fd8..000000000 --- a/UnityPy/classes/MonoScript.py +++ /dev/null @@ -1,22 +0,0 @@ -from .NamedObject import NamedObject - - -class MonoScript(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - version = self.version - if version >= (3, 4): # 3.4 and up - self.execution_order = reader.read_int() - if version < (5,): # 5.0 down - self.properties_hash = reader.read_u_int() - else: - self.properties_hash = reader.read_bytes(16) - if version < (3,): # 3.0 down - self.path_name = reader.read_aligned_string() - - self.class_name = reader.read_aligned_string() - if version >= (3,): # 3.0 and up - self.namespace = reader.read_aligned_string() - self.assembly_name = reader.read_aligned_string() - if version < (2018, 2): # 2018.2 down - self.is_editor_script = reader.read_boolean() diff --git a/UnityPy/classes/MovieTexture.py b/UnityPy/classes/MovieTexture.py deleted file mode 100644 index 9a1943ae9..000000000 --- a/UnityPy/classes/MovieTexture.py +++ /dev/null @@ -1,11 +0,0 @@ -from .PPtr import PPtr -from .Texture import Texture - - -class MovieTexture(Texture): - def __init__(self, reader): - super().__init__(reader=reader) - self.Loop = reader.read_boolean() - reader.align_stream() - self.AudioClip = PPtr(reader) # AudioClip - self.m_MovieData = reader.read_bytes(reader.read_int()) diff --git a/UnityPy/classes/NamedObject.py b/UnityPy/classes/NamedObject.py deleted file mode 100644 index 40a1905ef..000000000 --- a/UnityPy/classes/NamedObject.py +++ /dev/null @@ -1,13 +0,0 @@ -from .EditorExtension import EditorExtension -from ..streams import EndianBinaryWriter - - -class NamedObject(EditorExtension): - def __init__(self, reader): - super().__init__(reader=reader) - self.reader.reset() - self.name = self.reader.read_aligned_string() - - def save(self, writer: EndianBinaryWriter): - super().save(writer) - writer.write_aligned_string(self.name) diff --git a/UnityPy/classes/Object.py b/UnityPy/classes/Object.py index 550d43c9e..c6ea23738 100644 --- a/UnityPy/classes/Object.py +++ b/UnityPy/classes/Object.py @@ -1,128 +1,59 @@ -from ..enums import BuildTarget -from ..helpers import TypeTreeHelper -from ..streams import EndianBinaryWriter, EndianBinaryReader +from __future__ import annotations +from abc import ABC, ABCMeta +from typing import TYPE_CHECKING, Any, Dict, Optional -class Object: - type_tree: dict +if TYPE_CHECKING: + from ..files.ObjectReader import ObjectReader + from ..files.SerializedFile import SerializedFile - def __init__(self, reader): - self.reader = reader - self.assets_file = reader.assets_file - self.type = reader.type - self.path_id = reader.path_id - self.version = reader.version - self.build_type = reader.build_type - self.platform = reader.platform - self.serialized_type = reader.serialized_type - self.byte_size = reader.byte_size - self.environment = reader.assets_file.environment - if self.platform == BuildTarget.NoTarget: - self._object_hide_flags = reader.read_u_int() +class Object(ABC, metaclass=ABCMeta): + object_reader: Optional[ObjectReader] = None - self.container = ( - self.assets_file._container[self.path_id] - if self.path_id in self.assets_file._container - else None - ) + def __init__(self, **kwargs: Dict[str, Any]) -> None: + self.__dict__.update(**kwargs) - self.reader.reset() - if type(self) == Object: - self.__dict__.update(self.read_type_tree().__dict__) + def set_object_reader(self, object_info: ObjectReader[Any]): + self.object_reader = object_info - def has_struct_member(self, name: str) -> bool: - return self.serialized_type.m_Nodes and any( - [x.name == name for x in self.serialized_type.m_Nodes] - ) + def __repr__(self) -> str: + return f"<{self.__class__.__name__}>" - def dump(self) -> str: - self.reader.reset() - if getattr(self.serialized_type, "nodes", None): - sb = [] - TypeTreeHelper(self.reader).read_type_string(sb, self.serialized_type.nodes) - return "".join(sb) - return "" + @property + def assets_file(self) -> Optional[SerializedFile]: + if self.object_reader: + return self.object_reader.assets_file + return None - def read_type_tree(self) -> dict: - self.reader.reset() - if self.serialized_type.nodes: - self.type_tree = TypeTreeHelper(self.reader).read_value( - self.serialized_type.nodes, 0 - ) - else: - self.type_tree = {} - return NodeHelper(self.type_tree, self.assets_file) + def save(self) -> None: + if self.object_reader is None: + raise ValueError("ObjectReader not set") - def get_raw_data(self) -> bytes: - self.reader.reset() - return self.reader.read_bytes(self.byte_size) + self.object_reader.save_typetree(self) - def save(self, writer: EndianBinaryWriter, intern_call=False): - if intern_call: - if self.platform == BuildTarget.NoTarget: - writer.write_u_int(self._object_hide_flags) + def __copy__(self) -> Object: + clz = self.__class__ + data = { + key: value + for key, value in self.__dict__.items() + if isinstance(key, str) and (not key.startswith("__") or key == "__node__") and not callable(value) + } + try: + # covers UnknownObject + return clz(**data) + except TypeError: + from ..helpers.TypeTreeHelper import get_annotation_keys - def _save(self, writer): - # the reader is actually an ObjectReader, - # the data value is written back into the asset - self.reader.data = writer.bytes + keys = set(self.__dict__.keys()) + annotation_keys = get_annotation_keys(clz) + extra_keys = keys - annotation_keys + instance = clz(**{key: data[key] for key in annotation_keys}) + for key in extra_keys: + setattr(instance, key, data[key]) + return instance - def __getattr__(self, item): - """ - If item not found in __dict__, read type_tree and check if it is in there. - """ - self.read_type_tree() - if item in self.type_tree: - return self.type_tree[item] - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.name) - - -from .PPtr import PPtr - - -class NodeHelper(object): - def __init__(self, data, assets_file): - if "m_PathID" in data and "m_FileID" in data: - # used to make pointers directly useable - self.path_id = data["m_PathID"] - self.file_id = data["m_FileID"] - self.index = data.get("m_Index", -2) - self.assets_file = assets_file - self.__class__ = PPtr - else: - self.__dict__ = { - str(key): NodeHelper(val, assets_file) for key, val in data.items() - } - - def __new__(cls, data, assets_file): - if isinstance(data, dict): - return super(NodeHelper, cls).__new__(cls) - elif isinstance(data, list): - return [NodeHelper(x, assets_file) for x in data] - return data - - def __item__(self, key): - return self.__dict__[key] - - def to_dict(self): - def dump(val): - return ( - val.to_dict() - if isinstance(val, NodeHelper) - else [dump(item) for item in val] - if isinstance(val, list) - else {"m_PathID": val.path_id, "m_FileID": val.file_id} - if isinstance(val, PPtr) - else [x for x in val] - if isinstance(val, (bytearray, bytes)) - else val - ) - - return {key: dump(val) for key, val in self.__dict__.items()} - - def __repr__(self): - return "" % self.__dict__.__repr__() +__all__ = [ + "Object", +] diff --git a/UnityPy/classes/PPtr.py b/UnityPy/classes/PPtr.py index 153e573b1..313e62745 100644 --- a/UnityPy/classes/PPtr.py +++ b/UnityPy/classes/PPtr.py @@ -1,177 +1,112 @@ -from ..streams import EndianBinaryReader, EndianBinaryWriter - - -class PPtr: - def __init__(self, reader: EndianBinaryReader): - self.index = -2 - self.file_id = reader.read_int() - self.path_id = reader.read_int() if reader.version2 < 14 else reader.read_long() - self.assets_file = reader.assets_file - - def __getattr__(self, key): - # get manager - manager = None - if self.file_id == 0: - manager = self.assets_file - - elif self.file_id > 0 and self.file_id - 1 < len(self.assets_file.externals): - if self.index == -2: - external_name = self.assets_file.externals[self.file_id - 1].name - - files = self.assets_file.parent.files - if external_name not in files: - external_name = external_name.upper() - manager = self.assets_file.parent.files[external_name] - - if manager and self.path_id in manager.objects: - self = manager.objects[self.path_id] - return getattr(self, key) - - if self.index != -2: - raise NotImplementedError("PPtr") - - """ - if (!assetsFileIndexCache.TryGetValue(name, out index)) - { - index = assetsFileList.FindIndex(x => x.upperFileName == name); - assetsFileIndexCache.Add(name, index); - } - } - - if (index >= 0) - { - result = assetsFileList[index]; - return true; - } - } - if self.path_id in self.assets_file.objects: - return self.assets_file.objects[self.path_id] - else: - return super(PPtr, self).__new__(self) - """ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Generic, Optional, TypeVar, cast + +from attr import define + +if TYPE_CHECKING: + from ..enums.ClassIDType import ClassIDType + from ..files.ObjectReader import ObjectReader + from ..files.SerializedFile import SerializedFile + +T = TypeVar("T") + + +@define(slots=True, kw_only=True) +class PPtr(Generic[T]): + m_FileID: int + m_PathID: int + assetsfile: Optional[SerializedFile] = None + + @property + def file_id(self) -> int: + # backwards compatibility + return self.m_FileID + + @property + def path_id(self) -> int: + # backwards compatibility + return self.m_PathID + + @property + def type(self) -> ClassIDType: + return self.deref().type - def __repr__(self): - return self.__class__.__name__ + # backwards compatibility - to be removed in UnityPy 2 + def read(self): + return self.deref_parse_as_object() + + # backwards compatibility - to be removed in UnityPy 2 + def read_typetree(self): + return self.deref_parse_as_dict() + + def deref(self, assetsfile: Optional[SerializedFile] = None) -> ObjectReader[T]: + assetsfile = assetsfile or self.assetsfile + + if assetsfile is None: + raise ValueError("PPtr can't deref without an assetsfile!") + + if self.m_PathID == 0: + raise ValueError("PPtr can't deref with m_PathID == 0!") + + assetsfile_dst = None + if self.m_FileID == 0: + assetsfile_dst = assetsfile or self.assetsfile + else: + # resolve file id to external name + external_id = self.m_FileID - 1 + if external_id >= len(assetsfile.externals): + raise FileNotFoundError("Failed to resolve pointer - invalid m_FileID!") + external = assetsfile.externals[external_id] + + # resolve external name to assetsfile + container = assetsfile.parent + if container is None: + # TODO - use default fs + raise FileNotFoundError(f"PPtr points to {external.path} but no container is set!") + + external_clean_path = external.path + if external_clean_path.startswith("archive:/"): + external_clean_path = external_clean_path[9:] + if external_clean_path.startswith("assets/"): + external_clean_path = external_clean_path[7:] + external_clean_path = external_clean_path.rsplit("/")[-1].lower() + + for key, file in container.files.items(): + if key.lower() == external_clean_path: + assetsfile_dst = file + break + else: + env = assetsfile.environment + cab = env.find_file(external_clean_path) + if cab: + assetsfile_dst = cab + else: + raise FileNotFoundError(f"Failed to resolve pointer - {external.path} not found!") + + if assetsfile_dst is None: + raise FileNotFoundError(f"Failed to resolve pointer - {self.m_FileID} not found!") + + return cast("ObjectReader[T]", assetsfile_dst.objects[self.m_PathID]) + + def deref_parse_as_object(self, assetsfile: Optional[SerializedFile] = None) -> T: + return self.deref(assetsfile).parse_as_object() + + def deref_parse_as_dict(self, assetsfile: Optional[SerializedFile] = None) -> dict[str, Any]: + return self.deref(assetsfile).parse_as_dict() def __bool__(self): - return False - - -def save_ptr(obj, writer: EndianBinaryWriter): - if isinstance(obj, PPtr): - writer.write_int(obj.file_id) - writer.write_int(obj.path_id) - else: - writer.write_int(0) # it's usually 0...... - writer.write_int(obj.path_id) - - -""" - def TryGetAssetsFile(self): - result = None - if m_FileID == 0: - return self.assets_file - - if m_FileID > 0 and m_FileID - 1 < len(self.assets_file.externals): - var assetsManager = assetsFile.assetsManager - var assetsFileList = assetsManager.assetsFileList - var assetsFileIndexCache = assetsManager.assetsFileIndexCache - - if self.index == -2: - external_name = self.assets_file.eternals[self.file_id - 1].filename - - - if (!assetsFileIndexCache.TryGetValue(name, out index)) - { - index = assetsFileList.FindIndex(x => x.upperFileName == name); - assetsFileIndexCache.Add(name, index); - } - } - - if (index >= 0) - { - result = assetsFileList[index]; - return true; - } - } - - return false; - } - - public bool TryGet(out T result) - { - if (TryGetAssetsFile(out var sourceFile)) - { - if (sourceFile.Objects.TryGetValue(m_PathID, out var obj)) - { - if (obj is T variable) - { - result = variable; - return true; - } - } - } - - result = null; - return false; - } - - public bool TryGet(out T2 result) where T2 : Object - { - if (TryGetAssetsFile(out var sourceFile)) - { - if (sourceFile.Objects.TryGetValue(m_PathID, out var obj)) - { - if (obj is T2 variable) - { - result = variable; - return true; - } - } - } - - result = null; - return false; - } - - public void Set(T m_Object) - { - var name = m_Object.assetsFile.upperFileName; - if (string.Equals(assetsFile.upperFileName, name, StringComparison.Ordinal)) - { - m_FileID = 0; - } - else - { - m_FileID = assetsFile.m_Externals.FindIndex(x => string.Equals(x.fileName, name, StringComparison.OrdinalIgnoreCase)); - if (m_FileID == -1) - { - assetsFile.m_Externals.Add(new FileIdentifier - { - fileName = m_Object.assetsFile.fileName - }); - m_FileID = assetsFile.m_Externals.Count; - } - else - { - m_FileID += 1; - } - } - - var assetsManager = assetsFile.assetsManager; - var assetsFileList = assetsManager.assetsFileList; - var assetsFileIndexCache = assetsManager.assetsFileIndexCache; - - if (!assetsFileIndexCache.TryGetValue(name, out index)) - { - index = assetsFileList.FindIndex(x => x.upperFileName == name); - assetsFileIndexCache.Add(name, index); - } - - m_PathID = m_Object.m_PathID; - } - - public bool IsNull => m_PathID == 0 || m_FileID < 0; - } -} -""" + return self.m_PathID != 0 + + def __hash__(self) -> int: + return hash((self.m_FileID, self.m_PathID)) + + def __eq__(self, other: object) -> bool: + if not isinstance(other, PPtr): + return False + return self.m_FileID == other.m_FileID and self.m_PathID == other.m_PathID + + +__all__ = [ + "PPtr", +] diff --git a/UnityPy/classes/PlayerSettings.py b/UnityPy/classes/PlayerSettings.py deleted file mode 100644 index e291d7ea5..000000000 --- a/UnityPy/classes/PlayerSettings.py +++ /dev/null @@ -1,29 +0,0 @@ -from .Object import Object - - -class PlayerSettings(Object): - def __init__(self, reader): - super().__init__(reader=reader) - version = self.version - if version >= (5, 4): # 5.4.0 nad up - self.productGUID = reader.read_bytes(16) - - self.AndroidProfiler = reader.read_boolean() - # bool AndroidFilterTouchesWhenObscured 2017.2 and up - # bool AndroidEnableSustainedPerformanceMode 2018 and up - reader.align_stream() - self.defaultScreenOrientation = reader.read_int() - self.targetDevice = reader.read_int() - if version < (5, 3): # 5.3 down - if version < (5,): # 5.0 down - self.targetPlatform = reader.read_int() # 4.0 and up targetGlesGraphics - if version >= (4, 6): # 4.6 and up - self.targetIOSGraphics = reader.read_int() - self.targetResolution = reader.read_int() - else: - self.useOnDemandResources = reader.read_boolean() - reader.align_stream() - if version >= (3, 5): # 3.5 and up - self.accelerometerFrequency = reader.read_int() - self.companyName = reader.read_aligned_string() - self.productName = reader.read_aligned_string() diff --git a/UnityPy/classes/RectTransform.py b/UnityPy/classes/RectTransform.py deleted file mode 100644 index 735149205..000000000 --- a/UnityPy/classes/RectTransform.py +++ /dev/null @@ -1,6 +0,0 @@ -from .Transform import Transform - - -class RectTransform(Transform): - def __init__(self, reader): - super().__init__(reader=reader) diff --git a/UnityPy/classes/Renderer.py b/UnityPy/classes/Renderer.py deleted file mode 100644 index 21fc334f4..000000000 --- a/UnityPy/classes/Renderer.py +++ /dev/null @@ -1,91 +0,0 @@ -from .Component import Component -from .PPtr import PPtr - - -class StaticBatchInfo: - def __init__(self, reader): - self.firstSubMesh = reader.read_u_short() - self.subMeshCount = reader.read_u_short() - - -class Renderer(Component): - def __init__(self, reader): - super().__init__(reader=reader) - version = self.version - if version < (5,): # 5.0 down - self.m_Enabled = reader.read_boolean() - self.m_CastShadows = reader.read_boolean() - self.m_ReceiveShadows = reader.read_boolean() - self.m_LightmapIndex = reader.read_byte() - else: # 5.0 and up - if version >= (5, 4): # 5.4 and up - self.m_Enabled = reader.read_boolean() - self.m_CastShadows = reader.read_byte() - self.m_ReceiveShadows = reader.read_byte() - if version[:2] > (2017, 2): # 2017.2 and up - self.m_DynamicOccludee = reader.read_byte() - self.m_MotionVectors = reader.read_byte() - self.m_LightProbeUsage = reader.read_byte() - self.m_ReflectionProbeUsage = reader.read_byte() - if version >= (2019, 3): # 2019.3 and up - self.m_RayTracingMode = reader.ReadByte() - if version >= (2020,): # 2020.1 and up - self.m_RayTraceProcedural = reader.read_byte() - reader.align_stream() - else: - self.m_Enabled = reader.read_boolean() - reader.align_stream() - self.m_CastShadows = reader.read_byte() - self.m_ReceiveShadows = reader.read_boolean() - reader.align_stream() - - if version >= (2018,): # 2018 and up - self.m_RenderingLayerMask = reader.read_u_int() - - if version >= (2018, 3): # 2018.3 and up - self.m_RendererPriority = reader.read_int() - - self.m_LightmapIndex = reader.read_u_short() - self.m_LightmapIndexDynamic = reader.read_u_short() - - if version >= (3,): # 3.0 and up - self.m_LightmapTilingOffset = reader.read_vector4() - - if version >= (5,): # 5.0 and up - self.m_LightmapTilingOffsetDynamic = reader.read_vector4() - - m_MaterialsSize = reader.read_int() - self.m_Materials = [PPtr(reader) for _ in range(m_MaterialsSize)] # Material - - if version < (3,): # 3.0 down - self.m_LightmapTilingOffset = reader.read_vector4() - else: # 3.0 and up - if version >= (5, 5): # 5.5 and up - self.m_StaticBatchInfo = StaticBatchInfo(reader) - else: - self.m_SubsetIndices = reader.read_u_int_array() - - self.m_StaticBatchRoot = PPtr(reader) # Transform - - if version >= (5, 4): # 5.4 and up - self.m_ProbeAnchor = PPtr(reader) # Transform - self.m_LightProbeVolumeOverride = PPtr(reader) # GameObject - elif version >= (3, 5): # 3.5 - 5.3 - self.m_UseLightProbes = reader.read_boolean() - reader.align_stream() - - if version >= (5,): # 5.0 and up - self.m_ReflectionProbeUsage = reader.read_int() - - # Transform #5.0 and up m_ProbeAnchor - self.m_LightProbeAnchor = PPtr(reader) - - if version >= (4, 3): # 4.3 and up - if version[:2] == (4, 3): # 4.3 - self.m_SortingLayer = reader.read_short() - else: - self.m_SortingLayerID = reader.read_u_int() - - # SInt16 m_SortingLayer 5.6 and up - self.m_SortingOrder = reader.read_short() - reader.align_stream() diff --git a/UnityPy/classes/RuntimeAnimatorController.py b/UnityPy/classes/RuntimeAnimatorController.py deleted file mode 100644 index 8b44885c5..000000000 --- a/UnityPy/classes/RuntimeAnimatorController.py +++ /dev/null @@ -1,6 +0,0 @@ -from .NamedObject import NamedObject - - -class RuntimeAnimatorController(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) diff --git a/UnityPy/classes/Shader.py b/UnityPy/classes/Shader.py deleted file mode 100644 index a6aae90d2..000000000 --- a/UnityPy/classes/Shader.py +++ /dev/null @@ -1,439 +0,0 @@ -from enum import IntEnum - -from .NamedObject import NamedObject - - -class Shader(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - version = reader.version - if version >= (5, 5): # 5.5 and up - self.m_ParsedForm = SerializedShader(reader) - self.platforms = [ - ShaderCompilerPlatform(x) for x in reader.read_u_int_array() - ] - - if version >= (2019, 3): # 2019.3 and up - offsets = reader.read_u_int_array_array()[0] - compressedLengths = reader.read_u_int_array_array()[0] - decompressedLengths = reader.read_u_int_array_array()[0] - else: - self.offsets = reader.read_u_int_array() - self.compressedLengths = reader.read_u_int_array() - self.decompressedLengths = reader.read_u_int_array() - self.compressedBlob = reader.read_bytes(reader.read_int()) - else: - self.m_Script = reader.read_bytes(reader.read_int()) - reader.align_stream() - self.m_PathName = reader.read_aligned_string() - if version >= (5, 3): # 5.3 - 5.4 - self.decompressedSize = reader.read_u_int() - self.m_SubProgramBlob = reader.read_bytes(reader.read_int()) - - -class StructParameter: - def __init__(self, reader): - self.m_NameIndex = reader.read_int() - self.m_Index = reader.read_int() - self.m_ArraySize = reader.read_int() - self.m_StructSize = reader.read_int() - - numVectorParams = reader.read_int() - self.m_VectorParams = [VectorParameter(reader) for _ in range(numVectorParams)] - - numMatrixParams = reader.read_int() - self.m_MatrixParams = [MatrixParameter(reader) for _ in range(numMatrixParams)] - - -class SamplerParameter: - def __init__(self, reader): - self.sampler = reader.read_u_int() - self.bindPoint = reader.read_int() - - -class TextureDimension(IntEnum): - kTexDimUnknown = (-1,) - kTexDimNone = (0,) - kTexDimAny = (1,) - kTexDim2D = (2,) - kTexDim3D = (3,) - kTexDimCUBE = (4,) - kTexDim2DArray = (5,) - kTexDimCubeArray = (6,) - kTexDimForce32Bit = (2147483647,) - - -class SerializedTextureProperty: - def __init__(self, reader): - self.m_DefaultName = reader.read_aligned_string() - self.m_TexDim = TextureDimension(reader.read_int()) - - -class SerializedPropertyType(IntEnum): - kColor = (0,) - kVector = (1,) - kFloat = (2,) - kRange = (3,) - kTexture = (4,) - - -class SerializedProperty: - def __init__(self, reader): - self.m_Name = reader.read_aligned_string() - self.m_Description = reader.read_aligned_string() - self.m_Attributes = reader.read_string_array() - self.m_Type = SerializedPropertyType(reader.read_int()) - self.m_Flags = reader.read_u_int() - self.m_DefValue = reader.read_float_array(4) - self.m_DefTexture = SerializedTextureProperty(reader) - - -class SerializedProperties: - def __init__(self, reader): - numProps = reader.read_int() - self.m_Props = [SerializedProperty(reader) for _ in range(numProps)] - - -class SerializedShaderFloatValue: - def __init__(self, reader): - self.val = reader.read_float() - self.name = reader.read_aligned_string() - - -class SerializedShaderRTBlendState: - def __init__(self, reader): - self.srcBlend = SerializedShaderFloatValue(reader) - self.destBlend = SerializedShaderFloatValue(reader) - self.srcBlendAlpha = SerializedShaderFloatValue(reader) - self.destBlendAlpha = SerializedShaderFloatValue(reader) - self.blendOp = SerializedShaderFloatValue(reader) - self.blendOpAlpha = SerializedShaderFloatValue(reader) - self.colMask = SerializedShaderFloatValue(reader) - - -class SerializedStencilOp: - def __init__(self, reader): - self.pass_ = SerializedShaderFloatValue(reader) - self.fail = SerializedShaderFloatValue(reader) - self.zFail = SerializedShaderFloatValue(reader) - self.comp = SerializedShaderFloatValue(reader) - - -class SerializedShaderVectorValue: - def __init__(self, reader): - self.x = SerializedShaderFloatValue(reader) - self.y = SerializedShaderFloatValue(reader) - self.z = SerializedShaderFloatValue(reader) - self.w = SerializedShaderFloatValue(reader) - self.name = reader.read_aligned_string() - - -class FogMode(IntEnum): - kFogUnknown = (-1,) - kFogDisabled = (0,) - kFogLinear = (1,) - kFogExp = (2,) - kFogExp2 = (3,) - - -class SerializedShaderState: - def __init__(self, reader): - version = reader.version - - self.m_Name = reader.read_aligned_string() - self.rtBlend = [SerializedShaderRTBlendState(reader) for _ in range(8)] - self.rtSeparateBlend = reader.read_boolean() - reader.align_stream() - if version >= (2017, 2): # 2017.2 and up - self.zClip = SerializedShaderFloatValue(reader) - self.zTest = SerializedShaderFloatValue(reader) - self.zWrite = SerializedShaderFloatValue(reader) - self.culling = SerializedShaderFloatValue(reader) - self.offsetFactor = SerializedShaderFloatValue(reader) - self.offsetUnits = SerializedShaderFloatValue(reader) - self.alphaToMask = SerializedShaderFloatValue(reader) - self.stencilOp = SerializedStencilOp(reader) - self.stencilOpFront = SerializedStencilOp(reader) - self.stencilOpBack = SerializedStencilOp(reader) - self.stencilReadMask = SerializedShaderFloatValue(reader) - self.stencilWriteMask = SerializedShaderFloatValue(reader) - self.stencilRef = SerializedShaderFloatValue(reader) - self.fogStart = SerializedShaderFloatValue(reader) - self.fogEnd = SerializedShaderFloatValue(reader) - self.fogDensity = SerializedShaderFloatValue(reader) - self.fogColor = SerializedShaderVectorValue(reader) - self.fogMode = FogMode(reader.read_int()) - self.gpuProgramID = reader.read_int() - self.m_Tags = SerializedTagMap(reader) - self.m_LOD = reader.read_int() - self.lighting = reader.read_boolean() - reader.align_stream() - - -class ShaderBindChannel: - def __init__(self, reader): - self.source = reader.read_byte() - self.target = reader.read_byte() - - -class ParserBindChannels: - def __init__(self, reader): - numChannels = reader.read_int() - self.m_Channels = [ShaderBindChannel(reader) for _ in range(numChannels)] - reader.align_stream() - self.m_SourceMap = reader.read_u_int() - - -class VectorParameter: - def __init__(self, reader): - self.m_NameIndex = reader.read_int() - self.m_Index = reader.read_int() - self.m_ArraySize = reader.read_int() - self.m_Type = reader.read_byte() - self.m_Dim = reader.read_byte() - reader.align_stream() - - -class MatrixParameter: - def __init__(self, reader): - self.m_NameIndex = reader.read_int() - self.m_Index = reader.read_int() - self.m_ArraySize = reader.read_int() - self.m_Type = reader.read_byte() - self.m_RowCount = reader.read_byte() - reader.align_stream() - - -class TextureParameter: - def __init__(self, reader): - version = reader.version - self.m_NameIndex = reader.read_int() - self.m_Index = reader.read_int() - self.m_SamplerIndex = reader.read_int() - if version >= (2017, 3): # 2017.3 and up - self.m_MultiSampled = reader.read_boolean() - self.m_Dim = reader.read_byte() - reader.align_stream() - - -class BufferBinding: - def __init__(self, reader): - self.m_NameIndex = reader.read_int() - self.m_Index = reader.read_int() - - -class ConstantBuffer: - def __init__(self, reader): - version = reader.version - - self.m_NameIndex = reader.read_int() - - numMatrixParams = reader.read_int() - self.m_MatrixParams = [MatrixParameter(reader) for _ in range(numMatrixParams)] - - numVectorParams = reader.read_int() - self.m_VectorParams = [VectorParameter(reader) for _ in range(numVectorParams)] - if version >= (2017, 3): # 2017.3 and up - numStructParams = reader.read_int() - self.m_StructParams = [ - StructParameter(reader) for _ in range(numStructParams) - ] - self.m_Size = reader.read_int() - - -class UAVParameter: - def __init__(self, reader): - self.m_NameIndex = reader.read_int() - self.m_Index = reader.read_int() - self.m_OriginalIndex = reader.read_int() - - -class ShaderGpuProgramType(IntEnum): - kShaderGpuProgramUnknown = (0,) - kShaderGpuProgramGLLegacy = (1,) - kShaderGpuProgramGLES31AEP = (2,) - kShaderGpuProgramGLES31 = (3,) - kShaderGpuProgramGLES3 = (4,) - kShaderGpuProgramGLES = (5,) - kShaderGpuProgramGLCore32 = (6,) - kShaderGpuProgramGLCore41 = (7,) - kShaderGpuProgramGLCore43 = (8,) - kShaderGpuProgramDX9VertexSM20 = (9,) - kShaderGpuProgramDX9VertexSM30 = (10,) - kShaderGpuProgramDX9PixelSM20 = (11,) - kShaderGpuProgramDX9PixelSM30 = (12,) - kShaderGpuProgramDX10Level9Vertex = (13,) - kShaderGpuProgramDX10Level9Pixel = (14,) - kShaderGpuProgramDX11VertexSM40 = (15,) - kShaderGpuProgramDX11VertexSM50 = (16,) - kShaderGpuProgramDX11PixelSM40 = (17,) - kShaderGpuProgramDX11PixelSM50 = (18,) - kShaderGpuProgramDX11GeometrySM40 = (19,) - kShaderGpuProgramDX11GeometrySM50 = (20,) - kShaderGpuProgramDX11HullSM50 = (21,) - kShaderGpuProgramDX11DomainSM50 = (22,) - kShaderGpuProgramMetalVS = (23,) - kShaderGpuProgramMetalFS = (24,) - kShaderGpuProgramSPIRV = (25,) - kShaderGpuProgramConsole = (26,) - - -class SerializedSubProgram: - def __init__(self, reader): - version = reader.version - - self.m_BlobIndex = reader.read_u_int() - self.m_Channels = ParserBindChannels(reader) - - if version >= (2019,): # 2019 and up - self.m_GlobalKeywordIndices = reader.read_u_short_array() - reader.align_stream() - self.m_LocalKeywordIndices = reader.read_u_short_array() - reader.align_stream() - else: - self.m_KeywordIndices = reader.read_u_short_array() - if version >= (2017,): # 2017 and up - reader.align_stream() - - self.m_ShaderHardwareTier = reader.read_byte() - self.m_GpuProgramType = ShaderGpuProgramType(reader.read_byte()) - reader.align_stream() - - numVectorParams = reader.read_int() - self.m_VectorParams = [VectorParameter(reader) for _ in range(numVectorParams)] - - numMatrixParams = reader.read_int() - self.m_MatrixParams = [MatrixParameter(reader) for _ in range(numMatrixParams)] - - numTextureParams = reader.read_int() - self.m_TextureParams = [ - TextureParameter(reader) for _ in range(numTextureParams) - ] - - numBufferParams = reader.read_int() - self.m_BufferParams = [BufferBinding(reader) for _ in range(numBufferParams)] - - numConstantBuffers = reader.read_int() - self.m_ConstantBuffers = [ - ConstantBuffer(reader) for _ in range(numConstantBuffers) - ] - - numConstantBufferBindings = reader.read_int() - self.m_ConstantBufferBindings = [ - BufferBinding(reader) for _ in range(numConstantBufferBindings) - ] - - numUAVParams = reader.read_int() - self.m_UAVParams = [UAVParameter(reader) for _ in range(numUAVParams)] - - if version >= (2017,): # 2017 and up - numSamplers = reader.read_int() - self.m_Samplers = [SamplerParameter(reader) for _ in range(numSamplers)] - if version >= (2017, 2): # 2017.2 and up - self.m_ShaderRequirements = reader.read_int() - - -class SerializedProgram: - def __init__(self, reader): - numSubPrograms = reader.read_int() - self.m_SubPrograms = [ - SerializedSubProgram(reader) for _ in range(numSubPrograms) - ] - - -class PassType(IntEnum): - kPassTypeNormal = (0,) - kPassTypeUse = (1,) - kPassTypeGrab = 2 - - -class SerializedPass: - def __init__(self, reader): - version = reader.version - numIndices = reader.read_int() - self.m_NameIndices = {} - for _ in range(numIndices): - key = reader.read_aligned_string() - self.m_NameIndices[key] = reader.read_int() - self.m_Type = PassType(reader.read_int()) - self.m_State = SerializedShaderState(reader) - self.m_ProgramMask = reader.read_u_int() - self.progVertex = SerializedProgram(reader) - self.progFragment = SerializedProgram(reader) - self.progGeometry = SerializedProgram(reader) - self.progHull = SerializedProgram(reader) - self.progDomain = SerializedProgram(reader) - if version >= (2019, 3): # 2019.3 and up - self.progRayTracing = SerializedProgram(reader) - self.m_HasInstancingVariant = reader.read_boolean() - if version >= (2018,): # 2018 and up - self.m_HasProceduralInstancingVariant = reader.read_boolean() - reader.align_stream() - self.m_UseName = reader.read_aligned_string() - self.m_Name = reader.read_aligned_string() - self.m_TextureName = reader.read_aligned_string() - self.m_Tags = SerializedTagMap(reader) - - -class SerializedTagMap: - def __init__(self, reader): - numTags = reader.read_int() - self.tags = {} - for _ in range(numTags): - key = reader.read_aligned_string() - self.tags[key] = reader.read_aligned_string() - - -class SerializedSubShader: - def __init__(self, reader): - numPasses = reader.read_int() - self.m_Passes = [SerializedPass(reader) for _ in range(numPasses)] - self.m_Tags = SerializedTagMap(reader) - self.m_LOD = reader.read_int() - - -class SerializedShaderDependency: - def __init__(self, reader): - self.from_ = reader.read_aligned_string() - self.to = reader.read_aligned_string() - - -class SerializedShader: - def __init__(self, reader): - self.m_PropInfo = SerializedProperties(reader) - numSubShaders = reader.read_int() - self.m_SubShaders = [SerializedSubShader(reader) for _ in range(numSubShaders)] - self.m_Name = reader.read_aligned_string() - self.m_CustomEditorName = reader.read_aligned_string() - self.m_FallbackName = reader.read_aligned_string() - numDependencies = reader.read_int() - self.m_Dependencies = [ - SerializedShaderDependency(reader) for _ in range(numDependencies) - ] - self.m_DisableNoSubshadersMessage = reader.read_boolean() - reader.align_stream() - - -class ShaderCompilerPlatform(IntEnum): - kShaderCompPlatformNone = (-1,) - kShaderCompPlatformGL = (0,) - kShaderCompPlatformD3D9 = (1,) - kShaderCompPlatformXbox360 = (2,) - kShaderCompPlatformPS3 = (3,) - kShaderCompPlatformD3D11 = (4,) - kShaderCompPlatformGLES20 = (5,) - kShaderCompPlatformNaCl = (6,) - kShaderCompPlatformFlash = (7,) - kShaderCompPlatformD3D11_9x = (8,) - kShaderCompPlatformGLES3Plus = (9,) - kShaderCompPlatformPSP2 = (10,) - kShaderCompPlatformPS4 = (11,) - kShaderCompPlatformXboxOne = (12,) - kShaderCompPlatformPSM = (13,) - kShaderCompPlatformMetal = (14,) - kShaderCompPlatformOpenGLCore = (15,) - kShaderCompPlatformN3DS = (16,) - kShaderCompPlatformWiiU = (17,) - kShaderCompPlatformVulkan = (18,) - kShaderCompPlatformSwitch = (19,) - kShaderCompPlatformXboxOneD3D12 = (20,) diff --git a/UnityPy/classes/SkinnedMeshRenderer.py b/UnityPy/classes/SkinnedMeshRenderer.py deleted file mode 100644 index 0594213e7..000000000 --- a/UnityPy/classes/SkinnedMeshRenderer.py +++ /dev/null @@ -1,23 +0,0 @@ -from .PPtr import PPtr -from .Renderer import Renderer - - -class SkinnedMeshRenderer(Renderer): - def __init__(self, reader): - super().__init__(reader=reader) - version = self.version - self.m_Quality = reader.read_int() - self.m_UpdateWhenOffscreen = reader.read_boolean() - self.m_SkinNormals = reader.read_boolean() # 3.1.0 and below - reader.align_stream() - - if version < (2, 6): # 2.6 down - self.m_DisableAnimationWhenOffscreen = PPtr(reader) # Animation - - self.m_Mesh = PPtr(reader) # Mesh - - m_BonesSize = reader.read_int() - self.m_Bones = [PPtr(reader) for _ in range(m_BonesSize)] - - if version >= (4, 3): # 4.3 and up - self.m_BlendShapeWeights = reader.read_float_array() diff --git a/UnityPy/classes/Sprite.py b/UnityPy/classes/Sprite.py deleted file mode 100644 index fa06ed290..000000000 --- a/UnityPy/classes/Sprite.py +++ /dev/null @@ -1,136 +0,0 @@ -from enum import IntEnum - -from .Mesh import BoneWeights4, SubMesh, VertexData -from .NamedObject import NamedObject -from .PPtr import PPtr -from ..export import SpriteHelper -from ..enums import SpriteMeshType - - -class Sprite(NamedObject): - @property - def image(self): - return SpriteHelper.get_image_from_sprite(self) - - def __init__(self, reader): - super().__init__(reader=reader) - version = self.version - self.m_Rect = reader.read_rectangle_f() - self.m_Offset = reader.read_vector2() - if version >= (4, 5): # 4.5 and up - self.m_Border = reader.read_vector4() - - self.m_PixelsToUnits = reader.read_float() - if version >= (5, 4, 2) or ( - version >= (5, 4, 1, 3) and self.build_type.IsPatch - ): # 5.4.1p3 and up - self.m_Pivot = reader.read_vector2() - - self.m_Extrude = reader.read_u_int() - if version >= (5, 3): # 5.3 and up - self.m_IsPolygon = reader.read_boolean() - reader.align_stream() - - if version >= (2017,): # 2017 and up - first = reader.read_bytes(16) # GUID - second = reader.read_long() - self.m_RenderDataKey = (first, second) - self.m_AtlasTags = reader.read_string_array() - self.m_SpriteAtlas = PPtr(reader) # SpriteAtlas - - self.m_RD = SpriteRenderData(reader) - - if version >= (2017,): # 2017 and up - m_PhysicsShapeSize = reader.read_int() - self.m_PhysicsShape = [ - reader.read_vector2_array() for _ in range(m_PhysicsShapeSize) - ] - """ - if version >= (2018,): # 2018 and up - m_BonesSize = reader.read_int() - self.m_Bones = [ - reader.read_vector2_array() for _ in range(m_BonesSize) - ] - """ - - -class SecondarySpriteTexture: - def __init__(self, reader): - self.texture = PPtr(reader) # Texture2D - self.name = reader.read_string_to_null() - - -class SpritePackingRotation(IntEnum): - kSPRNone = (0,) - kSPRFlipHorizontal = (1,) - kSPRFlipVertical = (2,) - kSPRRotate180 = (3,) - kSPRRotate90 = 4 - - -class SpritePackingMode(IntEnum): - kSPMTight = (0,) - kSPMRectangle = 1 - - -class SpriteSettings: - def __init__(self, reader): - settingsRaw = reader.read_u_int() - self.packed = settingsRaw & 1 # 1 - self.packingMode = SpritePackingMode((settingsRaw >> 1) & 1) # 1 - self.packingRotation = SpritePackingRotation((settingsRaw >> 2) & 0xF) # 4 - self.meshType = SpriteMeshType((settingsRaw >> 6) & 1) # 1 - # reserved - - -class SpriteVertex: - def __init__(self, reader): - version = reader.version - self.pos = reader.read_vector3() - if version[:2] <= (4, 3): # 4.3 and down - self.uv = reader.read_vector2() - - -class SpriteRenderData: - def __init__(self, reader): - version = reader.version - - self.texture = PPtr(reader) # Texture2D - if version >= (5, 2): # 5.2 and up - self.alphaTexture = PPtr(reader) # Texture2D - - if version >= (2019,): # 2019 and up - secondaryTexturesSize = reader.read_int() - self.secondaryTextures = [ - SecondarySpriteTexture(reader) for _ in range(secondaryTexturesSize) - ] - - if version >= (5, 6): # 5.6 and up - m_SubMeshesSize = reader.read_int() - self.m_SubMeshes = [SubMesh(reader) for _ in range(m_SubMeshesSize)] - self.m_IndexBuffer = reader.read_bytes(reader.read_int()) - reader.align_stream() - self.m_VertexData = VertexData(reader) - else: - verticesSize = reader.read_int() - self.vertices = [SpriteVertex(reader) for _ in range(verticesSize)] - self.indices = reader.read_u_short_array() - reader.align_stream() - - if version >= (2018,): # 2018 and up - self.m_Bindpose = reader.read_matrix_array() - if version < (2018, 2): # 2018.2 down - m_SourceSkinSize = reader.read_int() - self.m_SourceSkin = [BoneWeights4(reader)] - - self.textureRect = reader.read_rectangle_f() - self.textureRectOffset = reader.read_vector2() - if version >= (5, 6): # 5.6 and up - self.atlasRectOffset = reader.read_vector2() - - self.settingsRaw = SpriteSettings(reader) - if version >= (4, 5): # 4.5 and up - self.uvTransform = reader.read_vector4() - - if version >= (2017,): # 2017 and up - self.downscaleMultiplier = reader.read_float() diff --git a/UnityPy/classes/SpriteAtlas.py b/UnityPy/classes/SpriteAtlas.py deleted file mode 100644 index 82797dd39..000000000 --- a/UnityPy/classes/SpriteAtlas.py +++ /dev/null @@ -1,33 +0,0 @@ -from .NamedObject import NamedObject -from .PPtr import PPtr -from .Sprite import SpriteSettings - - -class SpriteAtlas(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - packed_sprites_size = reader.read_int() - self.PackSprites = [PPtr(reader) for _ in range(packed_sprites_size)] - - self.packed_sprite_names_to_index = reader.read_string_array() - m_render_data_map_size = reader.read_int() - self.render_data_map = {} - for _ in range(m_render_data_map_size): - first = reader.read_bytes(16) # GUID - second = reader.read_long() - value = SpriteAtlasData(reader) - self.render_data_map[(first, second)] = value - - -class SpriteAtlasData: - def __init__(self, reader): - self.version = reader.version - self.texture = PPtr(reader) # Texture2D - self.alphaTexture = PPtr(reader) # Texture2D - self.textureRect = reader.read_rectangle_f() - self.textureRectOffset = reader.read_vector2() - if self.version >= (2017, 2): # 2017.2 and up - self.atlasRectOffset = reader.read_vector2() - self.uvTransform = reader.read_vector4() - self.downscaleMultiplier = reader.read_float() - self.settingsRaw = SpriteSettings(reader) diff --git a/UnityPy/classes/TextAsset.py b/UnityPy/classes/TextAsset.py deleted file mode 100644 index 73ab3cec9..000000000 --- a/UnityPy/classes/TextAsset.py +++ /dev/null @@ -1,26 +0,0 @@ -from .NamedObject import NamedObject -from ..streams import EndianBinaryWriter, EndianBinaryReader - - -class TextAsset(NamedObject): - def __init__(self, reader: EndianBinaryReader): - super().__init__(reader=reader) - self.script = reader.read_bytes(reader.read_int()) - - @property - def text(self): - return bytes(self.script).decode("utf8") - - @text.setter - def text(self, val): - self.script = val.encode("utf8") - - def save(self, writer: EndianBinaryWriter = None): - if writer is None: - writer = EndianBinaryWriter(endian=self.reader.endian) - super().save(writer) - writer.write_int(len(self.script)) - writer.write_bytes(self.script) - writer.align_stream() - - self.reader.data = writer.bytes diff --git a/UnityPy/classes/Texture.py b/UnityPy/classes/Texture.py deleted file mode 100644 index 3399d4664..000000000 --- a/UnityPy/classes/Texture.py +++ /dev/null @@ -1,18 +0,0 @@ -from .NamedObject import NamedObject -from ..streams import EndianBinaryWriter - - -class Texture(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - if self.version >= (2017, 3): # 2017.3 and up - self.forced_fallback_format = reader.read_int() - self.downscale_fallback = reader.read_boolean() - reader.align_stream() - - def save(self, writer: EndianBinaryWriter): - super().save(writer) - if self.version >= (2017, 3): # 2017.3 and up - writer.write_int(self.forced_fallback_format) - writer.write_boolean(self.downscale_fallback) - writer.align_stream() diff --git a/UnityPy/classes/Texture2D.py b/UnityPy/classes/Texture2D.py deleted file mode 100644 index 94f47bca1..000000000 --- a/UnityPy/classes/Texture2D.py +++ /dev/null @@ -1,143 +0,0 @@ -from .Texture import Texture -from ..enums import TextureFormat -from ..export import Texture2DConverter -from ..helpers.ResourceReader import get_resource_data -from ..streams import EndianBinaryWriter - - -class Texture2D(Texture): - @property - def image(self): - return Texture2DConverter.get_image_from_texture2d(self) - - @image.setter - def image(self, img): - # img is PIL.Image - # overwrite original image data with the RGB(A) image data and sets the correct new format - writer = EndianBinaryWriter() - for pix in img.getdata(): - for val in pix: - writer.write_u_byte(val) - self.image_data = writer.bytes - self.m_TextureFormat = ( - TextureFormat.RGBA32 if len(pix) == 4 else TextureFormat.RGB24 - ) - - def __init__(self, reader): - super().__init__(reader=reader) - version = self.version - self.m_Width = reader.read_int() - self.m_Height = reader.read_int() - self.m_CompleteImageSize = reader.read_int() - if version >= (2020,): # 2020.1 and up - self.mips_stripped = reader.read_int() - self.m_TextureFormat = TextureFormat(reader.read_int()) - if version[:2] <= (5, 2): # 5.2 down - self.m_MipMap = reader.read_boolean() - else: - self.m_MipCount = reader.read_int() - - if version >= (2, 6): # 2.6.0 and up - self.m_IsReadable = reader.read_boolean() # 2.6.0 and up - if version >= (2020,): # 2020.1 and up - self.is_pre_processed = reader.read_boolean() - if version >= (2019, 3): # 2019.3 and up - self.ignore_master_texture_limit = reader.read_boolean() - if (3,) <= version[:2] <= (5, 4): # 3.0.0 - 5.4 - self.m_ReadAllowed = reader.read_boolean() - if version >= (2018, 2): # 2018.2 and up - self.m_streaming_mipmaps = reader.read_boolean() - - reader.align_stream() - if version >= (2018, 2): # 2018.2 and up - self.m_StreamingMipmapsPriority = reader.read_int() - self.m_ImageCount = reader.read_int() - self.m_TextureDimension = reader.read_int() - self.m_TextureSettings = GLTextureSettings(reader) - if version >= (3,): # 3.0 and up - self.m_LightmapFormat = reader.read_int() - if version >= (3, 5): # 3.5.0 and up - self.m_ColorSpace = reader.read_int() - - image_data_size = reader.read_int() - self.image_data = b"" - if image_data_size == 0 and version >= (5, 3): # 5.3.0 and up - self.m_StreamData = StreamingInfo(reader) - self.image_data = get_resource_data( - self.m_StreamData.path, - self.assets_file, - self.m_StreamData.offset, - self.m_StreamData.size, - ) - else: - self.image_data = reader.read_bytes(image_data_size) - - def save(self, writer=None): - if writer == None: - writer = EndianBinaryWriter() - version = self.version - - writer.write_int(self.m_Width) - writer.write_int(self.m_Height) - writer.write_int(self.m_CompleteImageSize) - writer.write_int(int(self.m_TextureFormat)) - if version < (5, 2): # 5.2 down - writer.write_boolean(self.m_MipMap) - else: - writer.write_int(self.m_MipCount) - writer.write_boolean(self.m_IsReadable) # 2.6.0 and up - writer.write_boolean(self.m_ReadAllowed) # 3.0.0 - 5.4 - # bool m_StreamingMipmaps 2018.2 and up - writer.align_stream() - if version >= (2018, 2): # 2018.2 and up - writer.write_int(self.m_StreamingMipmapsPriority) - writer.write_int(self.m_ImageCount) - writer.write_int(self.m_TextureDimension) - self.m_TextureSettings.save(writer, version) - if version >= (3,): # 3.0 and up - writer.write_int(self.m_LightmapFormat) - if version >= (3, 5): # 3.5.0 and up - writer.write_int(self.m_ColorSpace) - - writer.write_int(len(self.image_data)) - writer.write_bytes(self.image_data) - - -class StreamingInfo: - offset: int = 0 - size: int = 0 - path: str = "" - - def __init__(self, reader): - if reader.version >= (2020,): # 2020.1 and up - self.offset = reader.read_u_long() - else: - self.offset = reader.read_u_int() - self.size = reader.read_u_int() - self.path = reader.read_aligned_string() - - -class GLTextureSettings: - def __init__(self, reader): - version = reader.version - - self.m_FilterMode = reader.read_int() - self.m_Aniso = reader.read_int() - self.m_MipBias = reader.read_float() - if version >= (2017,): # 2017.x and up - self.m_WrapMode = reader.read_int() # m_WrapU - self.m_WrapV = reader.read_int() - self.m_WrapW = reader.read_int() - else: - self.m_WrapMode = reader.read_int() - - def save(self, writer, version): - writer.write_int(self.self.m_FilterMode) - writer.write_int(self.self.m_Aniso) - writer.write_float(self.self.m_MipBias) - if version >= (2017,): # 2017.x and up - writer.write_int(self.self.m_WrapMode) # m_WrapU - writer.write_int(self.self.m_WrapV) - writer.write_int(self.self.m_WrapW) - else: - writer.write_int(self.self.m_WrapMode) diff --git a/UnityPy/classes/Transform.py b/UnityPy/classes/Transform.py deleted file mode 100644 index 90341605e..000000000 --- a/UnityPy/classes/Transform.py +++ /dev/null @@ -1,14 +0,0 @@ -from .Component import Component -from .PPtr import PPtr - - -class Transform(Component): - def __init__(self, reader): - super().__init__(reader=reader) - self.local_rotation = reader.read_quaternion() - self.local_position = reader.read_vector3() - self.local_scale = reader.read_vector3() - - children_count = reader.read_int() - self.children = [PPtr(reader) for _ in range(children_count)] - self.father = PPtr(reader) diff --git a/UnityPy/classes/UnknownObject.py b/UnityPy/classes/UnknownObject.py new file mode 100644 index 000000000..6ffbab07d --- /dev/null +++ b/UnityPy/classes/UnknownObject.py @@ -0,0 +1,33 @@ +from typing import Optional + +from ..helpers.TypeTreeNode import TypeTreeNode +from .Object import Object + + +class UnknownObject(Object): + """An object of unknown type that showed up during typetree parsing.""" + + __node__: Optional[TypeTreeNode] + + def __init__(self, __node__: Optional[TypeTreeNode] = None, **kwargs): + self.__node__ = __node__ + self.__dict__.update(**kwargs) + + def get_type(self): + return self.__node__.m_Type if self.__node__ else None + + def __repr__(self) -> str: + def format_value(v): + vstr = repr(v) + if len(vstr) > 100: + return vstr[:97] + "..." + return vstr + + inner_str = ", ".join(f"{k}={format_value(v)}" for k, v in self.__dict__.items() if k != "__node__") + + return f" {inner_str}>" + + +__all__ = [ + "UnknownObject", +] diff --git a/UnityPy/classes/VideoClip.py b/UnityPy/classes/VideoClip.py deleted file mode 100644 index 1584b4331..000000000 --- a/UnityPy/classes/VideoClip.py +++ /dev/null @@ -1,42 +0,0 @@ -from .NamedObject import NamedObject -from ..helpers.ResourceReader import get_resource_data -from .PPtr import PPtr - - -class VideoClip(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - self.original_path = reader.read_aligned_string() - self.proxy_width = reader.read_u_int() - self.proxy_height = reader.read_u_int() - self.width = reader.read_u_int() - self.height = reader.read_u_int() - if self.version >= (2017, 2): # 2017.2 and up - self.pixel_aspec_ratio_num = reader.read_u_int() - self.pixel_aspec_ratio_den = reader.read_u_int() - self.frame_rate = reader.read_double() - self.frame_count = reader.read_u_long() - self.format = reader.read_int() - self.audio_channel_count = reader.read_u_short_array() - reader.align_stream() - self.audio_sample_rate = reader.read_u_int_array() - self.audio_language = reader.read_string_array() - # StreamedResource m_ExternalResources - if self.version >= (2020,): # 2020.1 and up - video_shader_size = reader.ReadInt32() - self.video_shaders = [PPtr(reader) for _ in range(video_shader_size)] - - # StreamedResource - source = reader.read_aligned_string() - offset = reader.read_u_long() - size = reader.read_u_long() - - self.has_split_alpha = reader.read_boolean() - - if self.version >= (2020,): # 2020.1 and up - self.m_sRGB = reader.read_boolean() - - if source: - self.VideoData = get_resource_data(source, self.assets_file, offset, size) - else: - self.VideoData = reader.read(size) diff --git a/UnityPy/classes/__init__.py b/UnityPy/classes/__init__.py index 89babcbcd..f3ecdb219 100644 --- a/UnityPy/classes/__init__.py +++ b/UnityPy/classes/__init__.py @@ -1,37 +1,28 @@ -from .Animation import Animation -from .AnimationClip import AnimationClip -from .Animator import Animator -from .AnimatorController import AnimatorController -from .AnimatorOverrideController import AnimatorOverrideController -from .AssetBundle import AssetBundle -from .AudioClip import AudioClip -from .Avatar import Avatar -from .Behaviour import Behaviour -from .BuildSettings import BuildSettings -from .Component import Component -from .EditorExtension import EditorExtension -from .Font import Font -from .GameObject import GameObject -from .Material import Material -from .Mesh import Mesh -from .MeshFilter import MeshFilter -from .MeshRenderer import MeshRenderer -from .MonoBehaviour import MonoBehaviour -from .MonoScript import MonoScript -from .MovieTexture import MovieTexture -from .NamedObject import NamedObject -from .Object import Object -from .PPtr import PPtr -from .PlayerSettings import PlayerSettings -from .RectTransform import RectTransform -from .Renderer import Renderer -from .RuntimeAnimatorController import RuntimeAnimatorController -from .Shader import Shader -from .SkinnedMeshRenderer import SkinnedMeshRenderer -from .Sprite import Sprite -from .SpriteAtlas import SpriteAtlas -from .TextAsset import TextAsset -from .Texture import Texture -from .Texture2D import Texture2D -from .Transform import Transform -from .VideoClip import VideoClip +from .generated import * # noqa: F403 +from .legacy_patch import ( + AudioClip as AudioClip, +) +from .legacy_patch import ( + GameObject as GameObject, +) +from .legacy_patch import ( + Mesh as Mesh, +) +from .legacy_patch import ( + Renderer as Renderer, +) +from .legacy_patch import ( + Shader as Shader, +) +from .legacy_patch import ( + Sprite as Sprite, +) +from .legacy_patch import ( + Texture2D as Texture2D, +) +from .legacy_patch import ( + Texture2DArray as Texture2DArray, +) +from .Object import Object as Object +from .PPtr import PPtr as PPtr +from .UnknownObject import UnknownObject as UnknownObject diff --git a/UnityPy/classes/generated.py b/UnityPy/classes/generated.py new file mode 100644 index 000000000..2e2697352 --- /dev/null +++ b/UnityPy/classes/generated.py @@ -0,0 +1,12350 @@ +# type: ignore +from __future__ import annotations + +from abc import ABC +from typing import List, Optional, Tuple, TypeVar, Union + +from attrs import define as attrs_define + +from .math import ( + ColorRGBA, + Matrix3x4f, + Matrix4x4f, + Quaternionf, + Vector2f, + Vector3f, + Vector4f, + float3, + float4, +) +from .Object import Object +from .PPtr import PPtr + +T = TypeVar("T") + + +def unitypy_define(cls: T) -> T: + """ + A hacky solution to bypass multiple problems related to attrs and inheritance. + + The class inheritance is very lax and based on the typetrees. + Some of the child classes might not have the same attributes as the parent class, + which would make type-hinting more tricky, and breaks attrs.define. + + Therefore this function bypasses the issue + by redefining the bases for problematic classes for the attrs.define call. + """ + bases = cls.__bases__ + if bases[0] in (object, Object, ABC): + cls = attrs_define(cls, slots=True, unsafe_hash=True) + else: + cls.__bases__ = (Object,) + cls = attrs_define(cls, slots=False, unsafe_hash=True) + cls.__bases__ = bases + return cls + + +@unitypy_define +class AnnotationManager(Object): + m_CurrentPreset_m_AnnotationList: List[Annotation] + m_RecentlyChanged: List[Annotation] + m_FadeGizmoSize: Optional[float] = None + m_FadeGizmos: Optional[bool] = None + m_IconSize: Optional[float] = None + m_ShowGrid: Optional[bool] = None + m_ShowLODLabels: Optional[bool] = None + m_ShowSelectionOutline: Optional[bool] = None + m_ShowSelectionWire: Optional[bool] = None + m_Use3dGizmos: Optional[bool] = None + m_WorldIconSize: Optional[float] = None + + +@unitypy_define +class AssetDatabaseV1(Object): + m_AssetBundleNames: List[Tuple[int, AssetBundleFullName]] + m_AssetTimeStamps: List[Tuple[str, AssetTimeStamp]] + m_Assets: List[Tuple[GUID, Asset]] + m_Metrics: AssetDatabaseMetrics + m_UnityShadersVersion: int + m_lastValidVersionHashes: Optional[List[Tuple[int, int]]] = None + m_lastValidVersions: Optional[List[Tuple[AssetImporterHashKey, int]]] = None + + +@unitypy_define +class AssetMetaData(Object): + assetStoreRef: int + guid: GUID + labels: List[str] + originalName: str + pathName: str + licenseType: Optional[int] = None + originalChangeset: Optional[int] = None + originalDigest: Optional[Union[Hash128, MdFour]] = None + originalParent: Optional[GUID] = None + timeCreated: Optional[int] = None + + +@unitypy_define +class AssetServerCache(Object): + m_CachesInitialized: int + m_CommitItemSelection: List[GUID] + m_DeletedItems: List[Tuple[GUID, DeletedItem]] + m_Items: List[Tuple[GUID, Item]] + m_LastCommitMessage: str + m_LatestServerChangeset: int + m_ModifiedItems: List[Tuple[GUID, Item]] + m_WorkingItemMetaData: List[Tuple[GUID, CachedAssetMetaData]] + + +@unitypy_define +class AudioBuildInfo(Object): + m_AudioClipCount: int + m_AudioMixerCount: int + m_IsAudioDisabled: bool + + +@unitypy_define +class BlockShaderSourceArtifact(Object): + shaderName: str + shaderSource: str + + +@unitypy_define +class BuiltAssetBundleInfoSet(Object): + bundleInfos: List[BuiltAssetBundleInfo] + + +@unitypy_define +class ContentSummary(Object): + m_assetStatsList: List[AssetStats] + m_headerSize: int + m_objectCount: int + m_resourceDataSize: int + m_resourceFileCount: int + m_serializedFileCount: int + m_serializedFileSize: int + m_typeStatsList: List[TypeStats] + m_generatedFileCount: Optional[int] = None + m_generatedFileSize: Optional[int] = None + m_reusedSerializedFileCount: Optional[int] = None + m_reusedSerializedFileSize: Optional[int] = None + m_sizeReusedContentInOutputDirectory: Optional[int] = None + + +@unitypy_define +class Derived(Object): + pass + + +@unitypy_define +class SubDerived(Derived): + pass + + +@unitypy_define +class DifferentMarshallingTestObject(Object): + pass + + +@unitypy_define +class EditorBuildSettings(Object): + m_Scenes: List[Scene] + m_UseUCBPForAssetBundles: Optional[bool] = None + m_configObjects: Optional[List[Tuple[str, PPtr[Object]]]] = None + + +@unitypy_define +class EditorExtension(Object, ABC): + pass + + +@unitypy_define +class Component(EditorExtension): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class Behaviour(Component): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class Animation(Behaviour): + m_AnimatePhysics: bool + m_Animation: PPtr[AnimationClip] + m_Animations: List[PPtr[AnimationClip]] + m_CullingType: int + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_PlayAutomatically: bool + m_WrapMode: int + m_UpdateMode: Optional[int] = None + m_UserAABB: Optional[AABB] = None + + +@unitypy_define +class Animator(Behaviour): + m_ApplyRootMotion: bool + m_Avatar: PPtr[Avatar] + m_Controller: Union[PPtr[AnimatorController], PPtr[RuntimeAnimatorController]] + m_CullingMode: int + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_AllowConstantClipSamplingOptimization: Optional[bool] = None + m_AnimatePhysics: Optional[bool] = None + m_HasTransformHierarchy: Optional[bool] = None + m_KeepAnimatorControllerStateOnDisable: Optional[bool] = None + m_KeepAnimatorStateOnDisable: Optional[bool] = None + m_LinearVelocityBlending: Optional[bool] = None + m_StabilizeFeet: Optional[bool] = None + m_UpdateMode: Optional[int] = None + m_WriteDefaultValuesOnDisable: Optional[bool] = None + + +@unitypy_define +class ArticulationBody(Behaviour): + m_AnchorPosition: Vector3f + m_AnchorRotation: Quaternionf + m_AngularDamping: float + m_ArticulationJointType: int + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Immovable: bool + m_JointFriction: float + m_LinearDamping: float + m_LinearX: int + m_LinearY: int + m_LinearZ: int + m_Mass: float + m_ParentAnchorPosition: Vector3f + m_ParentAnchorRotation: Quaternionf + m_SwingY: int + m_SwingZ: int + m_Twist: int + m_XDrive: ArticulationDrive + m_YDrive: ArticulationDrive + m_ZDrive: ArticulationDrive + m_CenterOfMass: Optional[Vector3f] = None + m_CollisionDetectionMode: Optional[int] = None + m_ComputeParentAnchor: Optional[bool] = None + m_ExcludeLayers: Optional[BitField] = None + m_ImplicitCom: Optional[bool] = None + m_ImplicitTensor: Optional[bool] = None + m_IncludeLayers: Optional[BitField] = None + m_InertiaRotation: Optional[Quaternionf] = None + m_InertiaTensor: Optional[Vector3f] = None + m_MatchAnchors: Optional[bool] = None + m_UseGravity: Optional[bool] = None + + +@unitypy_define +class AudioBehaviour(Behaviour): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class AudioListener(AudioBehaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_ExtensionPropertyValues: Optional[List[ExtensionPropertyValue]] = None + + +@unitypy_define +class AudioSource(AudioBehaviour): + BypassEffects: bool + DopplerLevel: float + Loop: bool + MaxDistance: float + MinDistance: float + Mute: bool + Pan2D: float + Priority: int + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Pitch: float + m_PlayOnAwake: bool + m_Volume: float + m_audioClip: PPtr[AudioClip] + panLevelCustomCurve: AnimationCurve + rolloffCustomCurve: AnimationCurve + rolloffMode: int + spreadCustomCurve: AnimationCurve + BypassListenerEffects: Optional[bool] = None + BypassReverbZones: Optional[bool] = None + OutputAudioMixerGroup: Optional[PPtr[AudioMixerGroup]] = None + Spatialize: Optional[bool] = None + SpatializePostEffects: Optional[bool] = None + m_ExtensionPropertyValues: Optional[List[ExtensionPropertyValue]] = None + m_Resource: Optional[Union[PPtr[AudioResource], PPtr[Object]]] = None + reverbZoneMixCustomCurve: Optional[AnimationCurve] = None + + +@unitypy_define +class AudioFilter(Behaviour): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class AudioChorusFilter(AudioFilter): + m_Delay: float + m_Depth: float + m_DryMix: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Rate: float + m_WetMix1: float + m_WetMix2: float + m_WetMix3: float + m_FeedBack: Optional[float] = None + + +@unitypy_define +class AudioDistortionFilter(AudioFilter): + m_DistortionLevel: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class AudioEchoFilter(AudioFilter): + m_DecayRatio: float + m_Delay: Union[float, int] + m_DryMix: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_WetMix: float + + +@unitypy_define +class AudioHighPassFilter(AudioFilter): + m_CutoffFrequency: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_HighpassResonanceQ: float + + +@unitypy_define +class AudioLowPassFilter(AudioFilter): + lowpassLevelCustomCurve: AnimationCurve + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_LowpassResonanceQ: float + m_CutoffFrequency: Optional[float] = None + + +@unitypy_define +class AudioReverbFilter(AudioFilter): + m_DecayHFRatio: float + m_DecayTime: float + m_Density: float + m_Diffusion: float + m_DryLevel: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_HFReference: float + m_LFReference: float + m_ReflectionsDelay: float + m_ReflectionsLevel: float + m_ReverbDelay: float + m_ReverbLevel: float + m_ReverbPreset: int + m_Room: float + m_RoomHF: float + m_RoomLF: float + m_RoomRolloff: Optional[float] = None + + +@unitypy_define +class AudioReverbZone(Behaviour): + m_DecayHFRatio: float + m_DecayTime: float + m_Density: float + m_Diffusion: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_HFReference: float + m_LFReference: float + m_MaxDistance: float + m_MinDistance: float + m_Reflections: int + m_ReflectionsDelay: float + m_Reverb: int + m_ReverbDelay: float + m_ReverbPreset: int + m_Room: int + m_RoomHF: int + m_RoomLF: int + m_RoomRolloffFactor: Optional[float] = None + + +@unitypy_define +class Camera(Behaviour): + far_clip_plane: float + field_of_view: float + m_BackGroundColor: ColorRGBA + m_ClearFlags: int + m_CullingMask: BitField + m_Depth: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_NormalizedViewPortRect: Rectf + m_RenderingPath: int + m_TargetTexture: PPtr[RenderTexture] + near_clip_plane: float + orthographic: bool + orthographic_size: float + m_AllowDynamicResolution: Optional[bool] = None + m_AllowMSAA: Optional[bool] = None + m_Anamorphism: Optional[float] = None + m_Aperture: Optional[float] = None + m_BarrelClipping: Optional[float] = None + m_BladeCount: Optional[int] = None + m_Curvature: Optional[Vector2f] = None + m_FocalLength: Optional[float] = None + m_FocusDistance: Optional[float] = None + m_ForceIntoRT: Optional[bool] = None + m_GateFitMode: Optional[int] = None + m_HDR: Optional[bool] = None + m_Iso: Optional[int] = None + m_LensShift: Optional[Vector2f] = None + m_OcclusionCulling: Optional[bool] = None + m_SensorSize: Optional[Vector2f] = None + m_ShutterSpeed: Optional[float] = None + m_StereoConvergence: Optional[float] = None + m_StereoMirrorMode: Optional[bool] = None + m_StereoSeparation: Optional[float] = None + m_TargetDisplay: Optional[int] = None + m_TargetEye: Optional[int] = None + m_projectionMatrixMode: Optional[int] = None + + +@unitypy_define +class ScriptableCamera(Camera): + far_clip_plane: float + field_of_view: float + m_AllowDynamicResolution: bool + m_AllowMSAA: bool + m_BackGroundColor: ColorRGBA + m_ClearFlags: int + m_CullingMask: BitField + m_Depth: float + m_Enabled: int + m_FocalLength: float + m_ForceIntoRT: bool + m_GameObject: PPtr[GameObject] + m_GateFitMode: int + m_HDR: bool + m_LensShift: Vector2f + m_NormalizedViewPortRect: Rectf + m_OcclusionCulling: bool + m_RenderingPath: int + m_Script: PPtr[MonoScript] + m_SensorSize: Vector2f + m_StereoConvergence: float + m_StereoSeparation: float + m_TargetDisplay: int + m_TargetEye: int + m_TargetTexture: PPtr[RenderTexture] + m_projectionMatrixMode: int + near_clip_plane: float + orthographic: bool + orthographic_size: float + + +@unitypy_define +class Canvas(Behaviour): + m_Camera: PPtr[Camera] + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_PixelPerfect: bool + m_RenderMode: int + m_AdditionalShaderChannelsFlag: Optional[int] = None + m_Alpha: Optional[float] = None + m_Normals: Optional[bool] = None + m_OverridePixelPerfect: Optional[bool] = None + m_OverrideSorting: Optional[bool] = None + m_PlaneDistance: Optional[float] = None + m_PositionUVs: Optional[bool] = None + m_ReceivesEvents: Optional[bool] = None + m_SortingBucketNormalizedSize: Optional[float] = None + m_SortingLayerID: Optional[int] = None + m_SortingOrder: Optional[int] = None + m_TargetDisplay: Optional[int] = None + m_UpdateRectTransformForStandalone: Optional[int] = None + m_UseReflectionProbes: Optional[bool] = None + m_VertexColorAlwaysGammaSpace: Optional[bool] = None + + +@unitypy_define +class CanvasGroup(Behaviour): + m_Alpha: float + m_BlocksRaycasts: bool + m_GameObject: PPtr[GameObject] + m_IgnoreParentGroups: bool + m_Interactable: bool + m_Enabled: Optional[int] = None + + +@unitypy_define +class Cloth(Behaviour): + m_GameObject: PPtr[GameObject] + m_BendingStiffness: Optional[float] = None + m_CapsuleColliders: Optional[List[PPtr[CapsuleCollider]]] = None + m_Coefficients: Optional[List[ClothConstrainCoefficients]] = None + m_CollisionMassScale: Optional[float] = None + m_Damping: Optional[float] = None + m_Enabled: Optional[int] = None + m_ExternalAcceleration: Optional[Vector3f] = None + m_Friction: Optional[float] = None + m_RandomAcceleration: Optional[Vector3f] = None + m_SelfAndInterCollisionIndices: Optional[List[int]] = None + m_SelfCollisionDistance: Optional[float] = None + m_SelfCollisionStiffness: Optional[float] = None + m_SleepThreshold: Optional[float] = None + m_SolverFrequency: Optional[Union[float, int]] = None + m_SphereColliders: Optional[Union[List[ClothSphereColliderPair], List[Tuple[PPtr[SphereCollider], PPtr[SphereCollider]]]]] = None + m_StretchingStiffness: Optional[float] = None + m_UseContinuousCollision: Optional[bool] = None + m_UseGravity: Optional[bool] = None + m_UseTethers: Optional[bool] = None + m_UseVirtualParticles: Optional[bool] = None + m_VirtualParticleIndices: Optional[List[int]] = None + m_VirtualParticleWeights: Optional[List[Vector3f]] = None + m_WorldAccelerationScale: Optional[float] = None + m_WorldVelocityScale: Optional[float] = None + + +@unitypy_define +class InteractiveCloth(Cloth): + m_AttachedColliders: List[ClothAttachment] + m_AttachmentResponse: float + m_AttachmentTearFactor: float + m_BendingStiffness: float + m_CollisionResponse: float + m_Damping: float + m_Density: float + m_Enabled: int + m_ExternalAcceleration: Vector3f + m_Friction: float + m_GameObject: PPtr[GameObject] + m_Mesh: PPtr[Mesh] + m_Pressure: float + m_RandomAcceleration: Vector3f + m_SelfCollision: bool + m_StretchingStiffness: float + m_TearFactor: float + m_Thickness: float + m_UseGravity: bool + + +@unitypy_define +class SkinnedCloth(Cloth): + m_BendingStiffness: float + m_Coefficients: List[ClothConstrainCoefficients] + m_Damping: float + m_Enabled: int + m_ExternalAcceleration: Vector3f + m_GameObject: PPtr[GameObject] + m_RandomAcceleration: Vector3f + m_SelfCollision: bool + m_StretchingStiffness: float + m_Thickness: float + m_UseGravity: bool + m_WorldAccelerationScale: float + m_WorldVelocityScale: float + + +@unitypy_define +class CloudServiceHandlerBehaviour(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class Collider2D(Behaviour): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class BoxCollider2D(Collider2D): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Material: PPtr[PhysicsMaterial2D] + m_Size: Vector2f + m_AutoTiling: Optional[bool] = None + m_CallbackLayers: Optional[BitField] = None + m_Center: Optional[Vector2f] = None + m_CompositeOperation: Optional[int] = None + m_CompositeOrder: Optional[int] = None + m_ContactCaptureLayers: Optional[BitField] = None + m_Density: Optional[float] = None + m_EdgeRadius: Optional[float] = None + m_ExcludeLayers: Optional[BitField] = None + m_ForceReceiveLayers: Optional[BitField] = None + m_ForceSendLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_Offset: Optional[Vector2f] = None + m_SpriteTilingProperty: Optional[SpriteTilingProperty] = None + m_UsedByComposite: Optional[bool] = None + m_UsedByEffector: Optional[bool] = None + + +@unitypy_define +class CapsuleCollider2D(Collider2D): + m_Density: float + m_Direction: int + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Material: PPtr[PhysicsMaterial2D] + m_Offset: Vector2f + m_Size: Vector2f + m_UsedByEffector: bool + m_CallbackLayers: Optional[BitField] = None + m_CompositeOperation: Optional[int] = None + m_CompositeOrder: Optional[int] = None + m_ContactCaptureLayers: Optional[BitField] = None + m_ExcludeLayers: Optional[BitField] = None + m_ForceReceiveLayers: Optional[BitField] = None + m_ForceSendLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_UsedByComposite: Optional[bool] = None + + +@unitypy_define +class CircleCollider2D(Collider2D): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Material: PPtr[PhysicsMaterial2D] + m_Radius: float + m_CallbackLayers: Optional[BitField] = None + m_Center: Optional[Vector2f] = None + m_CompositeOperation: Optional[int] = None + m_CompositeOrder: Optional[int] = None + m_ContactCaptureLayers: Optional[BitField] = None + m_Density: Optional[float] = None + m_ExcludeLayers: Optional[BitField] = None + m_ForceReceiveLayers: Optional[BitField] = None + m_ForceSendLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_Offset: Optional[Vector2f] = None + m_UsedByComposite: Optional[bool] = None + m_UsedByEffector: Optional[bool] = None + + +@unitypy_define +class CompositeCollider2D(Collider2D): + m_ColliderPaths: List[SubCollider] + m_CompositePaths: Polygon2D + m_Density: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_GenerationType: int + m_GeometryType: int + m_IsTrigger: bool + m_Material: PPtr[PhysicsMaterial2D] + m_Offset: Vector2f + m_UsedByEffector: bool + m_VertexDistance: float + m_CallbackLayers: Optional[BitField] = None + m_CompositeGameObject: Optional[PPtr[GameObject]] = None + m_CompositeOperation: Optional[int] = None + m_CompositeOrder: Optional[int] = None + m_ContactCaptureLayers: Optional[BitField] = None + m_EdgeRadius: Optional[float] = None + m_ExcludeLayers: Optional[BitField] = None + m_ForceReceiveLayers: Optional[BitField] = None + m_ForceSendLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_OffsetDistance: Optional[float] = None + m_UseDelaunayMesh: Optional[bool] = None + m_UsedByComposite: Optional[bool] = None + + +@unitypy_define +class CustomCollider2D(Collider2D): + m_CustomShapes: PhysicsShapeGroup2D + m_Density: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Material: PPtr[PhysicsMaterial2D] + m_Offset: Vector2f + m_UsedByEffector: bool + m_CallbackLayers: Optional[BitField] = None + m_CompositeOperation: Optional[int] = None + m_CompositeOrder: Optional[int] = None + m_ContactCaptureLayers: Optional[BitField] = None + m_ExcludeLayers: Optional[BitField] = None + m_ForceReceiveLayers: Optional[BitField] = None + m_ForceSendLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_UsedByComposite: Optional[bool] = None + + +@unitypy_define +class EdgeCollider2D(Collider2D): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Material: PPtr[PhysicsMaterial2D] + m_Points: List[Vector2f] + m_AdjacentEndPoint: Optional[Vector2f] = None + m_AdjacentStartPoint: Optional[Vector2f] = None + m_CallbackLayers: Optional[BitField] = None + m_CompositeOperation: Optional[int] = None + m_CompositeOrder: Optional[int] = None + m_ContactCaptureLayers: Optional[BitField] = None + m_Density: Optional[float] = None + m_EdgeRadius: Optional[float] = None + m_ExcludeLayers: Optional[BitField] = None + m_ForceReceiveLayers: Optional[BitField] = None + m_ForceSendLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_Offset: Optional[Vector2f] = None + m_UseAdjacentEndPoint: Optional[bool] = None + m_UseAdjacentStartPoint: Optional[bool] = None + m_UsedByComposite: Optional[bool] = None + m_UsedByEffector: Optional[bool] = None + + +@unitypy_define +class PolygonCollider2D(Collider2D): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Material: PPtr[PhysicsMaterial2D] + m_AutoTiling: Optional[bool] = None + m_CallbackLayers: Optional[BitField] = None + m_CompositeOperation: Optional[int] = None + m_CompositeOrder: Optional[int] = None + m_ContactCaptureLayers: Optional[BitField] = None + m_Density: Optional[float] = None + m_ExcludeLayers: Optional[BitField] = None + m_ForceReceiveLayers: Optional[BitField] = None + m_ForceSendLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_Offset: Optional[Vector2f] = None + m_Points: Optional[Polygon2D] = None + m_Poly: Optional[Polygon2D] = None + m_SpriteTilingProperty: Optional[SpriteTilingProperty] = None + m_UseDelaunayMesh: Optional[bool] = None + m_UsedByComposite: Optional[bool] = None + m_UsedByEffector: Optional[bool] = None + + +@unitypy_define +class PolygonColliderBase2D(Collider2D): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class TilemapCollider2D(Collider2D): + m_Density: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Material: PPtr[PhysicsMaterial2D] + m_Offset: Vector2f + m_UsedByEffector: bool + m_CallbackLayers: Optional[BitField] = None + m_CompositeOperation: Optional[int] = None + m_CompositeOrder: Optional[int] = None + m_ContactCaptureLayers: Optional[BitField] = None + m_ExcludeLayers: Optional[BitField] = None + m_ExtrusionFactor: Optional[float] = None + m_ForceReceiveLayers: Optional[BitField] = None + m_ForceSendLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_MaximumTileChangeCount: Optional[int] = None + m_UseDelaunayMesh: Optional[bool] = None + m_UsedByComposite: Optional[bool] = None + + +@unitypy_define +class ConstantForce(Behaviour): + m_Enabled: int + m_Force: Vector3f + m_GameObject: PPtr[GameObject] + m_RelativeForce: Vector3f + m_RelativeTorque: Vector3f + m_Torque: Vector3f + + +@unitypy_define +class Effector2D(Behaviour): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class AreaEffector2D(Effector2D): + m_ColliderMask: BitField + m_Enabled: int + m_ForceMagnitude: float + m_ForceTarget: int + m_ForceVariation: float + m_GameObject: PPtr[GameObject] + m_AngularDamping: Optional[float] = None + m_AngularDrag: Optional[float] = None + m_Drag: Optional[float] = None + m_ForceAngle: Optional[float] = None + m_ForceDirection: Optional[float] = None + m_LinearDamping: Optional[float] = None + m_UseColliderMask: Optional[bool] = None + m_UseGlobalAngle: Optional[bool] = None + + +@unitypy_define +class BuoyancyEffector2D(Effector2D): + m_ColliderMask: BitField + m_Density: float + m_Enabled: int + m_FlowAngle: float + m_FlowMagnitude: float + m_FlowVariation: float + m_GameObject: PPtr[GameObject] + m_SurfaceLevel: float + m_UseColliderMask: bool + m_AngularDamping: Optional[float] = None + m_AngularDrag: Optional[float] = None + m_LinearDamping: Optional[float] = None + m_LinearDrag: Optional[float] = None + + +@unitypy_define +class PlatformEffector2D(Effector2D): + m_ColliderMask: BitField + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_OneWay: Optional[bool] = None + m_RotationalOffset: Optional[float] = None + m_SideAngleVariance: Optional[float] = None + m_SideArc: Optional[float] = None + m_SideBounce: Optional[bool] = None + m_SideFriction: Optional[bool] = None + m_SurfaceArc: Optional[float] = None + m_UseColliderMask: Optional[bool] = None + m_UseOneWay: Optional[bool] = None + m_UseOneWayGrouping: Optional[bool] = None + m_UseSideBounce: Optional[bool] = None + m_UseSideFriction: Optional[bool] = None + + +@unitypy_define +class PointEffector2D(Effector2D): + m_ColliderMask: BitField + m_DistanceScale: float + m_Enabled: int + m_ForceMagnitude: float + m_ForceMode: int + m_ForceSource: int + m_ForceTarget: int + m_ForceVariation: float + m_GameObject: PPtr[GameObject] + m_AngularDamping: Optional[float] = None + m_AngularDrag: Optional[float] = None + m_Drag: Optional[float] = None + m_LinearDamping: Optional[float] = None + m_UseColliderMask: Optional[bool] = None + + +@unitypy_define +class SurfaceEffector2D(Effector2D): + m_ColliderMask: BitField + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Speed: float + m_SpeedVariation: float + m_ForceScale: Optional[float] = None + m_UseBounce: Optional[bool] = None + m_UseColliderMask: Optional[bool] = None + m_UseContactForce: Optional[bool] = None + m_UseFriction: Optional[bool] = None + + +@unitypy_define +class FlareLayer(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class GUIElement(Behaviour): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class GUIText(GUIElement): + m_Alignment: int + m_Anchor: int + m_Enabled: int + m_Font: PPtr[Font] + m_FontSize: int + m_FontStyle: int + m_GameObject: PPtr[GameObject] + m_LineSpacing: float + m_Material: PPtr[Material] + m_PixelCorrect: bool + m_PixelOffset: Vector2f + m_TabSize: float + m_Text: str + m_Color: Optional[ColorRGBA] = None + m_RichText: Optional[bool] = None + + +@unitypy_define +class GUITexture(GUIElement): + m_BottomBorder: int + m_Color: ColorRGBA + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_LeftBorder: int + m_PixelInset: Rectf + m_RightBorder: int + m_Texture: PPtr[Texture] + m_TopBorder: int + + +@unitypy_define +class GUILayer(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class GridLayout(Behaviour): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class Grid(GridLayout): + m_CellGap: Vector3f + m_CellLayout: int + m_CellSize: Vector3f + m_CellSwizzle: int + m_Enabled: int + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class Tilemap(GridLayout): + m_AnimatedTiles: List[Tuple[int3_storage, TileAnimationData]] + m_AnimationFrameRate: float + m_Color: ColorRGBA + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Origin: int3_storage + m_Size: int3_storage + m_TileAnchor: Vector3f + m_TileAssetArray: List[TilemapRefCountedData] + m_TileColorArray: List[TilemapRefCountedData] + m_TileMatrixArray: List[TilemapRefCountedData] + m_TileOrientation: int + m_TileOrientationMatrix: Matrix4x4f + m_TileSpriteArray: List[TilemapRefCountedData] + m_Tiles: List[Tuple[int3_storage, Tile]] + m_TileObjectToInstantiateArray: Optional[List[TilemapRefCountedData]] = None + + +@unitypy_define +class Halo(Behaviour): + m_Color: ColorRGBA + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Size: float + + +@unitypy_define +class HaloLayer(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class IConstraint(Behaviour): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class AimConstraint(IConstraint): + m_AffectRotationX: bool + m_AffectRotationY: bool + m_AffectRotationZ: bool + m_AimVector: Vector3f + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_RotationAtRest: Vector3f + m_RotationOffset: Vector3f + m_Sources: List[ConstraintSource] + m_UpType: int + m_UpVector: Vector3f + m_Weight: float + m_WorldUpObject: PPtr[Transform] + m_WorldUpVector: Vector3f + m_Active: Optional[bool] = None + m_IsContraintActive: Optional[bool] = None + + +@unitypy_define +class LookAtConstraint(IConstraint): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Roll: float + m_RotationAtRest: Vector3f + m_RotationOffset: Vector3f + m_Sources: List[ConstraintSource] + m_UseUpObject: bool + m_Weight: float + m_WorldUpObject: PPtr[Transform] + m_Active: Optional[bool] = None + m_IsContraintActive: Optional[bool] = None + + +@unitypy_define +class ParentConstraint(IConstraint): + m_AffectRotationX: bool + m_AffectRotationY: bool + m_AffectRotationZ: bool + m_AffectTranslationX: bool + m_AffectTranslationY: bool + m_AffectTranslationZ: bool + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_RotationAtRest: Vector3f + m_RotationOffsets: List[Vector3f] + m_Sources: List[ConstraintSource] + m_TranslationAtRest: Vector3f + m_TranslationOffsets: List[Vector3f] + m_Weight: float + m_Active: Optional[bool] = None + m_IsContraintActive: Optional[bool] = None + + +@unitypy_define +class PositionConstraint(IConstraint): + m_AffectTranslationX: bool + m_AffectTranslationY: bool + m_AffectTranslationZ: bool + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Sources: List[ConstraintSource] + m_TranslationAtRest: Vector3f + m_TranslationOffset: Vector3f + m_Weight: float + m_Active: Optional[bool] = None + m_IsContraintActive: Optional[bool] = None + + +@unitypy_define +class RotationConstraint(IConstraint): + m_AffectRotationX: bool + m_AffectRotationY: bool + m_AffectRotationZ: bool + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_RotationAtRest: Vector3f + m_RotationOffset: Vector3f + m_Sources: List[ConstraintSource] + m_Weight: float + m_Active: Optional[bool] = None + m_IsContraintActive: Optional[bool] = None + + +@unitypy_define +class ScaleConstraint(IConstraint): + m_AffectScalingX: bool + m_AffectScalingY: bool + m_AffectScalingZ: bool + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_ScaleAtRest: Vector3f + m_ScaleOffset: Vector3f + m_Sources: List[ConstraintSource] + m_Weight: float + m_Active: Optional[bool] = None + m_IsContraintActive: Optional[bool] = None + + +@unitypy_define +class Joint2D(Behaviour): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class AnchoredJoint2D(Joint2D): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class DistanceJoint2D(AnchoredJoint2D): + m_Anchor: Vector2f + m_ConnectedAnchor: Vector2f + m_ConnectedRigidBody: PPtr[Rigidbody2D] + m_Distance: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_AutoConfigureConnectedAnchor: Optional[bool] = None + m_AutoConfigureDistance: Optional[bool] = None + m_BreakAction: Optional[int] = None + m_BreakForce: Optional[float] = None + m_BreakTorque: Optional[float] = None + m_CollideConnected: Optional[bool] = None + m_EnableCollision: Optional[bool] = None + m_MaxDistanceOnly: Optional[bool] = None + + +@unitypy_define +class FixedJoint2D(AnchoredJoint2D): + m_Anchor: Vector2f + m_AutoConfigureConnectedAnchor: bool + m_BreakForce: float + m_BreakTorque: float + m_ConnectedAnchor: Vector2f + m_ConnectedRigidBody: PPtr[Rigidbody2D] + m_DampingRatio: float + m_EnableCollision: bool + m_Enabled: int + m_Frequency: float + m_GameObject: PPtr[GameObject] + m_BreakAction: Optional[int] = None + + +@unitypy_define +class FrictionJoint2D(AnchoredJoint2D): + m_Anchor: Vector2f + m_AutoConfigureConnectedAnchor: bool + m_BreakForce: float + m_BreakTorque: float + m_ConnectedAnchor: Vector2f + m_ConnectedRigidBody: PPtr[Rigidbody2D] + m_EnableCollision: bool + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_MaxForce: float + m_MaxTorque: float + m_BreakAction: Optional[int] = None + + +@unitypy_define +class HingeJoint2D(AnchoredJoint2D): + m_Anchor: Vector2f + m_AngleLimits: Union[JointAngleLimit2D, JointAngleLimits2D] + m_ConnectedAnchor: Vector2f + m_ConnectedRigidBody: PPtr[Rigidbody2D] + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Motor: JointMotor2D + m_UseLimits: bool + m_UseMotor: bool + m_AutoConfigureConnectedAnchor: Optional[bool] = None + m_BreakAction: Optional[int] = None + m_BreakForce: Optional[float] = None + m_BreakTorque: Optional[float] = None + m_CollideConnected: Optional[bool] = None + m_EnableCollision: Optional[bool] = None + m_UseConnectedAnchor: Optional[bool] = None + + +@unitypy_define +class SliderJoint2D(AnchoredJoint2D): + m_Anchor: Vector2f + m_Angle: float + m_ConnectedAnchor: Vector2f + m_ConnectedRigidBody: PPtr[Rigidbody2D] + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Motor: JointMotor2D + m_TranslationLimits: JointTranslationLimits2D + m_UseLimits: bool + m_UseMotor: bool + m_AutoConfigureAngle: Optional[bool] = None + m_AutoConfigureConnectedAnchor: Optional[bool] = None + m_BreakAction: Optional[int] = None + m_BreakForce: Optional[float] = None + m_BreakTorque: Optional[float] = None + m_CollideConnected: Optional[bool] = None + m_EnableCollision: Optional[bool] = None + + +@unitypy_define +class SpringJoint2D(AnchoredJoint2D): + m_Anchor: Vector2f + m_ConnectedAnchor: Vector2f + m_ConnectedRigidBody: PPtr[Rigidbody2D] + m_DampingRatio: float + m_Distance: float + m_Enabled: int + m_Frequency: float + m_GameObject: PPtr[GameObject] + m_AutoConfigureConnectedAnchor: Optional[bool] = None + m_AutoConfigureDistance: Optional[bool] = None + m_BreakAction: Optional[int] = None + m_BreakForce: Optional[float] = None + m_BreakTorque: Optional[float] = None + m_CollideConnected: Optional[bool] = None + m_EnableCollision: Optional[bool] = None + + +@unitypy_define +class WheelJoint2D(AnchoredJoint2D): + m_Anchor: Vector2f + m_ConnectedAnchor: Vector2f + m_ConnectedRigidBody: PPtr[Rigidbody2D] + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Motor: JointMotor2D + m_Suspension: JointSuspension2D + m_UseMotor: bool + m_AutoConfigureConnectedAnchor: Optional[bool] = None + m_BreakAction: Optional[int] = None + m_BreakForce: Optional[float] = None + m_BreakTorque: Optional[float] = None + m_CollideConnected: Optional[bool] = None + m_EnableCollision: Optional[bool] = None + + +@unitypy_define +class RelativeJoint2D(Joint2D): + m_AngularOffset: float + m_AutoConfigureOffset: bool + m_BreakForce: float + m_BreakTorque: float + m_ConnectedRigidBody: PPtr[Rigidbody2D] + m_CorrectionScale: float + m_EnableCollision: bool + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_LinearOffset: Vector2f + m_MaxForce: float + m_MaxTorque: float + m_BreakAction: Optional[int] = None + + +@unitypy_define +class TargetJoint2D(Joint2D): + m_Anchor: Vector2f + m_AutoConfigureTarget: bool + m_BreakForce: float + m_BreakTorque: float + m_ConnectedRigidBody: PPtr[Rigidbody2D] + m_DampingRatio: float + m_EnableCollision: bool + m_Enabled: int + m_Frequency: float + m_GameObject: PPtr[GameObject] + m_MaxForce: float + m_Target: Vector2f + m_BreakAction: Optional[int] = None + + +@unitypy_define +class LensFlare(Behaviour): + m_Brightness: float + m_Color: ColorRGBA + m_Directional: bool + m_Enabled: int + m_Flare: PPtr[Flare] + m_GameObject: PPtr[GameObject] + m_IgnoreLayers: BitField + m_FadeSpeed: Optional[float] = None + + +@unitypy_define +class Light(Behaviour): + m_Color: ColorRGBA + m_Cookie: PPtr[Texture] + m_CullingMask: BitField + m_DrawHalo: bool + m_Enabled: int + m_Flare: PPtr[Flare] + m_GameObject: PPtr[GameObject] + m_Intensity: float + m_Lightmapping: int + m_Range: float + m_RenderMode: int + m_Shadows: ShadowSettings + m_SpotAngle: float + m_Type: int + m_ActuallyLightmapped: Optional[bool] = None + m_AreaSize: Optional[Vector2f] = None + m_BakedIndex: Optional[int] = None + m_BakingOutput: Optional[LightBakingOutput] = None + m_BounceIntensity: Optional[float] = None + m_BoundingSphereOverride: Optional[Vector4f] = None + m_CCT: Optional[float] = None + m_ColorTemperature: Optional[float] = None + m_CookieSize: Optional[float] = None + m_CookieSize2D: Optional[Vector2f] = None + m_EnableSpotReflector: Optional[bool] = None + m_FalloffTable: Optional[FalloffTable] = None + m_ForceVisible: Optional[bool] = None + m_InnerSpotAngle: Optional[float] = None + m_LightShadowCasterMode: Optional[int] = None + m_LightUnit: Optional[int] = None + m_LuxAtDistance: Optional[float] = None + m_RenderingLayerMask: Optional[int] = None + m_Shape: Optional[int] = None + m_ShapeRadius: Optional[float] = None + m_UseBoundingSphereOverride: Optional[bool] = None + m_UseColorTemperature: Optional[bool] = None + m_UseViewFrustumForShadowCasterCull: Optional[bool] = None + + +@unitypy_define +class LightProbeGroup(Behaviour): + m_GameObject: PPtr[GameObject] + m_Enabled: Optional[int] = None + + +@unitypy_define +class LightProbeProxyVolume(Behaviour): + m_BoundingBoxMode: int + m_BoundingBoxOrigin: Vector3f + m_BoundingBoxSize: Vector3f + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_ProbePositionMode: int + m_RefreshMode: int + m_ResolutionMode: int + m_ResolutionProbesPerUnit: float + m_ResolutionX: int + m_ResolutionY: int + m_ResolutionZ: int + m_DataFormat: Optional[int] = None + m_QualityMode: Optional[int] = None + + +@unitypy_define +class MonoBehaviour(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Name: str + m_Script: PPtr[MonoScript] + + +@unitypy_define +class NavMeshAgent(Behaviour): + m_Acceleration: float + m_AngularSpeed: float + m_AutoRepath: bool + m_AutoTraverseOffMeshLink: bool + m_BaseOffset: float + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Height: float + m_ObstacleAvoidanceType: int + m_Radius: float + m_Speed: float + m_StoppingDistance: float + m_WalkableMask: int + avoidancePriority: Optional[int] = None + m_AgentTypeID: Optional[int] = None + m_AutoBraking: Optional[bool] = None + + +@unitypy_define +class NavMeshObstacle(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Carve: Optional[bool] = None + m_CarveOnlyStationary: Optional[bool] = None + m_Center: Optional[Vector3f] = None + m_Extents: Optional[Vector3f] = None + m_Height: Optional[float] = None + m_MoveThreshold: Optional[float] = None + m_Radius: Optional[float] = None + m_Shape: Optional[int] = None + m_TimeToStationary: Optional[float] = None + + +@unitypy_define +class NetworkView(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Observed: PPtr[Component] + m_StateSynchronization: int + m_ViewID: NetworkViewID + + +@unitypy_define +class OffMeshLink(Behaviour): + m_Activated: bool + m_BiDirectional: bool + m_CostOverride: float + m_End: PPtr[Transform] + m_GameObject: PPtr[GameObject] + m_Start: PPtr[Transform] + m_AgentTypeID: Optional[int] = None + m_AreaIndex: Optional[int] = None + m_AutoUpdatePositions: Optional[bool] = None + m_DtPolyRef: Optional[int] = None + m_Enabled: Optional[int] = None + m_NavMeshLayer: Optional[int] = None + + +@unitypy_define +class ParticleSystemForceField(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Parameters: ParticleSystemForceFieldParameters + + +@unitypy_define +class PhysicsUpdateBehaviour2D(Behaviour): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class ConstantForce2D(PhysicsUpdateBehaviour2D): + m_Enabled: int + m_Force: Vector2f + m_GameObject: PPtr[GameObject] + m_RelativeForce: Vector2f + m_Torque: float + + +@unitypy_define +class PlayableDirector(Behaviour): + m_DirectorUpdateMode: int + m_Enabled: int + m_ExposedReferences: ExposedReferenceTable + m_GameObject: PPtr[GameObject] + m_InitialState: int + m_InitialTime: float + m_PlayableAsset: PPtr[Object] + m_SceneBindings: List[DirectorGenericBinding] + m_WrapMode: int + + +@unitypy_define +class Projector(Behaviour): + m_AspectRatio: float + m_Enabled: int + m_FarClipPlane: float + m_FieldOfView: float + m_GameObject: PPtr[GameObject] + m_IgnoreLayers: BitField + m_Material: PPtr[Material] + m_NearClipPlane: float + m_Orthographic: bool + m_OrthographicSize: float + + +@unitypy_define +class ReflectionProbe(Behaviour): + m_BackGroundColor: ColorRGBA + m_BakedTexture: PPtr[Texture] + m_BoxOffset: Vector3f + m_BoxProjection: bool + m_BoxSize: Vector3f + m_ClearFlags: int + m_CullingMask: BitField + m_CustomBakedTexture: PPtr[Texture] + m_Enabled: int + m_FarClip: float + m_GameObject: PPtr[GameObject] + m_HDR: bool + m_Importance: int + m_IntensityMultiplier: float + m_Mode: int + m_NearClip: float + m_RefreshMode: int + m_RenderDynamicObjects: bool + m_Resolution: int + m_ShadowDistance: float + m_TimeSlicingMode: int + m_Type: int + m_UpdateFrequency: int + m_UseOcclusionCulling: bool + m_BlendDistance: Optional[float] = None + + +@unitypy_define +class Skybox(Behaviour): + m_CustomSkybox: PPtr[Material] + m_Enabled: int + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class SortingGroup(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_SortingLayer: int + m_SortingOrder: int + m_Sort3DAs2D: Optional[bool] = None + m_SortAtRoot: Optional[bool] = None + m_SortingLayerID: Optional[int] = None + + +@unitypy_define +class StreamingController(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_StreamingMipmapBias: float + + +@unitypy_define +class Terrain(Behaviour): + m_BakeLightProbesForTrees: bool + m_ChunkDynamicUVST: Vector4f + m_DetailObjectDensity: float + m_DetailObjectDistance: float + m_DrawHeightmap: bool + m_DrawTreesAndFoliage: bool + m_DynamicUVST: Vector4f + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_HeightmapMaximumLOD: int + m_HeightmapPixelError: float + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_MaterialTemplate: PPtr[Material] + m_ReflectionProbeUsage: int + m_SplatMapDistance: float + m_TerrainData: PPtr[TerrainData] + m_TreeBillboardDistance: float + m_TreeCrossFadeLength: float + m_TreeDistance: float + m_TreeMaximumFullLODCount: int + m_AllowAutoConnect: Optional[bool] = None + m_CastShadows: Optional[bool] = None + m_DefaultSmoothness: Optional[float] = None + m_DrawInstanced: Optional[bool] = None + m_EnableHeightmapLODFrustumCulling: Optional[bool] = None + m_EnableHeightmapRayTracing: Optional[bool] = None + m_EnableTreesAndDetailsRayTracing: Optional[bool] = None + m_ExplicitProbeSetHash: Optional[Hash128] = None + m_GroupingID: Optional[int] = None + m_HeightmapMinimumLODSimplification: Optional[int] = None + m_IgnoreQualitySettings: Optional[bool] = None + m_LegacyShininess: Optional[float] = None + m_LegacySpecular: Optional[ColorRGBA] = None + m_MaterialType: Optional[int] = None + m_PreserveTreePrototypeLayers: Optional[bool] = None + m_RenderingLayerMask: Optional[int] = None + m_ShadowCastingMode: Optional[int] = None + m_StaticShadowCaster: Optional[bool] = None + m_TreeMotionVectorModeOverride: Optional[int] = None + m_UseDefaultSmoothness: Optional[bool] = None + + +@unitypy_define +class VideoPlayer(Behaviour): + m_AspectRatio: int + m_AudioOutputMode: int + m_ControlledAudioTrackCount: int + m_DataSource: int + m_DirectAudioMutes: List[bool] + m_DirectAudioVolumes: List[float] + m_Enabled: int + m_EnabledAudioTracks: List[bool] + m_FrameReadyEventEnabled: bool + m_GameObject: PPtr[GameObject] + m_Looping: bool + m_PlayOnAwake: bool + m_PlaybackSpeed: float + m_RenderMode: int + m_SkipOnDrop: bool + m_TargetAudioSources: List[PPtr[AudioSource]] + m_TargetCamera: PPtr[Camera] + m_TargetCameraAlpha: float + m_TargetMaterialProperty: str + m_TargetMaterialRenderer: PPtr[Renderer] + m_TargetTexture: PPtr[RenderTexture] + m_Url: str + m_VideoClip: PPtr[VideoClip] + m_WaitForFirstFrame: bool + m_TargetCamera3DLayout: Optional[int] = None + m_TargetMaterialName: Optional[str] = None + m_TimeReference: Optional[int] = None + m_TimeUpdateMode: Optional[int] = None + m_VideoShaders: Optional[List[PPtr[Shader]]] = None + + +@unitypy_define +class VisualEffect(Behaviour): + m_Asset: PPtr[VisualEffectAsset] + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_PropertySheet: VFXPropertySheetSerializedBase + m_ResetSeedOnPlay: Union[bool, int] + m_StartSeed: int + m_AllowInstancing: Optional[int] = None + m_InitialEventName: Optional[str] = None + m_InitialEventNameOverriden: Optional[int] = None + m_ReleaseInstanceOnDisable: Optional[int] = None + + +@unitypy_define +class WindZone(Behaviour): + m_Enabled: int + m_GameObject: PPtr[GameObject] + m_Mode: int + m_Radius: float + m_WindMain: float + m_WindPulseFrequency: float + m_WindPulseMagnitude: float + m_WindTurbulence: float + + +@unitypy_define +class CanvasRenderer(Component): + m_GameObject: PPtr[GameObject] + m_CullTransparentMesh: Optional[bool] = None + + +@unitypy_define +class Collider(Component): + m_GameObject: Optional[PPtr[GameObject]] = None + m_MaxLimitX: Optional[float] = None + m_MaxLimitY: Optional[float] = None + m_MaxLimitZ: Optional[float] = None + m_MinLimitX: Optional[float] = None + m_Type: Optional[int] = None + m_X: Optional[xform] = None + m_XMotionType: Optional[int] = None + m_YMotionType: Optional[int] = None + m_ZMotionType: Optional[int] = None + + +@unitypy_define +class BoxCollider(Collider): + m_Center: Vector3f + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Material: Union[PPtr[PhysicMaterial], PPtr[PhysicsMaterial]] + m_Size: Vector3f + m_ExcludeLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_ProvidesContacts: Optional[bool] = None + + +@unitypy_define +class CapsuleCollider(Collider): + m_Center: Vector3f + m_Direction: int + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_Height: float + m_IsTrigger: bool + m_Material: Union[PPtr[PhysicMaterial], PPtr[PhysicsMaterial]] + m_Radius: float + m_ExcludeLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_ProvidesContacts: Optional[bool] = None + + +@unitypy_define +class CharacterController(Collider): + m_Center: Vector3f + m_GameObject: PPtr[GameObject] + m_Height: float + m_MinMoveDistance: float + m_Radius: float + m_SkinWidth: float + m_SlopeLimit: float + m_StepOffset: float + m_Enabled: Optional[bool] = None + m_ExcludeLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_IsTrigger: Optional[bool] = None + m_LayerOverridePriority: Optional[int] = None + m_Material: Optional[Union[PPtr[PhysicMaterial], PPtr[PhysicsMaterial]]] = None + m_ProvidesContacts: Optional[bool] = None + + +@unitypy_define +class MeshCollider(Collider): + m_Convex: bool + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Material: Union[PPtr[PhysicMaterial], PPtr[PhysicsMaterial]] + m_Mesh: PPtr[Mesh] + m_CookingOptions: Optional[int] = None + m_ExcludeLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_InflateMesh: Optional[bool] = None + m_LayerOverridePriority: Optional[int] = None + m_ProvidesContacts: Optional[bool] = None + m_SkinWidth: Optional[float] = None + m_SmoothSphereCollisions: Optional[bool] = None + + +@unitypy_define +class RaycastCollider(Collider): + m_Center: Vector3f + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Length: float + m_Material: PPtr[PhysicMaterial] + + +@unitypy_define +class SphereCollider(Collider): + m_Center: Vector3f + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_IsTrigger: bool + m_Material: Union[PPtr[PhysicMaterial], PPtr[PhysicsMaterial]] + m_Radius: float + m_ExcludeLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_ProvidesContacts: Optional[bool] = None + + +@unitypy_define +class TerrainCollider(Collider): + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_TerrainData: PPtr[TerrainData] + m_CreateTreeColliders: Optional[bool] = None + m_EnableTreeColliders: Optional[bool] = None + m_ExcludeLayers: Optional[BitField] = None + m_IncludeLayers: Optional[BitField] = None + m_IsTrigger: Optional[bool] = None + m_LayerOverridePriority: Optional[int] = None + m_Material: Optional[Union[PPtr[PhysicMaterial], PPtr[PhysicsMaterial]]] = None + m_ProvidesContacts: Optional[bool] = None + + +@unitypy_define +class WheelCollider(Collider): + m_Center: Vector3f + m_ForwardFriction: WheelFrictionCurve + m_GameObject: PPtr[GameObject] + m_Mass: float + m_Radius: float + m_SidewaysFriction: WheelFrictionCurve + m_SuspensionDistance: float + m_SuspensionSpring: JointSpring + m_Enabled: Optional[bool] = None + m_ExcludeLayers: Optional[BitField] = None + m_ForceAppPointDistance: Optional[float] = None + m_IncludeLayers: Optional[BitField] = None + m_LayerOverridePriority: Optional[int] = None + m_ProvidesContacts: Optional[bool] = None + m_WheelDampingRate: Optional[float] = None + + +@unitypy_define +class FakeComponent(Component): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class Joint(Component): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class CharacterJoint(Joint): + m_Anchor: Vector3f + m_Axis: Vector3f + m_BreakForce: float + m_BreakTorque: float + m_ConnectedBody: PPtr[Rigidbody] + m_GameObject: PPtr[GameObject] + m_HighTwistLimit: SoftJointLimit + m_LowTwistLimit: SoftJointLimit + m_Swing1Limit: SoftJointLimit + m_Swing2Limit: SoftJointLimit + m_SwingAxis: Vector3f + m_AutoConfigureConnectedAnchor: Optional[bool] = None + m_ConnectedAnchor: Optional[Vector3f] = None + m_ConnectedArticulationBody: Optional[PPtr[ArticulationBody]] = None + m_ConnectedMassScale: Optional[float] = None + m_EnableCollision: Optional[bool] = None + m_EnablePreprocessing: Optional[bool] = None + m_EnableProjection: Optional[bool] = None + m_Enabled: Optional[bool] = None + m_MassScale: Optional[float] = None + m_ProjectionAngle: Optional[float] = None + m_ProjectionDistance: Optional[float] = None + m_SwingLimitSpring: Optional[SoftJointLimitSpring] = None + m_TwistLimitSpring: Optional[SoftJointLimitSpring] = None + + +@unitypy_define +class ConfigurableJoint(Joint): + m_Anchor: Vector3f + m_AngularXDrive: JointDrive + m_AngularXMotion: int + m_AngularYLimit: SoftJointLimit + m_AngularYMotion: int + m_AngularYZDrive: JointDrive + m_AngularZLimit: SoftJointLimit + m_AngularZMotion: int + m_Axis: Vector3f + m_BreakForce: float + m_BreakTorque: float + m_ConfiguredInWorldSpace: bool + m_ConnectedBody: PPtr[Rigidbody] + m_GameObject: PPtr[GameObject] + m_HighAngularXLimit: SoftJointLimit + m_LinearLimit: SoftJointLimit + m_LowAngularXLimit: SoftJointLimit + m_ProjectionAngle: float + m_ProjectionDistance: float + m_ProjectionMode: int + m_RotationDriveMode: int + m_SecondaryAxis: Vector3f + m_SlerpDrive: JointDrive + m_TargetAngularVelocity: Vector3f + m_TargetPosition: Vector3f + m_TargetRotation: Quaternionf + m_TargetVelocity: Vector3f + m_XDrive: JointDrive + m_XMotion: int + m_YDrive: JointDrive + m_YMotion: int + m_ZDrive: JointDrive + m_ZMotion: int + m_AngularXLimitSpring: Optional[SoftJointLimitSpring] = None + m_AngularYZLimitSpring: Optional[SoftJointLimitSpring] = None + m_AutoConfigureConnectedAnchor: Optional[bool] = None + m_ConnectedAnchor: Optional[Vector3f] = None + m_ConnectedArticulationBody: Optional[PPtr[ArticulationBody]] = None + m_ConnectedMassScale: Optional[float] = None + m_EnableCollision: Optional[bool] = None + m_EnablePreprocessing: Optional[bool] = None + m_Enabled: Optional[bool] = None + m_LinearLimitSpring: Optional[SoftJointLimitSpring] = None + m_MassScale: Optional[float] = None + m_SwapBodies: Optional[bool] = None + + +@unitypy_define +class FixedJoint(Joint): + m_BreakForce: float + m_BreakTorque: float + m_ConnectedBody: PPtr[Rigidbody] + m_GameObject: PPtr[GameObject] + m_ConnectedArticulationBody: Optional[PPtr[ArticulationBody]] = None + m_ConnectedMassScale: Optional[float] = None + m_EnableCollision: Optional[bool] = None + m_EnablePreprocessing: Optional[bool] = None + m_Enabled: Optional[bool] = None + m_MassScale: Optional[float] = None + + +@unitypy_define +class HingeJoint(Joint): + m_Anchor: Vector3f + m_Axis: Vector3f + m_BreakForce: float + m_BreakTorque: float + m_ConnectedBody: PPtr[Rigidbody] + m_GameObject: PPtr[GameObject] + m_Limits: JointLimits + m_Motor: JointMotor + m_Spring: JointSpring + m_UseLimits: bool + m_UseMotor: bool + m_UseSpring: bool + m_AutoConfigureConnectedAnchor: Optional[bool] = None + m_ConnectedAnchor: Optional[Vector3f] = None + m_ConnectedArticulationBody: Optional[PPtr[ArticulationBody]] = None + m_ConnectedMassScale: Optional[float] = None + m_EnableCollision: Optional[bool] = None + m_EnablePreprocessing: Optional[bool] = None + m_Enabled: Optional[bool] = None + m_ExtendedLimits: Optional[bool] = None + m_MassScale: Optional[float] = None + m_UseAcceleration: Optional[bool] = None + + +@unitypy_define +class SpringJoint(Joint): + m_Anchor: Vector3f + m_BreakForce: float + m_BreakTorque: float + m_ConnectedBody: PPtr[Rigidbody] + m_Damper: float + m_GameObject: PPtr[GameObject] + m_MaxDistance: float + m_MinDistance: float + m_Spring: float + m_AutoConfigureConnectedAnchor: Optional[bool] = None + m_Axis: Optional[Vector3f] = None + m_ConnectedAnchor: Optional[Vector3f] = None + m_ConnectedArticulationBody: Optional[PPtr[ArticulationBody]] = None + m_ConnectedMassScale: Optional[float] = None + m_EnableCollision: Optional[bool] = None + m_EnablePreprocessing: Optional[bool] = None + m_Enabled: Optional[bool] = None + m_MassScale: Optional[float] = None + m_Tolerance: Optional[float] = None + + +@unitypy_define +class LODGroup(Component): + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LODs: List[LOD] + m_LocalReferencePoint: Vector3f + m_Size: float + m_AnimateCrossFading: Optional[bool] = None + m_FadeMode: Optional[int] = None + m_GlobalIlluminationLOD: Optional[int] = None + m_LastLODIsBillboard: Optional[bool] = None + m_ScreenRelativeTransitionHeight: Optional[float] = None + + +@unitypy_define +class MeshFilter(Component): + m_GameObject: PPtr[GameObject] + m_Mesh: PPtr[Mesh] + + +@unitypy_define +class MultiplayerRolesData(Component): + m_ComponentsRolesMasks: List[ObjectRolePair] + m_GameObject: PPtr[GameObject] + m_GameObjectRolesMask: int + + +@unitypy_define +class OcclusionArea(Component): + m_Center: Vector3f + m_GameObject: PPtr[GameObject] + m_IsViewVolume: bool + m_Size: Vector3f + m_IsTargetVolume: Optional[bool] = None + m_TargetResolution: Optional[int] = None + + +@unitypy_define +class OcclusionPortal(Component): + m_Center: Vector3f + m_GameObject: PPtr[GameObject] + m_Open: bool + m_Size: Vector3f + + +@unitypy_define +class ParticleAnimator(Component): + Does_Animate_Color: bool + autodestruct: bool + colorAnimation_0_: ColorRGBA + colorAnimation_1_: ColorRGBA + colorAnimation_2_: ColorRGBA + colorAnimation_3_: ColorRGBA + colorAnimation_4_: ColorRGBA + damping: float + force: Vector3f + localRotationAxis: Vector3f + m_GameObject: PPtr[GameObject] + rndForce: Vector3f + sizeGrow: float + stopSimulation: bool + worldRotationAxis: Vector3f + + +@unitypy_define +class ParticleEmitter(Component): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class EllipsoidParticleEmitter(ParticleEmitter): + Simulate_in_Worldspace: bool + angularVelocity: float + emitterVelocityScale: float + localVelocity: Vector3f + m_Ellipsoid: Vector3f + m_Emit: bool + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_MinEmitterRange: float + m_OneShot: bool + maxEmission: float + maxEnergy: float + maxSize: float + minEmission: float + minEnergy: float + minSize: float + rndAngularVelocity: float + rndRotation: bool + rndVelocity: Vector3f + tangentVelocity: Vector3f + worldVelocity: Vector3f + + +@unitypy_define +class MeshParticleEmitter(ParticleEmitter): + Simulate_in_Worldspace: bool + angularVelocity: float + emitterVelocityScale: float + localVelocity: Vector3f + m_Emit: bool + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_InterpolateTriangles: bool + m_MaxNormalVelocity: float + m_Mesh: PPtr[Mesh] + m_MinNormalVelocity: float + m_OneShot: bool + m_Systematic: bool + maxEmission: float + maxEnergy: float + maxSize: float + minEmission: float + minEnergy: float + minSize: float + rndAngularVelocity: float + rndRotation: bool + rndVelocity: Vector3f + tangentVelocity: Vector3f + worldVelocity: Vector3f + + +@unitypy_define +class ParticleSystem(Component): + ClampVelocityModule: ClampVelocityModule + CollisionModule: CollisionModule + ColorBySpeedModule: ColorBySpeedModule + ColorModule: ColorModule + EmissionModule: EmissionModule + ForceModule: ForceModule + InitialModule: InitialModule + RotationBySpeedModule: RotationBySpeedModule + RotationModule: RotationModule + ShapeModule: ShapeModule + SizeBySpeedModule: SizeBySpeedModule + SizeModule: SizeModule + SubModule: SubModule + UVModule: UVModule + VelocityModule: VelocityModule + lengthInSec: float + looping: bool + m_GameObject: PPtr[GameObject] + moveWithTransform: Union[bool, int] + playOnAwake: bool + prewarm: bool + randomSeed: int + startDelay: Union[MinMaxCurve, float] + CustomDataModule: Optional[CustomDataModule] = None + ExternalForcesModule: Optional[ExternalForcesModule] = None + InheritVelocityModule: Optional[InheritVelocityModule] = None + LifetimeByEmitterSpeedModule: Optional[LifetimeByEmitterSpeedModule] = None + LightsModule: Optional[LightsModule] = None + NoiseModule: Optional[NoiseModule] = None + TrailModule: Optional[TrailModule] = None + TriggerModule: Optional[TriggerModule] = None + autoRandomSeed: Optional[bool] = None + cullingMode: Optional[int] = None + emitterVelocityMode: Optional[int] = None + moveWithCustomTransform: Optional[PPtr[Transform]] = None + ringBufferLoopRange: Optional[Vector2f] = None + ringBufferMode: Optional[int] = None + scalingMode: Optional[int] = None + simulationSpeed: Optional[float] = None + speed: Optional[float] = None + stopAction: Optional[int] = None + useRigidbodyForVelocity: Optional[bool] = None + useUnscaledTime: Optional[bool] = None + + +@unitypy_define +class Pipeline(Component): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class Renderer(Component): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class BillboardRenderer(Renderer): + m_Billboard: PPtr[BillboardAsset] + m_CastShadows: int + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_Materials: List[PPtr[Material]] + m_ProbeAnchor: PPtr[Transform] + m_ReceiveShadows: Union[bool, int] + m_ReflectionProbeUsage: int + m_SortingOrder: int + m_StaticBatchRoot: PPtr[Transform] + m_DynamicOccludee: Optional[int] = None + m_ForceMeshLod: Optional[int] = None + m_LightProbeUsage: Optional[int] = None + m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None + m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None + m_MotionVectors: Optional[int] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_SmallMeshCulling: Optional[int] = None + m_SortingLayer: Optional[int] = None + m_SortingLayerID: Optional[int] = None + m_StaticBatchInfo: Optional[StaticBatchInfo] = None + m_StaticShadowCaster: Optional[int] = None + m_SubsetIndices: Optional[List[int]] = None + m_UseLightProbes: Optional[bool] = None + + +@unitypy_define +class ClothRenderer(Renderer): + m_CastShadows: bool + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapTilingOffset: Vector4f + m_Materials: List[PPtr[Material]] + m_PauseWhenNotVisible: bool + m_ReceiveShadows: bool + m_StaticBatchRoot: PPtr[Transform] + m_SubsetIndices: List[int] + m_LightProbeAnchor: Optional[PPtr[Transform]] = None + m_SortingLayer: Optional[int] = None + m_SortingLayerID: Optional[int] = None + m_SortingOrder: Optional[int] = None + m_UseLightProbes: Optional[bool] = None + + +@unitypy_define +class LineRenderer(Renderer): + m_CastShadows: Union[bool, int] + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapTilingOffset: Vector4f + m_Materials: List[PPtr[Material]] + m_Parameters: LineParameters + m_Positions: List[Vector3f] + m_ReceiveShadows: Union[bool, int] + m_StaticBatchRoot: PPtr[Transform] + m_UseWorldSpace: bool + m_ApplyActiveColorSpace: Optional[bool] = None + m_DynamicOccludee: Optional[int] = None + m_ForceMeshLod: Optional[int] = None + m_LightProbeAnchor: Optional[PPtr[Transform]] = None + m_LightProbeUsage: Optional[int] = None + m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None + m_LightmapIndexDynamic: Optional[int] = None + m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_Loop: Optional[bool] = None + m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None + m_MotionVectors: Optional[int] = None + m_ProbeAnchor: Optional[PPtr[Transform]] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_ReflectionProbeUsage: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_SmallMeshCulling: Optional[int] = None + m_SortingLayer: Optional[int] = None + m_SortingLayerID: Optional[int] = None + m_SortingOrder: Optional[int] = None + m_StaticBatchInfo: Optional[StaticBatchInfo] = None + m_StaticShadowCaster: Optional[int] = None + m_SubsetIndices: Optional[List[int]] = None + m_UseLightProbes: Optional[bool] = None + + +@unitypy_define +class RendererFake(LineRenderer): + m_CastShadows: int + m_DynamicOccludee: int + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightProbeUsage: int + m_LightProbeVolumeOverride: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_Loop: bool + m_Materials: List[PPtr[Material]] + m_MotionVectors: int + m_Parameters: LineParameters + m_Positions: List[Vector3f] + m_ProbeAnchor: PPtr[Transform] + m_ReceiveShadows: int + m_ReflectionProbeUsage: int + m_RendererPriority: int + m_RenderingLayerMask: int + m_SortingLayer: int + m_SortingLayerID: int + m_SortingOrder: int + m_StaticBatchInfo: StaticBatchInfo + m_StaticBatchRoot: PPtr[Transform] + m_UseWorldSpace: bool + m_RayTraceProcedural: Optional[int] = None + m_RayTracingMode: Optional[int] = None + + +@unitypy_define +class MeshRenderer(Renderer): + m_CastShadows: Union[bool, int] + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapTilingOffset: Vector4f + m_Materials: List[PPtr[Material]] + m_ReceiveShadows: Union[bool, int] + m_StaticBatchRoot: PPtr[Transform] + m_AdditionalVertexStreams: Optional[PPtr[Mesh]] = None + m_DynamicOccludee: Optional[int] = None + m_EnlightenVertexStream: Optional[PPtr[Mesh]] = None + m_ForceMeshLod: Optional[int] = None + m_LightProbeAnchor: Optional[PPtr[Transform]] = None + m_LightProbeUsage: Optional[int] = None + m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None + m_LightmapIndexDynamic: Optional[int] = None + m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None + m_MotionVectors: Optional[int] = None + m_ProbeAnchor: Optional[PPtr[Transform]] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_ReflectionProbeUsage: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_SmallMeshCulling: Optional[int] = None + m_SortingLayer: Optional[int] = None + m_SortingLayerID: Optional[int] = None + m_SortingOrder: Optional[int] = None + m_StaticBatchInfo: Optional[StaticBatchInfo] = None + m_StaticShadowCaster: Optional[int] = None + m_SubsetIndices: Optional[List[int]] = None + m_UseLightProbes: Optional[bool] = None + + +@unitypy_define +class PanelRenderer(Renderer): + m_CastShadows: int + m_DynamicOccludee: int + m_Enabled: bool + m_ForceMeshLod: int + m_GameObject: PPtr[GameObject] + m_LightProbeUsage: int + m_LightProbeVolumeOverride: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_MaskInteraction: int + m_Materials: List[PPtr[Material]] + m_MeshLodSelectionBias: float + m_MotionVectors: int + m_PanelSettings: PPtr[MonoBehaviour] + m_ParentUI: PPtr[PanelRenderer] + m_Pivot: int + m_PivotReferenceSize: int + m_Position: int + m_ProbeAnchor: PPtr[Transform] + m_RayTraceProcedural: int + m_RayTracingAccelStructBuildFlags: int + m_RayTracingAccelStructBuildFlagsOverride: int + m_RayTracingMode: int + m_ReceiveShadows: int + m_ReflectionProbeUsage: int + m_RendererPriority: int + m_RenderingLayerMask: int + m_SmallMeshCulling: int + m_SortingLayer: int + m_SortingLayerID: int + m_SortingOrder: int + m_StaticBatchInfo: StaticBatchInfo + m_StaticBatchRoot: PPtr[Transform] + m_StaticShadowCaster: int + m_WorldSpaceHeight: float + m_WorldSpaceSizeMode: int + m_WorldSpaceWidth: float + sourceAsset: PPtr[MonoBehaviour] + + +@unitypy_define +class ParticleRenderer(Renderer): + UV_Animation: UVAnimation + m_CameraVelocityScale: float + m_CastShadows: Union[bool, int] + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LengthScale: float + m_LightmapIndex: int + m_LightmapTilingOffset: Vector4f + m_Materials: List[PPtr[Material]] + m_MaxParticleSize: float + m_ReceiveShadows: Union[bool, int] + m_StaticBatchRoot: PPtr[Transform] + m_StretchParticles: int + m_VelocityScale: float + m_DynamicOccludee: Optional[int] = None + m_LightProbeAnchor: Optional[PPtr[Transform]] = None + m_LightProbeUsage: Optional[int] = None + m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None + m_LightmapIndexDynamic: Optional[int] = None + m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_MotionVectors: Optional[int] = None + m_ProbeAnchor: Optional[PPtr[Transform]] = None + m_ReflectionProbeUsage: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_SortingLayer: Optional[int] = None + m_SortingLayerID: Optional[int] = None + m_SortingOrder: Optional[int] = None + m_StaticBatchInfo: Optional[StaticBatchInfo] = None + m_SubsetIndices: Optional[List[int]] = None + m_UseLightProbes: Optional[bool] = None + + +@unitypy_define +class ParticleSystemRenderer(Renderer): + m_CameraVelocityScale: float + m_CastShadows: Union[bool, int] + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LengthScale: float + m_LightmapIndex: int + m_LightmapTilingOffset: Vector4f + m_Materials: List[PPtr[Material]] + m_MaxParticleSize: float + m_Mesh: PPtr[Mesh] + m_ReceiveShadows: Union[bool, int] + m_RenderMode: int + m_SortMode: int + m_SortingFudge: float + m_StaticBatchRoot: PPtr[Transform] + m_VelocityScale: float + m_AllowRoll: Optional[bool] = None + m_ApplyActiveColorSpace: Optional[bool] = None + m_DynamicOccludee: Optional[int] = None + m_EnableGPUInstancing: Optional[bool] = None + m_Flip: Optional[Vector3f] = None + m_ForceMeshLod: Optional[int] = None + m_FreeformStretching: Optional[bool] = None + m_LightProbeAnchor: Optional[PPtr[Transform]] = None + m_LightProbeUsage: Optional[int] = None + m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None + m_LightmapIndexDynamic: Optional[int] = None + m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_MaskInteraction: Optional[int] = None + m_Mesh1: Optional[PPtr[Mesh]] = None + m_Mesh2: Optional[PPtr[Mesh]] = None + m_Mesh3: Optional[PPtr[Mesh]] = None + m_MeshDistribution: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None + m_MeshWeighting: Optional[float] = None + m_MeshWeighting1: Optional[float] = None + m_MeshWeighting2: Optional[float] = None + m_MeshWeighting3: Optional[float] = None + m_MinParticleSize: Optional[float] = None + m_MotionVectors: Optional[int] = None + m_NormalDirection: Optional[float] = None + m_Pivot: Optional[Vector3f] = None + m_ProbeAnchor: Optional[PPtr[Transform]] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_ReflectionProbeUsage: Optional[int] = None + m_RenderAlignment: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_RotateWithStretchDirection: Optional[bool] = None + m_ShadowBias: Optional[float] = None + m_SmallMeshCulling: Optional[int] = None + m_SortingLayer: Optional[int] = None + m_SortingLayerID: Optional[int] = None + m_SortingOrder: Optional[int] = None + m_StaticBatchInfo: Optional[StaticBatchInfo] = None + m_StaticShadowCaster: Optional[int] = None + m_SubsetIndices: Optional[List[int]] = None + m_TrailVertexStreams: Optional[List[int]] = None + m_UseCustomTrailVertexStreams: Optional[bool] = None + m_UseCustomVertexStreams: Optional[bool] = None + m_UseLightProbes: Optional[bool] = None + m_VertexStreamMask: Optional[int] = None + m_VertexStreams: Optional[List[int]] = None + + +@unitypy_define +class RenderAs2D(Renderer): + m_CastShadows: int + m_DynamicOccludee: int + m_Enabled: bool + m_ForceMeshLod: int + m_GameObject: PPtr[GameObject] + m_LightProbeUsage: int + m_LightProbeVolumeOverride: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_MaskInteraction: int + m_Materials: List[PPtr[Material]] + m_MeshLodSelectionBias: float + m_MotionVectors: int + m_ProbeAnchor: PPtr[Transform] + m_RayTraceProcedural: int + m_RayTracingAccelStructBuildFlags: int + m_RayTracingAccelStructBuildFlagsOverride: int + m_RayTracingMode: int + m_ReceiveShadows: int + m_ReflectionProbeUsage: int + m_RendererPriority: int + m_RenderingLayerMask: int + m_SmallMeshCulling: int + m_SortingLayer: int + m_SortingLayerID: int + m_SortingOrder: int + m_StaticBatchInfo: StaticBatchInfo + m_StaticBatchRoot: PPtr[Transform] + m_StaticShadowCaster: int + m_Owner: Optional[PPtr[Component]] = None + m_OwningSortingGroup: Optional[PPtr[SortingGroup]] = None + + +@unitypy_define +class SkinnedMeshRenderer(Renderer): + m_AABB: AABB + m_Bones: List[PPtr[Transform]] + m_CastShadows: Union[bool, int] + m_DirtyAABB: bool + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapTilingOffset: Vector4f + m_Materials: List[PPtr[Material]] + m_Mesh: PPtr[Mesh] + m_Quality: int + m_ReceiveShadows: Union[bool, int] + m_StaticBatchRoot: PPtr[Transform] + m_UpdateWhenOffscreen: bool + m_BlendShapeWeights: Optional[List[float]] = None + m_DynamicOccludee: Optional[int] = None + m_ForceMeshLod: Optional[int] = None + m_LightProbeAnchor: Optional[PPtr[Transform]] = None + m_LightProbeUsage: Optional[int] = None + m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None + m_LightmapIndexDynamic: Optional[int] = None + m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None + m_MotionVectors: Optional[int] = None + m_ProbeAnchor: Optional[PPtr[Transform]] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_ReflectionProbeUsage: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_RootBone: Optional[PPtr[Transform]] = None + m_SkinnedMotionVectors: Optional[bool] = None + m_SmallMeshCulling: Optional[int] = None + m_SortingLayer: Optional[int] = None + m_SortingLayerID: Optional[int] = None + m_SortingOrder: Optional[int] = None + m_StaticBatchInfo: Optional[StaticBatchInfo] = None + m_StaticShadowCaster: Optional[int] = None + m_SubsetIndices: Optional[List[int]] = None + m_UseLightProbes: Optional[bool] = None + + +@unitypy_define +class SpriteMask(Renderer): + m_BackSortingLayer: int + m_BackSortingOrder: int + m_CastShadows: int + m_Enabled: bool + m_FrontSortingLayer: int + m_FrontSortingOrder: int + m_GameObject: PPtr[GameObject] + m_IsCustomRangeActive: bool + m_LightProbeUsage: int + m_LightProbeVolumeOverride: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_MaskAlphaCutoff: float + m_Materials: List[PPtr[Material]] + m_MotionVectors: int + m_ProbeAnchor: PPtr[Transform] + m_ReceiveShadows: int + m_ReflectionProbeUsage: int + m_SortingLayer: int + m_SortingLayerID: int + m_SortingOrder: int + m_Sprite: PPtr[Sprite] + m_StaticBatchInfo: StaticBatchInfo + m_StaticBatchRoot: PPtr[Transform] + m_BackSortingLayerID: Optional[int] = None + m_DynamicOccludee: Optional[int] = None + m_ForceMeshLod: Optional[int] = None + m_FrontSortingLayerID: Optional[int] = None + m_MaskInteraction: Optional[int] = None + m_MaskSource: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_SmallMeshCulling: Optional[int] = None + m_SpriteSortPoint: Optional[int] = None + m_StaticShadowCaster: Optional[int] = None + + +@unitypy_define +class SpriteRenderer(Renderer): + m_CastShadows: Union[bool, int] + m_Color: ColorRGBA + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapTilingOffset: Vector4f + m_Materials: List[PPtr[Material]] + m_ReceiveShadows: Union[bool, int] + m_SortingOrder: int + m_Sprite: PPtr[Sprite] + m_StaticBatchRoot: PPtr[Transform] + m_AdaptiveModeThreshold: Optional[float] = None + m_BlendShapeWeights: Optional[List[float]] = None + m_DrawMode: Optional[int] = None + m_DynamicOccludee: Optional[int] = None + m_FlipX: Optional[bool] = None + m_FlipY: Optional[bool] = None + m_ForceMeshLod: Optional[int] = None + m_LightProbeAnchor: Optional[PPtr[Transform]] = None + m_LightProbeUsage: Optional[int] = None + m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None + m_LightmapIndexDynamic: Optional[int] = None + m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None + m_MotionVectors: Optional[int] = None + m_ProbeAnchor: Optional[PPtr[Transform]] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_ReflectionProbeUsage: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_Size: Optional[Vector2f] = None + m_SmallMeshCulling: Optional[int] = None + m_SortingLayer: Optional[int] = None + m_SortingLayerID: Optional[int] = None + m_SpriteSortPoint: Optional[int] = None + m_SpriteTileMode: Optional[int] = None + m_StaticBatchInfo: Optional[StaticBatchInfo] = None + m_StaticShadowCaster: Optional[int] = None + m_SubsetIndices: Optional[List[int]] = None + m_UseLightProbes: Optional[bool] = None + m_WasSpriteAssigned: Optional[bool] = None + + +@unitypy_define +class SpriteShapeRenderer(Renderer): + m_CastShadows: int + m_Color: ColorRGBA + m_DynamicOccludee: int + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightProbeUsage: int + m_LightProbeVolumeOverride: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_LocalAABB: AABB + m_MaskInteraction: int + m_Materials: List[PPtr[Material]] + m_MotionVectors: int + m_ProbeAnchor: PPtr[Transform] + m_ReceiveShadows: int + m_ReflectionProbeUsage: int + m_RenderingLayerMask: int + m_ShapeTexture: PPtr[Texture2D] + m_SortingLayer: int + m_SortingLayerID: int + m_SortingOrder: int + m_Sprites: List[PPtr[Sprite]] + m_StaticBatchInfo: StaticBatchInfo + m_StaticBatchRoot: PPtr[Transform] + m_ForceMeshLod: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_SmallMeshCulling: Optional[int] = None + m_SpriteSortPoint: Optional[int] = None + m_StaticShadowCaster: Optional[int] = None + + +@unitypy_define +class TilemapRenderer(Renderer): + m_CastShadows: int + m_ChunkSize: int3_storage + m_DynamicOccludee: int + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightProbeUsage: int + m_LightProbeVolumeOverride: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_MaskInteraction: int + m_Materials: List[PPtr[Material]] + m_MaxChunkCount: int + m_MaxFrameAge: int + m_MotionVectors: int + m_ProbeAnchor: PPtr[Transform] + m_ReceiveShadows: int + m_ReflectionProbeUsage: int + m_SortOrder: int + m_SortingLayer: int + m_SortingLayerID: int + m_SortingOrder: int + m_StaticBatchInfo: StaticBatchInfo + m_StaticBatchRoot: PPtr[Transform] + m_ChunkCullingBounds: Optional[Vector3f] = None + m_DetectChunkCullingBounds: Optional[int] = None + m_ForceMeshLod: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None + m_Mode: Optional[int] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_SmallMeshCulling: Optional[int] = None + m_StaticShadowCaster: Optional[int] = None + + +@unitypy_define +class TrailRenderer(Renderer): + m_Autodestruct: bool + m_CastShadows: Union[bool, int] + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapTilingOffset: Vector4f + m_Materials: List[PPtr[Material]] + m_MinVertexDistance: float + m_ReceiveShadows: Union[bool, int] + m_StaticBatchRoot: PPtr[Transform] + m_Time: float + m_ApplyActiveColorSpace: Optional[bool] = None + m_Colors: Optional[Gradient] = None + m_DynamicOccludee: Optional[int] = None + m_Emitting: Optional[bool] = None + m_EndWidth: Optional[float] = None + m_ForceMeshLod: Optional[int] = None + m_LightProbeAnchor: Optional[PPtr[Transform]] = None + m_LightProbeUsage: Optional[int] = None + m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None + m_LightmapIndexDynamic: Optional[int] = None + m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_MaskInteraction: Optional[int] = None + m_MeshLodSelectionBias: Optional[float] = None + m_MotionVectors: Optional[int] = None + m_Parameters: Optional[LineParameters] = None + m_ProbeAnchor: Optional[PPtr[Transform]] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_ReflectionProbeUsage: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_SmallMeshCulling: Optional[int] = None + m_SortingLayer: Optional[int] = None + m_SortingLayerID: Optional[int] = None + m_SortingOrder: Optional[int] = None + m_StartWidth: Optional[float] = None + m_StaticBatchInfo: Optional[StaticBatchInfo] = None + m_StaticShadowCaster: Optional[int] = None + m_SubsetIndices: Optional[List[int]] = None + m_UseLightProbes: Optional[bool] = None + + +@unitypy_define +class UIRenderer(Renderer): + m_GameObject: PPtr[GameObject] + m_CastShadows: Optional[int] = None + m_DynamicOccludee: Optional[int] = None + m_Enabled: Optional[bool] = None + m_ForceMeshLod: Optional[int] = None + m_LightProbeUsage: Optional[int] = None + m_LightProbeVolumeOverride: Optional[PPtr[GameObject]] = None + m_LightmapIndex: Optional[int] = None + m_LightmapIndexDynamic: Optional[int] = None + m_LightmapTilingOffset: Optional[Vector4f] = None + m_LightmapTilingOffsetDynamic: Optional[Vector4f] = None + m_MaskInteraction: Optional[int] = None + m_Materials: Optional[List[PPtr[Material]]] = None + m_MeshLodSelectionBias: Optional[float] = None + m_MotionVectors: Optional[int] = None + m_ProbeAnchor: Optional[PPtr[Transform]] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_ReceiveShadows: Optional[int] = None + m_ReflectionProbeUsage: Optional[int] = None + m_RendererPriority: Optional[int] = None + m_RenderingLayerMask: Optional[int] = None + m_SmallMeshCulling: Optional[int] = None + m_SortingLayer: Optional[int] = None + m_SortingLayerID: Optional[int] = None + m_SortingOrder: Optional[int] = None + m_StaticBatchInfo: Optional[StaticBatchInfo] = None + m_StaticBatchRoot: Optional[PPtr[Transform]] = None + m_StaticShadowCaster: Optional[int] = None + + +@unitypy_define +class VFXRenderer(Renderer): + m_CastShadows: int + m_DynamicOccludee: int + m_Enabled: bool + m_GameObject: PPtr[GameObject] + m_LightProbeUsage: int + m_LightProbeVolumeOverride: PPtr[GameObject] + m_LightmapIndex: int + m_LightmapIndexDynamic: int + m_LightmapTilingOffset: Vector4f + m_LightmapTilingOffsetDynamic: Vector4f + m_MotionVectors: int + m_ProbeAnchor: PPtr[Transform] + m_ReceiveShadows: int + m_ReflectionProbeUsage: int + m_RendererPriority: int + m_RenderingLayerMask: int + m_SortingLayer: int + m_SortingLayerID: int + m_SortingOrder: int + m_StaticBatchInfo: StaticBatchInfo + m_StaticBatchRoot: PPtr[Transform] + m_ForceMeshLod: Optional[int] = None + m_MaskInteraction: Optional[int] = None + m_Materials: Optional[List[PPtr[Material]]] = None + m_MeshLodSelectionBias: Optional[float] = None + m_RayTraceProcedural: Optional[int] = None + m_RayTracingAccelStructBuildFlags: Optional[int] = None + m_RayTracingAccelStructBuildFlagsOverride: Optional[int] = None + m_RayTracingMode: Optional[int] = None + m_SmallMeshCulling: Optional[int] = None + m_StaticShadowCaster: Optional[int] = None + + +@unitypy_define +class Rigidbody(Component): + m_CollisionDetection: int + m_Constraints: int + m_GameObject: PPtr[GameObject] + m_Interpolate: int + m_IsKinematic: bool + m_Mass: float + m_UseGravity: bool + m_AngularDamping: Optional[float] = None + m_AngularDrag: Optional[float] = None + m_CenterOfMass: Optional[Vector3f] = None + m_Drag: Optional[float] = None + m_ExcludeLayers: Optional[BitField] = None + m_ImplicitCom: Optional[bool] = None + m_ImplicitTensor: Optional[bool] = None + m_IncludeLayers: Optional[BitField] = None + m_InertiaRotation: Optional[Quaternionf] = None + m_InertiaTensor: Optional[Vector3f] = None + m_LinearDamping: Optional[float] = None + + +@unitypy_define +class Rigidbody2D(Component): + m_CollisionDetection: int + m_GameObject: PPtr[GameObject] + m_GravityScale: float + m_Interpolate: int + m_Mass: float + m_SleepingMode: int + m_AngularDamping: Optional[float] = None + m_AngularDrag: Optional[float] = None + m_BodyType: Optional[int] = None + m_Constraints: Optional[int] = None + m_ExcludeLayers: Optional[BitField] = None + m_FixedAngle: Optional[bool] = None + m_IncludeLayers: Optional[BitField] = None + m_IsKinematic: Optional[bool] = None + m_LinearDamping: Optional[float] = None + m_LinearDrag: Optional[float] = None + m_Material: Optional[PPtr[PhysicsMaterial2D]] = None + m_Simulated: Optional[bool] = None + m_UseAutoMass: Optional[bool] = None + m_UseFullKinematicContacts: Optional[bool] = None + + +@unitypy_define +class TextMesh(Component): + m_Alignment: int + m_Anchor: int + m_CharacterSize: float + m_Font: PPtr[Font] + m_FontSize: int + m_FontStyle: int + m_GameObject: PPtr[GameObject] + m_LineSpacing: float + m_OffsetZ: float + m_TabSize: float + m_Text: str + m_Color: Optional[ColorRGBA] = None + m_RichText: Optional[bool] = None + + +@unitypy_define +class Transform(Component): + m_Children: List[PPtr[Transform]] + m_Father: PPtr[Transform] + m_GameObject: PPtr[GameObject] + m_LocalPosition: Vector3f + m_LocalRotation: Quaternionf + m_LocalScale: Vector3f + + +@unitypy_define +class RectTransform(Transform): + m_AnchorMax: Vector2f + m_AnchorMin: Vector2f + m_GameObject: PPtr[GameObject] + m_Pivot: Vector2f + m_SizeDelta: Vector2f + m_AnchoredPosition: Optional[Vector2f] = None + m_Children: Optional[List[PPtr[Transform]]] = None + m_Father: Optional[PPtr[Transform]] = None + m_LocalPosition: Optional[Vector3f] = None + m_LocalRotation: Optional[Quaternionf] = None + m_LocalScale: Optional[Vector3f] = None + m_Position: Optional[Vector2f] = None + + +@unitypy_define +class Tree(Component): + m_GameObject: PPtr[GameObject] + m_SpeedTreeWindAsset: Optional[PPtr[SpeedTreeWindAsset]] = None + + +@unitypy_define +class WorldAnchor(Component): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class WorldParticleCollider(Component): + m_BounceFactor: float + m_CollidesWith: BitField + m_CollisionEnergyLoss: float + m_GameObject: PPtr[GameObject] + m_MinKillVelocity: float + m_SendCollisionMessage: bool + + +@unitypy_define +class GameObject(EditorExtension): + m_Component: Union[List[ComponentPair], List[Tuple[int, PPtr[Component]]]] + m_IsActive: Union[bool, int] + m_Layer: int + m_Name: str + m_Tag: int + + +@unitypy_define +class NamedObject(EditorExtension, ABC): + pass + + +@unitypy_define +class AnimatorState(NamedObject): + m_CycleOffset: float + m_IKOnFeet: bool + m_Mirror: bool + m_Motion: PPtr[Motion] + m_Name: str + m_Position: Vector3f + m_Speed: float + m_StateMachineBehaviours: List[PPtr[MonoBehaviour]] + m_Tag: str + m_Transitions: List[PPtr[AnimatorStateTransition]] + m_WriteDefaultValues: bool + m_CycleOffsetParameter: Optional[str] = None + m_CycleOffsetParameterActive: Optional[bool] = None + m_MirrorParameter: Optional[str] = None + m_MirrorParameterActive: Optional[bool] = None + m_SpeedParameter: Optional[str] = None + m_SpeedParameterActive: Optional[bool] = None + m_TimeParameter: Optional[str] = None + m_TimeParameterActive: Optional[bool] = None + + +@unitypy_define +class AnimatorStateMachine(NamedObject): + m_AnyStatePosition: Vector3f + m_AnyStateTransitions: List[PPtr[AnimatorStateTransition]] + m_ChildStateMachines: List[ChildAnimatorStateMachine] + m_ChildStates: List[ChildAnimatorState] + m_DefaultState: PPtr[AnimatorState] + m_EntryPosition: Vector3f + m_EntryTransitions: List[PPtr[AnimatorTransition]] + m_ExitPosition: Vector3f + m_Name: str + m_ParentStateMachinePosition: Vector3f + m_StateMachineBehaviours: List[PPtr[MonoBehaviour]] + m_StateMachineTransitions: List[Tuple[PPtr[AnimatorStateMachine], List[PPtr[AnimatorTransition]]]] + + +@unitypy_define +class AnimatorTransitionBase(NamedObject): + m_Conditions: List[AnimatorCondition] + m_DstState: PPtr[AnimatorState] + m_DstStateMachine: PPtr[AnimatorStateMachine] + m_IsExit: bool + m_Mute: bool + m_Name: str + m_Solo: bool + + +@unitypy_define +class AnimatorStateTransition(AnimatorTransitionBase): + m_CanTransitionToSelf: bool + m_Conditions: List[AnimatorCondition] + m_DstState: PPtr[AnimatorState] + m_DstStateMachine: PPtr[AnimatorStateMachine] + m_ExitTime: float + m_HasExitTime: bool + m_InterruptionSource: int + m_IsExit: bool + m_Mute: bool + m_Name: str + m_OrderedInterruption: bool + m_Solo: bool + m_TransitionDuration: float + m_TransitionOffset: float + m_HasFixedDuration: Optional[bool] = None + + +@unitypy_define +class AnimatorTransition(AnimatorTransitionBase): + m_Conditions: List[AnimatorCondition] + m_DstState: PPtr[AnimatorState] + m_DstStateMachine: PPtr[AnimatorStateMachine] + m_IsExit: bool + m_Mute: bool + m_Name: str + m_Solo: bool + + +@unitypy_define +class AssetBundle(NamedObject): + m_Container: List[Tuple[str, AssetInfo]] + m_MainAsset: AssetInfo + m_Name: str + m_PreloadTable: List[PPtr[Object]] + m_AssetBundleName: Optional[str] = None + m_ClassCompatibility: Optional[List[Tuple[int, int]]] = None + m_ClassVersionMap: Optional[List[Tuple[int, int]]] = None + m_Dependencies: Optional[List[str]] = None + m_ExplicitDataLayout: Optional[int] = None + m_IsStreamedSceneAssetBundle: Optional[bool] = None + m_PathFlags: Optional[int] = None + m_RuntimeCompatibility: Optional[int] = None + m_SceneHashes: Optional[List[Tuple[str, str]]] = None + m_ScriptCompatibility: Optional[List[AssetBundleScriptInfo]] = None + + +@unitypy_define +class AssetBundleManifest(NamedObject): + AssetBundleInfos: List[Tuple[int, AssetBundleInfo]] + AssetBundleNames: List[Tuple[int, str]] + AssetBundlesWithVariant: List[int] + m_Name: str + + +@unitypy_define +class AssetImportInProgressProxy(NamedObject): + m_Name: str + + +@unitypy_define +class AssetImporter(NamedObject, ABC): + pass + + +@unitypy_define +class ASTCImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_Name: str + m_UserData: str + + +@unitypy_define +class AndroidAssetPackImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class AssemblyDefinitionImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UserData: str + m_UsedFileIDs: Optional[List[int]] = None + + +@unitypy_define +class AssemblyDefinitionReferenceImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class AudioImporter(AssetImporter): + m_3D: bool + m_ForceToMono: bool + m_Name: str + audio_preview_data: Optional[bytes] = None + m_Ambisonic: Optional[bool] = None + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_DefaultSettings: Optional[SampleSettings] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_Format: Optional[int] = None + m_LoadInBackground: Optional[bool] = None + m_Loopable: Optional[bool] = None + m_NewHashIdentity: Optional[MdFour] = None + m_Normalize: Optional[bool] = None + m_OldHashIdentity: Optional[MdFour] = None + m_Output: Optional[Union[AudioImporterOutput, Output]] = None + m_PlatformSettingOverrides: Optional[Union[List[Tuple[int, SampleSettings]], List[Tuple[str, SampleSettings]]]] = None + m_PreloadAudioData: Optional[bool] = None + m_PreviewData: Optional[PreviewData] = None + m_PreviewDataLength: Optional[int] = None + m_Quality: Optional[float] = None + m_Stream: Optional[int] = None + m_UseHardware: Optional[bool] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class BlockShaderImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_EmbedBlocksInGeneratedShader: bool + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class BuildArchiveImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class BuildInstructionImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class BuildMetaDataImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class C4DImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class ComputeShaderImporter(AssetImporter): + m_Name: str + m_UserData: str + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_CurrentAPIMask: Optional[int] = None + m_CurrentBuildTarget: Optional[int] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_PreprocessorOverride: Optional[int] = None + m_UsedFileIDs: Optional[List[int]] = None + + +@unitypy_define +class DDSImporter(AssetImporter): + m_Name: str + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_IsReadable: Optional[bool] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class DefaultImporter(AssetImporter): + m_Name: str + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class IHVImageFormatImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_IsReadable: bool + m_Name: str + m_TextureSettings: GLTextureSettings + m_UserData: str + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_IgnoreMipmapLimit: Optional[bool] = None + m_MipmapLimitGroupName: Optional[str] = None + m_StreamingMipmaps: Optional[bool] = None + m_StreamingMipmapsPriority: Optional[int] = None + m_UsedFileIDs: Optional[List[int]] = None + m_sRGBTexture: Optional[bool] = None + + +@unitypy_define +class KTXImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_Name: str + m_UserData: str + + +@unitypy_define +class LibraryAssetImporter(AssetImporter): + m_Name: str + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class LocalizationImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UserData: str + m_UsedFileIDs: Optional[List[int]] = None + + +@unitypy_define +class ModelImporter(AssetImporter, ABC): + pass + + +@unitypy_define +class FBXImporter(ModelImporter): + m_AddColliders: bool + m_AnimationCompression: int + m_AnimationPositionError: float + m_AnimationRotationError: float + m_AnimationScaleError: float + m_AnimationWrapMode: int + m_BakeSimulation: bool + m_ClipAnimations: List[ClipAnimationInfo] + m_GlobalScale: float + m_HasExtraRoot: bool + m_ImportedRoots: List[PPtr[GameObject]] + m_MeshCompression: int + m_Name: str + m_UseFileUnits: bool + normalSmoothAngle: float + bakeAxisConversion: Optional[bool] = None + blendShapeNormalImportMode: Optional[int] = None + calculateBlendshapeNormalsDeltaFromImportedNormals: Optional[bool] = None + generateMeshLods: Optional[bool] = None + generateSecondaryUV: Optional[bool] = None + importUVs: Optional[int] = None + importVertexColors: Optional[bool] = None + indexFormat: Optional[int] = None + keepQuads: Optional[bool] = None + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = None + m_AddHumanoidExtraRootOnlyWhenUsingAvatar: Optional[bool] = None + m_AdditionalBone: Optional[bool] = None + m_AnimationDoRetargetingWarnings: Optional[bool] = None + m_AnimationImportErrors: Optional[str] = None + m_AnimationImportWarnings: Optional[str] = None + m_AnimationRetargetingWarnings: Optional[str] = None + m_AnimationType: Optional[int] = None + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_AutoGenerateAvatarMappingIfUnspecified: Optional[bool] = None + m_AutoMapExternalMaterials: Optional[bool] = None + m_AvatarSetup: Optional[int] = None + m_ContainsAnimation: Optional[bool] = None + m_CopyAvatar: Optional[bool] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_ExtraExposedTransformPaths: Optional[List[str]] = None + m_ExtraUserProperties: Optional[List[str]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_FileIdsGeneration: Optional[int] = None + m_FileScale: Optional[float] = None + m_FileScaleFactor: Optional[float] = None + m_FileScaleUnit: Optional[str] = None + m_FirstImportVersion: Optional[int] = None + m_GenerateAnimations: Optional[int] = None + m_GenerateMaterials: Optional[int] = None + m_HasEmbeddedTextures: Optional[bool] = None + m_HasPreviousCalculatedGlobalScale: Optional[bool] = None + m_HumanDescription: Optional[HumanDescription] = None + m_HumanoidOversampling: Optional[int] = None + m_ImportAnimatedCustomProperties: Optional[bool] = None + m_ImportAnimation: Optional[bool] = None + m_ImportBlendShapeDeformPercent: Optional[bool] = None + m_ImportBlendShapes: Optional[bool] = None + m_ImportCameras: Optional[bool] = None + m_ImportConstraints: Optional[bool] = None + m_ImportLights: Optional[bool] = None + m_ImportMaterials: Optional[bool] = None + m_ImportPhysicalCameras: Optional[bool] = None + m_ImportVisibility: Optional[bool] = None + m_ImportedTakeInfos: Optional[List[TakeInfo]] = None + m_InternalIDToNameTable: Optional[List[Tuple[Tuple[int, int], str]]] = None + m_IsReadable: Optional[bool] = None + m_LODScreenPercentages: Optional[List[float]] = None + m_LastHumanDescriptionAvatarSource: Optional[PPtr[Avatar]] = None + m_LegacyGenerateAnimations: Optional[int] = None + m_MaterialImportMode: Optional[int] = None + m_MaterialLocation: Optional[int] = None + m_MaterialName: Optional[int] = None + m_MaterialSearch: Optional[int] = None + m_Materials: Optional[List[SourceAssetIdentifier]] = None + m_MeshSettings_generateSecondaryUV: Optional[bool] = None + m_MeshSettings_normalImportMode: Optional[int] = None + m_MeshSettings_secondaryUVAngleDistortion: Optional[float] = None + m_MeshSettings_secondaryUVAreaDistortion: Optional[float] = None + m_MeshSettings_secondaryUVHardAngle: Optional[float] = None + m_MeshSettings_secondaryUVPackMargin: Optional[float] = None + m_MeshSettings_swapUVChannels: Optional[bool] = None + m_MeshSettings_tangentImportMode: Optional[int] = None + m_MotionNodeName: Optional[str] = None + m_NewHashIdentity: Optional[MdFour] = None + m_NodeNameCollisionStrategy: Optional[int] = None + m_OldHashIdentity: Optional[MdFour] = None + m_OptimizeGameObjects: Optional[bool] = None + m_PreBakeConvexCollisionMesh: Optional[bool] = None + m_PreBakeTriangleCollisionMesh: Optional[bool] = None + m_PreserveHierarchy: Optional[bool] = None + m_PreviousCalculatedGlobalScale: Optional[float] = None + m_ReferencedClips: Optional[List[GUID]] = None + m_RemapMaterialsIfMaterialImportModeIsNone: Optional[bool] = None + m_RemoveConstantScaleCurves: Optional[bool] = None + m_ResampleCurves: Optional[bool] = None + m_ResampleRotations: Optional[bool] = None + m_RigImportErrors: Optional[str] = None + m_RigImportWarnings: Optional[str] = None + m_SearchTexturesGlobally: Optional[bool] = None + m_SortHierarchyByName: Optional[bool] = None + m_SplitAnimations: Optional[bool] = None + m_StrictVertexDataChecks: Optional[bool] = None + m_SupportsEmbeddedMaterials: Optional[bool] = None + m_UseFileScale: Optional[bool] = None + m_UseSRGBMaterialColor: Optional[bool] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + maxBonesPerVertex: Optional[int] = None + maximumMeshLod: Optional[int] = None + meshLodGenerationFlags: Optional[int] = None + meshOptimizationFlags: Optional[int] = None + minBoneWeight: Optional[float] = None + normalCalculationMode: Optional[int] = None + normalImportMode: Optional[int] = None + normalSmoothingSource: Optional[int] = None + optimizeBones: Optional[bool] = None + optimizeMesh: Optional[bool] = None + optimizeMeshForGPU: Optional[bool] = None + secondaryUVAngleDistortion: Optional[float] = None + secondaryUVAreaDistortion: Optional[float] = None + secondaryUVHardAngle: Optional[float] = None + secondaryUVMarginMethod: Optional[int] = None + secondaryUVMinLightmapResolution: Optional[float] = None + secondaryUVMinObjectScale: Optional[float] = None + secondaryUVPackMargin: Optional[float] = None + skinWeightsMode: Optional[int] = None + splitTangentsAcrossUV: Optional[bool] = None + swapUVChannels: Optional[bool] = None + tangentImportMode: Optional[int] = None + weldVertices: Optional[bool] = None + + +@unitypy_define +class Mesh3DSImporter(ModelImporter): + m_AddColliders: bool + m_AnimationCompression: int + m_AnimationPositionError: float + m_AnimationRotationError: float + m_AnimationScaleError: float + m_AnimationWrapMode: int + m_BakeSimulation: bool + m_ClipAnimations: List[ClipAnimationInfo] + m_GlobalScale: float + m_HasExtraRoot: bool + m_ImportedRoots: List[PPtr[GameObject]] + m_MeshCompression: int + m_Name: str + m_UseFileUnits: bool + normalSmoothAngle: float + bakeAxisConversion: Optional[bool] = None + blendShapeNormalImportMode: Optional[int] = None + calculateBlendshapeNormalsDeltaFromImportedNormals: Optional[bool] = None + generateMeshLods: Optional[bool] = None + generateSecondaryUV: Optional[bool] = None + importUVs: Optional[int] = None + importVertexColors: Optional[bool] = None + indexFormat: Optional[int] = None + keepQuads: Optional[bool] = None + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = None + m_AddHumanoidExtraRootOnlyWhenUsingAvatar: Optional[bool] = None + m_AdditionalBone: Optional[bool] = None + m_AnimationDoRetargetingWarnings: Optional[bool] = None + m_AnimationImportErrors: Optional[str] = None + m_AnimationImportWarnings: Optional[str] = None + m_AnimationRetargetingWarnings: Optional[str] = None + m_AnimationType: Optional[int] = None + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_AutoGenerateAvatarMappingIfUnspecified: Optional[bool] = None + m_AutoMapExternalMaterials: Optional[bool] = None + m_AvatarSetup: Optional[int] = None + m_ContainsAnimation: Optional[bool] = None + m_CopyAvatar: Optional[bool] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_ExtraExposedTransformPaths: Optional[List[str]] = None + m_ExtraUserProperties: Optional[List[str]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_FileIdsGeneration: Optional[int] = None + m_FileScale: Optional[float] = None + m_FileScaleFactor: Optional[float] = None + m_FileScaleUnit: Optional[str] = None + m_FirstImportVersion: Optional[int] = None + m_GenerateAnimations: Optional[int] = None + m_GenerateMaterials: Optional[int] = None + m_HasEmbeddedTextures: Optional[bool] = None + m_HasPreviousCalculatedGlobalScale: Optional[bool] = None + m_HumanDescription: Optional[HumanDescription] = None + m_HumanoidOversampling: Optional[int] = None + m_ImportAnimatedCustomProperties: Optional[bool] = None + m_ImportAnimation: Optional[bool] = None + m_ImportBlendShapeDeformPercent: Optional[bool] = None + m_ImportBlendShapes: Optional[bool] = None + m_ImportCameras: Optional[bool] = None + m_ImportConstraints: Optional[bool] = None + m_ImportLights: Optional[bool] = None + m_ImportMaterials: Optional[bool] = None + m_ImportPhysicalCameras: Optional[bool] = None + m_ImportVisibility: Optional[bool] = None + m_ImportedTakeInfos: Optional[List[TakeInfo]] = None + m_InternalIDToNameTable: Optional[List[Tuple[Tuple[int, int], str]]] = None + m_IsReadable: Optional[bool] = None + m_LODScreenPercentages: Optional[List[float]] = None + m_LastHumanDescriptionAvatarSource: Optional[PPtr[Avatar]] = None + m_LegacyGenerateAnimations: Optional[int] = None + m_MaterialImportMode: Optional[int] = None + m_MaterialLocation: Optional[int] = None + m_MaterialName: Optional[int] = None + m_MaterialSearch: Optional[int] = None + m_Materials: Optional[List[SourceAssetIdentifier]] = None + m_MeshSettings_generateSecondaryUV: Optional[bool] = None + m_MeshSettings_normalImportMode: Optional[int] = None + m_MeshSettings_secondaryUVAngleDistortion: Optional[float] = None + m_MeshSettings_secondaryUVAreaDistortion: Optional[float] = None + m_MeshSettings_secondaryUVHardAngle: Optional[float] = None + m_MeshSettings_secondaryUVPackMargin: Optional[float] = None + m_MeshSettings_swapUVChannels: Optional[bool] = None + m_MeshSettings_tangentImportMode: Optional[int] = None + m_MotionNodeName: Optional[str] = None + m_NewHashIdentity: Optional[MdFour] = None + m_NodeNameCollisionStrategy: Optional[int] = None + m_OldHashIdentity: Optional[MdFour] = None + m_OptimizeGameObjects: Optional[bool] = None + m_PreBakeConvexCollisionMesh: Optional[bool] = None + m_PreBakeTriangleCollisionMesh: Optional[bool] = None + m_PreserveHierarchy: Optional[bool] = None + m_PreviousCalculatedGlobalScale: Optional[float] = None + m_ReferencedClips: Optional[List[GUID]] = None + m_RemapMaterialsIfMaterialImportModeIsNone: Optional[bool] = None + m_RemoveConstantScaleCurves: Optional[bool] = None + m_ResampleCurves: Optional[bool] = None + m_ResampleRotations: Optional[bool] = None + m_RigImportErrors: Optional[str] = None + m_RigImportWarnings: Optional[str] = None + m_SearchTexturesGlobally: Optional[bool] = None + m_SortHierarchyByName: Optional[bool] = None + m_SplitAnimations: Optional[bool] = None + m_StrictVertexDataChecks: Optional[bool] = None + m_SupportsEmbeddedMaterials: Optional[bool] = None + m_UseFileScale: Optional[bool] = None + m_UseSRGBMaterialColor: Optional[bool] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + maxBonesPerVertex: Optional[int] = None + maximumMeshLod: Optional[int] = None + meshLodGenerationFlags: Optional[int] = None + meshOptimizationFlags: Optional[int] = None + minBoneWeight: Optional[float] = None + normalCalculationMode: Optional[int] = None + normalImportMode: Optional[int] = None + normalSmoothingSource: Optional[int] = None + optimizeBones: Optional[bool] = None + optimizeMesh: Optional[bool] = None + optimizeMeshForGPU: Optional[bool] = None + secondaryUVAngleDistortion: Optional[float] = None + secondaryUVAreaDistortion: Optional[float] = None + secondaryUVHardAngle: Optional[float] = None + secondaryUVMarginMethod: Optional[int] = None + secondaryUVMinLightmapResolution: Optional[float] = None + secondaryUVMinObjectScale: Optional[float] = None + secondaryUVPackMargin: Optional[float] = None + skinWeightsMode: Optional[int] = None + splitTangentsAcrossUV: Optional[bool] = None + swapUVChannels: Optional[bool] = None + tangentImportMode: Optional[int] = None + weldVertices: Optional[bool] = None + + +@unitypy_define +class SketchUpImporter(ModelImporter): + generateSecondaryUV: bool + keepQuads: bool + m_AddColliders: bool + m_AdditionalBone: bool + m_AnimationCompression: int + m_AnimationDoRetargetingWarnings: bool + m_AnimationImportErrors: str + m_AnimationImportWarnings: str + m_AnimationPositionError: float + m_AnimationRetargetingWarnings: str + m_AnimationRotationError: float + m_AnimationScaleError: float + m_AnimationType: int + m_AnimationWrapMode: int + m_AssetBundleName: str + m_AssetBundleVariant: str + m_AssetHash: Hash128 + m_BakeSimulation: bool + m_ClipAnimations: List[ClipAnimationInfo] + m_ExtraExposedTransformPaths: List[str] + m_FileScale: float + m_FileUnit: int + m_GenerateBackFace: bool + m_GlobalScale: float + m_HasExtraRoot: bool + m_HumanDescription: HumanDescription + m_ImportAnimation: bool + m_ImportBlendShapes: bool + m_ImportedRoots: List[PPtr[GameObject]] + m_ImportedTakeInfos: List[TakeInfo] + m_IsReadable: bool + m_LODScreenPercentages: List[float] + m_LastHumanDescriptionAvatarSource: PPtr[Avatar] + m_Latitude: float + m_LegacyGenerateAnimations: int + m_Longitude: float + m_MaterialName: int + m_MaterialSearch: int + m_MergeCoplanarFaces: bool + m_MeshCompression: int + m_MotionNodeName: str + m_Name: str + m_NorthCorrection: float + m_OptimizeGameObjects: bool + m_ReferencedClips: List[GUID] + m_SelectedNodes: List[int] + m_SketchUpImportData: SketchUpImportData + m_UseFileScale: bool + m_UseFileUnits: bool + m_UserData: str + normalImportMode: int + normalSmoothAngle: float + secondaryUVAngleDistortion: float + secondaryUVAreaDistortion: float + secondaryUVHardAngle: float + secondaryUVPackMargin: float + swapUVChannels: bool + tangentImportMode: int + weldVertices: bool + bakeAxisConversion: Optional[bool] = None + blendShapeNormalImportMode: Optional[int] = None + calculateBlendshapeNormalsDeltaFromImportedNormals: Optional[bool] = None + generateMeshLods: Optional[bool] = None + importUVs: Optional[int] = None + importVertexColors: Optional[bool] = None + indexFormat: Optional[int] = None + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: Optional[bool] = None + m_AddHumanoidExtraRootOnlyWhenUsingAvatar: Optional[bool] = None + m_AutoGenerateAvatarMappingIfUnspecified: Optional[bool] = None + m_AutoMapExternalMaterials: Optional[bool] = None + m_AvatarSetup: Optional[int] = None + m_ContainsAnimation: Optional[bool] = None + m_CopyAvatar: Optional[bool] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_ExtraUserProperties: Optional[List[str]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_FileIdsGeneration: Optional[int] = None + m_FileScaleFactor: Optional[float] = None + m_FileScaleUnit: Optional[str] = None + m_HasEmbeddedTextures: Optional[bool] = None + m_HasPreviousCalculatedGlobalScale: Optional[bool] = None + m_HumanoidOversampling: Optional[int] = None + m_ImportAnimatedCustomProperties: Optional[bool] = None + m_ImportBlendShapeDeformPercent: Optional[bool] = None + m_ImportCameras: Optional[bool] = None + m_ImportConstraints: Optional[bool] = None + m_ImportLights: Optional[bool] = None + m_ImportMaterials: Optional[bool] = None + m_ImportPhysicalCameras: Optional[bool] = None + m_ImportVisibility: Optional[bool] = None + m_InternalIDToNameTable: Optional[List[Tuple[Tuple[int, int], str]]] = None + m_MaterialImportMode: Optional[int] = None + m_MaterialLocation: Optional[int] = None + m_Materials: Optional[List[SourceAssetIdentifier]] = None + m_NodeNameCollisionStrategy: Optional[int] = None + m_PreBakeConvexCollisionMesh: Optional[bool] = None + m_PreBakeTriangleCollisionMesh: Optional[bool] = None + m_PreserveHierarchy: Optional[bool] = None + m_PreviousCalculatedGlobalScale: Optional[float] = None + m_RemapMaterialsIfMaterialImportModeIsNone: Optional[bool] = None + m_RemoveConstantScaleCurves: Optional[bool] = None + m_ResampleCurves: Optional[bool] = None + m_ResampleRotations: Optional[bool] = None + m_RigImportErrors: Optional[str] = None + m_RigImportWarnings: Optional[str] = None + m_SearchTexturesGlobally: Optional[bool] = None + m_SortHierarchyByName: Optional[bool] = None + m_StrictVertexDataChecks: Optional[bool] = None + m_SupportsEmbeddedMaterials: Optional[bool] = None + m_UseSRGBMaterialColor: Optional[bool] = None + m_UsedFileIDs: Optional[List[int]] = None + maxBonesPerVertex: Optional[int] = None + maximumMeshLod: Optional[int] = None + meshLodGenerationFlags: Optional[int] = None + meshOptimizationFlags: Optional[int] = None + minBoneWeight: Optional[float] = None + normalCalculationMode: Optional[int] = None + normalSmoothingSource: Optional[int] = None + optimizeBones: Optional[bool] = None + optimizeMeshForGPU: Optional[bool] = None + secondaryUVMarginMethod: Optional[int] = None + secondaryUVMinLightmapResolution: Optional[float] = None + secondaryUVMinObjectScale: Optional[float] = None + skinWeightsMode: Optional[int] = None + splitTangentsAcrossUV: Optional[bool] = None + + +@unitypy_define +class MonoImporter(AssetImporter): + executionOrder: int + icon: PPtr[Texture2D] + m_DefaultReferences: List[Tuple[str, PPtr[Object]]] + m_Name: str + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class MovieImporter(AssetImporter): + m_Name: str + m_Quality: float + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_LinearTexture: Optional[bool] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class MultiArtifactTestImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class NativeFormatImporter(AssetImporter): + m_Name: str + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_MainObjectFileID: Optional[int] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class PVRImporter(AssetImporter): + m_Name: str + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class PackageManifestImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class PluginImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExecutionOrder: List[Tuple[str, int]] + m_IconMap: List[Tuple[str, PPtr[Texture2D]]] + m_IsPreloaded: bool + m_Name: str + m_Output: PluginImportOutput + m_PlatformData: Union[List[Tuple[Tuple[str, str], PlatformSettingsData]], List[Tuple[str, PlatformSettingsData]]] + m_UserData: str + m_DefineConstraints: Optional[List[str]] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_IsExplicitlyReferenced: Optional[bool] = None + m_IsOverridable: Optional[bool] = None + m_UsedFileIDs: Optional[List[int]] = None + m_ValidateReferences: Optional[bool] = None + + +@unitypy_define +class PrefabImporter(AssetImporter): + m_AddedObjectFileIDs: List[int] + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_IsPrefabVariant: bool + m_Name: str + m_UserData: str + m_UnableToImportOnPreviousDomainReload: Optional[bool] = None + m_UsedFileIDs: Optional[List[int]] = None + m_VariantParentGUID: Optional[GUID] = None + + +@unitypy_define +class PreviewImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class RayTracingShaderImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + m_CurrentAPIMask: Optional[int] = None + + +@unitypy_define +class ReferencesArtifactGenerator(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class RoslynAdditionalFileImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class RoslynAnalyzerConfigImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class RuleSetFileImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class ScriptedImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_Name: str + m_Script: PPtr[MonoScript] + m_UserData: str + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_InternalIDToNameTable: Optional[List[Tuple[Tuple[int, int], str]]] = None + m_UsedFileIDs: Optional[List[int]] = None + + +@unitypy_define +class ShaderImporter(AssetImporter): + m_Name: str + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_DefaultTextures: Optional[List[Tuple[str, PPtr[Texture]]]] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_NewHashIdentity: Optional[MdFour] = None + m_NonModifiableTextures: Optional[List[Tuple[str, PPtr[Texture]]]] = None + m_OldHashIdentity: Optional[MdFour] = None + m_PreprocessorOverride: Optional[int] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class ShaderIncludeImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + + +@unitypy_define +class SpeedTreeImporter(AssetImporter): + m_AlphaTestRef: float + m_AssetBundleName: str + m_AssetBundleVariant: str + m_BestWindQuality: int + m_BillboardTransitionCrossFadeWidth: float + m_EnableSmoothLODTransition: bool + m_FadeOutWidth: float + m_HasBillboard: bool + m_HueVariation: ColorRGBA + m_LODSettings: List[PerLODSettings] + m_MainColor: ColorRGBA + m_Name: str + m_ScaleFactor: float + m_UserData: str + m_AnimateCrossFading: Optional[bool] = None + m_EnableBumpMapping: Optional[bool] = None + m_EnableHueVariation: Optional[bool] = None + m_EnableLightProbes: Optional[bool] = None + m_EnableShadowCasting: Optional[bool] = None + m_EnableShadowReceiving: Optional[bool] = None + m_EnableSubsurfaceScattering: Optional[bool] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDType: Optional[int] = None + m_GenerateColliders: Optional[bool] = None + m_GenerateRigidbody: Optional[bool] = None + m_MaterialLocation: Optional[int] = None + m_MaterialVersion: Optional[int] = None + m_Materials: Optional[List[SourceAssetIdentifier]] = None + m_MotionVectorModeEnumValue: Optional[int] = None + m_ReflectionProbeEnumValue: Optional[int] = None + m_SelectedWindQuality: Optional[int] = None + m_Shininess: Optional[float] = None + m_SpecColor: Optional[ColorRGBA] = None + m_SupportsEmbeddedMaterials: Optional[bool] = None + m_UnitConversionEnumValue: Optional[int] = None + m_UsedFileIDs: Optional[List[int]] = None + + +@unitypy_define +class SpriteAtlasImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UsedFileIDs: List[int] + m_UserData: str + m_BindAsDefault: Optional[bool] = None + m_PackingSettings: Optional[PackingSettings] = None + m_PlatformSettings: Optional[List[TextureImporterPlatformSettings]] = None + m_SecondaryTextureSettings: Optional[List[Tuple[str, SecondaryTextureSettings]]] = None + m_TextureSettings: Optional[TextureSettings] = None + m_VariantMultiplier: Optional[float] = None + + +@unitypy_define +class StyleSheetImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_Name: str + m_UserData: str + + +@unitypy_define +class SubstanceImporter(AssetImporter): + m_Name: str + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_DeletedPrototypes: Optional[List[str]] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_IsFirstImport: Optional[int] = None + m_MaterialImportOutputs: Optional[List[MaterialImportOutput]] = None + m_MaterialInstances: Optional[List[MaterialInstanceSettings]] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class TextScriptImporter(AssetImporter): + m_Name: str + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class TextureImporter(AssetImporter): + m_BorderMipMap: int + m_ConvertToNormalMap: int + m_EnableMipMap: int + m_ExternalNormalMap: int + m_FadeOut: int + m_GenerateCubemap: int + m_GrayScaleToAlpha: int + m_HeightScale: float + m_IsReadable: int + m_Lightmap: int + m_MaxTextureSize: int + m_MipMapFadeDistanceEnd: int + m_MipMapFadeDistanceStart: int + m_MipMapMode: int + m_NPOTScale: int + m_Name: str + m_NormalMapFilter: int + m_TextureFormat: int + m_TextureSettings: GLTextureSettings + m_TextureType: int + correctGamma: Optional[int] = None + m_Alignment: Optional[int] = None + m_AllowsAlphaSplitting: Optional[int] = None + m_AlphaIsTransparency: Optional[int] = None + m_AlphaTestReferenceValue: Optional[float] = None + m_AlphaUsage: Optional[int] = None + m_ApplyGammaDecoding: Optional[int] = None + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_BuildTargetSettings: Optional[List[BuildTargetSettings]] = None + m_CompressionQuality: Optional[int] = None + m_CompressionQualitySet: Optional[int] = None + m_CookieLightType: Optional[int] = None + m_CorrectGamma: Optional[int] = None + m_CubemapConvolution: Optional[int] = None + m_CubemapConvolutionExponent: Optional[float] = None + m_CubemapConvolutionSteps: Optional[int] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_FlipGreenChannel: Optional[int] = None + m_FlipbookColumns: Optional[int] = None + m_FlipbookRows: Optional[int] = None + m_IgnoreMasterTextureLimit: Optional[int] = None + m_IgnoreMipmapLimit: Optional[int] = None + m_IgnorePngGamma: Optional[Union[bool, int]] = None + m_InternalIDToNameTable: Optional[List[Tuple[Tuple[int, int], str]]] = None + m_LinearTexture: Optional[int] = None + m_MaxTextureSizeSet: Optional[int] = None + m_MipMapsPreserveCoverage: Optional[int] = None + m_MipmapLimitGroupName: Optional[str] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_Output: Optional[TextureImportOutput] = None + m_PSDRemoveMatte: Optional[bool] = None + m_PSDShowRemoveMatteOption: Optional[bool] = None + m_PlatformSettings: Optional[Union[List[PlatformSettings], List[TextureImporterPlatformSettings]]] = None + m_PushPullDilation: Optional[int] = None + m_RGBM: Optional[int] = None + m_RecommendedTextureFormat: Optional[int] = None + m_SeamlessCubemap: Optional[int] = None + m_SingleChannelComponent: Optional[int] = None + m_SourceTextureInformation: Optional[SourceTextureInformation] = None + m_SpriteBorder: Optional[Vector4f] = None + m_SpriteExtrude: Optional[int] = None + m_SpriteGenerateFallbackPhysicsShape: Optional[int] = None + m_SpriteGeometrySubdivision: Optional[float] = None + m_SpriteMeshType: Optional[int] = None + m_SpriteMode: Optional[int] = None + m_SpritePackingTag: Optional[str] = None + m_SpritePivot: Optional[Vector2f] = None + m_SpritePixelsToUnits: Optional[float] = None + m_SpriteSheet: Optional[SpriteSheetMetaData] = None + m_SpriteTessellationDetail: Optional[float] = None + m_SpriteTessellationMethod: Optional[int] = None + m_StreamingMipmaps: Optional[int] = None + m_StreamingMipmapsPriority: Optional[int] = None + m_Swizzle: Optional[int] = None + m_TextureFormatSet: Optional[int] = None + m_TextureShape: Optional[int] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + m_VTOnly: Optional[int] = None + m_sRGBTexture: Optional[int] = None + + +@unitypy_define +class TrueTypeFontImporter(AssetImporter): + m_FontNames: List[str] + m_FontSize: int + m_ForceTextureCase: int + m_IncludeFontData: bool + m_Name: str + m_AscentCalculationMode: Optional[int] = None + m_AssetBundleName: Optional[str] = None + m_AssetBundleVariant: Optional[str] = None + m_CharacterPadding: Optional[int] = None + m_CharacterSpacing: Optional[int] = None + m_CustomCharacters: Optional[str] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FallbackFontReferences: Optional[List[PPtr[Font]]] = None + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_FontColor: Optional[ColorRGBA] = None + m_FontName: Optional[str] = None + m_FontRenderingMode: Optional[int] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_Output: Optional[Output] = None + m_RenderMode: Optional[int] = None + m_ShouldRoundAdvanceValue: Optional[bool] = None + m_Style: Optional[int] = None + m_Use2xBehaviour: Optional[bool] = None + m_UseLegacyBoundsCalculation: Optional[bool] = None + m_UsedFileIDs: Optional[List[int]] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class VideoClipImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ColorSpace: int + m_Deinterlace: int + m_EncodeAlpha: bool + m_EndFrame: int + m_FlipHorizontal: bool + m_FlipVertical: bool + m_FrameRange: int + m_Name: str + m_Output: VideoClipImporterOutput + m_StartFrame: int + m_TargetSettings: Union[List[Tuple[int, VideoClipImporterTargetSettings]], List[Tuple[str, VideoClipImporterTargetSettings]]] + m_UserData: str + m_AudioImportMode: Optional[int] = None + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + m_FrameCount: Optional[int] = None + m_FrameRate: Optional[float] = None + m_ImportAudio: Optional[bool] = None + m_IsColorLinear: Optional[bool] = None + m_OriginalHeight: Optional[int] = None + m_OriginalWidth: Optional[int] = None + m_PixelAspectRatioDenominator: Optional[int] = None + m_PixelAspectRatioNumerator: Optional[int] = None + m_Quality: Optional[float] = None + m_SourceAudioChannelCount: Optional[List[int]] = None + m_SourceAudioSampleRate: Optional[List[int]] = None + m_SourceFileSize: Optional[int] = None + m_SourceHasAlpha: Optional[bool] = None + m_UseLegacyImporter: Optional[bool] = None + m_UsedFileIDs: Optional[List[int]] = None + + +@unitypy_define +class VisualEffectImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_ExternalObjects: List[Tuple[SourceAssetIdentifier, PPtr[Object]]] + m_Name: str + m_UserData: str + m_Template: Optional[VFXTemplate] = None + m_UseAsTemplate: Optional[bool] = None + m_UsedFileIDs: Optional[List[int]] = None + + +@unitypy_define +class AudioContainerElement(NamedObject): + m_AudioClip: PPtr[AudioClip] + m_Enabled: bool + m_Name: str + m_Volume: float + + +@unitypy_define +class AudioMixer(NamedObject): + m_EnableSuspend: bool + m_MasterGroup: PPtr[AudioMixerGroup] + m_MixerConstant: AudioMixerConstant + m_Name: str + m_OutputGroup: PPtr[AudioMixerGroup] + m_Snapshots: List[PPtr[AudioMixerSnapshot]] + m_StartSnapshot: PPtr[AudioMixerSnapshot] + m_SuspendThreshold: float + m_UpdateMode: Optional[int] = None + + +@unitypy_define +class AudioMixerController(AudioMixer): + m_EnableSuspend: bool + m_MasterGroup: PPtr[AudioMixerGroup] + m_MixerConstant: AudioMixerConstant + m_Name: str + m_OutputGroup: PPtr[AudioMixerGroup] + m_Snapshots: List[PPtr[AudioMixerSnapshot]] + m_StartSnapshot: PPtr[AudioMixerSnapshot] + m_SuspendThreshold: float + m_UpdateMode: Optional[int] = None + + +@unitypy_define +class AudioMixerEffectController(NamedObject): + m_Bypass: bool + m_EffectID: GUID + m_EffectName: str + m_EnableWetMix: bool + m_MixLevel: GUID + m_Name: str + m_Parameters: List[Parameter] + m_SendTarget: PPtr[AudioMixerEffectController] + + +@unitypy_define +class AudioMixerGroup(NamedObject): + m_AudioMixer: PPtr[AudioMixer] + m_Children: List[PPtr[AudioMixerGroup]] + m_GroupID: GUID + m_Name: str + + +@unitypy_define +class AudioMixerGroupController(AudioMixerGroup): + m_AudioMixer: PPtr[AudioMixer] + m_Children: List[PPtr[AudioMixerGroup]] + m_GroupID: GUID + m_Name: str + + +@unitypy_define +class AudioMixerSnapshot(NamedObject): + m_AudioMixer: PPtr[AudioMixer] + m_Name: str + m_SnapshotID: GUID + + +@unitypy_define +class AudioMixerSnapshotController(AudioMixerSnapshot): + m_AudioMixer: PPtr[AudioMixer] + m_Name: str + m_SnapshotID: GUID + + +@unitypy_define +class AudioResource(NamedObject): + m_Name: str + + +@unitypy_define +class AudioRandomContainer(AudioResource): + m_AutomaticTriggerMode: int + m_AutomaticTriggerTime: float + m_AutomaticTriggerTimeRandomizationEnabled: bool + m_AutomaticTriggerTimeRandomizationRange: Vector2f + m_AvoidRepeatingLast: int + m_Elements: List[PPtr[AudioContainerElement]] + m_LoopCount: int + m_LoopCountRandomizationEnabled: bool + m_LoopCountRandomizationRange: Vector2f + m_LoopMode: int + m_Name: str + m_Pitch: float + m_PitchRandomizationEnabled: bool + m_PitchRandomizationRange: Vector2f + m_PlaybackMode: int + m_TriggerMode: int + m_Volume: float + m_VolumeRandomizationEnabled: bool + m_VolumeRandomizationRange: Vector2f + + +@unitypy_define +class SampleClip(AudioResource): + m_Name: str + + +@unitypy_define +class AudioClip(SampleClip): + m_Name: str + m_3D: Optional[bool] = None + m_Ambisonic: Optional[bool] = None + m_AudioData: Optional[List[int]] = None + m_BitsPerSample: Optional[int] = None + m_Channels: Optional[int] = None + m_CompressionFormat: Optional[int] = None + m_Format: Optional[int] = None + m_Frequency: Optional[int] = None + m_IsTrackerFormat: Optional[bool] = None + m_Legacy3D: Optional[bool] = None + m_Length: Optional[float] = None + m_LoadInBackground: Optional[bool] = None + m_LoadType: Optional[int] = None + m_PreloadAudioData: Optional[bool] = None + m_Resource: Optional[StreamedResource] = None + m_Stream: Optional[int] = None + m_SubsoundIndex: Optional[int] = None + m_Type: Optional[int] = None + m_UseHardware: Optional[bool] = None + + +@unitypy_define +class Avatar(NamedObject): + m_Avatar: AvatarConstant + m_AvatarSize: int + m_Name: str + m_TOS: List[Tuple[int, str]] + m_HumanDescription: Optional[HumanDescription] = None + + +@unitypy_define +class AvatarMask(NamedObject): + m_Elements: List[TransformMaskElement] + m_Mask: List[int] + m_Name: str + + +@unitypy_define +class AvatarSkeletonMask(NamedObject): + elements: List[AvatarSkeletonMaskElement] + m_Name: str + + +@unitypy_define +class BaseAnimationTrack(NamedObject, ABC): + pass + + +@unitypy_define +class NewAnimationTrack(BaseAnimationTrack): + m_ClassID: int + m_Curves: List[Channel] + m_Name: str + + +@unitypy_define +class BillboardAsset(NamedObject): + bottom: float + height: float + imageTexCoords: List[Vector4f] + indices: List[int] + m_Name: str + material: PPtr[Material] + vertices: List[Vector2f] + width: float + rotated: Optional[List[int]] = None + + +@unitypy_define +class BlobObject(NamedObject): + m_BlobData: List[int] + m_BlobTypeHash: int + m_Name: str + m_NestedBlobObjectReferenceOffsets: List[int] + m_NestedBlobObjectReferences: List[PPtr[BlobObject]] + + +@unitypy_define +class BlockShaderContainer(NamedObject): + blob: List[int] + dependencies: List[PPtr[BlockShaderContainer]] + generatedPaths: List[str] + guid: GUID + m_Name: str + state: int + + +@unitypy_define +class BlockShaderErrors(NamedObject): + errors: List[Error] + filePath: str + m_Name: str + + +@unitypy_define +class BlockShaderSyntaxTree(NamedObject): + filePath: str + m_Name: str + source: str + + +@unitypy_define +class BuildProfilePlayerSettings(NamedObject): + AID: Hash128 + AndroidEnableSustainedPerformanceMode: bool + AndroidFilterTouchesWhenObscured: bool + AndroidProfiler: bool + Force_IOS_Speakers_When_Recording: bool + Prepare_IOS_For_Recording: bool + accelerometerFrequency: int + activeInputHandler: int + adjustIOSFPSUsingThermalState: bool + allowFullscreenSwitch: bool + allowHDRDisplaySupport: bool + allowedAutorotateToLandscapeLeft: bool + allowedAutorotateToLandscapeRight: bool + allowedAutorotateToPortrait: bool + allowedAutorotateToPortraitUpsideDown: bool + allowedHttpConnections: int + androidApplicationEntry: int + androidAutoRotationBehavior: int + androidBlitType: int + androidDefaultWindowHeight: int + androidDefaultWindowWidth: int + androidDisplayOptions: int + androidFullscreenMode: int + androidMaxAspectRatio: float + androidMinAspectRatio: float + androidMinimumWindowHeight: int + androidMinimumWindowWidth: int + androidPredictiveBackSupport: bool + androidRenderOutsideSafeArea: bool + androidRequestedVisibleInsets: int + androidResizeableActivity: bool + androidShowActivityIndicatorOnLoading: int + androidStartInFullscreen: bool + androidSupportedAspectRatio: int + androidSystemBarsBehavior: int + androidUseSwappy: bool + androidVulkanAllowFilterList: List[AndroidDeviceFilterData] + androidVulkanDenyFilterList: List[AndroidDeviceFilterData] + androidVulkanDeviceFilterListAsset: PPtr[VulkanDeviceFilterLists] + audioSpatialExperience: int + bakeCollisionMeshes: bool + bundleVersion: str + callOnDisableOnAssetBundleUnload: bool + cloudEnabled: bool + cloudProjectId: str + companyName: str + cursorHotspot: Vector2f + d3d12DeviceFilterListAsset: PPtr[D3D12DeviceFilterLists] + dedicatedServerOptimizations: bool + defaultCursor: PPtr[Texture2D] + defaultIsNativeResolution: bool + defaultScreenHeight: int + defaultScreenHeightWeb: int + defaultScreenOrientation: int + defaultScreenWidth: int + defaultScreenWidthWeb: int + deferSystemGesturesMode: int + disableDepthAndStencilBuffers: bool + enableDirectStorage: bool + enableFrameTimingStats: bool + enableOpenGLProfilerGPURecorders: bool + forceSingleInstance: bool + framebufferDepthMemorylessMode: int + fullscreenMode: int + gpuSkinning: bool + hdrBitDepth: int + hideHomeButton: bool + hmiLoadingImage: PPtr[Texture2D] + insecureHttpOption: int + invalidatedPatternTexture: PPtr[Texture2D] + iosShowActivityIndicatorOnLoading: int + iosUseCustomAppBackgroundBehavior: bool + legacyClampBlendShapeWeights: bool + loadStoreDebugModeEnabled: bool + m_ActiveColorSpace: int + m_ColorGamuts: List[int] + m_MTRendering: bool + m_Name: str + m_ShowUnitySplashLogo: bool + m_ShowUnitySplashScreen: bool + m_SplashScreenAnimation: int + m_SplashScreenBackgroundAnimationZoom: float + m_SplashScreenBackgroundColor: ColorRGBA + m_SplashScreenBackgroundLandscape: PPtr[Texture2D] + m_SplashScreenBackgroundLandscapeAspect: float + m_SplashScreenBackgroundLandscapeUvs: Rectf + m_SplashScreenBackgroundPortrait: PPtr[Texture2D] + m_SplashScreenBackgroundPortraitAspect: float + m_SplashScreenBackgroundPortraitUvs: Rectf + m_SplashScreenDrawMode: int + m_SplashScreenLogoAnimationZoom: float + m_SplashScreenLogoStyle: int + m_SplashScreenLogos: List[SplashScreenLogo] + m_SplashScreenOverlayOpacity: float + m_SpriteBatchMaxVertexCount: int + m_SpriteBatchVertexThreshold: int + m_StackTraceTypes: List[int] + m_StereoRenderingPath: int + m_UnitySplashLogo: PPtr[Sprite] + m_VirtualRealitySplashScreen: PPtr[Texture2D] + macAppStoreCategory: str + macRetinaSupport: bool + meshDeformation: int + metalFramebufferOnly: bool + metalUseMetalDisplayLink: bool + metroInputSource: int + mipStripping: bool + mobileMTRenderingBaked: bool + muteOtherAudioSources: bool + numberOfMipsStripped: int + numberOfMipsStrippedPerMipmapLimitGroup: List[Tuple[str, int]] + organizationId: str + platformRequiresReadableAssets: bool + playerMinOpenGLESVersion: int + preloadedAssets: List[PPtr[Object]] + preserveFramebufferAlpha: bool + productGUID: GUID + productName: str + projectName: str + qualitySettingsNames: List[str] + resetResolutionOnWindowResize: bool + resizableWindow: bool + resolutionScalingMode: int + runInBackground: bool + submitAnalytics: bool + switchAllowGpuScratchShrinking: bool + switchGpuScratchPoolGranularity: int + switchGraphicsJobsSyncAfterKick: bool + switchMaxWorkerMultiple: int + switchNVNDefaultPoolsGranularity: int + switchNVNGraphicsFirmwareMemory: int + switchNVNMaxPublicSamplerIDCount: int + switchNVNMaxPublicTextureIDCount: int + switchNVNOtherPoolsGranularity: int + switchNVNShaderPoolsGranularity: int + switchQueueCommandMemory: int + switchQueueComputeMemory: int + switchQueueControlMemory: int + targetDevice: int + targetPixelDensity: int + thermalStateCriticalIOSFPS: int + thermalStateSeriousIOSFPS: int + tvOSBundleVersion: str + unsupportedMSAAFallback: int + use32BitDisplayBuffer: bool + useFlipModelSwapchain: bool + useHDRDisplay: bool + useMacAppStoreValidation: bool + useOSAutorotation: bool + useOnDemandResources: bool + usePlayerLog: bool + virtualTexturingSupportEnabled: bool + visibleInBackground: bool + visionOSBundleVersion: str + vrSettings: VRSettings + vulkanEnableCommandBufferRecycling: bool + vulkanEnableLateAcquireNextImage: bool + vulkanEnablePreTransform: bool + vulkanEnableSetSRGBWrite: bool + vulkanNumSwapchainBuffers: int + webGPUDeviceFilterListAsset: PPtr[WebGPUDeviceFilterLists] + windowsGamepadBackendHint: int + wsaTransparentSwapchain: bool + xboxEnableAvatar: bool + xboxEnableFitness: bool + xboxEnableGuest: bool + xboxEnableHeadOrientation: bool + xboxEnableKinect: bool + xboxEnableKinectAutoTracking: bool + xboxEnablePIXSampling: bool + xboxOneDisableEsram: bool + xboxOneDisableKinectGpuReservation: bool + xboxOneEnable7thCore: bool + xboxOneEnableTypeOptimization: bool + xboxOneLoggingLevel: int + xboxOneMonoLoggingLevel: int + xboxOnePresentImmediateThreshold: int + xboxOneResolution: int + xboxOneSResolution: int + xboxOneXResolution: int + xboxPIXTextureCapture: bool + xboxSpeechDB: int + webProgressiveAssetLoading: Optional[bool] = None + + +@unitypy_define +class BuildReport(NamedObject): + m_Appendices: List[PPtr[Object]] + m_BuildSteps: List[BuildStepInfo] + m_Files: List[BuildReportFile] + m_Name: str + m_Summary: BuildSummary + m_RootAssetPaths: Optional[List[str]] = None + + +@unitypy_define +class CachedSpriteAtlas(NamedObject): + frames: List[Tuple[Tuple[GUID, int], SpriteRenderData]] + textures: List[PPtr[Texture2D]] + alphaTextures: Optional[List[PPtr[Texture2D]]] = None + + +@unitypy_define +class CachedSpriteAtlasRuntimeData(NamedObject): + alphaTextures: List[PPtr[Texture2D]] + frames: List[Tuple[Tuple[GUID, int], SpriteAtlasData]] + textures: List[PPtr[Texture2D]] + currentPackingHash: Optional[Hash128] = None + + +@unitypy_define +class ComputeShader(NamedObject): + m_Name: str + constantBuffers: Optional[List[ComputeShaderCB]] = None + kernels: Optional[List[ComputeShaderKernel]] = None + variants: Optional[Union[List[ComputeShaderPlatformVariant], List[ComputeShaderVariant]]] = None + + +@unitypy_define +class D3D12DeviceFilterLists(NamedObject): + m_AllowFilterList: List[D3D12DeviceFilterData] + m_DenyFilterList: List[D3D12DeviceFilterData] + m_GraphicsJobsFilterList: List[D3D12GraphicsJobsDeviceFilterData] + m_Name: str + + +@unitypy_define +class DefaultAsset(NamedObject): + m_Name: str + m_ErrorCode: Optional[int] = None + m_IsWarning: Optional[bool] = None + m_Message: Optional[str] = None + + +@unitypy_define +class BrokenPrefabAsset(DefaultAsset): + m_BrokenParentPrefab: PPtr[BrokenPrefabAsset] + m_IsPrefabFileValid: bool + m_IsVariant: bool + m_IsWarning: bool + m_Message: str + m_Name: str + m_ErrorCode: Optional[int] = None + + +@unitypy_define +class SceneAsset(DefaultAsset): + m_Name: str + m_ErrorCode: Optional[int] = None + m_IsWarning: Optional[bool] = None + m_Message: Optional[str] = None + + +@unitypy_define +class EditorProjectAccess(NamedObject): + m_Name: str + + +@unitypy_define +class Flare(NamedObject): + m_Elements: List[FlareElement] + m_FlareTexture: PPtr[Texture] + m_Name: str + m_TextureLayout: int + m_UseFog: bool + + +@unitypy_define +class Font(NamedObject): + m_Ascent: float + m_AsciiStartOffset: int + m_CharacterRects: List[CharacterInfo] + m_ConvertCase: int + m_DefaultMaterial: PPtr[Material] + m_DefaultStyle: int + m_FontData: List[int] + m_FontNames: List[str] + m_FontSize: float + m_KerningValues: List[Tuple[Tuple[int, int], float]] + m_LineSpacing: float + m_Name: str + m_Texture: PPtr[Texture] + m_CharacterPadding: Optional[int] = None + m_CharacterSpacing: Optional[int] = None + m_Descent: Optional[float] = None + m_FallbackFonts: Optional[List[PPtr[Font]]] = None + m_FontCountX: Optional[int] = None + m_FontCountY: Optional[int] = None + m_FontRenderingMode: Optional[int] = None + m_GridFont: Optional[bool] = None + m_Kerning: Optional[float] = None + m_PerCharacterKerning: Optional[List[Tuple[int, float]]] = None + m_PixelScale: Optional[float] = None + m_ShouldRoundAdvanceValue: Optional[bool] = None + m_Tracking: Optional[float] = None + m_UseLegacyBoundsCalculation: Optional[bool] = None + + +@unitypy_define +class GameObjectRecorder(NamedObject): + m_Name: str + + +@unitypy_define +class GraphicsStateCollection(NamedObject): + m_DeviceRenderer: int + m_Name: str + m_QualityLevelName: str + m_RenderPassInfoMap: List[Tuple[int, RenderPassInfo]] + m_RenderStateMap: List[Tuple[int, RenderStateInfo]] + m_RuntimePlatform: int + m_VariantInfoMap: List[Tuple[Hash128, VariantInfo]] + m_Version: int + m_VertexLayoutInfoMap: List[Tuple[int, VertexLayoutInfo]] + + +@unitypy_define +class HumanTemplate(NamedObject): + m_BoneTemplate: List[Tuple[str, str]] + m_Name: str + + +@unitypy_define +class ImportLog(NamedObject): + m_Logs: List[ImportLog_ImportLogEntry] + m_Name: str + + +@unitypy_define +class LightProbes(NamedObject): + m_Name: str + bakedCoefficients: Optional[List[LightmapData]] = None + bakedPositions: Optional[List[Vector3f]] = None + hullRays: Optional[List[Vector3f]] = None + m_BakedCoefficients: Optional[List[SphericalHarmonicsL2]] = None + m_BakedLightOcclusion: Optional[List[LightProbeOcclusion]] = None + m_Data: Optional[LightProbeData] = None + m_HasBeenEdited: Optional[bool] = None + tetrahedra: Optional[List[Tetrahedron]] = None + + +@unitypy_define +class LightingDataAsset(NamedObject): + m_BakedAmbientProbeInLinear: SphericalHarmonicsL2 + m_BakedReflectionProbeCubemaps: List[PPtr[Texture]] + m_BakedReflectionProbes: List[SceneObjectIdentifier] + m_EnlightenData: List[int] + m_EnlightenSceneMapping: EnlightenSceneMapping + m_EnlightenSceneMappingRendererIDs: List[SceneObjectIdentifier] + m_LightProbes: PPtr[LightProbes] + m_LightmappedRendererData: List[RendererData] + m_LightmappedRendererDataIDs: List[SceneObjectIdentifier] + m_Lightmaps: List[LightmapData] + m_Lights: List[SceneObjectIdentifier] + m_Name: str + m_AOTextures: Optional[List[PPtr[Texture2D]]] = None + m_BakedLightIndices: Optional[List[int]] = None + m_BakedReflectionProbeCubemapCacheFiles: Optional[List[str]] = None + m_EnlightenDataVersion: Optional[int] = None + m_LightBakingOutputs: Optional[List[LightBakingOutput]] = None + m_LightmapsCacheFiles: Optional[List[str]] = None + m_LightmapsMode: Optional[int] = None + m_Scene: Optional[PPtr[SceneAsset]] = None + m_SceneGUID: Optional[GUID] = None + + +@unitypy_define +class LightingDataAssetParent(NamedObject): + m_Name: str + + +@unitypy_define +class LightingSettings(NamedObject): + m_AlbedoBoost: float + m_BounceScale: float + m_EnableBakedLightmaps: bool + m_EnableRealtimeLightmaps: bool + m_Name: str + m_RealtimeEnvironmentLighting: bool + m_UsingShadowmask: bool + m_AO: Optional[bool] = None + m_AOMaxDistance: Optional[float] = None + m_BakeBackend: Optional[int] = None + m_BakeResolution: Optional[float] = None + m_CompAOExponent: Optional[float] = None + m_CompAOExponentDirect: Optional[float] = None + m_ExportTrainingData: Optional[bool] = None + m_ExtractAO: Optional[bool] = None + m_FilterMode: Optional[int] = None + m_FinalGather: Optional[bool] = None + m_FinalGatherFiltering: Optional[bool] = None + m_FinalGatherRayCount: Optional[int] = None + m_ForceUpdates: Optional[bool] = None + m_ForceWhiteAlbedo: Optional[bool] = None + m_GIWorkflowMode: Optional[int] = None + m_IndirectOutputScale: Optional[float] = None + m_LightmapMaxSize: Optional[int] = None + m_LightmapParameters: Optional[PPtr[LightmapParameters]] = None + m_LightmapsBakeMode: Optional[int] = None + m_MixedBakeMode: Optional[int] = None + m_PVRBounces: Optional[int] = None + m_PVRCulling: Optional[bool] = None + m_PVRDenoiserTypeAO: Optional[int] = None + m_PVRDenoiserTypeDirect: Optional[int] = None + m_PVRDenoiserTypeIndirect: Optional[int] = None + m_PVRDirectSampleCount: Optional[int] = None + m_PVREnvironmentMIS: Optional[int] = None + m_PVREnvironmentReferencePointCount: Optional[int] = None + m_PVREnvironmentSampleCount: Optional[int] = None + m_PVRFilterTypeAO: Optional[int] = None + m_PVRFilterTypeDirect: Optional[int] = None + m_PVRFilterTypeIndirect: Optional[int] = None + m_PVRFilteringAtrousPositionSigmaAO: Optional[float] = None + m_PVRFilteringAtrousPositionSigmaDirect: Optional[float] = None + m_PVRFilteringAtrousPositionSigmaIndirect: Optional[float] = None + m_PVRFilteringGaussRadiusAO: Optional[int] = None + m_PVRFilteringGaussRadiusDirect: Optional[int] = None + m_PVRFilteringGaussRadiusIndirect: Optional[int] = None + m_PVRFilteringMode: Optional[int] = None + m_PVRSampleCount: Optional[int] = None + m_PVRSampling: Optional[int] = None + m_Padding: Optional[int] = None + m_RealtimeResolution: Optional[float] = None + m_TextureCompression: Optional[bool] = None + m_TrainingDataDestination: Optional[str] = None + + +@unitypy_define +class LightmapParameters(NamedObject): + AOAntiAliasingSamples: int + AOQuality: int + antiAliasingSamples: int + backFaceTolerance: float + bakedLightmapTag: int + blurRadius: int + clusterResolution: float + directLightQuality: int + edgeStitching: int + irradianceBudget: int + irradianceQuality: int + isTransparent: int + m_Name: str + modellingTolerance: float + resolution: float + systemTag: int + limitLightmapCount: Optional[bool] = None + maxLightmapCount: Optional[int] = None + pushoff: Optional[float] = None + + +@unitypy_define +class LocalizationAsset(NamedObject): + Editor_Asset: bool + Locale_ISO_Code: str + String_Table: List[Tuple[str, str]] + m_Name: str + + +@unitypy_define +class Material(NamedObject): + m_Name: str + m_SavedProperties: UnityPropertySheet + m_Shader: PPtr[Shader] + disabledShaderPasses: Optional[List[str]] = None + m_BuildTextureStacks: Optional[List[BuildTextureStackReference]] = None + m_CustomRenderQueue: Optional[int] = None + m_DoubleSidedGI: Optional[bool] = None + m_EnableInstancingVariants: Optional[bool] = None + m_InvalidKeywords: Optional[List[str]] = None + m_LightmapFlags: Optional[int] = None + m_ShaderKeywords: Optional[Union[List[str], str]] = None + m_ValidKeywords: Optional[List[str]] = None + stringTagMap: Optional[List[Tuple[str, str]]] = None + + +@unitypy_define +class ProceduralMaterial(Material): + m_Name: str + m_SavedProperties: UnityPropertySheet + m_Shader: PPtr[Shader] + disabledShaderPasses: Optional[List[str]] = None + m_AnimationUpdateRate: Optional[int] = None + m_BuildTextureStacks: Optional[List[BuildTextureStackReference]] = None + m_CacheSize: Optional[int] = None + m_CustomRenderQueue: Optional[int] = None + m_DoubleSidedGI: Optional[bool] = None + m_EnableInstancingVariants: Optional[bool] = None + m_Flags: Optional[int] = None + m_GenerateMipmaps: Optional[bool] = None + m_Hash: Optional[Hash128] = None + m_Height: Optional[int] = None + m_Inputs: Optional[List[SubstanceInput]] = None + m_InvalidKeywords: Optional[List[str]] = None + m_LightmapFlags: Optional[int] = None + m_LoadingBehavior: Optional[int] = None + m_MaximumSize: Optional[int] = None + m_PrototypeName: Optional[str] = None + m_ShaderKeywords: Optional[Union[List[str], str]] = None + m_SubstancePackage: Optional[PPtr[SubstanceArchive]] = None + m_Textures: Optional[List[PPtr[ProceduralTexture]]] = None + m_ValidKeywords: Optional[List[str]] = None + m_Width: Optional[int] = None + stringTagMap: Optional[List[Tuple[str, str]]] = None + + +@unitypy_define +class Mesh(NamedObject): + m_BindPose: List[Matrix4x4f] + m_CompressedMesh: CompressedMesh + m_IndexBuffer: List[int] + m_LocalAABB: AABB + m_MeshCompression: int + m_Name: str + m_SubMeshes: List[SubMesh] + m_BakedConvexCollisionMesh: Optional[List[int]] = None + m_BakedTriangleCollisionMesh: Optional[List[int]] = None + m_BoneNameHashes: Optional[List[int]] = None + m_BonesAABB: Optional[List[MinMaxAABB]] = None + m_CollisionTriangles: Optional[List[int]] = None + m_CollisionVertexCount: Optional[int] = None + m_Colors: Optional[List[ColorRGBA]] = None + m_CookingOptions: Optional[int] = None + m_IndexFormat: Optional[int] = None + m_IsReadable: Optional[bool] = None + m_KeepIndices: Optional[bool] = None + m_KeepVertices: Optional[bool] = None + m_MeshLodInfo: Optional[MeshLodInfo] = None + m_MeshMetrics_0_: Optional[float] = None + m_MeshMetrics_1_: Optional[float] = None + m_MeshUsageFlags: Optional[int] = None + m_Normals: Optional[List[Vector3f]] = None + m_PreBakeConvexCollisionMesh: Optional[bool] = None + m_PreBakeTriangleCollisionMesh: Optional[bool] = None + m_RootBoneNameHash: Optional[int] = None + m_ShapeVertices: Optional[List[MeshBlendShapeVertex]] = None + m_Shapes: Optional[Union[BlendShapeData, List[MeshBlendShape]]] = None + m_Skin: Optional[Union[List[BoneInfluence], List[BoneWeights4]]] = None + m_StreamCompression: Optional[int] = None + m_StreamData: Optional[StreamingInfo] = None + m_Tangents: Optional[List[Vector4f]] = None + m_UV: Optional[List[Vector2f]] = None + m_UV1: Optional[List[Vector2f]] = None + m_Use16BitIndices: Optional[int] = None + m_VariableBoneCountWeights: Optional[VariableBoneCountWeights] = None + m_VertexData: Optional[VertexData] = None + m_Vertices: Optional[List[Vector3f]] = None + + +@unitypy_define +class Motion(NamedObject, ABC): + pass + + +@unitypy_define +class AnimationClip(Motion): + m_Bounds: AABB + m_Compressed: bool + m_CompressedRotationCurves: List[CompressedAnimationCurve] + m_Events: List[AnimationEvent] + m_FloatCurves: List[FloatCurve] + m_Name: str + m_PositionCurves: List[Vector3Curve] + m_RotationCurves: List[QuaternionCurve] + m_SampleRate: float + m_ScaleCurves: List[Vector3Curve] + m_WrapMode: int + m_AnimationType: Optional[int] = None + m_ClipBindingConstant: Optional[AnimationClipBindingConstant] = None + m_EulerCurves: Optional[List[Vector3Curve]] = None + m_HasGenericRootTransform: Optional[bool] = None + m_HasMotionFloatCurves: Optional[bool] = None + m_Legacy: Optional[bool] = None + m_MuscleClip: Optional[ClipMuscleConstant] = None + m_MuscleClipSize: Optional[int] = None + m_PPtrCurves: Optional[List[PPtrCurve]] = None + m_UseHighQualityCurve: Optional[bool] = None + + +@unitypy_define +class PreviewAnimationClip(AnimationClip): + m_Bounds: AABB + m_ClipBindingConstant: AnimationClipBindingConstant + m_Compressed: bool + m_CompressedRotationCurves: List[CompressedAnimationCurve] + m_EulerCurves: List[Vector3Curve] + m_Events: List[AnimationEvent] + m_FloatCurves: List[FloatCurve] + m_Legacy: bool + m_MuscleClip: ClipMuscleConstant + m_MuscleClipSize: int + m_Name: str + m_PPtrCurves: List[PPtrCurve] + m_PositionCurves: List[Vector3Curve] + m_RotationCurves: List[QuaternionCurve] + m_SampleRate: float + m_ScaleCurves: List[Vector3Curve] + m_UseHighQualityCurve: bool + m_WrapMode: int + m_HasGenericRootTransform: Optional[bool] = None + m_HasMotionFloatCurves: Optional[bool] = None + + +@unitypy_define +class BlendTree(Motion): + m_Childs: Union[List[ChildMotion], List[Child]] + m_MaxThreshold: float + m_MinThreshold: float + m_Name: str + m_UseAutomaticThresholds: bool + m_BlendEvent: Optional[str] = None + m_BlendEventY: Optional[str] = None + m_BlendParameter: Optional[str] = None + m_BlendParameterY: Optional[str] = None + m_BlendType: Optional[int] = None + m_NormalizedBlendValues: Optional[bool] = None + + +@unitypy_define +class NavMeshData(NamedObject): + m_HeightMeshes: List[HeightMeshData] + m_Heightmaps: List[HeightmapData] + m_Name: str + m_NavMeshTiles: List[NavMeshTileData] + m_OffMeshLinks: List[AutoOffMeshLinkData] + m_AgentTypeID: Optional[int] = None + m_NavMeshBuildSettings: Optional[NavMeshBuildSettings] = None + m_NavMeshParams: Optional[NavMeshParams] = None + m_Position: Optional[Vector3f] = None + m_Rotation: Optional[Quaternionf] = None + m_SourceBounds: Optional[AABB] = None + + +@unitypy_define +class NavMeshObsolete(NamedObject): + m_Name: str + + +@unitypy_define +class OcclusionCullingData(NamedObject): + m_Name: str + m_PVSData: List[int] + m_Scenes: List[OcclusionScene] + + +@unitypy_define +class PhysicsMaterial(NamedObject): + m_Name: str + bounceCombine: Optional[int] = None + bounciness: Optional[float] = None + dynamicFriction: Optional[float] = None + frictionCombine: Optional[int] = None + m_BounceCombine: Optional[int] = None + m_Bounciness: Optional[float] = None + m_DynamicFriction: Optional[float] = None + m_FrictionCombine: Optional[int] = None + m_StaticFriction: Optional[float] = None + staticFriction: Optional[float] = None + + +@unitypy_define +class PhysicsMaterial2D(NamedObject): + bounciness: float + friction: float + m_Name: str + m_BounceCombine: Optional[int] = None + m_FrictionCombine: Optional[int] = None + + +@unitypy_define +class PreloadData(NamedObject): + m_Assets: List[PPtr[Object]] + m_Name: str + m_Dependencies: Optional[List[str]] = None + m_ExplicitDataLayout: Optional[bool] = None + + +@unitypy_define +class Preset(NamedObject): + m_Name: str + m_Properties: List[PropertyModification] + m_TargetType: PresetType + m_CoupledProperties: Optional[List[PropertyModification]] = None + m_CoupledType: Optional[PresetType] = None + m_ExcludedProperties: Optional[List[str]] = None + + +@unitypy_define +class RayTracingShader(NamedObject): + m_MaxRecursionDepth: int + m_Name: str + variants: Union[List[RayTracingShaderPlatformVariant], List[RayTracingShaderVariant]] + m_EnableRayPayloadSizeChecks: Optional[bool] = None + + +@unitypy_define +class RoslynAdditionalFileAsset(NamedObject): + m_Name: str + + +@unitypy_define +class RoslynAnalyzerConfigAsset(NamedObject): + m_Name: str + + +@unitypy_define +class RuntimeAnimatorController(NamedObject): + m_AnimationClips: List[PPtr[AnimationClip]] + m_Controller: ControllerConstant + m_ControllerSize: int + m_Name: str + m_TOS: List[Tuple[int, str]] + + +@unitypy_define +class AnimatorController(RuntimeAnimatorController): + m_AnimationClips: List[PPtr[AnimationClip]] + m_Controller: ControllerConstant + m_ControllerSize: int + m_Name: str + m_TOS: List[Tuple[int, str]] + m_EvaluateTransitionsOnStart: Optional[bool] = None + m_MultiThreadedStateMachine: Optional[bool] = None + m_StateMachineBehaviourVectorDescription: Optional[StateMachineBehaviourVectorDescription] = None + m_StateMachineBehaviours: Optional[List[PPtr[MonoBehaviour]]] = None + + +@unitypy_define +class AnimatorOverrideController(RuntimeAnimatorController): + m_Clips: List[AnimationClipOverride] + m_Controller: PPtr[RuntimeAnimatorController] + m_Name: str + + +@unitypy_define +class Shader(NamedObject): + m_Name: str + compressedBlob: Optional[List[int]] = None + compressedLengths: Optional[Union[List[List[int]], List[int]]] = None + decompressedLengths: Optional[Union[List[List[int]], List[int]]] = None + decompressedSize: Optional[int] = None + m_AssetGUID: Optional[GUID] = None + m_AssetLocalIdentifierInFile: Optional[int] = None + m_Dependencies: Optional[List[PPtr[Shader]]] = None + m_NonModifiableTextures: Optional[List[Tuple[str, PPtr[Texture]]]] = None + m_ParsedForm: Optional[SerializedShader] = None + m_PathName: Optional[str] = None + m_Script: Optional[str] = None + m_ShaderIsBaked: Optional[bool] = None + m_SubProgramBlob: Optional[List[int]] = None + offsets: Optional[Union[List[List[int]], List[int]]] = None + platforms: Optional[List[int]] = None + stageCounts: Optional[List[int]] = None + + +@unitypy_define +class ShaderVariantCollection(NamedObject): + m_Name: str + m_Shaders: List[Tuple[PPtr[Shader], ShaderInfo]] + + +@unitypy_define +class SpeedTreeWindAsset(NamedObject): + m_Name: str + m_Config8: Optional[SpeedTreeWindConfig8] = None + m_Config9: Optional[SpeedTreeWindConfig9] = None + m_Wind: Optional[SpeedTreeWind] = None + m_eVersion: Optional[int] = None + + +@unitypy_define +class Sprite(NamedObject): + m_Extrude: int + m_Name: str + m_Offset: Vector2f + m_PixelsToUnits: float + m_RD: SpriteRenderData + m_Rect: Rectf + m_AtlasTags: Optional[List[str]] = None + m_Bones: Optional[List[SpriteBone]] = None + m_Border: Optional[Vector4f] = None + m_IsPolygon: Optional[bool] = None + m_PhysicsShape: Optional[List[List[Vector2f]]] = None + m_Pivot: Optional[Vector2f] = None + m_RenderDataKey: Optional[Tuple[GUID, int]] = None + m_ScriptableObjects: Optional[List[PPtr[MonoBehaviour]]] = None + m_SpriteAtlas: Optional[PPtr[SpriteAtlas]] = None + + +@unitypy_define +class SpriteAtlas(NamedObject): + m_IsVariant: bool + m_Name: str + m_RenderDataMap: List[Tuple[Tuple[GUID, int], SpriteAtlasData]] + m_Tag: str + m_Guid: Optional[GUID] = None + m_PackedSpriteNamesToIndex: Optional[List[str]] = None + m_PackedSprites: Optional[List[PPtr[Sprite]]] = None + + +@unitypy_define +class SpriteAtlasAsset(NamedObject): + m_ImporterData: Union[SpriteAtlasAssetData, SpriteAtlasEditorData] + m_IsVariant: bool + m_MasterAtlas: PPtr[SpriteAtlas] + m_Name: str + m_ScriptablePacker: Optional[PPtr[Object]] = None + + +@unitypy_define +class SubstanceArchive(NamedObject): + m_Name: str + m_PackageData: Optional[List[int]] = None + + +@unitypy_define +class TerrainData(NamedObject): + m_DetailDatabase: DetailDatabase + m_Heightmap: Heightmap + m_Name: str + m_SplatDatabase: SplatDatabase + m_PreloadShaders: Optional[List[PPtr[Shader]]] = None + + +@unitypy_define +class TerrainLayer(NamedObject): + m_DiffuseRemapMax: Vector4f + m_DiffuseRemapMin: Vector4f + m_DiffuseTexture: PPtr[Texture2D] + m_MaskMapRemapMax: Vector4f + m_MaskMapRemapMin: Vector4f + m_MaskMapTexture: PPtr[Texture2D] + m_Metallic: float + m_Name: str + m_NormalMapTexture: PPtr[Texture2D] + m_NormalScale: float + m_Smoothness: float + m_Specular: ColorRGBA + m_TileOffset: Vector2f + m_TileSize: Vector2f + m_SmoothnessSource: Optional[int] = None + + +@unitypy_define +class TextAsset(NamedObject): + m_Name: str + m_Script: str + m_PathName: Optional[str] = None + + +@unitypy_define +class AssemblyDefinitionAsset(TextAsset): + m_Name: str + m_Script: str + + +@unitypy_define +class AssemblyDefinitionReferenceAsset(TextAsset): + m_Name: str + m_Script: str + + +@unitypy_define +class MonoScript(TextAsset): + m_AssemblyName: str + m_ClassName: str + m_ExecutionOrder: int + m_Name: str + m_Namespace: str + m_PropertiesHash: Union[Hash128, int] + m_IsEditorScript: Optional[bool] = None + + +@unitypy_define +class PackageManifest(TextAsset): + m_Name: str + m_Script: str + + +@unitypy_define +class RuleSetFileAsset(TextAsset): + m_Name: str + m_Script: str + + +@unitypy_define +class ShaderInclude(TextAsset): + m_Name: str + m_Script: Optional[str] = None + + +@unitypy_define +class Texture(NamedObject, ABC): + pass + + +@unitypy_define +class BaseVideoTexture(Texture, ABC): + pass + + +@unitypy_define +class WebCamTexture(BaseVideoTexture): + m_Name: str + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IsAlphaChannelOptional: Optional[bool] = None + + +@unitypy_define +class CubemapArray(Texture): + image_data: bytes + m_ColorSpace: int + m_CubemapCount: int + m_DataSize: int + m_Format: int + m_IsReadable: bool + m_MipCount: int + m_Name: str + m_TextureSettings: GLTextureSettings + m_Width: int + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_StreamData: Optional[StreamingInfo] = None + m_UsageMode: Optional[int] = None + + +@unitypy_define +class LowerResBlitTexture(Texture): + m_Name: str + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IsAlphaChannelOptional: Optional[bool] = None + + +@unitypy_define +class MovieTexture(Texture): + m_Name: str + m_AudioClip: Optional[PPtr[AudioClip]] = None + m_ColorSpace: Optional[int] = None + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_Loop: Optional[bool] = None + m_MovieData: Optional[List[int]] = None + + +@unitypy_define +class ProceduralTexture(Texture): + m_Name: str + AlphaSource: Optional[int] = None + AlphaSourceIsGrayscale: Optional[bool] = None + Format: Optional[int] = None + Type: Optional[int] = None + m_AlphaSourceIsInverted: Optional[bool] = None + m_AlphaSourceUID: Optional[int] = None + m_BakedData: Optional[List[int]] = None + m_BakedParameters: Optional[TextureParameters] = None + m_ColorSpace: Optional[int] = None + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_LightmapFormat: Optional[int] = None + m_Mipmaps: Optional[int] = None + m_SubstanceMaterial: Optional[PPtr[ProceduralMaterial]] = None + m_SubstanceTextureUID: Optional[int] = None + m_TextureParameters: Optional[TextureParameters] = None + m_TextureSettings: Optional[GLTextureSettings] = None + + +@unitypy_define +class RenderTexture(Texture): + m_ColorFormat: int + m_Height: int + m_MipMap: bool + m_Name: str + m_TextureSettings: GLTextureSettings + m_Width: int + m_AntiAliasing: Optional[int] = None + m_BindMS: Optional[bool] = None + m_DepthFormat: Optional[int] = None + m_DepthStencilFormat: Optional[int] = None + m_Dimension: Optional[int] = None + m_DownscaleFallback: Optional[bool] = None + m_EnableCompatibleFormat: Optional[bool] = None + m_EnableRandomWrite: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_GenerateMips: Optional[bool] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_IsCubemap: Optional[bool] = None + m_IsPowerOfTwo: Optional[bool] = None + m_MipCount: Optional[int] = None + m_SRGB: Optional[bool] = None + m_ShadowSamplingMode: Optional[int] = None + m_UseDynamicScale: Optional[bool] = None + m_UseDynamicScaleExplicit: Optional[bool] = None + m_VolumeDepth: Optional[int] = None + + +@unitypy_define +class CustomRenderTexture(RenderTexture): + m_AntiAliasing: int + m_ColorFormat: int + m_CubemapFaceMask: int + m_CurrentUpdateZoneSpace: int + m_Dimension: int + m_DoubleBuffered: bool + m_GenerateMips: bool + m_Height: int + m_InitColor: ColorRGBA + m_InitMaterial: PPtr[Material] + m_InitTexture: PPtr[Texture] + m_InitializationMode: int + m_Material: PPtr[Material] + m_MipMap: bool + m_Name: str + m_SRGB: bool + m_ShaderPass: int + m_TextureSettings: GLTextureSettings + m_UpdateMode: int + m_UpdatePeriod: float + m_UpdateZoneSpace: int + m_UpdateZones: List[UpdateZoneInfo] + m_VolumeDepth: int + m_Width: int + m_WrapUpdateZones: bool + m_BindMS: Optional[bool] = None + m_DepthFormat: Optional[int] = None + m_DepthStencilFormat: Optional[int] = None + m_DownscaleFallback: Optional[bool] = None + m_EnableCompatibleFormat: Optional[bool] = None + m_EnableRandomWrite: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_InitSource: Optional[int] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_MipCount: Optional[int] = None + m_ShadowSamplingMode: Optional[int] = None + m_UseDynamicScale: Optional[bool] = None + m_UseDynamicScaleExplicit: Optional[bool] = None + + +@unitypy_define +class SparseTexture(Texture): + m_ColorSpace: int + m_Format: int + m_Height: int + m_MipCount: int + m_Name: str + m_TextureSettings: GLTextureSettings + m_Width: int + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IsAlphaChannelOptional: Optional[bool] = None + + +@unitypy_define +class Texture2D(Texture): + image_data: bytes + m_CompleteImageSize: int + m_Height: int + m_ImageCount: int + m_IsReadable: bool + m_LightmapFormat: int + m_Name: str + m_TextureDimension: int + m_TextureFormat: int + m_TextureSettings: GLTextureSettings + m_Width: int + m_ColorSpace: Optional[int] = None + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IgnoreMasterTextureLimit: Optional[bool] = None + m_IgnoreMipmapLimit: Optional[bool] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_IsPreProcessed: Optional[bool] = None + m_MipCount: Optional[int] = None + m_MipMap: Optional[bool] = None + m_MipmapLimitGroupName: Optional[str] = None + m_MipsStripped: Optional[int] = None + m_PlatformBlob: Optional[List[int]] = None + m_ReadAllowed: Optional[bool] = None + m_StreamData: Optional[StreamingInfo] = None + m_StreamingMipmaps: Optional[bool] = None + m_StreamingMipmapsPriority: Optional[int] = None + + +@unitypy_define +class Cubemap(Texture2D): + image_data: bytes + m_CompleteImageSize: int + m_Height: int + m_ImageCount: int + m_IsReadable: bool + m_LightmapFormat: int + m_Name: str + m_TextureDimension: int + m_TextureFormat: int + m_TextureSettings: GLTextureSettings + m_Width: int + m_ColorSpace: Optional[int] = None + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IgnoreMasterTextureLimit: Optional[bool] = None + m_IgnoreMipmapLimit: Optional[bool] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_IsPreProcessed: Optional[bool] = None + m_MipCount: Optional[int] = None + m_MipMap: Optional[bool] = None + m_MipmapLimitGroupName: Optional[str] = None + m_MipsStripped: Optional[int] = None + m_PlatformBlob: Optional[List[int]] = None + m_ReadAllowed: Optional[bool] = None + m_SourceTextures: Optional[List[PPtr[Texture2D]]] = None + m_StreamData: Optional[StreamingInfo] = None + m_StreamingMipmaps: Optional[bool] = None + m_StreamingMipmapsPriority: Optional[int] = None + + +@unitypy_define +class Texture2DArray(Texture): + image_data: bytes + m_ColorSpace: int + m_DataSize: int + m_Depth: int + m_Format: int + m_Height: int + m_IsReadable: bool + m_MipCount: int + m_Name: str + m_TextureSettings: GLTextureSettings + m_Width: int + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IgnoreMipmapLimit: Optional[bool] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_MipmapLimitGroupName: Optional[str] = None + m_MipsStripped: Optional[int] = None + m_StreamData: Optional[StreamingInfo] = None + m_UsageMode: Optional[int] = None + + +@unitypy_define +class Texture3D(Texture): + image_data: bytes + m_Height: int + m_Name: str + m_TextureSettings: GLTextureSettings + m_Width: int + m_ColorSpace: Optional[int] = None + m_CompleteImageSize: Optional[int] = None + m_DataSize: Optional[int] = None + m_Depth: Optional[int] = None + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_Format: Optional[int] = None + m_ImageCount: Optional[int] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_IsReadable: Optional[bool] = None + m_LightmapFormat: Optional[int] = None + m_MipCount: Optional[int] = None + m_MipMap: Optional[bool] = None + m_ReadAllowed: Optional[bool] = None + m_StreamData: Optional[StreamingInfo] = None + m_TextureDimension: Optional[int] = None + m_TextureFormat: Optional[int] = None + m_UsageMode: Optional[int] = None + + +@unitypy_define +class UIAnimationClip(NamedObject): + m_AnimationClip: PPtr[AnimationClip] + m_Name: str + + +@unitypy_define +class VideoClip(NamedObject): + Height: int + Width: int + m_AudioChannelCount: List[int] + m_AudioLanguage: List[str] + m_AudioSampleRate: List[int] + m_ExternalResources: StreamedResource + m_Format: int + m_FrameCount: int + m_FrameRate: float + m_HasSplitAlpha: bool + m_Name: str + m_OriginalPath: str + m_ProxyHeight: int + m_ProxyWidth: int + m_PixelAspecRatioDen: Optional[int] = None + m_PixelAspecRatioNum: Optional[int] = None + m_VideoShaders: Optional[List[PPtr[Shader]]] = None + m_sRGB: Optional[bool] = None + + +@unitypy_define +class VisualEffectObject(NamedObject, ABC): + pass + + +@unitypy_define +class VisualEffectAsset(VisualEffectObject): + m_Infos: VisualEffectInfo + m_Name: str + m_Systems: List[VFXSystemDesc] + + +@unitypy_define +class VisualEffectSubgraph(VisualEffectObject, ABC): + pass + + +@unitypy_define +class VisualEffectSubgraphBlock(VisualEffectSubgraph): + m_Name: str + + +@unitypy_define +class VisualEffectSubgraphOperator(VisualEffectSubgraph): + m_Name: str + + +@unitypy_define +class VisualEffectResource(NamedObject): + m_Graph: PPtr[MonoBehaviour] + m_Infos: Union[VisualEffectInfo, VisualEffectSettings] + m_Name: str + m_ShaderSources: Optional[List[VFXShaderSourceDesc]] = None + m_Systems: Optional[List[VFXEditorSystemDesc]] = None + + +@unitypy_define +class VulkanDeviceFilterLists(NamedObject): + m_GfxJobFilterList: List[VulkanGraphicsJobsDeviceFilterData] + m_Name: str + m_VulkanAllowFilterList: List[AndroidDeviceFilterData] + m_VulkanDenyFilterList: List[AndroidDeviceFilterData] + + +@unitypy_define +class WebGPUDeviceFilterLists(NamedObject): + m_AllowFilterList: List[WebGPUDeviceFilterData] + m_DenyFilterList: List[WebGPUDeviceFilterData] + m_Name: str + + +@unitypy_define +class EditorExtensionImpl(Object): + gFlattenedTypeTree: Optional[List[int]] = None + m_DataTemplate: Optional[PPtr[DataTemplate]] = None + m_Object: Optional[PPtr[EditorExtension]] = None + m_OverrideVariable: Optional[bitset] = None + m_TemplateFather: Optional[PPtr[EditorExtensionImpl]] = None + + +@unitypy_define +class EditorSettings(Object): + m_AssetNamingUsesSpace: Optional[bool] = None + m_AssetPipelineMode: Optional[int] = None + m_AsyncShaderCompilation: Optional[bool] = None + m_Bc7TextureCompressor: Optional[int] = None + m_BlockShaders: Optional[bool] = None + m_CacheServerDownloadBatchSize: Optional[int] = None + m_CacheServerEnableAuth: Optional[bool] = None + m_CacheServerEnableDownload: Optional[bool] = None + m_CacheServerEnableTls: Optional[bool] = None + m_CacheServerEnableUpload: Optional[bool] = None + m_CacheServerEndpoint: Optional[str] = None + m_CacheServerImportResultCachingEnabled: Optional[bool] = None + m_CacheServerMode: Optional[int] = None + m_CacheServerNamespacePrefix: Optional[str] = None + m_CacheServerValidationMode: Optional[int] = None + m_CachingShaderPreprocessor: Optional[bool] = None + m_CollabEditorSettings: Optional[CollabEditorSettings] = None + m_DefaultBehaviorMode: Optional[int] = None + m_DisableCookiesInLightmapper: Optional[bool] = None + m_EnableEditorAsyncCPUTextureLoading: Optional[bool] = None + m_EnableEnlightenBakedGI: Optional[bool] = None + m_EnableMSBuildCompilationPipeline: Optional[bool] = None + m_EnableRoslynAnalyzers: Optional[bool] = None + m_EnableTextureStreamingInEditMode: Optional[bool] = None + m_EnableTextureStreamingInPlayMode: Optional[bool] = None + m_EnterPlayModeOptions: Optional[int] = None + m_EnterPlayModeOptionsEnabled: Optional[bool] = None + m_EtcTextureBestCompressor: Optional[int] = None + m_EtcTextureCompressorBehavior: Optional[int] = None + m_EtcTextureFastCompressor: Optional[int] = None + m_EtcTextureNormalCompressor: Optional[int] = None + m_ExternalVersionControlSupport: Optional[Union[int, str]] = None + m_ForceAssetUnloadAndGCOnSceneLoad: Optional[bool] = None + m_GameObjectNamingDigits: Optional[int] = None + m_GameObjectNamingScheme: Optional[int] = None + m_HideBuildProfileClassicPlatforms: Optional[bool] = None + m_InspectorUseIMGUIDefaultInspector: Optional[bool] = None + m_LineEndingsForNewScripts: Optional[int] = None + m_PrefabModeAllowAutoSave: Optional[bool] = None + m_PrefabRegularEnvironment: Optional[PPtr[SceneAsset]] = None + m_PrefabUIEnvironment: Optional[PPtr[SceneAsset]] = None + m_ProjectGenerationIncludedExtensions: Optional[str] = None + m_ProjectGenerationRootNamespace: Optional[str] = None + m_RecalculateEnvironmentLighting: Optional[bool] = None + m_ReferencedClipsExactNaming: Optional[bool] = None + m_RefreshImportMode: Optional[int] = None + m_SerializationMode: Optional[int] = None + m_SerializeInlineMappingsOnOneLine: Optional[bool] = None + m_ShadowmaskStitching: Optional[bool] = None + m_ShowLightmapResolutionOverlay: Optional[bool] = None + m_SpritePackerCacheSize: Optional[int] = None + m_SpritePackerMode: Optional[int] = None + m_SpritePackerPaddingPower: Optional[int] = None + m_UnlockBlockShaders: Optional[bool] = None + m_UseLegacyHierarchy: Optional[bool] = None + m_UseLegacyProbeSampleCount: Optional[bool] = None + m_UserGeneratedProjectSuffix: Optional[str] = None + m_WebSecurityEmulationEnabled: Optional[int] = None + m_WebSecurityEmulationHostUrl: Optional[str] = None + + +@unitypy_define +class EditorUserBuildSettings(Object): + m_ActiveBuildTarget: int + m_AllowDebugging: bool + m_ArchitectureFlags: int + m_BuildLocation: List[str] + m_ConnectProfiler: bool + m_Development: bool + m_InstallInBuildFolder: bool + m_SelectedAndroidSubtarget: int + m_SelectedBuildTargetGroup: int + m_SelectedStandaloneTarget: int + m_ActiveBuildPlatformGroupName: Optional[str] = None + m_ActiveBuildProfile: Optional[PPtr[MonoBehaviour]] = None + m_ActiveBuildTargetGroup: Optional[int] = None + m_ActivePlatformGuid: Optional[GUID] = None + m_ActiveProfilePath: Optional[str] = None + m_ActiveStandaloneBuildSubtarget: Optional[int] = None + m_AndroidBuildSystem: Optional[int] = None + m_AndroidBuildType: Optional[int] = None + m_AndroidCreateSymbols: Optional[int] = None + m_AndroidCreateSymbolsZip: Optional[bool] = None + m_AndroidCurrentDeploymentTargetId: Optional[str] = None + m_AndroidDebugMinification: Optional[int] = None + m_AndroidDeviceSocketAddress: Optional[str] = None + m_AndroidReleaseMinification: Optional[int] = None + m_AndroidUseLegacySdkTools: Optional[bool] = None + m_BuildAppBundle: Optional[bool] = None + m_BuildOutputToBuildMetadataMap: Optional[List[Tuple[str, str]]] = None + m_BuildScriptsOnly: Optional[bool] = None + m_BuildWithCodeCoverage: Optional[bool] = None + m_BuildWithDeepProfilingSupport: Optional[bool] = None + m_CompressFilesInPackage: Optional[bool] = None + m_CompressWithPsArc: Optional[bool] = None + m_CreateRomFileForSwitch: Optional[bool] = None + m_CreateSolutionFileForSwitch: Optional[bool] = None + m_DatalessPlayer: Optional[bool] = None + m_EnableDebugPadForSwitch: Optional[bool] = None + m_EnableHeadlessMode: Optional[bool] = None + m_EnableHeapInspectorForSwitch: Optional[bool] = None + m_EnableHostIOForSwitch: Optional[bool] = None + m_EnableMemoryTrackerForSwitch: Optional[bool] = None + m_EnableRomCompressionForSwitch: Optional[bool] = None + m_EnableUnpublishableErrorsForSwitch: Optional[bool] = None + m_ExplicitArrayBoundsChecks: Optional[bool] = None + m_ExplicitDivideByZeroChecks: Optional[bool] = None + m_ExplicitNullChecks: Optional[bool] = None + m_ExportAsGoogleAndroidProject: Optional[bool] = None + m_FacebookAccessToken: Optional[str] = None + m_FacebookCreatePackageForSubmission: Optional[bool] = None + m_ForceInstallation: Optional[bool] = None + m_ForceOptimizeScriptCompilation: Optional[bool] = None + m_GenerateMetroReferenceProjects: Optional[bool] = None + m_GenerateNintendoSwitchShaderInfo: Optional[bool] = None + m_GenerateWSAReferenceProjects: Optional[bool] = None + m_HTCSScriptDebuggingForSwitch: Optional[bool] = None + m_Il2CppCodeGeneration: Optional[int] = None + m_MovePackageToDiscOuterEdge: Optional[bool] = None + m_NVNAftermath: Optional[bool] = None + m_NVNAftermathLevel: Optional[int] = None + m_NVNDrawValidation: Optional[bool] = None + m_NVNDrawValidationHeavy: Optional[bool] = None + m_NVNDrawValidationLight: Optional[bool] = None + m_NVNGraphicsDebuggerForSwitch: Optional[bool] = None + m_NVNShaderDebugging: Optional[bool] = None + m_NeedSubmissionMaterials: Optional[bool] = None + m_OverrideMaxTextureSize: Optional[int] = None + m_OverrideTextureCompression: Optional[int] = None + m_PS4HardwareTarget: Optional[int] = None + m_PS5KeepPackageFiles: Optional[bool] = None + m_PS5WorkspaceName: Optional[str] = None + m_PathOnRemoteDevice: Optional[str] = None + m_PlatformSettings: Optional[List[Tuple[str, PlatformSettingsData]]] = None + m_RedirectWritesToHostMountForSwitch: Optional[bool] = None + m_RemoteDeviceAddress: Optional[str] = None + m_RemoteDeviceExports: Optional[str] = None + m_RemoteDeviceInfo: Optional[bool] = None + m_RemoteDeviceUsername: Optional[str] = None + m_RomCompressionConfigForSwitch: Optional[str] = None + m_RomCompressionLevelForSwitch: Optional[int] = None + m_RomCompressionTypeForSwitch: Optional[int] = None + m_SaveADFForSwitch: Optional[bool] = None + m_SelectedAndroidETC2Fallback: Optional[int] = None + m_SelectedBlackBerryBuildType: Optional[int] = None + m_SelectedBlackBerrySubtarget: Optional[int] = None + m_SelectedBuildPlatformGroupName: Optional[str] = None + m_SelectedBuildTarget: Optional[int] = None + m_SelectedCompressionType: Optional[List[Tuple[str, int]]] = None + m_SelectedDiagnosticSetting: Optional[List[Tuple[str, int]]] = None + m_SelectedEmbeddedLinuxArchitecture: Optional[int] = None + m_SelectedFacebookTarget: Optional[int] = None + m_SelectedIOSBuildType: Optional[int] = None + m_SelectedMetroBuildAndRunDeployTarget: Optional[int] = None + m_SelectedMetroBuildType: Optional[int] = None + m_SelectedMetroSDK: Optional[int] = None + m_SelectedMetroTarget: Optional[int] = None + m_SelectedPS3Subtarget: Optional[int] = None + m_SelectedPS4Subtarget: Optional[int] = None + m_SelectedPS5CompressionLevel: Optional[int] = None + m_SelectedPS5CompressionType: Optional[int] = None + m_SelectedPS5Subtarget: Optional[int] = None + m_SelectedPSMSubtarget: Optional[int] = None + m_SelectedPSP2Subtarget: Optional[int] = None + m_SelectedQNXArchitecture: Optional[int] = None + m_SelectedQNXOsVersion: Optional[int] = None + m_SelectedStandaloneBuildSubtarget: Optional[int] = None + m_SelectedTizenSubtarget: Optional[int] = None + m_SelectedWSAArchitecture: Optional[str] = None + m_SelectedWSABuildAndRunDeployTarget: Optional[int] = None + m_SelectedWSAMinUWPSDK: Optional[str] = None + m_SelectedWSASDK: Optional[int] = None + m_SelectedWSASubtarget: Optional[int] = None + m_SelectedWSAUWPBuildType: Optional[int] = None + m_SelectedWSAUWPSDK: Optional[str] = None + m_SelectedWSAUWPVSVersion: Optional[str] = None + m_SelectedWebGLSubtarget: Optional[int] = None + m_SelectedWiiDebugLevel: Optional[int] = None + m_SelectedWiiSubtarget: Optional[int] = None + m_SelectedWiiUBootMode: Optional[int] = None + m_SelectedWiiUBuildOutput: Optional[int] = None + m_SelectedWiiUDebugLevel: Optional[int] = None + m_SelectedWindowsBuildAndRunDeployTarget: Optional[int] = None + m_SelectedXboxOneDeployDrive: Optional[int] = None + m_SelectedXboxOneDeployMethod: Optional[int] = None + m_SelectedXboxRunMethod: Optional[int] = None + m_SelectedXboxSubtarget: Optional[int] = None + m_SymlinkLibraries: Optional[bool] = None + m_SymlinkSources: Optional[bool] = None + m_SymlinkTrampoline: Optional[bool] = None + m_UseLegacyNvnPoolAllocatorForSwitch: Optional[bool] = None + m_WSADotNetNativeEnabled: Optional[List[bool]] = None + m_WaitForPlayerConnection: Optional[bool] = None + m_WaitForSwitchMemoryTrackerOnStartup: Optional[bool] = None + m_WebGLClientBrowserPath: Optional[str] = None + m_WebGLClientBrowserType: Optional[int] = None + m_WebGLClientPlatform: Optional[int] = None + m_WebGLOptimizationLevel: Optional[int] = None + m_WebGLUsePreBuiltUnityEngine: Optional[bool] = None + m_WebPlayerDeployOnline: Optional[bool] = None + m_WebPlayerNaClSupport: Optional[bool] = None + m_WebPlayerOfflineDeployment: Optional[bool] = None + m_WebPlayerStreamed: Optional[bool] = None + m_WiiUEnableNetAPI: Optional[bool] = None + m_WindowsDevicePortalAddress: Optional[str] = None + m_WindowsDevicePortalUsername: Optional[str] = None + m_WsaHolographicRemoting: Optional[bool] = None + m_XboxCompressedXex: Optional[bool] = None + m_XboxOneNetworkSharePath: Optional[str] = None + m_XboxOneStreamingInstallLaunchChunkRange: Optional[int] = None + m_XboxOneUsername: Optional[str] = None + m_macosXcodeBuildConfig: Optional[int] = None + + +@unitypy_define +class EditorUserSettings(Object): + m_VCAutomaticAdd: bool + m_VCDebugCmd: bool + m_VCDebugCom: bool + m_VCDebugOut: bool + m_ArtifactGarbageCollection: Optional[bool] = None + m_AssetPipelineMode: Optional[int] = None + m_AssetPipelineMode2: Optional[int] = None + m_CacheServerMode: Optional[int] = None + m_CacheServers: Optional[List[str]] = None + m_CompressAssetsOnImport: Optional[bool] = None + m_ConfigSettings: Optional[List[Tuple[str, ConfigSetting]]] = None + m_ConfigValues: Optional[List[Tuple[str, str]]] = None + m_DesiredImportWorkerCount: Optional[int] = None + m_IdleImportWorkerShutdownDelay: Optional[int] = None + m_SemanticMergeMode: Optional[int] = None + m_StandbyImportWorkerCount: Optional[int] = None + m_VCAllowAsyncUpdate: Optional[bool] = None + m_VCAutoRevertUnchangedFiles: Optional[bool] = None + m_VCHierarchyOverlayIcons: Optional[bool] = None + m_VCOtherOverlayIcons: Optional[bool] = None + m_VCOverlayIcons: Optional[bool] = None + m_VCOverwriteFailedCheckoutAssets: Optional[bool] = None + m_VCPassword: Optional[str] = None + m_VCProjectOverlayIcons: Optional[bool] = None + m_VCScanLocalPackagesOnConnect: Optional[bool] = None + m_VCServer: Optional[str] = None + m_VCShowFailedCheckout: Optional[bool] = None + m_VCUserName: Optional[str] = None + m_VCWorkspace: Optional[str] = None + + +@unitypy_define +class EmptyObject(Object): + pass + + +@unitypy_define +class GUIDSerializer(Object): + guidToPath: List[Tuple[GUID, str]] + + +@unitypy_define +class GameManager(Object, ABC): + pass + + +@unitypy_define +class GlobalGameManager(GameManager, ABC): + pass + + +@unitypy_define +class AnimationManager(GlobalGameManager): + pass + + +@unitypy_define +class AudioManager(GlobalGameManager): + Default_Speaker_Mode: int + Doppler_Factor: float + Rolloff_Scale: float + m_DSPBufferSize: int + m_Volume: float + m_AmbisonicDecoderPlugin: Optional[str] = None + m_AudioFoundation: Optional[int] = None + m_DisableAudio: Optional[bool] = None + m_OutputChannelLayout: Optional[int] = None + m_OutputSamplingRate: Optional[int] = None + m_RealVoiceCount: Optional[int] = None + m_RequestedDSPBufferSize: Optional[int] = None + m_SampleRate: Optional[int] = None + m_SpatializerPlugin: Optional[str] = None + m_SpeedOfSound: Optional[float] = None + m_VirtualVoiceCount: Optional[int] = None + m_VirtualizeEffects: Optional[bool] = None + + +@unitypy_define +class BuildSettings(GlobalGameManager): + hasAdvancedVersion: bool + hasPROVersion: bool + hasPublishingRights: bool + hasShadows: bool + isEducationalBuild: bool + m_Version: str + buildGUID: Optional[Union[GUID, str]] = None + buildTags: Optional[List[str]] = None + enableDynamicBatching: Optional[bool] = None + enableMultipleDisplays: Optional[bool] = None + enabledVRDevices: Optional[List[str]] = None + hasClusterRendering: Optional[bool] = None + hasLocalLightShadows: Optional[bool] = None + hasOculusPlugin: Optional[bool] = None + hasRenderTexture: Optional[bool] = None + hasSoftShadows: Optional[bool] = None + isDebugBuild: Optional[bool] = None + isEmbedded: Optional[bool] = None + isNoWatermarkBuild: Optional[bool] = None + isPrototypingBuild: Optional[bool] = None + isTrial: Optional[bool] = None + isWsaHolographicRemotingEnabled: Optional[bool] = None + levels: Optional[List[str]] = None + m_AuthToken: Optional[str] = None + m_GraphicsAPIs: Optional[List[int]] = None + preloadedPlugins: Optional[List[str]] = None + runtimeClassHashes: Optional[Union[List[Tuple[int, Hash128]], List[Tuple[int, int]]]] = None + scenes: Optional[List[str]] = None + scriptHashes: Optional[List[Tuple[Hash128, Hash128]]] = None + usesOnMouseEvents: Optional[bool] = None + + +@unitypy_define +class CloudWebServicesManager(GlobalGameManager): + pass + + +@unitypy_define +class ClusterInputManager(GlobalGameManager): + m_Inputs: List[ClusterInput] + + +@unitypy_define +class CrashReportManager(GlobalGameManager): + pass + + +@unitypy_define +class DelayedCallManager(GlobalGameManager): + pass + + +@unitypy_define +class GraphicsSettings(GlobalGameManager): + m_AlwaysIncludedShaders: List[PPtr[Shader]] + m_AdditionalWarmupCollections: Optional[List[PPtr[GraphicsStateCollection]]] = None + m_AllowEnlightenSupportForUpgradedProject: Optional[bool] = None + m_CacheMissCollectionPath: Optional[str] = None + m_CameraRelativeLightCulling: Optional[bool] = None + m_CameraRelativeShadowCulling: Optional[bool] = None + m_CollectionStartupAction: Optional[int] = None + m_CurrentRenderPipelineGlobalSettings: Optional[PPtr[Object]] = None + m_CustomRenderPipeline: Optional[PPtr[MonoBehaviour]] = None + m_DefaultRenderingLayerMask: Optional[int] = None + m_Deferred: Optional[BuiltinShaderSettings] = None + m_DeferredReflections: Optional[BuiltinShaderSettings] = None + m_DepthNormals: Optional[BuiltinShaderSettings] = None + m_EnableCacheMissTracing: Optional[bool] = None + m_GraphicsStateCollection: Optional[PPtr[GraphicsStateCollection]] = None + m_LegacyDeferred: Optional[BuiltinShaderSettings] = None + m_LensFlare: Optional[BuiltinShaderSettings] = None + m_LightHalo: Optional[BuiltinShaderSettings] = None + m_LightProbeOutsideHullStrategy: Optional[int] = None + m_LightsUseCCT: Optional[bool] = None + m_LightsUseColorTemperature: Optional[bool] = None + m_LightsUseLinearIntensity: Optional[bool] = None + m_LogWhenShaderIsCompiled: Optional[bool] = None + m_MotionVectors: Optional[BuiltinShaderSettings] = None + m_PreloadShadersBatchTimeLimit: Optional[int] = None + m_PreloadedShaders: Optional[List[PPtr[ShaderVariantCollection]]] = None + m_SRPDefaultSettings: Optional[List[Tuple[str, PPtr[Object]]]] = None + m_ScreenSpaceShadows: Optional[BuiltinShaderSettings] = None + m_ShaderDefinesPerShaderCompiler: Optional[List[PlatformShaderDefines]] = None + m_ShaderSettings: Optional[PlatformShaderSettings] = None + m_ShaderSettings_Tier1: Optional[PlatformShaderSettings] = None + m_ShaderSettings_Tier2: Optional[PlatformShaderSettings] = None + m_ShaderSettings_Tier3: Optional[PlatformShaderSettings] = None + m_SpritesDefaultMaterial: Optional[PPtr[Material]] = None + m_TierSettings_Tier1: Optional[TierGraphicsSettings] = None + m_TierSettings_Tier2: Optional[TierGraphicsSettings] = None + m_TierSettings_Tier3: Optional[TierGraphicsSettings] = None + m_TraceSavePath: Optional[str] = None + m_TraceSendToEditor: Optional[bool] = None + m_TransparencySortAxis: Optional[Vector3f] = None + m_TransparencySortMode: Optional[int] = None + m_VideoShadersIncludeMode: Optional[int] = None + m_WarmupAsync: Optional[bool] = None + m_WarmupProgressivelyLimit: Optional[int] = None + + +@unitypy_define +class InputManager(GlobalGameManager): + m_Axes: List[InputAxis] + m_UsePhysicalKeys: Optional[bool] = None + + +@unitypy_define +class MasterServerInterface(GlobalGameManager): + pass + + +@unitypy_define +class MonoManager(GlobalGameManager): + m_Scripts: List[PPtr[MonoScript]] + m_AssemblyNames: Optional[List[str]] = None + m_AssemblyTypes: Optional[List[int]] = None + m_RuntimeClassHashes: Optional[List[Tuple[int, Hash128]]] = None + m_ScriptHashes: Optional[List[Tuple[Hash128, Hash128]]] = None + + +@unitypy_define +class MultiplayerManager(GlobalGameManager): + m_ActiveMultiplayerRole: Optional[int] = None + m_ActiveMultiplayerRoles: Optional[int] = None + + +@unitypy_define +class NavMeshProjectSettings(GlobalGameManager): + areas: List[NavMeshAreaData] + m_LastAgentTypeID: Optional[int] = None + m_SettingNames: Optional[List[str]] = None + m_Settings: Optional[List[NavMeshBuildSettings]] = None + + +@unitypy_define +class NetworkManager(GlobalGameManager): + m_AssetToPrefab: List[Tuple[GUID, PPtr[GameObject]]] + m_DebugLevel: int + m_Sendrate: float + + +@unitypy_define +class NotificationManager(GlobalGameManager): + pass + + +@unitypy_define +class PerformanceReportingManager(GlobalGameManager): + pass + + +@unitypy_define +class Physics2DSettings(GlobalGameManager): + m_DefaultMaterial: PPtr[PhysicsMaterial2D] + m_Gravity: Vector2f + m_LayerCollisionMatrix: List[int] + m_PositionIterations: int + m_VelocityIterations: int + m_AngularSleepTolerance: Optional[float] = None + m_AutoSimulation: Optional[bool] = None + m_AutoSyncTransforms: Optional[bool] = None + m_BaumgarteScale: Optional[float] = None + m_BaumgarteTimeOfImpactScale: Optional[float] = None + m_BounceThreshold: Optional[float] = None + m_CallbacksOnDisable: Optional[bool] = None + m_ChangeStopsCallbacks: Optional[bool] = None + m_ContactThreshold: Optional[float] = None + m_DefaultContactOffset: Optional[float] = None + m_DeleteStopsCallbacks: Optional[bool] = None + m_JobOptions: Optional[PhysicsJobOptions2D] = None + m_LinearSleepTolerance: Optional[float] = None + m_MaxAngularCorrection: Optional[float] = None + m_MaxLinearCorrection: Optional[float] = None + m_MaxRotationSpeed: Optional[float] = None + m_MaxSubStepCount: Optional[int] = None + m_MaxTranslationSpeed: Optional[float] = None + m_MinPenetrationForPenalty: Optional[float] = None + m_MinSubStepFPS: Optional[float] = None + m_PhysicsLowLevelSettings: Optional[PPtr[Object]] = None + m_QueriesHitTriggers: Optional[bool] = None + m_QueriesStartInColliders: Optional[bool] = None + m_RaycastsHitTriggers: Optional[bool] = None + m_RaycastsStartInColliders: Optional[bool] = None + m_ReuseCollisionCallbacks: Optional[bool] = None + m_SimulationLayers: Optional[BitField] = None + m_SimulationMode: Optional[int] = None + m_TimeToSleep: Optional[float] = None + m_UseSubStepContacts: Optional[bool] = None + m_UseSubStepping: Optional[bool] = None + m_VelocityThreshold: Optional[float] = None + + +@unitypy_define +class PhysicsCoreProjectSettings2D(GlobalGameManager): + m_PhysicsCoreSettings: PPtr[Object] + + +@unitypy_define +class PhysicsManager(GlobalGameManager): + m_BounceThreshold: float + m_DefaultMaterial: Union[PPtr[PhysicMaterial], PPtr[PhysicsMaterial]] + m_Gravity: Vector3f + m_LayerCollisionMatrix: List[int] + m_AutoSimulation: Optional[bool] = None + m_AutoSyncTransforms: Optional[bool] = None + m_BroadphaseType: Optional[int] = None + m_ClothGravity: Optional[Vector3f] = None + m_ClothInterCollisionDistance: Optional[float] = None + m_ClothInterCollisionSettingsToggle: Optional[bool] = None + m_ClothInterCollisionStiffness: Optional[float] = None + m_ContactPairsMode: Optional[int] = None + m_ContactsGeneration: Optional[int] = None + m_CurrentBackendId: Optional[int] = None + m_DefaultContactOffset: Optional[float] = None + m_DefaultMaxAngluarSpeed: Optional[float] = None + m_DefaultMaxAngularSpeed: Optional[float] = None + m_DefaultMaxDepenetrationVelocity: Optional[float] = None + m_DefaultSolverIterations: Optional[int] = None + m_DefaultSolverVelocityIterations: Optional[int] = None + m_EnableAdaptiveForce: Optional[bool] = None + m_EnableEnhancedDeterminism: Optional[bool] = None + m_EnablePCM: Optional[bool] = None + m_EnableUnifiedHeightmaps: Optional[bool] = None + m_FastMotionThreshold: Optional[float] = None + m_FrictionType: Optional[int] = None + m_GenerateOnTriggerStayEvents: Optional[bool] = None + m_ImprovedPatchFriction: Optional[bool] = None + m_IncrementalStaticBroadphase: Optional[bool] = None + m_InvokeCollisionCallbacks: Optional[bool] = None + m_LogVerbosity: Optional[int] = None + m_MaxAngularVelocity: Optional[float] = None + m_MinPenetrationForPenalty: Optional[float] = None + m_QueriesHitBackfaces: Optional[bool] = None + m_QueriesHitTriggers: Optional[bool] = None + m_RaycastsHitTriggers: Optional[bool] = None + m_ReleaseSceneBuffers: Optional[bool] = None + m_ReuseCollisionCallbacks: Optional[bool] = None + m_SceneBuffersReleaseInterval: Optional[int] = None + m_ScratchBufferChunkCount: Optional[int] = None + m_SimulationMode: Optional[int] = None + m_SleepAngularVelocity: Optional[float] = None + m_SleepThreshold: Optional[float] = None + m_SleepVelocity: Optional[float] = None + m_SolverIterationCount: Optional[int] = None + m_SolverType: Optional[int] = None + m_SolverVelocityIterations: Optional[int] = None + m_ThreadingMode: Optional[int] = None + m_WorldBounds: Optional[AABB] = None + m_WorldSubdivisions: Optional[int] = None + + +@unitypy_define +class PlayerSettings(GlobalGameManager): + AndroidProfiler: bool + allowedAutorotateToLandscapeLeft: bool + allowedAutorotateToLandscapeRight: bool + allowedAutorotateToPortrait: bool + allowedAutorotateToPortraitUpsideDown: bool + companyName: str + defaultScreenHeight: int + defaultScreenHeightWeb: int + defaultScreenOrientation: int + defaultScreenWidth: int + defaultScreenWidthWeb: int + productName: str + runInBackground: bool + targetDevice: int + use32BitDisplayBuffer: bool + useMacAppStoreValidation: bool + useOSAutorotation: bool + usePlayerLog: bool + AID: Optional[Hash128] = None + AndroidEnableSustainedPerformanceMode: Optional[bool] = None + AndroidFilterTouchesWhenObscured: Optional[bool] = None + AndroidLicensePublicKey: Optional[str] = None + D3DHDRBitDepth: Optional[int] = None + Force_IOS_Speakers_When_Recording: Optional[bool] = None + Override_IPod_Music: Optional[bool] = None + Prepare_IOS_For_Recording: Optional[bool] = None + accelerometerFrequency: Optional[int] = None + activeInputHandler: Optional[int] = None + adjustIOSFPSUsingThermalState: Optional[bool] = None + allowFullscreenSwitch: Optional[bool] = None + allowHDRDisplaySupport: Optional[bool] = None + allowedHttpConnections: Optional[int] = None + androidApplicationEntry: Optional[int] = None + androidAutoRotationBehavior: Optional[int] = None + androidBlitType: Optional[int] = None + androidDefaultWindowHeight: Optional[int] = None + androidDefaultWindowWidth: Optional[int] = None + androidDisplayOptions: Optional[int] = None + androidFullscreenMode: Optional[int] = None + androidMaxAspectRatio: Optional[float] = None + androidMinAspectRatio: Optional[float] = None + androidMinimumWindowHeight: Optional[int] = None + androidMinimumWindowWidth: Optional[int] = None + androidPredictiveBackSupport: Optional[bool] = None + androidRenderOutsideSafeArea: Optional[bool] = None + androidRequestedVisibleInsets: Optional[int] = None + androidResizableWindow: Optional[bool] = None + androidResizeableActivity: Optional[bool] = None + androidShowActivityIndicatorOnLoading: Optional[int] = None + androidStartInFullscreen: Optional[bool] = None + androidSupportedAspectRatio: Optional[int] = None + androidSystemBarsBehavior: Optional[int] = None + androidUseSwappy: Optional[bool] = None + androidVulkanAllowFilterList: Optional[List[AndroidDeviceFilterData]] = None + androidVulkanDenyFilterList: Optional[List[AndroidDeviceFilterData]] = None + androidVulkanDeviceFilterListAsset: Optional[PPtr[VulkanDeviceFilterLists]] = None + audioSpatialExperience: Optional[int] = None + bakeCollisionMeshes: Optional[bool] = None + bundleIdentifier: Optional[str] = None + bundleVersion: Optional[str] = None + callOnDisableOnAssetBundleUnload: Optional[bool] = None + captureSingleScreen: Optional[bool] = None + cloudEnabled: Optional[bool] = None + cloudProjectId: Optional[str] = None + cpuConfiguration: Optional[List[int]] = None + cursorHotspot: Optional[Vector2f] = None + d3d11ForceExclusiveMode: Optional[bool] = None + d3d11FullscreenMode: Optional[int] = None + d3d12DeviceFilterListAsset: Optional[PPtr[D3D12DeviceFilterLists]] = None + d3d9FullscreenMode: Optional[int] = None + debugUnloadMode: Optional[int] = None + dedicatedServerOptimizations: Optional[bool] = None + defaultCursor: Optional[PPtr[Texture2D]] = None + defaultIsFullScreen: Optional[bool] = None + defaultIsNativeResolution: Optional[bool] = None + deferSystemGesturesMode: Optional[int] = None + disableDepthAndStencilBuffers: Optional[bool] = None + disableOldInputManagerSupport: Optional[bool] = None + displayResolutionDialog: Optional[int] = None + enableDirectStorage: Optional[bool] = None + enableFrameTimingStats: Optional[bool] = None + enableGamepadInput: Optional[bool] = None + enableHWStatistics: Optional[bool] = None + enableNativePlatformBackendsForNewInputSystem: Optional[bool] = None + enableNewInputSystem: Optional[bool] = None + enableOpenGLProfilerGPURecorders: Optional[bool] = None + forceSRGBBlit: Optional[bool] = None + forceSingleInstance: Optional[bool] = None + framebufferDepthMemorylessMode: Optional[int] = None + fullscreenMode: Optional[int] = None + gpuSkinning: Optional[bool] = None + graphicsJobMode: Optional[int] = None + graphicsJobs: Optional[bool] = None + hdrBitDepth: Optional[int] = None + hideHomeButton: Optional[bool] = None + hmiLoadingImage: Optional[PPtr[Texture2D]] = None + iPhoneBundleIdentifier: Optional[str] = None + ignoreAlphaClear: Optional[bool] = None + insecureHttpOption: Optional[int] = None + invalidatedPatternTexture: Optional[PPtr[Texture2D]] = None + iosAllowHTTPDownload: Optional[bool] = None + iosAppInBackgroundBehavior: Optional[int] = None + iosShowActivityIndicatorOnLoading: Optional[int] = None + iosUseCustomAppBackgroundBehavior: Optional[bool] = None + isWsaHolographicRemotingEnabled: Optional[bool] = None + legacyClampBlendShapeWeights: Optional[bool] = None + loadStoreDebugModeEnabled: Optional[bool] = None + m_ActiveColorSpace: Optional[int] = None + m_ColorGamuts: Optional[List[int]] = None + m_HolographicPauseOnTrackingLoss: Optional[bool] = None + m_HolographicTrackingLossScreen: Optional[PPtr[Texture2D]] = None + m_MTRendering: Optional[bool] = None + m_MobileMTRendering: Optional[bool] = None + m_MobileRenderingPath: Optional[int] = None + m_RenderingPath: Optional[int] = None + m_ShowUnitySplashLogo: Optional[bool] = None + m_ShowUnitySplashScreen: Optional[bool] = None + m_SplashScreenAnimation: Optional[int] = None + m_SplashScreenBackgroundAnimationZoom: Optional[float] = None + m_SplashScreenBackgroundColor: Optional[ColorRGBA] = None + m_SplashScreenBackgroundLandscape: Optional[PPtr[Texture2D]] = None + m_SplashScreenBackgroundLandscapeAspect: Optional[float] = None + m_SplashScreenBackgroundLandscapeUvs: Optional[Rectf] = None + m_SplashScreenBackgroundPortrait: Optional[PPtr[Texture2D]] = None + m_SplashScreenBackgroundPortraitAspect: Optional[float] = None + m_SplashScreenBackgroundPortraitUvs: Optional[Rectf] = None + m_SplashScreenDrawMode: Optional[int] = None + m_SplashScreenLogoAnimationZoom: Optional[float] = None + m_SplashScreenLogoStyle: Optional[int] = None + m_SplashScreenLogos: Optional[List[SplashScreenLogo]] = None + m_SplashScreenOverlayOpacity: Optional[float] = None + m_SplashScreenStyle: Optional[int] = None + m_SpriteBatchMaxVertexCount: Optional[int] = None + m_SpriteBatchVertexThreshold: Optional[int] = None + m_StackTraceTypes: Optional[List[int]] = None + m_StereoRenderingPath: Optional[int] = None + m_Stereoscopic3D: Optional[bool] = None + m_SupportedAspectRatios: Optional[AspectRatios] = None + m_UnitySplashLogo: Optional[PPtr[Sprite]] = None + m_UseDX11: Optional[bool] = None + m_VirtualRealitySplashScreen: Optional[PPtr[Texture2D]] = None + macAppStoreCategory: Optional[str] = None + macFullscreenMode: Optional[int] = None + macRetinaSupport: Optional[bool] = None + meshDeformation: Optional[int] = None + metalFramebufferOnly: Optional[bool] = None + metalUseMetalDisplayLink: Optional[bool] = None + metroEnableIndependentInputSource: Optional[bool] = None + metroEnableLowLatencyPresentationAPI: Optional[bool] = None + metroInputSource: Optional[int] = None + mipStripping: Optional[bool] = None + mobileMTRenderingBaked: Optional[bool] = None + muteOtherAudioSources: Optional[bool] = None + n3dsDisableStereoscopicView: Optional[bool] = None + n3dsEnableSharedListOpt: Optional[bool] = None + n3dsEnableVSync: Optional[bool] = None + numberOfMipsStripped: Optional[int] = None + numberOfMipsStrippedPerMipmapLimitGroup: Optional[List[Tuple[str, int]]] = None + organizationId: Optional[str] = None + platformRequiresReadableAssets: Optional[bool] = None + playerDataPath: Optional[str] = None + playerMinOpenGLESVersion: Optional[int] = None + preloadedAssets: Optional[List[PPtr[Object]]] = None + preserveFramebufferAlpha: Optional[bool] = None + productGUID: Optional[GUID] = None + projectId: Optional[str] = None + projectName: Optional[str] = None + protectGraphicsMemory: Optional[bool] = None + ps3SplashScreen: Optional[PPtr[Texture2D]] = None + psp2AcquireBGM: Optional[bool] = None + psp2PowerMode: Optional[int] = None + qualitySettingsNames: Optional[List[str]] = None + resetResolutionOnWindowResize: Optional[bool] = None + resizableWindow: Optional[bool] = None + resolutionScalingMode: Optional[int] = None + singlePassStereoRendering: Optional[bool] = None + stadiaPresentMode: Optional[int] = None + stadiaTargetFramerate: Optional[int] = None + stripPhysics: Optional[bool] = None + submitAnalytics: Optional[bool] = None + switchAllowGpuScratchShrinking: Optional[bool] = None + switchGpuScratchPoolGranularity: Optional[int] = None + switchGraphicsJobsSyncAfterKick: Optional[bool] = None + switchMaxWorkerMultiple: Optional[int] = None + switchNVNDefaultPoolsGranularity: Optional[int] = None + switchNVNGraphicsFirmwareMemory: Optional[int] = None + switchNVNMaxPublicSamplerIDCount: Optional[int] = None + switchNVNMaxPublicTextureIDCount: Optional[int] = None + switchNVNOtherPoolsGranularity: Optional[int] = None + switchNVNShaderPoolsGranularity: Optional[int] = None + switchQueueCommandMemory: Optional[int] = None + switchQueueComputeMemory: Optional[int] = None + switchQueueControlMemory: Optional[int] = None + targetGlesGraphics: Optional[int] = None + targetIOSGraphics: Optional[int] = None + targetPixelDensity: Optional[int] = None + targetPlatform: Optional[int] = None + targetResolution: Optional[int] = None + thermalStateCriticalIOSFPS: Optional[int] = None + thermalStateSeriousIOSFPS: Optional[int] = None + tizenShowActivityIndicatorOnLoading: Optional[int] = None + tvOSBundleVersion: Optional[str] = None + uiUse16BitDepthBuffer: Optional[bool] = None + unsupportedMSAAFallback: Optional[int] = None + uploadClearedTextureDataAfterCreationFromScript: Optional[bool] = None + use24BitDepthBuffer: Optional[bool] = None + useAlphaInDashboard: Optional[bool] = None + useFlipModelSwapchain: Optional[bool] = None + useHDRDisplay: Optional[bool] = None + useOnDemandResources: Optional[bool] = None + videoMemoryForVertexBuffers: Optional[int] = None + virtualRealitySupported: Optional[bool] = None + virtualTexturingSupportEnabled: Optional[bool] = None + visibleInBackground: Optional[bool] = None + visionOSBundleVersion: Optional[str] = None + vrSettings: Optional[VRSettings] = None + vulkanEnableCommandBufferRecycling: Optional[bool] = None + vulkanEnableLateAcquireNextImage: Optional[bool] = None + vulkanEnablePreTransform: Optional[bool] = None + vulkanEnableSetSRGBWrite: Optional[bool] = None + vulkanNumSwapchainBuffers: Optional[int] = None + vulkanUseSWCommandBuffers: Optional[bool] = None + webGPUDeviceFilterListAsset: Optional[PPtr[WebGPUDeviceFilterLists]] = None + webProgressiveAssetLoading: Optional[bool] = None + wiiHio2Usage: Optional[int] = None + wiiLoadingScreenBackground: Optional[ColorRGBA] = None + wiiLoadingScreenFileName: Optional[str] = None + wiiLoadingScreenPeriod: Optional[int] = None + wiiLoadingScreenRect: Optional[Rectf] = None + wiiLoadingScreenRectPlacement: Optional[int] = None + wiiUAllowScreenCapture: Optional[bool] = None + wiiUControllerCount: Optional[int] = None + wiiUGamePadMSAA: Optional[int] = None + wiiUSupportsBalanceBoard: Optional[bool] = None + wiiUSupportsClassicController: Optional[bool] = None + wiiUSupportsMotionPlus: Optional[bool] = None + wiiUSupportsNunchuk: Optional[bool] = None + wiiUSupportsProController: Optional[bool] = None + wiiUTVResolution: Optional[int] = None + windowsGamepadBackendHint: Optional[int] = None + wsaTransparentSwapchain: Optional[bool] = None + xboxEnableAvatar: Optional[bool] = None + xboxEnableEnableRenderThreadRunsJobs: Optional[bool] = None + xboxEnableFitness: Optional[bool] = None + xboxEnableGuest: Optional[bool] = None + xboxEnableHeadOrientation: Optional[bool] = None + xboxEnableKinect: Optional[bool] = None + xboxEnableKinectAutoTracking: Optional[bool] = None + xboxEnablePIXSampling: Optional[bool] = None + xboxEnableSpeech: Optional[bool] = None + xboxOneDisableEsram: Optional[bool] = None + xboxOneDisableKinectGpuReservation: Optional[bool] = None + xboxOneEnable7thCore: Optional[bool] = None + xboxOneEnableTypeOptimization: Optional[bool] = None + xboxOneLoggingLevel: Optional[int] = None + xboxOneMonoLoggingLevel: Optional[int] = None + xboxOnePresentImmediateThreshold: Optional[int] = None + xboxOneResolution: Optional[int] = None + xboxOneSResolution: Optional[int] = None + xboxOneXResolution: Optional[int] = None + xboxPIXTextureCapture: Optional[bool] = None + xboxSkinOnGPU: Optional[bool] = None + xboxSpeechDB: Optional[int] = None + + +@unitypy_define +class QualitySettings(GlobalGameManager): + Beautiful: Optional[QualitySetting] = None + Fantastic: Optional[QualitySetting] = None + Fast: Optional[QualitySetting] = None + Fastest: Optional[QualitySetting] = None + Good: Optional[QualitySetting] = None + Simple: Optional[QualitySetting] = None + m_CurrentQuality: Optional[int] = None + m_DefaultMobileQuality: Optional[int] = None + m_DefaultStandaloneQuality: Optional[int] = None + m_DefaultWebPlayerQuality: Optional[int] = None + m_EditorQuality: Optional[int] = None + m_QualitySettings: Optional[List[QualitySetting]] = None + m_StrippedMaximumLODLevel: Optional[int] = None + m_TextureMipmapLimitGroupNames: Optional[List[str]] = None + + +@unitypy_define +class ResourceManager(GlobalGameManager): + m_Container: List[Tuple[str, PPtr[Object]]] + m_DependentAssets: Optional[List[ResourceManager_Dependency]] = None + + +@unitypy_define +class RuntimeInitializeOnLoadManager(GlobalGameManager): + m_AfterAssembliesLoadedMethodExecutionOrders: Optional[List[int]] = None + m_AfterAssembliesLoadedUnityMethodExecutionOrders: Optional[List[int]] = None + m_AfterMethodExecutionOrders: Optional[List[int]] = None + m_AfterUnityMethodExecutionOrders: Optional[List[int]] = None + m_AssemblyNames: Optional[List[str]] = None + m_BeforeMethodExecutionOrders: Optional[List[int]] = None + m_BeforeSplashScreenMethodExecutionOrders: Optional[List[int]] = None + m_BeforeSplashScreenUnityMethodExecutionOrders: Optional[List[int]] = None + m_BeforeUnityMethodExecutionOrders: Optional[List[int]] = None + m_ClassInfos: Optional[List[ClassInfo]] = None + m_ClassMethodInfos: Optional[List[ClassMethodInfo]] = None + m_MethodExecutionOrders: Optional[List[int]] = None + m_NamespaceNames: Optional[List[str]] = None + m_SubsystemRegistrationMethodExecutionOrders: Optional[List[int]] = None + m_SubsystemRegistrationUnityMethodExecutionOrders: Optional[List[int]] = None + m_UnityMethodExecutionOrders: Optional[List[int]] = None + + +@unitypy_define +class ShaderNameRegistry(GlobalGameManager): + m_PreloadShaders: bool + m_Shaders: NameToObjectMap + + +@unitypy_define +class StreamingManager(GlobalGameManager): + pass + + +@unitypy_define +class TagManager(GlobalGameManager): + tags: List[str] + Builtin_Layer_0: Optional[str] = None + Builtin_Layer_1: Optional[str] = None + Builtin_Layer_2: Optional[str] = None + Builtin_Layer_3: Optional[str] = None + Builtin_Layer_4: Optional[str] = None + Builtin_Layer_5: Optional[str] = None + Builtin_Layer_6: Optional[str] = None + Builtin_Layer_7: Optional[str] = None + User_Layer_10: Optional[str] = None + User_Layer_11: Optional[str] = None + User_Layer_12: Optional[str] = None + User_Layer_13: Optional[str] = None + User_Layer_14: Optional[str] = None + User_Layer_15: Optional[str] = None + User_Layer_16: Optional[str] = None + User_Layer_17: Optional[str] = None + User_Layer_18: Optional[str] = None + User_Layer_19: Optional[str] = None + User_Layer_20: Optional[str] = None + User_Layer_21: Optional[str] = None + User_Layer_22: Optional[str] = None + User_Layer_23: Optional[str] = None + User_Layer_24: Optional[str] = None + User_Layer_25: Optional[str] = None + User_Layer_26: Optional[str] = None + User_Layer_27: Optional[str] = None + User_Layer_28: Optional[str] = None + User_Layer_29: Optional[str] = None + User_Layer_30: Optional[str] = None + User_Layer_31: Optional[str] = None + User_Layer_8: Optional[str] = None + User_Layer_9: Optional[str] = None + layers: Optional[List[str]] = None + m_RenderingLayers: Optional[List[str]] = None + m_SortingLayers: Optional[List[SortingLayerEntry]] = None + + +@unitypy_define +class TimeManager(GlobalGameManager): + Fixed_Timestep: Union[RationalTime, float] + Maximum_Allowed_Timestep: float + m_TimeScale: float + Maximum_Particle_Timestep: Optional[float] = None + + +@unitypy_define +class UnityAdsManager(GlobalGameManager): + pass + + +@unitypy_define +class UnityAnalyticsManager(GlobalGameManager): + m_Enabled: Optional[bool] = None + m_InitializeOnStartup: Optional[bool] = None + m_TestConfigUrl: Optional[str] = None + m_TestEventUrl: Optional[str] = None + m_TestMode: Optional[bool] = None + + +@unitypy_define +class UnityConnectSettings(GlobalGameManager): + UnityAnalyticsSettings: UnityAnalyticsSettings + UnityPurchasingSettings: UnityPurchasingSettings + CrashReportingSettings: Optional[CrashReportingSettings] = None + InsightsSettings: Optional[InsightsSettings] = None + PerformanceReportingSettings: Optional[PerformanceReportingSettings] = None + UnityAdsSettings: Optional[UnityAdsSettings] = None + m_ConfigUrl: Optional[str] = None + m_DashboardUrl: Optional[str] = None + m_Enabled: Optional[bool] = None + m_EventOldUrl: Optional[str] = None + m_EventUrl: Optional[str] = None + m_TestConfigUrl: Optional[str] = None + m_TestEventUrl: Optional[str] = None + m_TestInitMode: Optional[int] = None + m_TestMode: Optional[bool] = None + + +@unitypy_define +class VFXManager(GlobalGameManager): + m_CopyBufferShader: PPtr[ComputeShader] + m_FixedTimeStep: float + m_IndirectShader: PPtr[ComputeShader] + m_MaxDeltaTime: float + m_RenderPipeSettingsPath: str + m_SortShader: PPtr[ComputeShader] + m_BatchEmptyLifetime: Optional[int] = None + m_CompiledVersion: Optional[int] = None + m_EmptyShader: Optional[PPtr[Shader]] = None + m_MaxCapacity: Optional[int] = None + m_MaxScrubTime: Optional[float] = None + m_PrefixSumShader: Optional[PPtr[ComputeShader]] = None + m_RuntimeResources: Optional[PPtr[MonoBehaviour]] = None + m_RuntimeVersion: Optional[int] = None + m_StripUpdateShader: Optional[PPtr[ComputeShader]] = None + + +@unitypy_define +class LevelGameManager(GameManager, ABC): + pass + + +@unitypy_define +class HaloManager(LevelGameManager): + pass + + +@unitypy_define +class LightmapSettings(LevelGameManager): + m_Lightmaps: List[LightmapData] + m_LightmapsMode: int + m_BakeOnSceneLoad: Optional[int] = None + m_BakedColorSpace: Optional[int] = None + m_EnlightenSceneMapping: Optional[EnlightenSceneMapping] = None + m_GISettings: Optional[GISettings] = None + m_LightProbes: Optional[PPtr[LightProbes]] = None + m_LightingSettings: Optional[PPtr[LightingSettings]] = None + m_RuntimeCPUUsage: Optional[int] = None + m_ShadowMaskMode: Optional[int] = None + m_UseDualLightmapsInForward: Optional[bool] = None + m_UseShadowmask: Optional[bool] = None + + +@unitypy_define +class NavMeshSettings(LevelGameManager): + m_NavMesh: Optional[PPtr[NavMesh]] = None + m_NavMeshData: Optional[PPtr[NavMeshData]] = None + + +@unitypy_define +class OcclusionCullingSettings(LevelGameManager): + m_OcclusionCullingData: PPtr[OcclusionCullingData] + m_Portals: List[PPtr[OcclusionPortal]] + m_SceneGUID: GUID + m_StaticRenderers: List[PPtr[Renderer]] + + +@unitypy_define +class RenderSettings(LevelGameManager): + m_FlareStrength: float + m_Fog: bool + m_FogColor: ColorRGBA + m_FogDensity: float + m_FogMode: int + m_HaloStrength: float + m_HaloTexture: PPtr[Texture2D] + m_LinearFogEnd: float + m_LinearFogStart: float + m_SkyboxMaterial: PPtr[Material] + m_SpotCookie: PPtr[Texture2D] + m_AmbientEquatorColor: Optional[ColorRGBA] = None + m_AmbientGroundColor: Optional[ColorRGBA] = None + m_AmbientIntensity: Optional[float] = None + m_AmbientLight: Optional[ColorRGBA] = None + m_AmbientMode: Optional[int] = None + m_AmbientProbe: Optional[SphericalHarmonicsL2] = None + m_AmbientProbeInGamma: Optional[SphericalHarmonicsL2] = None + m_AmbientSkyColor: Optional[ColorRGBA] = None + m_CustomReflection: Optional[Union[PPtr[Cubemap], PPtr[Texture]]] = None + m_DefaultReflectionMode: Optional[int] = None + m_DefaultReflectionResolution: Optional[int] = None + m_FlareFadeSpeed: Optional[float] = None + m_GeneratedSkyboxReflection: Optional[PPtr[Cubemap]] = None + m_IndirectSpecularColor: Optional[ColorRGBA] = None + m_ReflectionBounces: Optional[int] = None + m_ReflectionIntensity: Optional[float] = None + m_SubtractiveShadowColor: Optional[ColorRGBA] = None + m_Sun: Optional[PPtr[Light]] = None + m_UseRadianceAmbientProbe: Optional[bool] = None + + +@unitypy_define +class HierarchyState(Object): + expanded: List[PPtr[Object]] + selection: List[PPtr[Object]] + scrollposition_x: Optional[float] = None + scrollposition_x: Optional[float] = None + scrollposition_y: Optional[float] = None + scrollposition_y: Optional[float] = None + + +@unitypy_define +class InspectorExpandedState(Object): + m_ExpandedData: List[ExpandedData] + + +@unitypy_define +class MarshallingTestObject(Object): + m_Prop: int + + +@unitypy_define +class MemorySettings(Object): + pass + + +@unitypy_define +class NScreenBridge(Object): + pass + + +@unitypy_define +class NativeObjectType(Object): + m_Inner: NativeType + + +@unitypy_define +class PackedAssets(Object): + m_Contents: List[BuildReportPackedAssetInfo] + m_Overhead: int + m_ShortPath: str + m_File: Optional[int] = None + + +@unitypy_define +class PlatformModuleSetup(Object): + modules: List[Module] + + +@unitypy_define +class PluginBuildInfo(Object): + m_EditorPlugins: List[str] + m_RuntimePlugins: List[str] + + +@unitypy_define +class Prefab(Object): + m_RootGameObject: PPtr[GameObject] + m_ContainsMissingSerializeReferenceTypes: Optional[bool] = None + m_HideFlagsBehaviour: Optional[int] = None + m_IsExploded: Optional[bool] = None + m_IsPrefabAsset: Optional[bool] = None + m_IsPrefabParent: Optional[bool] = None + m_Modification: Optional[PrefabModification] = None + m_ParentPrefab: Optional[PPtr[Prefab]] = None + m_SourcePrefab: Optional[PPtr[Prefab]] = None + + +@unitypy_define +class PrefabInstance(Object): + m_Modification: PrefabModification + m_RootGameObject: PPtr[GameObject] + m_SourcePrefab: PPtr[Prefab] + + +@unitypy_define +class PresetManager(Object): + m_DefaultList: Optional[List[DefaultPresetList]] = None + m_DefaultPresets: Optional[List[Tuple[PresetType, List[DefaultPreset]]]] = None + + +@unitypy_define +class PropertyModificationsTargetTestObject(Object): + m_Array: List[PropertyModificationsTargetTestNativeObject] + m_Data: PropertyModificationsTargetTestNativeObject + m_FloatTestValue: float + byte_data: Optional[bytes] = None + m_Bytes: Optional[List[int]] = None + m_BytesSize: Optional[int] = None + m_Floats: Optional[List[float]] = None + + +@unitypy_define +class RenderPassAttachment(Object): + pass + + +@unitypy_define +class SceneRoots(Object): + m_Roots: List[PPtr[Object]] + + +@unitypy_define +class SceneVisibilityState(Object): + m_IsolationMode: Optional[bool] = None + m_MainStageIsolated: Optional[bool] = None + m_PrefabStageIsolated: Optional[bool] = None + m_SceneData: Optional[List[Tuple[SceneIdentifier, SceneVisibilityData]]] = None + m_ScenePickingData: Optional[SceneDataContainer] = None + m_SceneVisibilityData: Optional[SceneDataContainer] = None + m_SceneVisibilityDataIsolated: Optional[SceneDataContainer] = None + + +@unitypy_define +class ScenesUsingAssets(Object): + m_ListOfScenesUsingEachAsset: List[Tuple[str, List[str]]] + m_ScenesUsingAssets: List[BuildReportScenesUsingAsset] + + +@unitypy_define +class SerializableManagedHost(Object): + m_Script: PPtr[MonoScript] + + +@unitypy_define +class SerializableManagedRefTestClass(Object): + m_Script: PPtr[MonoScript] + + +@unitypy_define +class ShaderContainer(Object): + pass + + +@unitypy_define +class ShaderIncludeReflection(Object): + m_Functions: List[ReflectedFunction] + m_ReflectionLog: ErrorLog + + +@unitypy_define +class SiblingDerived(Object): + pass + + +@unitypy_define +class SpriteAtlasDatabase(Object): + pass + + +@unitypy_define +class TestObjectVectorPairStringBool(Object): + m_Map: List[Tuple[str, bool]] + m_String: str + + +@unitypy_define +class TestObjectWithSerializedAnimationCurve(Object): + m_Curve: AnimationCurve + + +@unitypy_define +class TestObjectWithSerializedArray(Object): + m_ClampTestValue: float + m_IntegerArray: List[int] + + +@unitypy_define +class TestObjectWithSerializedMapStringBool(Object): + m_Map: List[Tuple[str, bool]] + m_String: str + + +@unitypy_define +class TestObjectWithSerializedMapStringNonAlignedStruct(Object): + m_Map: List[Tuple[str, NonAlignedStruct]] + m_String: str + + +@unitypy_define +class TestObjectWithSpecialLayoutOne(Object): + differentLayout: LayoutDataOne + sameLayout: LayoutDataOne + + +@unitypy_define +class TestObjectWithSpecialLayoutTwo(Object): + differentLayout: LayoutDataTwo + sameLayout: LayoutDataThree + + +@unitypy_define +class TilemapEditorUserSettings(Object): + m_FocusMode: int + m_LastUsedPalette: PPtr[GameObject] + + +@unitypy_define +class UIAnimationBinder(Object): + pass + + +@unitypy_define +class VersionControlSettings(Object): + m_Mode: str + m_CollabEditorSettings: Optional[CollabEditorSettings] = None + m_TrackPackagesOutsideProject: Optional[bool] = None + + +@unitypy_define +class VideoBuildInfo(Object): + m_IsVideoModuleDisabled: bool + m_VideoClipCount: int + + +@unitypy_define +class AABB: + m_Center: Vector3f + m_Extent: Vector3f + + +@unitypy_define +class AddedComponent: + addedObject: PPtr[Component] + insertIndex: int + targetCorrespondingSourceObject: PPtr[GameObject] + + +@unitypy_define +class AddedGameObject: + addedObject: PPtr[Transform] + insertIndex: int + targetCorrespondingSourceObject: PPtr[Transform] + + +@unitypy_define +class AndroidDeviceFilterData: + androidOsVersionString: str + brandName: str + deviceName: str + driverVersionString: str + productName: str + vendorName: str + vulkanApiVersionString: str + + +@unitypy_define +class AnimationClipBindingConstant: + genericBindings: List[GenericBinding] + pptrCurveMapping: List[PPtr[Object]] + + +@unitypy_define +class AnimationClipOverride: + m_OriginalClip: PPtr[AnimationClip] + m_OverrideClip: PPtr[AnimationClip] + + +@unitypy_define +class AnimationCurve: + m_Curve: List[Keyframe] + m_PostInfinity: int + m_PreInfinity: int + m_RotationOrder: Optional[int] = None + + +@unitypy_define +class AnimationEvent: + data: str + floatParameter: float + functionName: str + intParameter: int + messageOptions: int + objectReferenceParameter: PPtr[Object] + time: float + + +@unitypy_define +class AnimatorCondition: + m_ConditionEvent: str + m_ConditionMode: int + m_EventTreshold: float + + +@unitypy_define +class Annotation: + m_ClassID: int + m_Flags: int + m_GizmoEnabled: bool + m_IconEnabled: bool + m_ScriptClass: str + + +@unitypy_define +class ArticulationDrive: + damping: float + forceLimit: float + lowerLimit: float + stiffness: float + target: float + targetVelocity: float + upperLimit: float + driveType: Optional[int] = None + + +@unitypy_define +class AspectRatios: + Others: bool + x16_10: bool + x16_9: bool + x4_3: bool + x5_4: bool + + +@unitypy_define +class AssemblyJsonAsset(TextAsset): + m_Name: str + m_Script: str + m_PathName: Optional[str] = None + + +@unitypy_define +class AssemblyJsonImporter(AssetImporter): + m_AssetBundleName: str + m_AssetBundleVariant: str + m_Name: str + m_UserData: str + m_ExternalObjects: Optional[List[Tuple[SourceAssetIdentifier, PPtr[Object]]]] = None + + +@unitypy_define +class Asset: + children: List[GUID] + labels: AssetLabels + mainRepresentation: LibraryRepresentation + parent: GUID + representations: List[LibraryRepresentation] + type: int + assetBundleIndex: Optional[int] = None + digest: Optional[MdFour] = None + guidOfPathLocationDependencies: Optional[Union[List[Tuple[GUID, str]], List[Tuple[str, GUID]]]] = None + hash: Optional[Union[Hash128, MdFour]] = None + hashOfImportedAssetDependencies: Optional[List[GUID]] = None + hashOfSourceAssetDependencies: Optional[List[GUID]] = None + importerClassId: Optional[int] = None + importerVersionHash: Optional[int] = None + metaModificationDate_0_: Optional[int] = None + metaModificationDate_1_: Optional[int] = None + modificationDate_0_: Optional[int] = None + modificationDate_1_: Optional[int] = None + scriptedImporterClassID: Optional[str] = None + + +@unitypy_define +class AssetBundleFullName: + m_AssetBundleName: str + m_AssetBundleVariant: str + + +@unitypy_define +class AssetBundleInfo: + AssetBundleDependencies: List[int] + AssetBundleHash: Hash128 + + +@unitypy_define +class AssetBundleScriptInfo: + assemblyName: str + className: str + hash: int + nameSpace: str + + +@unitypy_define +class AssetDatabase(Object): + m_Assets: List[Tuple[GUID, Asset]] + m_AssetBundleNames: Optional[List[Tuple[int, AssetBundleFullName]]] = None + m_AssetTimeStamps: Optional[List[Tuple[str, AssetTimeStamp]]] = None + m_Metrics: Optional[AssetDatabaseMetrics] = None + m_UnityShadersVersion: Optional[int] = None + m_lastValidVersionHashes: Optional[List[Tuple[int, int]]] = None + + +@unitypy_define +class AssetDatabaseMetrics: + totalAssetCount: int + nonProAssetCount: Optional[int] = None + nonProAssetsCreatedAfterProLicense: Optional[int] = None + + +@unitypy_define +class AssetImporterHashKey: + ScriptClass: str + type: int + + +@unitypy_define +class AssetImporterLog(NamedObject): + m_Logs: List[AssetImporter_ImportError] + m_Name: str + + +@unitypy_define +class AssetImporter_ImportError: + error: str + file: str + line: int + mode: int + object: PPtr[Object] + + +@unitypy_define +class AssetInfo: + asset: PPtr[Object] + preloadIndex: int + preloadSize: int + + +@unitypy_define +class AssetLabels: + m_Labels: List[str] + + +@unitypy_define +class AssetStats: + objectCount: int + resourceCount: int + size: int + sourceAssetGUID: GUID + sourceAssetPath: str + + +@unitypy_define +class AssetTimeStamp: + metaModificationDate_0_: int + metaModificationDate_1_: int + modificationDate_0_: int + modificationDate_1_: int + + +@unitypy_define +class AttachmentIndexArray: + activeAttachments: int + attachments: List[int] + + +@unitypy_define +class AttachmentInfo: + format: int + needsResolve: bool + canMultiview: Optional[bool] = None + loadAction: Optional[int] = None + sampleCount: Optional[int] = None + storeAction: Optional[int] = None + + +@unitypy_define +class AudioImporterOutput: + editorOutputContainerFormat: int + editorOutputSettings: SampleSettings + outputContainerFormat: int + outputSettings: SampleSettings + playerResource: Optional[StreamedResource] = None + + +@unitypy_define +class AudioMixerConstant: + effectGUIDs: List[GUID] + effects: List[EffectConstant] + exposedParameterIndices: List[int] + exposedParameterNames: List[int] + groupGUIDs: List[GUID] + groupNameBuffer: List[int] + groups: List[GroupConstant] + numSideChainBuffers: int + pluginEffectNameBuffer: List[int] + snapshotGUIDs: List[GUID] + snapshotNameBuffer: List[int] + snapshots: List[SnapshotConstant] + groupConnections: Optional[List[GroupConnection]] = None + + +@unitypy_define +class AudioMixerLiveUpdateBool(ABC): + pass + + +@unitypy_define +class AudioMixerLiveUpdateFloat(ABC): + pass + + +@unitypy_define +class AutoOffMeshLinkData: + m_Area: int + m_End: Vector3f + m_LinkDirection: int + m_LinkType: int + m_Radius: float + m_Start: Vector3f + + +@unitypy_define +class AvatarBodyMask(NamedObject): + m_Mask: List[int] + m_Name: str + + +@unitypy_define +class AvatarConstant: + m_Human: OffsetPtr + m_HumanSkeletonIndexArray: List[int] + m_RootMotionBoneIndex: int + m_RootMotionBoneX: xform + m_AvatarSkeleton: Optional[OffsetPtr] = None + m_AvatarSkeletonPose: Optional[OffsetPtr] = None + m_DefaultPose: Optional[OffsetPtr] = None + m_HumanSkeletonReverseIndexArray: Optional[List[int]] = None + m_RootMotionSkeleton: Optional[OffsetPtr] = None + m_RootMotionSkeletonIndexArray: Optional[List[int]] = None + m_RootMotionSkeletonPose: Optional[OffsetPtr] = None + m_Skeleton: Optional[OffsetPtr] = None + m_SkeletonNameIDArray: Optional[List[int]] = None + m_SkeletonPose: Optional[OffsetPtr] = None + + +@unitypy_define +class AvatarSkeletonMaskElement: + path: str + weight: float + + +@unitypy_define +class Axes: + m_Length: float + m_Limit: Limit + m_PostQ: float4 + m_PreQ: float4 + m_Sgn: Union[float3, float4] + m_Type: int + + +@unitypy_define +class Binding: + m_Slot: int + m_EncodedData: Optional[int] = None + m_Set: Optional[int] = None + + +@unitypy_define +class BitField: + m_Bits: int + + +@unitypy_define +class Blend1dDataConstant: + m_ChildThresholdArray: List[float] + + +@unitypy_define +class Blend2dDataConstant: + m_ChildMagnitudeArray: Optional[List[float]] = None + m_ChildNeighborListArray: Optional[List[MotionNeighborList]] = None + m_ChildPairAvgMagInvArray: Optional[List[float]] = None + m_ChildPairVectorArray: Optional[List[Vector2f]] = None + m_ChildPositionArray: Optional[List[Vector2f]] = None + m_ChildThresholdArray: Optional[List[float]] = None + + +@unitypy_define +class BlendDirectDataConstant: + m_ChildBlendEventIDArray: List[int] + m_NormalizedBlendValues: bool + + +@unitypy_define +class BlendShapeData: + channels: List[MeshBlendShapeChannel] + fullWeights: List[float] + shapes: List[MeshBlendShape] + vertices: List[BlendShapeVertex] + + +@unitypy_define +class BlendShapeVertex: + index: int + normal: Vector3f + tangent: Vector3f + vertex: Vector3f + + +@unitypy_define +class BlendTreeConstant: + m_NodeArray: List[OffsetPtr] + m_BlendEventArrayConstant: Optional[OffsetPtr] = None + + +@unitypy_define +class BlendTreeNodeConstant: + m_BlendEventID: int + m_ChildIndices: List[int] + m_ClipID: int + m_Duration: float + m_Blend1dData: Optional[OffsetPtr] = None + m_Blend2dData: Optional[OffsetPtr] = None + m_BlendDirectData: Optional[OffsetPtr] = None + m_BlendEventYID: Optional[int] = None + m_BlendType: Optional[int] = None + m_ChildThresholdArray: Optional[List[float]] = None + m_ClipIndex: Optional[int] = None + m_CycleOffset: Optional[float] = None + m_Mirror: Optional[bool] = None + + +@unitypy_define +class BoneInfluence: + boneIndex_0_: int + boneIndex_1_: int + boneIndex_2_: int + boneIndex_3_: int + weight_0_: float + weight_1_: float + weight_2_: float + weight_3_: float + + +@unitypy_define +class BoneWeights4: + boneIndex_0_: int + boneIndex_1_: int + boneIndex_2_: int + boneIndex_3_: int + weight_0_: float + weight_1_: float + weight_2_: float + weight_3_: float + + +@unitypy_define +class BranchWindLevel: + m_afBend_0: float + m_afBend_1: float + m_afBend_10: float + m_afBend_11: float + m_afBend_12: float + m_afBend_13: float + m_afBend_14: float + m_afBend_15: float + m_afBend_16: float + m_afBend_17: float + m_afBend_18: float + m_afBend_19: float + m_afBend_2: float + m_afBend_3: float + m_afBend_4: float + m_afBend_5: float + m_afBend_6: float + m_afBend_7: float + m_afBend_8: float + m_afBend_9: float + m_afFlexibility_0: float + m_afFlexibility_1: float + m_afFlexibility_10: float + m_afFlexibility_11: float + m_afFlexibility_12: float + m_afFlexibility_13: float + m_afFlexibility_14: float + m_afFlexibility_15: float + m_afFlexibility_16: float + m_afFlexibility_17: float + m_afFlexibility_18: float + m_afFlexibility_19: float + m_afFlexibility_2: float + m_afFlexibility_3: float + m_afFlexibility_4: float + m_afFlexibility_5: float + m_afFlexibility_6: float + m_afFlexibility_7: float + m_afFlexibility_8: float + m_afFlexibility_9: float + m_afOscillation_0: float + m_afOscillation_1: float + m_afOscillation_10: float + m_afOscillation_11: float + m_afOscillation_12: float + m_afOscillation_13: float + m_afOscillation_14: float + m_afOscillation_15: float + m_afOscillation_16: float + m_afOscillation_17: float + m_afOscillation_18: float + m_afOscillation_19: float + m_afOscillation_2: float + m_afOscillation_3: float + m_afOscillation_4: float + m_afOscillation_5: float + m_afOscillation_6: float + m_afOscillation_7: float + m_afOscillation_8: float + m_afOscillation_9: float + m_afSpeed_0: float + m_afSpeed_1: float + m_afSpeed_10: float + m_afSpeed_11: float + m_afSpeed_12: float + m_afSpeed_13: float + m_afSpeed_14: float + m_afSpeed_15: float + m_afSpeed_16: float + m_afSpeed_17: float + m_afSpeed_18: float + m_afSpeed_19: float + m_afSpeed_2: float + m_afSpeed_3: float + m_afSpeed_4: float + m_afSpeed_5: float + m_afSpeed_6: float + m_afSpeed_7: float + m_afSpeed_8: float + m_afSpeed_9: float + m_afTurbulence_0: float + m_afTurbulence_1: float + m_afTurbulence_10: float + m_afTurbulence_11: float + m_afTurbulence_12: float + m_afTurbulence_13: float + m_afTurbulence_14: float + m_afTurbulence_15: float + m_afTurbulence_16: float + m_afTurbulence_17: float + m_afTurbulence_18: float + m_afTurbulence_19: float + m_afTurbulence_2: float + m_afTurbulence_3: float + m_afTurbulence_4: float + m_afTurbulence_5: float + m_afTurbulence_6: float + m_afTurbulence_7: float + m_afTurbulence_8: float + m_afTurbulence_9: float + m_fIndependence: float + + +@unitypy_define +class BufferBinding: + m_Index: int + m_NameIndex: int + m_ArraySize: Optional[int] = None + + +@unitypy_define +class BufferBindingParameter: + m_ArraySize: int + m_NameIndex: int + m_Binding: Optional[Binding] = None + m_Index: Optional[int] = None + m_ResourceType: Optional[int] = None + + +@unitypy_define +class BuildReportFile: + id: int + path: str + role: str + totalSize: int + flags: Optional[int] = None + + +@unitypy_define +class BuildReportPackedAssetInfo: + classID: int + fileID: int + packedSize: int + sourceAssetGUID: GUID + buildTimeAssetPath: Optional[str] = None + offset: Optional[int] = None + + +@unitypy_define +class BuildReportScenesUsingAsset: + assetPath: str + scenePaths: List[str] + + +@unitypy_define +class BuildStepInfo: + messages: List[BuildStepMessage] + stepName: str + depth: Optional[int] = None + duration: Optional[int] = None + durationTicks: Optional[int] = None + + +@unitypy_define +class BuildStepMessage: + content: str + type: int + + +@unitypy_define +class BuildSummary: + assetBundleOptions: int + crc: int + options: int + outputPath: str + platformName: str + totalErrors: int + totalSize: int + totalWarnings: int + buildContentOptions: Optional[int] = None + buildGUID: Optional[GUID] = None + buildManifestHash: Optional[Hash128] = None + buildName: Optional[str] = None + buildProfileGuid: Optional[GUID] = None + buildProfilePath: Optional[str] = None + buildResult: Optional[int] = None + buildSessionGUID: Optional[GUID] = None + buildStartTime: Optional[DateTime] = None + buildType: Optional[int] = None + dataPath: Optional[str] = None + multiProcessEnabled: Optional[bool] = None + name: Optional[str] = None + platformGroupName: Optional[str] = None + subtarget: Optional[int] = None + success: Optional[bool] = None + totalTimeMS: Optional[int] = None + totalTimeTicks: Optional[int] = None + + +@unitypy_define +class BuildTargetSettings: + m_BuildTarget: str + m_TextureFormat: int + m_AllowsAlphaSplitting: Optional[bool] = None + m_CompressionQuality: Optional[int] = None + m_LoadingBehavior: Optional[int] = None + m_MaxTextureSize: Optional[int] = None + m_TextureHeight: Optional[int] = None + m_TextureWidth: Optional[int] = None + + +@unitypy_define +class BuildTextureStackReference: + groupName: str + itemName: str + + +@unitypy_define +class BuiltAssetBundleInfo: + bundleArchiveFile: int + bundleName: str + packagedFileIndices: List[int] + + +@unitypy_define +class BuiltinShaderSettings: + m_Mode: int + m_Shader: PPtr[Shader] + + +@unitypy_define +class CGProgram(TextAsset): + m_Name: str + m_Script: str + m_PathName: Optional[str] = None + + +@unitypy_define +class CachedAssetMetaData: + guid: GUID + originalChangeset: int + originalDigest: Union[Hash128, MdFour] + originalName: str + originalParent: GUID + pathName: str + + +@unitypy_define +class Channel: + attributeName: str + byteOffset: int + curve: AnimationCurve + + +@unitypy_define +class ChannelInfo: + dimension: int + format: int + offset: int + stream: int + + +@unitypy_define +class CharacterInfo: + index: int + uv: Rectf + vert: Rectf + advance: Optional[float] = None + flipped: Optional[bool] = None + width: Optional[float] = None + + +@unitypy_define +class Child: + m_IsAnim: bool + m_Motion: PPtr[Motion] + m_Threshold: float + m_TimeScale: float + m_CycleOffset: Optional[float] = None + m_Mirror: Optional[bool] = None + m_Position: Optional[Vector2f] = None + + +@unitypy_define +class ChildAnimatorState: + m_Position: Vector3f + m_State: PPtr[AnimatorState] + + +@unitypy_define +class ChildAnimatorStateMachine: + m_Position: Vector3f + m_StateMachine: PPtr[AnimatorStateMachine] + + +@unitypy_define +class ChildMotion: + m_CycleOffset: float + m_DirectBlendParameter: str + m_Mirror: bool + m_Motion: PPtr[Motion] + m_Position: Vector2f + m_Threshold: float + m_TimeScale: float + + +@unitypy_define +class ClampVelocityModule: + dampen: float + enabled: bool + magnitude: MinMaxCurve + separateAxis: bool + x: MinMaxCurve + y: MinMaxCurve + z: MinMaxCurve + drag: Optional[MinMaxCurve] = None + inWorldSpace: Optional[bool] = None + multiplyDragByParticleSize: Optional[bool] = None + multiplyDragByParticleVelocity: Optional[bool] = None + + +@unitypy_define +class ClassInfo: + m_AssemblyNameIndex: int + m_ClassName: str + m_IsUnityClass: bool + m_MethodIndex: int + m_NamespaceIndex: int + m_NumOfMethods: int + m_NamespaceName: Optional[str] = None + + +@unitypy_define +class ClassMethodInfo: + m_ClassIndex: int + m_MethodName: str + m_OrderNumber: int + + +@unitypy_define +class Clip: + m_DenseClip: DenseClip + m_StreamedClip: StreamedClip + m_Binding: Optional[OffsetPtr] = None + m_ConstantClip: Optional[ConstantClip] = None + + +@unitypy_define +class ClipAnimationInfo: + firstFrame: Union[float, int] + lastFrame: Union[float, int] + loop: bool + name: str + wrapMode: int + additiveReferencePoseFrame: Optional[float] = None + bodyMask: Optional[List[int]] = None + curves: Optional[List[ClipAnimationInfoCurve]] = None + cycleOffset: Optional[float] = None + events: Optional[List[AnimationEvent]] = None + hasAdditiveReferencePose: Optional[bool] = None + heightFromFeet: Optional[bool] = None + internalID: Optional[int] = None + keepAdditionalBonesAnimation: Optional[bool] = None + keepOriginalOrientation: Optional[bool] = None + keepOriginalPositionXZ: Optional[bool] = None + keepOriginalPositionY: Optional[bool] = None + level: Optional[float] = None + loopBlend: Optional[bool] = None + loopBlendOrientation: Optional[bool] = None + loopBlendPositionXZ: Optional[bool] = None + loopBlendPositionY: Optional[bool] = None + loopTime: Optional[bool] = None + maskSource: Optional[PPtr[AvatarMask]] = None + maskType: Optional[int] = None + mirror: Optional[bool] = None + orientationOffsetY: Optional[float] = None + skeletonMaskElements: Optional[List[AvatarSkeletonMaskElement]] = None + takeName: Optional[str] = None + transformMask: Optional[List[TransformMaskElement]] = None + + +@unitypy_define +class ClipAnimationInfoCurve: + curve: AnimationCurve + name: str + + +@unitypy_define +class ClipMuscleConstant: + m_AverageAngularSpeed: float + m_AverageSpeed: Union[float3, float4] + m_Clip: OffsetPtr + m_CycleOffset: float + m_DeltaPose: HumanPose + m_HeightFromFeet: bool + m_IndexArray: List[int] + m_KeepOriginalOrientation: bool + m_KeepOriginalPositionXZ: bool + m_KeepOriginalPositionY: bool + m_LeftFootStartX: xform + m_Level: float + m_LoopBlend: bool + m_LoopBlendOrientation: bool + m_LoopBlendPositionXZ: bool + m_LoopBlendPositionY: bool + m_Mirror: bool + m_OrientationOffsetY: float + m_RightFootStartX: xform + m_StartTime: float + m_StartX: xform + m_StopTime: float + m_ValueArrayDelta: List[ValueDelta] + m_AdditionalCurveIndexArray: Optional[List[int]] = None + m_LoopTime: Optional[bool] = None + m_MotionStartX: Optional[xform] = None + m_MotionStopX: Optional[xform] = None + m_StartAtOrigin: Optional[bool] = None + m_StopX: Optional[xform] = None + m_ValueArrayReferencePose: Optional[List[float]] = None + + +@unitypy_define +class ClothAttachment: + m_Collider: PPtr[Collider] + m_Tearable: bool + m_TwoWayInteraction: bool + + +@unitypy_define +class ClothConstrainCoefficients: + collisionSphereDistance: float + maxDistance: float + collisionSphereRadius: Optional[float] = None + maxDistanceBias: Optional[float] = None + + +@unitypy_define +class ClothSphereColliderPair: + first: PPtr[SphereCollider] + second: PPtr[SphereCollider] + + +@unitypy_define +class ClusterInput: + m_DeviceName: str + m_Index: int + m_Name: str + m_ServerUrl: str + m_Type: int + + +@unitypy_define +class CollabEditorSettings: + inProgressEnabled: bool + + +@unitypy_define +class Collision(ABC): + pass + + +@unitypy_define +class Collision2D(ABC): + pass + + +@unitypy_define +class CollisionModule: + enabled: bool + minKillSpeed: float + type: int + bounce: Optional[float] = None + colliderForce: Optional[float] = None + collidesWith: Optional[BitField] = None + collidesWithDynamic: Optional[bool] = None + collisionMessages: Optional[bool] = None + collisionMode: Optional[int] = None + dampen: Optional[float] = None + energyLossOnCollision: Optional[float] = None + interiorCollisions: Optional[bool] = None + m_Bounce: Optional[MinMaxCurve] = None + m_Dampen: Optional[MinMaxCurve] = None + m_EnergyLossOnCollision: Optional[MinMaxCurve] = None + m_Planes: Optional[List[PPtr[Transform]]] = None + maxCollisionShapes: Optional[int] = None + maxKillSpeed: Optional[float] = None + multiplyColliderForceByCollisionAngle: Optional[bool] = None + multiplyColliderForceByParticleSize: Optional[bool] = None + multiplyColliderForceByParticleSpeed: Optional[bool] = None + particleRadius: Optional[float] = None + plane0: Optional[PPtr[Transform]] = None + plane1: Optional[PPtr[Transform]] = None + plane2: Optional[PPtr[Transform]] = None + plane3: Optional[PPtr[Transform]] = None + plane4: Optional[PPtr[Transform]] = None + plane5: Optional[PPtr[Transform]] = None + quality: Optional[int] = None + radiusScale: Optional[float] = None + voxelSize: Optional[float] = None + + +@unitypy_define +class ColorBySpeedModule: + enabled: bool + gradient: MinMaxGradient + range: Vector2f + + +@unitypy_define +class ColorModule: + enabled: bool + gradient: MinMaxGradient + + +@unitypy_define +class ComponentPair: + component: PPtr[Component] + + +@unitypy_define +class CompressedAnimationCurve: + m_Path: str + m_PostInfinity: int + m_PreInfinity: int + m_Slopes: PackedBitVector + m_Times: PackedBitVector + m_Values: PackedBitVector + + +@unitypy_define +class CompressedMesh: + m_BoneIndices: PackedBitVector + m_NormalSigns: PackedBitVector + m_Normals: PackedBitVector + m_TangentSigns: PackedBitVector + m_Tangents: PackedBitVector + m_Triangles: PackedBitVector + m_UV: PackedBitVector + m_Vertices: PackedBitVector + m_Weights: PackedBitVector + m_BindPoses: Optional[PackedBitVector] = None + m_Colors: Optional[PackedBitVector] = None + m_FloatColors: Optional[PackedBitVector] = None + m_UVInfo: Optional[int] = None + + +@unitypy_define +class ComputeBufferCounter: + bindpoint: int + offset: int + + +@unitypy_define +class ComputeShaderBuiltinSampler: + bindPoint: int + sampler: int + + +@unitypy_define +class ComputeShaderCB: + byteSize: int + name: Union[FastPropertyName, str] + params: List[ComputeShaderParam] + + +@unitypy_define +class ComputeShaderKernel: + builtinSamplers: Union[List[ComputeShaderBuiltinSampler], List[SamplerParameter]] + cbs: List[ComputeShaderResource] + code: List[int] + inBuffers: List[ComputeShaderResource] + outBuffers: List[ComputeShaderResource] + textures: List[ComputeShaderResource] + cbVariantIndices: Optional[List[int]] = None + name: Optional[Union[FastPropertyName, str]] = None + requirements: Optional[int] = None + threadGroupSize: Optional[List[int]] = None + + +@unitypy_define +class ComputeShaderKernelParent: + name: str + dynamicKeywords: Optional[List[str]] = None + globalKeywords: Optional[List[str]] = None + localKeywords: Optional[List[str]] = None + uniqueVariants: Optional[List[ComputeShaderKernel]] = None + validKeywords: Optional[List[str]] = None + variantIndices: Optional[List[Tuple[str, int]]] = None + variantMap: Optional[List[Tuple[str, ComputeShaderKernel]]] = None + + +@unitypy_define +class ComputeShaderParam: + arraySize: int + colCount: int + name: Union[FastPropertyName, str] + offset: int + rowCount: int + type: int + + +@unitypy_define +class ComputeShaderPlatformVariant: + constantBuffers: List[ComputeShaderCB] + kernels: List[ComputeShaderKernelParent] + resourcesResolved: bool + targetLevel: int + targetRenderer: int + + +@unitypy_define +class ComputeShaderResource: + name: Union[FastPropertyName, str] + bindPoint: Optional[int] = None + counter: Optional[ComputeBufferCounter] = None + generatedName: Optional[Union[FastPropertyName, str]] = None + m_Binding: Optional[Binding] = None + m_SamplerBinding: Optional[Binding] = None + resType: Optional[int] = None + samplerBindPoint: Optional[int] = None + secondaryBindPoint: Optional[int] = None + texDimension: Optional[int] = None + + +@unitypy_define +class ComputeShaderVariant: + constantBuffers: List[ComputeShaderCB] + kernels: List[ComputeShaderKernel] + targetLevel: int + targetRenderer: int + resourcesResolved: Optional[bool] = None + + +@unitypy_define +class Condition: + m_ConditionEvent: str + m_ConditionMode: int + m_EventTreshold: float + m_ExitTime: float + + +@unitypy_define +class ConditionConstant: + m_ConditionMode: int + m_EventID: int + m_EventThreshold: float + m_ExitTime: float + + +@unitypy_define +class ConfigSetting: + flags: int + value: str + + +@unitypy_define +class ConstantBuffer: + m_MatrixParams: List[MatrixParameter] + m_NameIndex: int + m_Size: int + m_VectorParams: List[VectorParameter] + m_IsPartialCB: Optional[bool] = None + m_StructParams: Optional[List[StructParameter]] = None + + +@unitypy_define +class ConstantBufferParameter: + m_IsPartialCB: bool + m_MatrixParams: List[MatrixParameter] + m_NameIndex: int + m_Size: int + m_StructParams: List[StructParameter] + m_VectorParams: List[VectorParameter] + + +@unitypy_define +class ConstantClip: + data: List[float] + + +@unitypy_define +class ConstraintSource: + sourceTransform: PPtr[Transform] + weight: float + + +@unitypy_define +class ControllerConstant: + m_DefaultValues: OffsetPtr + m_StateMachineArray: List[OffsetPtr] + m_Values: OffsetPtr + m_HumanLayerArray: Optional[List[OffsetPtr]] = None + m_LayerArray: Optional[List[OffsetPtr]] = None + + +@unitypy_define +class CrashReportingSettings: + m_EventUrl: str + m_EnableCloudDiagnosticsReporting: Optional[bool] = None + m_Enabled: Optional[bool] = None + m_LogBufferSize: Optional[int] = None + m_NativeEventUrl: Optional[str] = None + + +@unitypy_define +class CustomDataModule: + color0: MinMaxGradient + color1: MinMaxGradient + enabled: bool + mode0: int + mode1: int + vector0_0: MinMaxCurve + vector0_1: MinMaxCurve + vector0_2: MinMaxCurve + vector0_3: MinMaxCurve + vector1_0: MinMaxCurve + vector1_1: MinMaxCurve + vector1_2: MinMaxCurve + vector1_3: MinMaxCurve + vectorComponentCount0: int + vectorComponentCount1: int + + +@unitypy_define +class D3D12DeviceFilterData: + deviceName: str + deviceType: int + driverVersion: str + driverVersionComparator: int + featureLevel: str + featureLevelComparator: int + graphicsMemory: str + graphicsMemoryComparator: int + processorCount: str + processorCountComparator: int + vendorName: str + + +@unitypy_define +class D3D12GraphicsJobsDeviceFilterData: + filter: D3D12DeviceFilterData + preferredMode: int + + +@unitypy_define +class DataTemplate(NamedObject): + m_Father: PPtr[DataTemplate] + m_IsDataTemplate: bool + m_LastMergeIdentifier: GUID + m_Name: str + m_Objects: List[PPtr[EditorExtension]] + + +@unitypy_define +class DateTime: + ticks: int + + +@unitypy_define +class DefaultPreset: + m_Preset: PPtr[Preset] + m_Disabled: Optional[bool] = None + m_Filter: Optional[str] = None + + +@unitypy_define +class DefaultPresetList: + defaultPresets: List[DefaultPreset] + type: PresetType + + +@unitypy_define +class DeletedItem: + changeset: int + digest: Union[Hash128, MdFour] + fullPath: str + guid: GUID + parent: GUID + type: int + + +@unitypy_define +class DenseClip: + m_BeginTime: float + m_CurveCount: int + m_FrameCount: int + m_SampleArray: List[float] + m_SampleRate: float + + +@unitypy_define +class DetailDatabase: + WavingGrassTint: ColorRGBA + m_DetailPrototypes: List[DetailPrototype] + m_PatchCount: int + m_PatchSamples: int + m_Patches: List[DetailPatch] + m_PreloadTextureAtlasData: List[PPtr[Texture2D]] + m_TreeInstances: List[TreeInstance] + m_TreePrototypes: List[TreePrototype] + m_WavingGrassAmount: float + m_WavingGrassSpeed: float + m_WavingGrassStrength: float + m_DefaultShaders_0_: Optional[PPtr[Shader]] = None + m_DefaultShaders_1_: Optional[PPtr[Shader]] = None + m_DefaultShaders_2_: Optional[PPtr[Shader]] = None + m_DetailBillboardShader: Optional[PPtr[Shader]] = None + m_DetailMeshGrassShader: Optional[PPtr[Shader]] = None + m_DetailMeshLitShader: Optional[PPtr[Shader]] = None + m_DetailScatterMode: Optional[int] = None + m_RandomRotations: Optional[List[Vector3f]] = None + + +@unitypy_define +class DetailPatch: + layerIndices: List[int] + bounds: Optional[AABB] = None + coverage: Optional[List[int]] = None + numberOfObjects: Optional[List[int]] = None + + +@unitypy_define +class DetailPrototype: + dryColor: ColorRGBA + healthyColor: ColorRGBA + maxHeight: float + maxWidth: float + minHeight: float + minWidth: float + noiseSpread: float + prototype: PPtr[GameObject] + prototypeTexture: PPtr[Texture2D] + renderMode: int + usePrototypeMesh: int + alignToGround: Optional[float] = None + bendFactor: Optional[float] = None + density: Optional[float] = None + holeTestRadius: Optional[float] = None + lightmapFactor: Optional[float] = None + noiseSeed: Optional[int] = None + positionJitter: Optional[float] = None + positionOrderliness: Optional[float] = None + targetCoverage: Optional[float] = None + useDensityScaling: Optional[int] = None + useInstancing: Optional[int] = None + + +@unitypy_define +class DeviceNone: + pass + + +@unitypy_define +class DirectorGenericBinding: + key: PPtr[Object] + value: PPtr[Object] + + +@unitypy_define +class DirectorPlayer(Behaviour): + m_GameObject: PPtr[GameObject] + + +@unitypy_define +class EffectConstant: + bypass: bool + groupConstantIndex: int + parameterIndices: List[int] + prevEffectIndex: int + sendTargetEffectIndex: int + type: int + wetMixLevelIndex: int + + +@unitypy_define +class EmbeddedNativeType: + m_FloatArray: List[float] + m_String: str + + +@unitypy_define +class EmissionModule: + enabled: bool + m_BurstCount: int + cnt0: Optional[int] = None + cnt1: Optional[int] = None + cnt2: Optional[int] = None + cnt3: Optional[int] = None + cntmax0: Optional[int] = None + cntmax1: Optional[int] = None + cntmax2: Optional[int] = None + cntmax3: Optional[int] = None + m_Bursts: Optional[List[ParticleSystemEmissionBurst]] = None + m_Type: Optional[int] = None + rate: Optional[MinMaxCurve] = None + rateOverDistance: Optional[MinMaxCurve] = None + rateOverTime: Optional[MinMaxCurve] = None + time0: Optional[float] = None + time1: Optional[float] = None + time2: Optional[float] = None + time3: Optional[float] = None + + +@unitypy_define +class EnlightenCAHMap: + m_Map: List[Tuple[LookupKey, Hash128]] + + +@unitypy_define +class EnlightenRendererInformation: + dynamicLightmapSTInSystem: Vector4f + instanceHash: Hash128 + renderer: PPtr[Object] + systemId: int + + +@unitypy_define +class EnlightenSceneMapping: + m_Renderers: List[EnlightenRendererInformation] + m_SystemAtlases: List[EnlightenSystemAtlasInformation] + m_Systems: List[EnlightenSystemInformation] + m_TerrainChunks: List[EnlightenTerrainChunksInformation] + m_CAHMap: Optional[EnlightenCAHMap] = None + m_Probesets: Optional[List[Hash128]] = None + + +@unitypy_define +class EnlightenSystemAtlasInformation: + atlasHash: Hash128 + atlasSize: int + firstSystemId: int + + +@unitypy_define +class EnlightenSystemInformation: + atlasIndex: int + atlasOffsetX: int + atlasOffsetY: int + inputSystemHash: Hash128 + radiositySystemHash: Hash128 + rendererIndex: int + rendererSize: int + + +@unitypy_define +class EnlightenTerrainChunksInformation: + firstSystemId: int + numChunksInX: int + numChunksInY: int + + +@unitypy_define +class EntityId: + pass + + +@unitypy_define +class Error: + filePath: str + message: str + severity: int + startChar: int + startLine: int + + +@unitypy_define +class ErrorLog: + m_HasErrors: bool + m_Messages: List[Message] + + +@unitypy_define +class ExpandedData: + m_ClassID: int + m_ExpandedProperties: List[str] + m_InspectorExpanded: bool + m_ScriptClass: str + + +@unitypy_define +class ExposedReferenceTable: + m_References: List[Tuple[str, PPtr[Object]]] + + +@unitypy_define +class Expression: + data_0_: int + data_1_: int + data_2_: int + data_3_: int + op: int + valueIndex: int + dataSize: Optional[int] = None + dataType: Optional[int] = None + jmpCode: Optional[int] = None + scalarSwitchCase: Optional[int] = None + + +@unitypy_define +class ExtensionPropertyValue: + extensionName: str + pluginName: str + propertyName: str + propertyValue: float + + +@unitypy_define +class ExternalForcesModule: + enabled: bool + influenceFilter: Optional[int] = None + influenceList: Optional[List[PPtr[ParticleSystemForceField]]] = None + influenceMask: Optional[BitField] = None + multiplier: Optional[float] = None + multiplierCurve: Optional[MinMaxCurve] = None + + +@unitypy_define +class FalloffTable: + m_Table_0_: float + m_Table_10_: float + m_Table_11_: float + m_Table_12_: float + m_Table_1_: float + m_Table_2_: float + m_Table_3_: float + m_Table_4_: float + m_Table_5_: float + m_Table_6_: float + m_Table_7_: float + m_Table_8_: float + m_Table_9_: float + + +@unitypy_define +class FastPropertyName: + name: str + + +@unitypy_define +class FlareElement: + m_Color: ColorRGBA + m_Fade: bool + m_ImageIndex: int + m_Position: float + m_Rotate: bool + m_Size: float + m_UseLightColor: bool + m_Zoom: bool + + +@unitypy_define +class FloatCurve: + attribute: str + classID: int + curve: AnimationCurve + path: str + script: PPtr[MonoScript] + flags: Optional[int] = None + + +@unitypy_define +class ForceModule: + enabled: bool + inWorldSpace: bool + randomizePerFrame: bool + x: MinMaxCurve + y: MinMaxCurve + z: MinMaxCurve + + +@unitypy_define +class GISettings: + m_AlbedoBoost: float + m_BounceScale: float + m_EnableBakedLightmaps: bool + m_EnableRealtimeLightmaps: bool + m_EnvironmentLightingMode: int + m_IndirectOutputScale: float + m_TemporalCoherenceThreshold: Optional[float] = None + + +@unitypy_define +class GLTextureSettings: + m_Aniso: int + m_FilterMode: int + m_MipBias: float + m_WrapMode: Optional[int] = None + m_WrapU: Optional[int] = None + m_WrapV: Optional[int] = None + m_WrapW: Optional[int] = None + + +@unitypy_define +class GUID: + data_0_: int + data_1_: int + data_2_: int + data_3_: int + + +@unitypy_define +class GenericBinding: + attribute: int + customType: int + isPPtrCurve: int + path: int + script: PPtr[Object] + classID: Optional[int] = None + isIntCurve: Optional[int] = None + isSerializeReferenceCurve: Optional[int] = None + metaData: Optional[int] = None + typeID: Optional[int] = None + + +@unitypy_define +class GfxBlendState: + alphaToMask: int + rt: List[GfxRenderTargetBlendState] + separateMRTBlend: int + + +@unitypy_define +class GfxDepthState: + depthFunc: int + depthWrite: int + + +@unitypy_define +class GfxRasterState: + conservative: int + cullMode: int + depthBias: int + depthClip: int + slopeScaledDepthBias: float + + +@unitypy_define +class GfxRenderTargetBlendState: + blendOp: int + blendOpAlpha: int + dstBlend: int + dstBlendAlpha: int + srcBlend: int + srcBlendAlpha: int + writeMask: int + + +@unitypy_define +class GfxStencilState: + padding: int + readMask: int + stencilEnable: int + stencilFailOpBack: int + stencilFailOpFront: int + stencilFuncBack: int + stencilFuncFront: int + stencilPassOpBack: int + stencilPassOpFront: int + stencilZFailOpBack: int + stencilZFailOpFront: int + writeMask: int + + +@unitypy_define +class Google: + depthFormat: int + enableTransitionView: Optional[bool] = None + enableVideoLayer: Optional[bool] = None + maximumSupportedHeadTracking: Optional[int] = None + minimumSupportedHeadTracking: Optional[int] = None + useProtectedVideoMemory: Optional[bool] = None + useSustainedPerformanceMode: Optional[bool] = None + + +@unitypy_define +class Gradient: + atime0: Optional[int] = None + atime1: Optional[int] = None + atime2: Optional[int] = None + atime3: Optional[int] = None + atime4: Optional[int] = None + atime5: Optional[int] = None + atime6: Optional[int] = None + atime7: Optional[int] = None + ctime0: Optional[int] = None + ctime1: Optional[int] = None + ctime2: Optional[int] = None + ctime3: Optional[int] = None + ctime4: Optional[int] = None + ctime5: Optional[int] = None + ctime6: Optional[int] = None + ctime7: Optional[int] = None + key0: Optional[ColorRGBA] = None + key1: Optional[ColorRGBA] = None + key2: Optional[ColorRGBA] = None + key3: Optional[ColorRGBA] = None + key4: Optional[ColorRGBA] = None + key5: Optional[ColorRGBA] = None + key6: Optional[ColorRGBA] = None + key7: Optional[ColorRGBA] = None + m_ColorSpace: Optional[int] = None + m_Color_0_: Optional[ColorRGBA] = None + m_Color_1_: Optional[ColorRGBA] = None + m_Color_2_: Optional[ColorRGBA] = None + m_Color_3_: Optional[ColorRGBA] = None + m_Color_4_: Optional[ColorRGBA] = None + m_Mode: Optional[int] = None + m_NumAlphaKeys: Optional[int] = None + m_NumColorKeys: Optional[int] = None + + +@unitypy_define +class GradientNEW: + atime0: int + atime1: int + atime2: int + atime3: int + atime4: int + atime5: int + atime6: int + atime7: int + ctime0: int + ctime1: int + ctime2: int + ctime3: int + ctime4: int + ctime5: int + ctime6: int + ctime7: int + key0: ColorRGBA + key1: ColorRGBA + key2: ColorRGBA + key3: ColorRGBA + key4: ColorRGBA + key5: ColorRGBA + key6: ColorRGBA + key7: ColorRGBA + m_NumAlphaKeys: int + m_NumColorKeys: int + + +@unitypy_define +class GraphicsStateInfo: + appBackface: bool + depthBias: float + forceCullMode: int + invertProjectionMatrix: bool + renderPass: int + renderState: int + slopeDepthBias: float + subPassIndex: int + topology: int + userBackface: bool + vertexLayout: int + wireframe: bool + baseShadingRate: Optional[int] = None + nonfilteringSamplerBindings: Optional[int] = None + shadingRateCombinerFragment: Optional[int] = None + shadingRateCombinerPrimitive: Optional[int] = None + unfilterableTextureBindings: Optional[int] = None + + +@unitypy_define +class GroupConnection: + sendEffectIndex: int + sourceGroupIndex: int + targetGroupIndex: int + + +@unitypy_define +class GroupConstant: + bypassEffects: bool + mute: bool + parentConstantIndex: int + pitchIndex: int + solo: bool + volumeIndex: int + sendIndex: Optional[int] = None + + +@unitypy_define +class Hand: + m_HandBoneIndex: List[int] + + +@unitypy_define +class HandPose: + m_CloseOpen: float + m_DoFArray: List[float] + m_Grab: float + m_GrabX: xform + m_InOut: float + m_Override: float + + +@unitypy_define +class Handle: + m_ID: int + m_ParentHumanIndex: int + m_X: xform + + +@unitypy_define +class Hash128: + bytes_0_: int + bytes_10_: int + bytes_11_: int + bytes_12_: int + bytes_13_: int + bytes_14_: int + bytes_15_: int + bytes_1_: int + bytes_2_: int + bytes_3_: int + bytes_4_: int + bytes_5_: int + bytes_6_: int + bytes_7_: int + bytes_8_: int + bytes_9_: int + + +@unitypy_define +class HeightMeshBVNode: + i: int + max: Vector3f + min: Vector3f + n: int + + +@unitypy_define +class HeightMeshData: + m_Bounds: AABB + m_Indices: List[int] + m_Nodes: List[HeightMeshBVNode] + m_Vertices: List[Vector3f] + + +@unitypy_define +class Heightmap: + m_Heights: List[int] + m_Levels: int + m_MinMaxPatchHeights: List[float] + m_PrecomputedError: List[float] + m_Scale: Vector3f + m_DefaultPhysicMaterial: Optional[PPtr[PhysicMaterial]] = None + m_EnableHolesTextureCompression: Optional[bool] = None + m_EnableSurfaceMaskTextureCompression: Optional[bool] = None + m_Height: Optional[int] = None + m_Holes: Optional[List[int]] = None + m_HolesLOD: Optional[List[int]] = None + m_Resolution: Optional[int] = None + m_SurfaceMask: Optional[List[int]] = None + m_SurfaceMaskLOD: Optional[List[int]] = None + m_Thickness: Optional[float] = None + m_Width: Optional[int] = None + + +@unitypy_define +class HeightmapData: + terrainData: PPtr[Object] + isRotated: Optional[bool] = None + position: Optional[Vector3f] = None + surfaceToTerrain: Optional[Matrix4x4f] = None + + +@unitypy_define +class HierarchicalSceneData: + m_SceneGUID: GUID + + +@unitypy_define +class Hint: + m_Key: str + m_Value: str + + +@unitypy_define +class HoloLens: + depthFormat: int + depthBufferSharingEnabled: Optional[bool] = None + + +@unitypy_define +class Human: + m_ArmStretch: float + m_ArmTwist: float + m_FeetSpacing: float + m_ForeArmTwist: float + m_HasLeftHand: bool + m_HasRightHand: bool + m_HumanBoneIndex: List[int] + m_HumanBoneMass: List[float] + m_LeftHand: OffsetPtr + m_LegStretch: float + m_LegTwist: float + m_RightHand: OffsetPtr + m_RootX: xform + m_Scale: float + m_Skeleton: OffsetPtr + m_SkeletonPose: OffsetPtr + m_UpperLegTwist: float + m_ColliderArray: Optional[List[Collider]] = None + m_ColliderIndex: Optional[List[int]] = None + m_Handles: Optional[List[Handle]] = None + m_HasTDoF: Optional[bool] = None + + +@unitypy_define +class HumanBone: + m_BoneName: str + m_HumanName: str + m_Limit: SkeletonBoneLimit + + +@unitypy_define +class HumanDescription: + m_ArmStretch: float + m_ArmTwist: float + m_FeetSpacing: float + m_ForeArmTwist: float + m_Human: List[HumanBone] + m_LegStretch: float + m_LegTwist: float + m_RootMotionBoneName: str + m_Skeleton: List[SkeletonBone] + m_UpperLegTwist: float + m_GlobalScale: Optional[float] = None + m_Handles: Optional[List[HumanHandle]] = None + m_HasExtraRoot: Optional[bool] = None + m_HasTranslationDoF: Optional[bool] = None + m_RootMotionBoneRotation: Optional[Quaternionf] = None + m_SkeletonHasParents: Optional[bool] = None + + +@unitypy_define +class HumanGoal: + m_WeightR: float + m_WeightT: float + m_X: xform + m_HintT: Optional[Union[float3, float4]] = None + m_HintWeightT: Optional[float] = None + + +@unitypy_define +class HumanHandle: + m_BoneName: str + m_LookAt: bool + m_Name: str + m_Position: Vector3f + m_Rotation: Quaternionf + m_Scale: Vector3f + + +@unitypy_define +class HumanLayerConstant: + m_Binding: int + m_BodyMask: HumanPoseMask + m_IKPass: bool + m_LayerBlendingMode: int + m_SkeletonMask: OffsetPtr + m_StateMachineIndex: int + m_StateMachineMotionSetIndex: int + m_DefaultWeight: Optional[float] = None + m_SyncedLayerAffectsTiming: Optional[bool] = None + + +@unitypy_define +class HumanPose: + m_DoFArray: List[float] + m_GoalArray: List[HumanGoal] + m_LeftHandPose: HandPose + m_LookAtPosition: Union[float3, float4] + m_LookAtWeight: float4 + m_RightHandPose: HandPose + m_RootX: xform + m_TDoFArray: Optional[Union[List[float3], List[float4]]] = None + + +@unitypy_define +class HumanPoseMask: + word0: int + word1: int + word2: Optional[int] = None + + +@unitypy_define +class Image: + image_data: bytes + m_Format: int + m_Height: int + m_RowBytes: int + m_Width: int + + +@unitypy_define +class ImportLog_ImportLogEntry: + file: str + line: int + message: str + mode: int + object: PPtr[Object] + + +@unitypy_define +class InheritVelocityModule: + enabled: bool + m_Curve: MinMaxCurve + m_Mode: int + + +@unitypy_define +class InitialModule: + enabled: bool + gravityModifier: Union[MinMaxCurve, float] + maxNumParticles: int + startColor: MinMaxGradient + startLifetime: MinMaxCurve + startRotation: MinMaxCurve + startSize: MinMaxCurve + startSpeed: MinMaxCurve + customEmitterVelocity: Optional[Vector3f] = None + gravitySource: Optional[int] = None + inheritVelocity: Optional[float] = None + randomizeRotationDirection: Optional[float] = None + rotation3D: Optional[bool] = None + size3D: Optional[bool] = None + startRotationX: Optional[MinMaxCurve] = None + startRotationY: Optional[MinMaxCurve] = None + startSizeY: Optional[MinMaxCurve] = None + startSizeZ: Optional[MinMaxCurve] = None + + +@unitypy_define +class InputAxis: + altNegativeButton: str + altPositiveButton: str + axis: int + dead: float + descriptiveName: str + descriptiveNegativeName: str + gravity: float + invert: bool + joyNum: int + m_Name: str + negativeButton: str + positiveButton: str + sensitivity: float + snap: bool + type: int + + +@unitypy_define +class InputImportSettings: + name: str + alphaSource: Optional[int] = None + aniso: Optional[int] = None + filterMode: Optional[int] = None + value: Optional[SubstanceValue] = None + wrapMode: Optional[int] = None + + +@unitypy_define +class InsightsSettings: + m_Enabled: bool + m_EngineDiagnosticsEnabled: bool + m_EventUrl: str + m_StackTraceUrl: str + + +@unitypy_define +class IntPoint: + X: int + Y: int + + +@unitypy_define +class Item: + changeFlags: Optional[int] = None + changeset: Optional[int] = None + digest: Optional[Union[Hash128, MdFour]] = None + downloadResolution: Optional[int] = None + guid: Optional[GUID] = None + markedForRemoval: Optional[bool] = None + name: Optional[str] = None + nameConflictResolution: Optional[int] = None + oldVersion: Optional[int] = None + origin: Optional[int] = None + parent: Optional[GUID] = None + parentFolderID: Optional[int] = None + type: Optional[int] = None + + +@unitypy_define +class JointAngleLimit2D: + m_LowerAngle: float + m_UpperAngle: float + + +@unitypy_define +class JointAngleLimits2D: + m_LowerAngle: float + m_UpperAngle: float + + +@unitypy_define +class JointDrive: + maximumForce: float + positionDamper: float + positionSpring: float + mode: Optional[int] = None + useAcceleration: Optional[int] = None + + +@unitypy_define +class JointLimits: + max: float + min: float + bounceMinVelocity: Optional[float] = None + bounciness: Optional[float] = None + contactDistance: Optional[float] = None + maxBounce: Optional[float] = None + minBounce: Optional[float] = None + + +@unitypy_define +class JointMotor: + force: float + freeSpin: int + targetVelocity: float + + +@unitypy_define +class JointMotor2D: + m_MaximumMotorForce: float + m_MotorSpeed: float + + +@unitypy_define +class JointSpring: + damper: float + spring: float + targetPosition: float + + +@unitypy_define +class JointSuspension2D: + m_Angle: float + m_DampingRatio: float + m_Frequency: float + + +@unitypy_define +class JointTranslationLimits2D: + m_LowerTranslation: float + m_UpperTranslation: float + + +@unitypy_define +class Keyframe: + inSlope: Union[Quaternionf, Vector3f, float] + outSlope: Union[Quaternionf, Vector3f, float] + time: float + value: Union[Quaternionf, Vector3f, float] + inWeight: Optional[Union[Quaternionf, Vector3f, float]] = None + outWeight: Optional[Union[Quaternionf, Vector3f, float]] = None + weightedMode: Optional[int] = None + + +@unitypy_define +class LOD: + renderers: List[LODRenderer] + screenRelativeHeight: float + fadeMode: Optional[int] = None + fadeTransitionWidth: Optional[float] = None + + +@unitypy_define +class LODRenderer: + renderer: PPtr[Renderer] + + +@unitypy_define +class LayerConstant: + m_Binding: int + m_BodyMask: HumanPoseMask + m_DefaultWeight: float + m_IKPass: bool + m_LayerBlendingMode: int + m_SkeletonMask: OffsetPtr + m_StateMachineIndex: int + m_SyncedLayerAffectsTiming: bool + m_StateMachineMotionSetIndex: Optional[int] = None + m_StateMachineSynchronizedLayerIndex: Optional[int] = None + + +@unitypy_define +class LayoutDataOne: + m_FloatArray: List[float] + + +@unitypy_define +class LayoutDataThree: + m_AnotherFloatArray: List[float] + + +@unitypy_define +class LayoutDataTwo: + m_FloatValue: float + m_IntegerValue: int + + +@unitypy_define +class LeafInfoConstant: + m_IDArray: List[int] + m_IndexOffset: int + + +@unitypy_define +class LibraryRepresentation: + name: str + scriptClassName: str + thumbnail: Image + thumbnailClassID: int + flags: Optional[int] = None + guid: Optional[GUID] = None + localIdentifier: Optional[int] = None + object: Optional[Union[PPtr[EditorExtension], PPtr[Object]]] = None + path: Optional[str] = None + + +@unitypy_define +class LifetimeByEmitterSpeedModule: + enabled: bool + m_Curve: MinMaxCurve + m_Range: Vector2f + + +@unitypy_define +class LightBakingOutput: + probeOcclusionLightIndex: int + isBaked: Optional[bool] = None + lightmapBakeMode: Optional[LightmapBakeMode] = None + lightmappingMask: Optional[int] = None + occlusionMaskChannel: Optional[int] = None + shadowMaskChannel: Optional[int] = None + + +@unitypy_define +class LightProbeData: + m_NonTetrahedralizedProbeSetIndexMap: List[Tuple[Hash128, int]] + m_Positions: List[Vector3f] + m_ProbeSets: List[ProbeSetIndex] + m_Tetrahedralization: ProbeSetTetrahedralization + + +@unitypy_define +class LightProbeOcclusion: + m_Occlusion: List[float] + m_BakedLightIndex: Optional[List[int]] = None + m_OcclusionMaskChannel: Optional[List[int]] = None + m_ProbeOcclusionLightIndex: Optional[List[int]] = None + m_ShadowMaskChannel: Optional[List[int]] = None + + +@unitypy_define +class LightmapBakeMode: + lightmapBakeType: int + mixedLightingMode: int + + +@unitypy_define +class LightmapData: + m_DirLightmap: Optional[PPtr[Texture2D]] = None + m_IndirectLightmap: Optional[PPtr[Texture2D]] = None + m_Lightmap: Optional[PPtr[Texture2D]] = None + m_ShadowMask: Optional[PPtr[Texture2D]] = None + sh_0_: Optional[float] = None + sh_10_: Optional[float] = None + sh_11_: Optional[float] = None + sh_12_: Optional[float] = None + sh_13_: Optional[float] = None + sh_14_: Optional[float] = None + sh_15_: Optional[float] = None + sh_16_: Optional[float] = None + sh_17_: Optional[float] = None + sh_18_: Optional[float] = None + sh_19_: Optional[float] = None + sh_1_: Optional[float] = None + sh_20_: Optional[float] = None + sh_21_: Optional[float] = None + sh_22_: Optional[float] = None + sh_23_: Optional[float] = None + sh_24_: Optional[float] = None + sh_25_: Optional[float] = None + sh_26_: Optional[float] = None + sh_2_: Optional[float] = None + sh_3_: Optional[float] = None + sh_4_: Optional[float] = None + sh_5_: Optional[float] = None + sh_6_: Optional[float] = None + sh_7_: Optional[float] = None + sh_8_: Optional[float] = None + sh_9_: Optional[float] = None + + +@unitypy_define +class LightmapSnapshot(NamedObject): + m_BakedReflectionProbeCubemaps: List[PPtr[Texture]] + m_BakedReflectionProbes: List[SceneObjectIdentifier] + m_EnlightenData: List[int] + m_EnlightenSceneMapping: EnlightenSceneMapping + m_EnlightenSceneMappingRendererIDs: List[SceneObjectIdentifier] + m_LightProbes: PPtr[LightProbes] + m_LightmappedRendererData: List[RendererData] + m_LightmappedRendererDataIDs: List[SceneObjectIdentifier] + m_Lightmaps: List[LightmapData] + m_Lights: List[SceneObjectIdentifier] + m_Name: str + m_BakedAmbientProbeInGamma: Optional[SphericalHarmonicsL2] = None + m_BakedAmbientProbeInLinear: Optional[SphericalHarmonicsL2] = None + m_BakedAmbientProbesInGamma: Optional[List[SphericalHarmonicsL2]] = None + m_BakedAmbientProbesInLinear: Optional[List[SphericalHarmonicsL2]] = None + m_BakedSkyboxProbeCubemaps: Optional[List[PPtr[Texture]]] = None + m_SceneGUID: Optional[GUID] = None + + +@unitypy_define +class LightsModule: + color: bool + enabled: bool + intensity: bool + intensityCurve: MinMaxCurve + light: PPtr[Light] + maxLights: int + randomDistribution: bool + range: bool + rangeCurve: MinMaxCurve + ratio: float + + +@unitypy_define +class Limit: + m_Max: Union[float3, float4] + m_Min: Union[float3, float4] + + +@unitypy_define +class LineParameters: + alignment: Optional[int] = None + colorGradient: Optional[Gradient] = None + endWidth: Optional[float] = None + generateLightingData: Optional[bool] = None + m_EndColor: Optional[ColorRGBA] = None + m_StartColor: Optional[ColorRGBA] = None + numCapVertices: Optional[int] = None + numCornerVertices: Optional[int] = None + shadowBias: Optional[float] = None + startWidth: Optional[float] = None + textureMode: Optional[int] = None + textureScale: Optional[Vector2f] = None + widthCurve: Optional[AnimationCurve] = None + widthMultiplier: Optional[float] = None + + +@unitypy_define +class LodSelectionCurve: + m_LodBias: float + m_LodSlope: float + + +@unitypy_define +class LookupKey: + extension: str + hash: Hash128 + + +@unitypy_define +class Lumin: + depthFormat: int + enableGLCache: bool + frameTiming: int + glCacheMaxBlobSize: int + glCacheMaxFileSize: int + + +@unitypy_define +class MaterialImportOutput: + baked: int + currentSettings: BuildTargetSettings + + +@unitypy_define +class MaterialInstanceSettings: + buildTargetSettings: List[BuildTargetSettings] + inputs: List[InputImportSettings] + materialInformation: ProceduralMaterialInformation + materialProperties: UnityPropertySheet + name: str + prototypeName: str + textureParameters: List[InputImportSettings] + lightmapFlags: Optional[int] = None + renderQueue: Optional[int] = None + shader: Optional[PPtr[Shader]] = None + shaderKeywords: Optional[str] = None + shaderName: Optional[str] = None + textureAssignments: Optional[List[ProceduralTextureAssignment]] = None + + +@unitypy_define +class MatrixParameter: + m_ArraySize: int + m_NameIndex: int + m_RowCount: int + m_Type: int + m_Index: Optional[int] = None + m_OffsetInConstantBuffer: Optional[int] = None + + +@unitypy_define +class MdFour: + md4_hash: bytes + + +@unitypy_define +class MeshBlendShape: + firstVertex: int + hasNormals: bool + hasTangents: bool + vertexCount: int + aabbMaxDelta: Optional[Vector3f] = None + aabbMinDelta: Optional[Vector3f] = None + name: Optional[str] = None + + +@unitypy_define +class MeshBlendShapeChannel: + frameCount: int + frameIndex: int + name: str + nameHash: int + + +@unitypy_define +class MeshBlendShapeVertex: + index: int + normal: Vector3f + tangent: Vector3f + vertex: Vector3f + + +@unitypy_define +class MeshLodInfo: + m_LodSelectionCurve: LodSelectionCurve + m_NumLevels: int + m_SubMeshes: List[MeshLodSubMesh] + + +@unitypy_define +class MeshLodRange: + m_IndexCount: int + m_IndexStart: int + + +@unitypy_define +class MeshLodSubMesh: + m_Levels: List[MeshLodRange] + + +@unitypy_define +class Message: + m_Code: int + m_Location: SourceLocation + m_Severity: int + m_Text: Optional[str] = None + + +@unitypy_define +class MinMaxAABB: + m_Max: Vector3f + m_Min: Vector3f + + +@unitypy_define +class MinMaxCurve: + maxCurve: AnimationCurve + minCurve: AnimationCurve + minMaxState: int + scalar: float + minScalar: Optional[float] = None + + +@unitypy_define +class MinMaxGradient: + maxColor: ColorRGBA + maxGradient: Union[Gradient, GradientNEW] + minColor: ColorRGBA + minGradient: Union[Gradient, GradientNEW] + minMaxState: int + + +@unitypy_define +class MipmapLimitSettings: + limitBias: int + limitBiasMode: int + + +@unitypy_define +class Module: + dependencies: List[str] + name: str + strippable: bool + controlledByBuiltinPackage: Optional[bool] = None + + +@unitypy_define +class MonoAssemblyImporter(AssetImporter): + m_ExecutionOrder: List[Tuple[str, int]] + m_IconMap: List[Tuple[str, PPtr[Texture2D]]] + m_Name: str + m_FileIDToRecycleName: Optional[List[Tuple[int, str]]] = None + m_NewHashIdentity: Optional[MdFour] = None + m_OldHashIdentity: Optional[MdFour] = None + m_UserData: Optional[str] = None + + +@unitypy_define +class MonoObject(ABC): + pass + + +@unitypy_define +class MotionNeighborList: + m_NeighborArray: List[int] + + +@unitypy_define +class MultiModeParameter: + mode: int + speed: MinMaxCurve + spread: float + value: Optional[float] = None + + +@unitypy_define +class NameToObjectMap: + m_ObjectToName: List[Tuple[PPtr[Shader], str]] + + +@unitypy_define +class NativeType: + a: int + b: float + embedded: EmbeddedNativeType + + +@unitypy_define +class NavMesh(NamedObject): + m_Heightmaps: List[HeightmapData] + m_MeshData: List[int] + m_Name: str + + +@unitypy_define +class NavMeshAreaData: + cost: float + name: str + + +@unitypy_define +class NavMeshAreas(GlobalGameManager): + areas: List[NavMeshAreaData] + + +@unitypy_define +class NavMeshBuildDebugSettings: + m_Flags: int + + +@unitypy_define +class NavMeshBuildSettings: + agentClimb: float + agentHeight: float + agentRadius: float + agentSlope: float + agentTypeID: int + cellSize: float + ledgeDropHeight: float + manualCellSize: Union[bool, int] + manualTileSize: Union[bool, int] + maxJumpAcrossDistance: float + minRegionArea: float + tileSize: int + accuratePlacement: Optional[Union[bool, int]] = None + buildHeightMesh: Optional[int] = None + debug: Optional[NavMeshBuildDebugSettings] = None + keepTiles: Optional[int] = None + maxJobWorkers: Optional[int] = None + preserveTilesOutsideBounds: Optional[int] = None + + +@unitypy_define +class NavMeshLayerData: + cost: float + editType: int + name: str + + +@unitypy_define +class NavMeshLayers(GlobalGameManager): + Built_in_Layer_0: NavMeshLayerData + Built_in_Layer_1: NavMeshLayerData + Built_in_Layer_2: NavMeshLayerData + User_Layer_0: NavMeshLayerData + User_Layer_1: NavMeshLayerData + User_Layer_10: NavMeshLayerData + User_Layer_11: NavMeshLayerData + User_Layer_12: NavMeshLayerData + User_Layer_13: NavMeshLayerData + User_Layer_14: NavMeshLayerData + User_Layer_15: NavMeshLayerData + User_Layer_16: NavMeshLayerData + User_Layer_17: NavMeshLayerData + User_Layer_18: NavMeshLayerData + User_Layer_19: NavMeshLayerData + User_Layer_2: NavMeshLayerData + User_Layer_20: NavMeshLayerData + User_Layer_21: NavMeshLayerData + User_Layer_22: NavMeshLayerData + User_Layer_23: NavMeshLayerData + User_Layer_24: NavMeshLayerData + User_Layer_25: NavMeshLayerData + User_Layer_26: NavMeshLayerData + User_Layer_27: NavMeshLayerData + User_Layer_28: NavMeshLayerData + User_Layer_3: NavMeshLayerData + User_Layer_4: NavMeshLayerData + User_Layer_5: NavMeshLayerData + User_Layer_6: NavMeshLayerData + User_Layer_7: NavMeshLayerData + User_Layer_8: NavMeshLayerData + User_Layer_9: NavMeshLayerData + + +@unitypy_define +class NavMeshParams: + cellSize: float + tileSize: float + walkableClimb: float + walkableHeight: float + walkableRadius: float + + +@unitypy_define +class NavMeshTileData: + m_MeshData: List[int] + m_Hash: Optional[Hash128] = None + + +@unitypy_define +class NetworkViewID: + m_ID: int + m_Type: int + + +@unitypy_define +class Node: + m_AxesId: int + m_ParentId: int + + +@unitypy_define +class NoiseModule: + damping: bool + enabled: bool + frequency: float + octaveMultiplier: float + octaveScale: float + octaves: int + quality: int + remap: MinMaxCurve + remapEnabled: bool + remapY: MinMaxCurve + remapZ: MinMaxCurve + scrollSpeed: MinMaxCurve + separateAxes: bool + strength: MinMaxCurve + strengthY: MinMaxCurve + strengthZ: MinMaxCurve + positionAmount: Optional[MinMaxCurve] = None + rotationAmount: Optional[MinMaxCurve] = None + sizeAmount: Optional[MinMaxCurve] = None + + +@unitypy_define +class NonAlignedStruct: + m_Bool: bool + + +@unitypy_define +class ObjectRolePair: + m_Object: PPtr[Object] + m_RolesMask: int + + +@unitypy_define +class OcclusionScene: + indexPortals: int + indexRenderers: int + scene: GUID + sizePortals: int + sizeRenderers: int + + +@unitypy_define +class Oculus: + dashSupport: bool + sharedDepthBuffer: bool + lowOverheadMode: Optional[bool] = None + protectedContext: Optional[bool] = None + v2Signing: Optional[bool] = None + + +@unitypy_define +class OffsetPtr: + data: Union[Blend1dDataConstant, Blend2dDataConstant, BlendDirectDataConstant, BlendTreeConstant, BlendTreeNodeConstant, Clip, ConditionConstant, Hand, Human, HumanLayerConstant, LayerConstant, SelectorStateConstant, SelectorTransitionConstant, Skeleton, SkeletonMask, SkeletonPose, StateConstant, StateMachineConstant, TransitionConstant, ValueArray, ValueArrayConstant] + + +@unitypy_define +class Output: + hasEmptyFontData: Optional[bool] = None + importedType: Optional[int] = None + previewData: Optional[List[float]] = None + + +@unitypy_define +class PPtrCurve: + attribute: str + classID: int + curve: List[PPtrKeyframe] + path: str + script: PPtr[MonoScript] + flags: Optional[int] = None + + +@unitypy_define +class PPtrKeyframe: + time: float + value: PPtr[Object] + + +@unitypy_define +class PackedBitVector: + m_Data: List[int] + m_NumItems: int + m_BitSize: Optional[int] = None + m_Range: Optional[float] = None + m_Start: Optional[float] = None + + +@unitypy_define +class PackingSettings: + allowAlphaSplitting: bool + blockOffset: int + enableRotation: bool + enableTightPacking: bool + padding: int + enableAlphaDilation: Optional[bool] = None + + +@unitypy_define +class Parameter: + m_GUID: GUID + m_ParameterName: str + + +@unitypy_define +class ParserBindChannels: + m_Channels: List[ShaderBindChannel] + m_SourceMap: int + + +@unitypy_define +class ParticleSystemEmissionBurst: + cycleCount: int + repeatInterval: float + time: float + countCurve: Optional[MinMaxCurve] = None + maxCount: Optional[int] = None + minCount: Optional[int] = None + probability: Optional[float] = None + + +@unitypy_define +class ParticleSystemForceFieldParameters: + m_DirectionCurveX: MinMaxCurve + m_DirectionCurveY: MinMaxCurve + m_DirectionCurveZ: MinMaxCurve + m_DragCurve: MinMaxCurve + m_EndRange: float + m_GravityCurve: MinMaxCurve + m_GravityFocus: float + m_Length: float + m_MultiplyDragByParticleSize: bool + m_MultiplyDragByParticleVelocity: bool + m_RotationAttractionCurve: MinMaxCurve + m_RotationRandomness: Vector2f + m_RotationSpeedCurve: MinMaxCurve + m_Shape: int + m_StartRange: float + m_VectorField: PPtr[Texture3D] + m_VectorFieldAttractionCurve: MinMaxCurve + m_VectorFieldSpeedCurve: MinMaxCurve + + +@unitypy_define +class PerLODSettings: + castShadows: bool + enableBump: bool + enableHue: bool + height: float + receiveShadows: bool + reflectionProbeUsage: int + useLightProbes: bool + windQuality: int + enableSettingOverride: Optional[bool] = None + enableSubsurface: Optional[bool] = None + + +@unitypy_define +class PerformanceReportingSettings: + m_Enabled: bool + + +@unitypy_define +class PhysicMaterial(NamedObject): + bounceCombine: int + bounciness: float + dynamicFriction: float + frictionCombine: int + m_Name: str + staticFriction: float + dynamicFriction2: Optional[float] = None + frictionDirection2: Optional[Vector3f] = None + staticFriction2: Optional[float] = None + + +@unitypy_define +class PhysicsJobOptions2D: + m_ClearBodyForcesPerJob: int + m_ClearFlagsPerJob: int + m_CollideContactsPerJob: int + m_FindNearestContactsPerJob: int + m_InterpolationPosesPerJob: int + m_IslandSolverBodiesPerJob: int + m_IslandSolverBodyCostScale: int + m_IslandSolverContactCostScale: int + m_IslandSolverContactsPerJob: int + m_IslandSolverCostThreshold: int + m_IslandSolverJointCostScale: int + m_NewContactsPerJob: int + m_SyncContinuousFixturesPerJob: int + m_SyncDiscreteFixturesPerJob: int + m_UpdateTriggerContactsPerJob: int + m_UseConsistencySorting: Optional[bool] = None + m_UseMultithreading: Optional[bool] = None + useConsistencySorting: Optional[bool] = None + useMultithreading: Optional[bool] = None + + +@unitypy_define +class PhysicsShape: + m_AdjacentEnd: Vector2f + m_AdjacentStart: Vector2f + m_Radius: float + m_ShapeType: int + m_UseAdjacentEnd: int + m_UseAdjacentStart: int + m_VertexCount: int + m_VertexStartIndex: int + + +@unitypy_define +class PhysicsShapeGroup2D: + m_Shapes: List[PhysicsShape] + m_Vertices: List[Vector2f] + + +@unitypy_define +class PlatformSettings: + m_AllowsAlphaSplitting: bool + m_BuildTarget: str + m_CompressionQuality: int + m_CrunchedCompression: bool + m_MaxTextureSize: int + m_Overridden: bool + m_TextureCompression: int + m_TextureFormat: int + m_ResizeAlgorithm: Optional[int] = None + + +@unitypy_define +class PlatformSettingsData: + settings: List[Tuple[str, str]] + enabled: Optional[bool] = None + + +@unitypy_define +class PlatformShaderDefines: + defines_Tier1: List[int] + defines_Tier2: List[int] + defines_Tier3: List[int] + shaderPlatform: int + + +@unitypy_define +class PlatformShaderSettings: + useCascadedShadowMaps: Optional[bool] = None + useScreenSpaceShadows: Optional[bool] = None + + +@unitypy_define +class PluginImportOutput: + dllType: Optional[int] = None + pluginType: Optional[int] = None + scriptingRuntimeVersion: Optional[int] = None + + +@unitypy_define +class Polygon2D(ABC): + m_Paths: Optional[List[List[Vector2f]]] = None + + +@unitypy_define +class PrefabModification: + m_Modifications: List[PropertyModification] + m_RemovedComponents: Union[List[PPtr[Component]], List[PPtr[Object]]] + m_TransformParent: PPtr[Transform] + m_AddedComponents: Optional[List[AddedComponent]] = None + m_AddedGameObjects: Optional[List[AddedGameObject]] = None + m_RemovedGameObjects: Optional[List[PPtr[GameObject]]] = None + + +@unitypy_define +class PresetType: + m_ManagedTypeFallback: str + m_ManagedTypePPtr: PPtr[MonoScript] + m_NativeTypeID: int + + +@unitypy_define +class PreviewData: + m_CompSize: int + m_OrigSize: int + m_PreviewData: List[float] + + +@unitypy_define +class ProbeSetIndex: + m_Hash: Hash128 + m_Offset: int + m_Size: int + + +@unitypy_define +class ProbeSetTetrahedralization: + m_HullRays: List[Vector3f] + m_Tetrahedra: List[Tetrahedron] + + +@unitypy_define +class ProceduralMaterialInformation: + m_Offset: Vector2f + m_Scale: Vector2f + m_AnimationUpdateRate: Optional[int] = None + m_GenerateAllOutputs: Optional[int] = None + m_GenerateMipmaps: Optional[bool] = None + m_GeneratedAtLoading: Optional[int] = None + + +@unitypy_define +class ProceduralTextureAssignment: + baseUID: int + material: PPtr[ProceduralMaterial] + shaderProp: Union[FastPropertyName, str] + + +@unitypy_define +class ProgramParameters: + m_BufferParams: List[BufferBindingParameter] + m_ConstantBufferBindings: List[BufferBindingParameter] + m_ConstantBuffers: List[ConstantBufferParameter] + m_Samplers: List[SamplerParameter] + m_TextureParams: List[TextureParameter] + m_UAVParams: List[UAVParameter] + m_MatrixParams: Optional[List[MatrixParameter]] = None + m_SpecializationConstantParams: Optional[List[SpecializationConstantParameter]] = None + m_VectorParams: Optional[List[VectorParameter]] = None + + +@unitypy_define +class PropertyModification: + objectReference: PPtr[Object] + propertyPath: str + target: PPtr[Object] + value: str + + +@unitypy_define +class PropertyModificationsTargetTestNativeObject: + m_FloatValue: float + m_IntegerValue: int + + +@unitypy_define +class QualitySetting: + anisotropicTextures: int + antiAliasing: int + pixelLightCount: int + shadowCascades: int + shadowDistance: float + shadowProjection: int + shadowResolution: int + shadows: int + softParticles: bool + softVegetation: bool + vSyncCount: int + adaptiveVsync: Optional[bool] = None + adaptiveVsyncExtraA: Optional[int] = None + adaptiveVsyncExtraB: Optional[int] = None + asyncUploadBufferSize: Optional[int] = None + asyncUploadPersistentBuffer: Optional[bool] = None + asyncUploadTimeSlice: Optional[int] = None + billboardsFaceCameraPosition: Optional[bool] = None + blendWeights: Optional[int] = None + customRenderPipeline: Optional[PPtr[MonoBehaviour]] = None + enableLODCrossFade: Optional[bool] = None + globalTextureMipmapLimit: Optional[int] = None + lodBias: Optional[float] = None + maximumLODLevel: Optional[int] = None + meshLodThreshold: Optional[float] = None + name: Optional[str] = None + particleRaycastBudget: Optional[int] = None + realtimeGICPUUsage: Optional[int] = None + realtimeReflectionProbes: Optional[bool] = None + resolutionScalingFixedDPIFactor: Optional[float] = None + shadowCascade2Split: Optional[float] = None + shadowCascade4Split: Optional[Vector3f] = None + shadowNearPlaneOffset: Optional[float] = None + shadowmaskMode: Optional[int] = None + skinWeights: Optional[int] = None + streamingMipmapsActive: Optional[bool] = None + streamingMipmapsAddAllCameras: Optional[bool] = None + streamingMipmapsMaxFileIORequests: Optional[int] = None + streamingMipmapsMaxLevelReduction: Optional[int] = None + streamingMipmapsMemoryBudget: Optional[float] = None + streamingMipmapsRenderersPerFrame: Optional[int] = None + terrainBasemapDistance: Optional[float] = None + terrainBillboardStart: Optional[float] = None + terrainDetailDensityScale: Optional[float] = None + terrainDetailDistance: Optional[float] = None + terrainFadeLength: Optional[float] = None + terrainMaxTrees: Optional[int] = None + terrainPixelError: Optional[float] = None + terrainQualityOverrides: Optional[int] = None + terrainTreeDistance: Optional[float] = None + textureMipmapLimitSettings: Optional[List[MipmapLimitSettings]] = None + textureQuality: Optional[int] = None + useLegacyDetailDistribution: Optional[bool] = None + + +@unitypy_define +class QuaternionCurve: + curve: AnimationCurve + path: str + + +@unitypy_define +class RationalTime: + m_Count: int + m_Rate: TicksPerSecond + + +@unitypy_define +class RayTracingShaderBuiltinSampler: + bindPoint: int + sampler: int + + +@unitypy_define +class RayTracingShaderConstantBuffer: + byteSize: int + name: str + params: List[RayTracingShaderParam] + hash: Optional[int] = None + + +@unitypy_define +class RayTracingShaderFunctionDesc: + attributeSizeInBytes: int + identifier: RayTracingShaderID + payloadSizeInBytes: int + + +@unitypy_define +class RayTracingShaderID: + name: str + type: int + + +@unitypy_define +class RayTracingShaderParam: + arraySize: int + colCount: int + name: str + offset: int + rowCount: int + dataSize: Optional[int] = None + dataType: Optional[int] = None + propertySheetType: Optional[int] = None + type: Optional[int] = None + + +@unitypy_define +class RayTracingShaderPlatformVariant: + dynamicKeywords: List[str] + editorOnlyVariant: bool + globalKeywords: List[str] + localKeywords: List[str] + targetRenderer: int + uniqueVariants: List[RayTracingShaderReflectionData] + variantIndices: List[Tuple[str, int]] + + +@unitypy_define +class RayTracingShaderReflectionData: + code: List[int] + functions: List[RayTracingShaderFunctionDesc] + globalResources: RayTracingShaderResources + hasErrors: bool + localResources: RayTracingShaderResources + codeHash: Optional[int] = None + precompiled: Optional[List[int]] = None + requirements: Optional[int] = None + + +@unitypy_define +class RayTracingShaderResource: + bindPoint: int + name: str + rayGenMask: int + samplerBindPoint: int + texDimension: int + arraySize: Optional[int] = None + multisampled: Optional[bool] = None + resType: Optional[int] = None + + +@unitypy_define +class RayTracingShaderResources: + builtinSamplers: List[RayTracingShaderBuiltinSampler] + constantBuffers: List[RayTracingShaderResource] + constantBuffersDesc: List[RayTracingShaderConstantBuffer] + inputBuffers: List[RayTracingShaderResource] + outputBuffers: List[RayTracingShaderResource] + textures: List[RayTracingShaderResource] + + +@unitypy_define +class RayTracingShaderVariant: + resourceReflectionData: RayTracingShaderReflectionData + targetRenderer: int + editorOnlyVariant: Optional[bool] = None + + +@unitypy_define +class Rectf: + height: float + width: float + x: float + y: float + + +@unitypy_define +class ReflectedFunction: + m_Hints: List[Hint] + m_Name: str + m_Parameters: List[ReflectedParameter] + m_ReturnTypeName: str + m_Body: Optional[str] = None + m_Namespace: Optional[List[str]] = None + + +@unitypy_define +class ReflectedParameter: + m_Direction: int + m_Hints: List[Hint] + m_Name: str + m_TypeName: str + + +@unitypy_define +class RenderManager(GlobalGameManager): + pass + + +@unitypy_define +class RenderPassInfo: + attachmentCount: int + attachments: List[AttachmentInfo] + depthAttachmentIndex: int + multiviewCount: int + sampleCount: int + shadingRateIndex: int + subPassCount: int + subPasses: List[SubPassDescriptor] + foveationImageIndex: Optional[int] = None + hasEyeTexture: Optional[bool] = None + + +@unitypy_define +class RenderStateBlock: + blendState: GfxBlendState + depthState: GfxDepthState + mask: int + rasterState: GfxRasterState + stencilRef: int + stencilState: GfxStencilState + + +@unitypy_define +class RenderStateInfo: + renderState: RenderStateBlock + + +@unitypy_define +class RendererData: + lightmapIndex: int + lightmapIndexDynamic: int + lightmapST: Vector4f + lightmapSTDynamic: Vector4f + terrainChunkDynamicUVST: Vector4f + terrainDynamicUVST: Vector4f + uvMesh: PPtr[Mesh] + explicitProbeSetHash: Optional[Hash128] = None + + +@unitypy_define +class ResourceManager_Dependency: + m_Dependencies: List[PPtr[Object]] + m_Object: PPtr[Object] + + +@unitypy_define +class RippleGroup: + m_afDirectional_0: float + m_afDirectional_1: float + m_afDirectional_10: float + m_afDirectional_11: float + m_afDirectional_12: float + m_afDirectional_13: float + m_afDirectional_14: float + m_afDirectional_15: float + m_afDirectional_16: float + m_afDirectional_17: float + m_afDirectional_18: float + m_afDirectional_19: float + m_afDirectional_2: float + m_afDirectional_3: float + m_afDirectional_4: float + m_afDirectional_5: float + m_afDirectional_6: float + m_afDirectional_7: float + m_afDirectional_8: float + m_afDirectional_9: float + m_afFlexibility_0: float + m_afFlexibility_1: float + m_afFlexibility_10: float + m_afFlexibility_11: float + m_afFlexibility_12: float + m_afFlexibility_13: float + m_afFlexibility_14: float + m_afFlexibility_15: float + m_afFlexibility_16: float + m_afFlexibility_17: float + m_afFlexibility_18: float + m_afFlexibility_19: float + m_afFlexibility_2: float + m_afFlexibility_3: float + m_afFlexibility_4: float + m_afFlexibility_5: float + m_afFlexibility_6: float + m_afFlexibility_7: float + m_afFlexibility_8: float + m_afFlexibility_9: float + m_afPlanar_0: float + m_afPlanar_1: float + m_afPlanar_10: float + m_afPlanar_11: float + m_afPlanar_12: float + m_afPlanar_13: float + m_afPlanar_14: float + m_afPlanar_15: float + m_afPlanar_16: float + m_afPlanar_17: float + m_afPlanar_18: float + m_afPlanar_19: float + m_afPlanar_2: float + m_afPlanar_3: float + m_afPlanar_4: float + m_afPlanar_5: float + m_afPlanar_6: float + m_afPlanar_7: float + m_afPlanar_8: float + m_afPlanar_9: float + m_afSpeed_0: float + m_afSpeed_1: float + m_afSpeed_10: float + m_afSpeed_11: float + m_afSpeed_12: float + m_afSpeed_13: float + m_afSpeed_14: float + m_afSpeed_15: float + m_afSpeed_16: float + m_afSpeed_17: float + m_afSpeed_18: float + m_afSpeed_19: float + m_afSpeed_2: float + m_afSpeed_3: float + m_afSpeed_4: float + m_afSpeed_5: float + m_afSpeed_6: float + m_afSpeed_7: float + m_afSpeed_8: float + m_afSpeed_9: float + m_fIndependence: float + m_fShimmer: float + + +@unitypy_define +class RootMotionData(ABC): + pass + + +@unitypy_define +class RotationBySpeedModule: + curve: MinMaxCurve + enabled: bool + range: Vector2f + separateAxes: Optional[bool] = None + x: Optional[MinMaxCurve] = None + y: Optional[MinMaxCurve] = None + + +@unitypy_define +class RotationModule: + curve: MinMaxCurve + enabled: bool + separateAxes: Optional[bool] = None + x: Optional[MinMaxCurve] = None + y: Optional[MinMaxCurve] = None + + +@unitypy_define +class SBranchWindLevel: + m_afDirectionAdherence_0: float + m_afDirectionAdherence_1: float + m_afDirectionAdherence_2: float + m_afDirectionAdherence_3: float + m_afDirectionAdherence_4: float + m_afDirectionAdherence_5: float + m_afDirectionAdherence_6: float + m_afDirectionAdherence_7: float + m_afDirectionAdherence_8: float + m_afDirectionAdherence_9: float + m_afDistance_0: float + m_afDistance_1: float + m_afDistance_2: float + m_afDistance_3: float + m_afDistance_4: float + m_afDistance_5: float + m_afDistance_6: float + m_afDistance_7: float + m_afDistance_8: float + m_afDistance_9: float + m_afWhip_0: float + m_afWhip_1: float + m_afWhip_2: float + m_afWhip_3: float + m_afWhip_4: float + m_afWhip_5: float + m_afWhip_6: float + m_afWhip_7: float + m_afWhip_8: float + m_afWhip_9: float + m_fTurbulence: float + m_fTwitch: float + m_fTwitchFreqScale: float + + +@unitypy_define +class SParams: + BranchLevel1: SBranchWindLevel + BranchLevel2: SBranchWindLevel + LeafGroup1: SWindGroup + LeafGroup2: SWindGroup + Oscillation0_0: float + Oscillation0_1: float + Oscillation0_2: float + Oscillation0_3: float + Oscillation0_4: float + Oscillation0_5: float + Oscillation0_6: float + Oscillation0_7: float + Oscillation0_8: float + Oscillation0_9: float + Oscillation1_0: float + Oscillation1_1: float + Oscillation1_2: float + Oscillation1_3: float + Oscillation1_4: float + Oscillation1_5: float + Oscillation1_6: float + Oscillation1_7: float + Oscillation1_8: float + Oscillation1_9: float + Oscillation2_0: float + Oscillation2_1: float + Oscillation2_2: float + Oscillation2_3: float + Oscillation2_4: float + Oscillation2_5: float + Oscillation2_6: float + Oscillation2_7: float + Oscillation2_8: float + Oscillation2_9: float + Oscillation3_0: float + Oscillation3_1: float + Oscillation3_2: float + Oscillation3_3: float + Oscillation3_4: float + Oscillation3_5: float + Oscillation3_6: float + Oscillation3_7: float + Oscillation3_8: float + Oscillation3_9: float + Oscillation4_0: float + Oscillation4_1: float + Oscillation4_2: float + Oscillation4_3: float + Oscillation4_4: float + Oscillation4_5: float + Oscillation4_6: float + Oscillation4_7: float + Oscillation4_8: float + Oscillation4_9: float + Oscillation5_0: float + Oscillation5_1: float + Oscillation5_2: float + Oscillation5_3: float + Oscillation5_4: float + Oscillation5_5: float + Oscillation5_6: float + Oscillation5_7: float + Oscillation5_8: float + Oscillation5_9: float + Oscillation6_0: float + Oscillation6_1: float + Oscillation6_2: float + Oscillation6_3: float + Oscillation6_4: float + Oscillation6_5: float + Oscillation6_6: float + Oscillation6_7: float + Oscillation6_8: float + Oscillation6_9: float + Oscillation7_0: float + Oscillation7_1: float + Oscillation7_2: float + Oscillation7_3: float + Oscillation7_4: float + Oscillation7_5: float + Oscillation7_6: float + Oscillation7_7: float + Oscillation7_8: float + Oscillation7_9: float + Oscillation8_0: float + Oscillation8_1: float + Oscillation8_2: float + Oscillation8_3: float + Oscillation8_4: float + Oscillation8_5: float + Oscillation8_6: float + Oscillation8_7: float + Oscillation8_8: float + Oscillation8_9: float + Oscillation9_0: float + Oscillation9_1: float + Oscillation9_2: float + Oscillation9_3: float + Oscillation9_4: float + Oscillation9_5: float + Oscillation9_6: float + Oscillation9_7: float + Oscillation9_8: float + Oscillation9_9: float + m_afFrondRippleDistance_0: float + m_afFrondRippleDistance_1: float + m_afFrondRippleDistance_2: float + m_afFrondRippleDistance_3: float + m_afFrondRippleDistance_4: float + m_afFrondRippleDistance_5: float + m_afFrondRippleDistance_6: float + m_afFrondRippleDistance_7: float + m_afFrondRippleDistance_8: float + m_afFrondRippleDistance_9: float + m_afGlobalDirectionAdherence_0: float + m_afGlobalDirectionAdherence_1: float + m_afGlobalDirectionAdherence_2: float + m_afGlobalDirectionAdherence_3: float + m_afGlobalDirectionAdherence_4: float + m_afGlobalDirectionAdherence_5: float + m_afGlobalDirectionAdherence_6: float + m_afGlobalDirectionAdherence_7: float + m_afGlobalDirectionAdherence_8: float + m_afGlobalDirectionAdherence_9: float + m_afGlobalDistance_0: float + m_afGlobalDistance_1: float + m_afGlobalDistance_2: float + m_afGlobalDistance_3: float + m_afGlobalDistance_4: float + m_afGlobalDistance_5: float + m_afGlobalDistance_6: float + m_afGlobalDistance_7: float + m_afGlobalDistance_8: float + m_afGlobalDistance_9: float + m_fAnchorDistanceScale: float + m_fAnchorOffset: float + m_fDirectionResponse: float + m_fFrondRippleLightingScalar: float + m_fFrondRippleTile: float + m_fGlobalHeight: float + m_fGlobalHeightExponent: float + m_fGustDurationMax: float + m_fGustDurationMin: float + m_fGustFallScalar: float + m_fGustFrequency: float + m_fGustRiseScalar: float + m_fGustStrengthMax: float + m_fGustStrengthMin: float + m_fRollingBranchFieldMin: float + m_fRollingBranchLightingAdjust: float + m_fRollingBranchVerticalOffset: float + m_fRollingLeafRippleMin: float + m_fRollingLeafTumbleMin: float + m_fRollingNoisePeriod: float + m_fRollingNoiseSize: float + m_fRollingNoiseSpeed: float + m_fRollingNoiseTurbulence: float + m_fRollingNoiseTwist: float + m_fStrengthResponse: float + + +@unitypy_define +class SWindGroup: + m_afRippleDistance_0: float + m_afRippleDistance_1: float + m_afRippleDistance_2: float + m_afRippleDistance_3: float + m_afRippleDistance_4: float + m_afRippleDistance_5: float + m_afRippleDistance_6: float + m_afRippleDistance_7: float + m_afRippleDistance_8: float + m_afRippleDistance_9: float + m_afTumbleDirectionAdherence_0: float + m_afTumbleDirectionAdherence_1: float + m_afTumbleDirectionAdherence_2: float + m_afTumbleDirectionAdherence_3: float + m_afTumbleDirectionAdherence_4: float + m_afTumbleDirectionAdherence_5: float + m_afTumbleDirectionAdherence_6: float + m_afTumbleDirectionAdherence_7: float + m_afTumbleDirectionAdherence_8: float + m_afTumbleDirectionAdherence_9: float + m_afTumbleFlip_0: float + m_afTumbleFlip_1: float + m_afTumbleFlip_2: float + m_afTumbleFlip_3: float + m_afTumbleFlip_4: float + m_afTumbleFlip_5: float + m_afTumbleFlip_6: float + m_afTumbleFlip_7: float + m_afTumbleFlip_8: float + m_afTumbleFlip_9: float + m_afTumbleTwist_0: float + m_afTumbleTwist_1: float + m_afTumbleTwist_2: float + m_afTumbleTwist_3: float + m_afTumbleTwist_4: float + m_afTumbleTwist_5: float + m_afTumbleTwist_6: float + m_afTumbleTwist_7: float + m_afTumbleTwist_8: float + m_afTumbleTwist_9: float + m_afTwitchThrow_0: float + m_afTwitchThrow_1: float + m_afTwitchThrow_2: float + m_afTwitchThrow_3: float + m_afTwitchThrow_4: float + m_afTwitchThrow_5: float + m_afTwitchThrow_6: float + m_afTwitchThrow_7: float + m_afTwitchThrow_8: float + m_afTwitchThrow_9: float + m_fLeewardScalar: float + m_fRollMaxScale: float + m_fRollMinScale: float + m_fRollSeparation: float + m_fRollSpeed: float + m_fTwitchSharpness: float + + +@unitypy_define +class SampleSettings: + compressionFormat: int + conversionMode: int + loadType: int + quality: float + sampleRateOverride: int + sampleRateSetting: int + preloadAudioData: Optional[bool] = None + + +@unitypy_define +class SamplerParameter: + sampler: int + bindPoint: Optional[int] = None + m_Binding: Optional[Binding] = None + + +@unitypy_define +class Scene(LevelGameManager): + enabled: Optional[bool] = None + guid: Optional[GUID] = None + m_PVSData: Optional[List[int]] = None + m_PVSObjectsArray: Optional[List[PPtr[Renderer]]] = None + m_PVSPortalsArray: Optional[List[PPtr[OcclusionPortal]]] = None + m_QueryMode: Optional[int] = None + path: Optional[str] = None + + +@unitypy_define +class SceneDataContainer: + m_SceneData: List[Tuple[SceneIdentifier, HierarchicalSceneData]] + + +@unitypy_define +class SceneIdentifier: + guid: GUID + handle: Union[UnitySceneHandle, int] + + +@unitypy_define +class SceneObjectIdentifier: + targetObject: int + targetPrefab: int + + +@unitypy_define +class SceneSettings(LevelGameManager): + m_PVSData: List[int] + m_PVSObjectsArray: List[PPtr[Renderer]] + m_PVSPortalsArray: List[PPtr[OcclusionPortal]] + m_QueryMode: Optional[int] = None + + +@unitypy_define +class SceneVisibilityData: + m_SceneGUID: GUID + + +@unitypy_define +class ScriptMapper(GlobalGameManager): + m_Shaders: NameToObjectMap + m_PreloadShaders: Optional[bool] = None + + +@unitypy_define +class SecondarySpriteTexture: + name: str + texture: PPtr[Texture2D] + + +@unitypy_define +class SecondaryTextureSettings: + platformSettings: List[TextureImporterPlatformSettings] + sRGB: Optional[bool] = None + + +@unitypy_define +class SelectorStateConstant: + m_FullPathID: int + m_IsEntry: bool + m_TransitionConstantArray: List[OffsetPtr] + + +@unitypy_define +class SelectorTransitionConstant: + m_ConditionConstantArray: List[OffsetPtr] + m_Destination: int + + +@unitypy_define +class SerializedCustomEditorForRenderPipeline: + customEditorName: str + renderPipelineType: str + + +@unitypy_define +class SerializedPass: + m_HasInstancingVariant: bool + m_Name: str + m_NameIndices: List[Tuple[str, int]] + m_ProgramMask: int + m_State: SerializedShaderState + m_Tags: SerializedTagMap + m_TextureName: str + m_Type: int + m_UseName: str + progDomain: SerializedProgram + progFragment: SerializedProgram + progGeometry: SerializedProgram + progHull: SerializedProgram + progVertex: SerializedProgram + m_EditorDataHash: Optional[List[Hash128]] = None + m_GlobalKeywordMask: Optional[List[int]] = None + m_HasProceduralInstancingVariant: Optional[bool] = None + m_LocalKeywordMask: Optional[List[int]] = None + m_Platforms: Optional[List[int]] = None + m_SerializedDynamicBranchKeywordMask: Optional[List[int]] = None + m_SerializedKeywordStateMask: Optional[List[int]] = None + progRayTracing: Optional[SerializedProgram] = None + + +@unitypy_define +class SerializedPlayerSubProgram: + m_BlobIndex: int + m_GpuProgramType: int + m_KeywordIndices: List[int] + m_ShaderRequirements: int + + +@unitypy_define +class SerializedProgram: + m_SubPrograms: List[SerializedSubProgram] + m_CommonParameters: Optional[Union[ProgramParameters, SerializedProgramParameters]] = None + m_ParameterBlobIndices: Optional[List[List[int]]] = None + m_PlayerSubPrograms: Optional[List[List[SerializedPlayerSubProgram]]] = None + m_SerializedKeywordStateMask: Optional[List[int]] = None + + +@unitypy_define +class SerializedProgramParameters: + m_BufferParams: List[BufferBinding] + m_ConstantBufferBindings: List[BufferBinding] + m_ConstantBuffers: List[ConstantBuffer] + m_MatrixParams: List[MatrixParameter] + m_Samplers: List[SamplerParameter] + m_TextureParams: List[TextureParameter] + m_UAVParams: List[UAVParameter] + m_VectorParams: List[VectorParameter] + + +@unitypy_define +class SerializedProperties: + m_Props: List[SerializedProperty] + + +@unitypy_define +class SerializedProperty: + m_Attributes: List[str] + m_DefTexture: SerializedTextureProperty + m_DefValue_0_: float + m_DefValue_1_: float + m_DefValue_2_: float + m_DefValue_3_: float + m_Description: str + m_Flags: int + m_Name: str + m_Type: int + + +@unitypy_define +class SerializedShader: + m_CustomEditorName: str + m_Dependencies: List[SerializedShaderDependency] + m_DisableNoSubshadersMessage: bool + m_FallbackName: str + m_Name: str + m_PropInfo: SerializedProperties + m_SubShaders: List[SerializedSubShader] + m_CustomEditorForRenderPipelines: Optional[List[SerializedCustomEditorForRenderPipeline]] = None + m_KeywordFlags: Optional[List[int]] = None + m_KeywordNames: Optional[List[str]] = None + + +@unitypy_define +class SerializedShaderDependency: + from_: str + to: str + + +@unitypy_define +class SerializedShaderFloatValue: + name: Union[FastPropertyName, str] + val: float + + +@unitypy_define +class SerializedShaderRTBlendState: + blendOp: SerializedShaderFloatValue + blendOpAlpha: SerializedShaderFloatValue + colMask: SerializedShaderFloatValue + destBlend: SerializedShaderFloatValue + destBlendAlpha: SerializedShaderFloatValue + srcBlend: SerializedShaderFloatValue + srcBlendAlpha: SerializedShaderFloatValue + + +@unitypy_define +class SerializedShaderState: + alphaToMask: SerializedShaderFloatValue + culling: SerializedShaderFloatValue + fogColor: SerializedShaderVectorValue + fogDensity: SerializedShaderFloatValue + fogEnd: SerializedShaderFloatValue + fogMode: int + fogStart: SerializedShaderFloatValue + gpuProgramID: int + lighting: bool + m_LOD: int + m_Name: str + m_Tags: SerializedTagMap + offsetFactor: SerializedShaderFloatValue + offsetUnits: SerializedShaderFloatValue + rtBlend0: SerializedShaderRTBlendState + rtBlend1: SerializedShaderRTBlendState + rtBlend2: SerializedShaderRTBlendState + rtBlend3: SerializedShaderRTBlendState + rtBlend4: SerializedShaderRTBlendState + rtBlend5: SerializedShaderRTBlendState + rtBlend6: SerializedShaderRTBlendState + rtBlend7: SerializedShaderRTBlendState + rtSeparateBlend: bool + stencilOp: SerializedStencilOp + stencilOpBack: SerializedStencilOp + stencilOpFront: SerializedStencilOp + stencilReadMask: SerializedShaderFloatValue + stencilRef: SerializedShaderFloatValue + stencilWriteMask: SerializedShaderFloatValue + zTest: SerializedShaderFloatValue + zWrite: SerializedShaderFloatValue + conservative: Optional[SerializedShaderFloatValue] = None + zClip: Optional[SerializedShaderFloatValue] = None + + +@unitypy_define +class SerializedShaderVectorValue: + name: Union[FastPropertyName, str] + w: SerializedShaderFloatValue + x: SerializedShaderFloatValue + y: SerializedShaderFloatValue + z: SerializedShaderFloatValue + + +@unitypy_define +class SerializedStencilOp: + comp: SerializedShaderFloatValue + fail: SerializedShaderFloatValue + pass_: SerializedShaderFloatValue + zFail: SerializedShaderFloatValue + + +@unitypy_define +class SerializedSubProgram: + m_BlobIndex: int + m_Channels: ParserBindChannels + m_GpuProgramType: int + m_ShaderHardwareTier: int + m_BufferParams: Optional[List[BufferBinding]] = None + m_ConstantBufferBindings: Optional[List[BufferBinding]] = None + m_ConstantBuffers: Optional[List[ConstantBuffer]] = None + m_GlobalKeywordIndices: Optional[List[int]] = None + m_KeywordIndices: Optional[List[int]] = None + m_LocalKeywordIndices: Optional[List[int]] = None + m_MatrixParams: Optional[List[MatrixParameter]] = None + m_Parameters: Optional[Union[ProgramParameters, SerializedProgramParameters]] = None + m_Samplers: Optional[List[SamplerParameter]] = None + m_ShaderRequirements: Optional[int] = None + m_TextureParams: Optional[List[TextureParameter]] = None + m_UAVParams: Optional[List[UAVParameter]] = None + m_VectorParams: Optional[List[VectorParameter]] = None + + +@unitypy_define +class SerializedSubShader: + m_LOD: int + m_Passes: List[SerializedPass] + m_Tags: SerializedTagMap + + +@unitypy_define +class SerializedTagMap: + tags: List[Tuple[str, str]] + + +@unitypy_define +class SerializedTextureProperty: + m_DefaultName: str + m_TexDim: int + + +@unitypy_define +class ShaderBindChannel: + source: int + target: int + + +@unitypy_define +class ShaderInfo: + variants: List[VariantInfo] + + +@unitypy_define +class ShadowSettings: + m_Bias: float + m_Resolution: int + m_Strength: float + m_Type: int + m_CullingMatrixOverride: Optional[Matrix4x4f] = None + m_CustomResolution: Optional[int] = None + m_NearPlane: Optional[float] = None + m_NormalBias: Optional[float] = None + m_Softness: Optional[float] = None + m_SoftnessFade: Optional[float] = None + m_UseCullingMatrixOverride: Optional[bool] = None + + +@unitypy_define +class ShapeModule: + angle: float + enabled: bool + m_Mesh: PPtr[Mesh] + placementMode: int + radius: Union[MultiModeParameter, float] + type: int + alignToDirection: Optional[bool] = None + arc: Optional[Union[MultiModeParameter, float]] = None + boxThickness: Optional[Vector3f] = None + boxX: Optional[float] = None + boxY: Optional[float] = None + boxZ: Optional[float] = None + donutRadius: Optional[float] = None + length: Optional[float] = None + m_MeshMaterialIndex: Optional[int] = None + m_MeshNormalOffset: Optional[float] = None + m_MeshRenderer: Optional[PPtr[MeshRenderer]] = None + m_MeshScale: Optional[float] = None + m_MeshSpawn: Optional[MultiModeParameter] = None + m_Position: Optional[Vector3f] = None + m_Rotation: Optional[Vector3f] = None + m_Scale: Optional[Vector3f] = None + m_SkinnedMeshRenderer: Optional[PPtr[SkinnedMeshRenderer]] = None + m_Sprite: Optional[PPtr[Sprite]] = None + m_SpriteRenderer: Optional[PPtr[SpriteRenderer]] = None + m_Texture: Optional[PPtr[Texture2D]] = None + m_TextureAlphaAffectsParticles: Optional[bool] = None + m_TextureBilinearFiltering: Optional[bool] = None + m_TextureClipChannel: Optional[int] = None + m_TextureClipThreshold: Optional[float] = None + m_TextureColorAffectsParticles: Optional[bool] = None + m_TextureUVChannel: Optional[int] = None + m_UseMeshColors: Optional[bool] = None + m_UseMeshMaterialIndex: Optional[bool] = None + radiusThickness: Optional[float] = None + randomDirection: Optional[bool] = None + randomDirectionAmount: Optional[float] = None + randomPositionAmount: Optional[float] = None + sphericalDirectionAmount: Optional[float] = None + + +@unitypy_define +class SizeBySpeedModule: + curve: MinMaxCurve + enabled: bool + range: Vector2f + separateAxes: Optional[bool] = None + y: Optional[MinMaxCurve] = None + z: Optional[MinMaxCurve] = None + + +@unitypy_define +class SizeModule: + curve: MinMaxCurve + enabled: bool + separateAxes: Optional[bool] = None + y: Optional[MinMaxCurve] = None + z: Optional[MinMaxCurve] = None + + +@unitypy_define +class Skeleton: + m_AxesArray: List[Axes] + m_ID: List[int] + m_Node: List[Node] + + +@unitypy_define +class SkeletonBone: + m_Name: str + m_Position: Vector3f + m_Rotation: Quaternionf + m_Scale: Vector3f + m_ParentName: Optional[str] = None + m_TransformModified: Optional[bool] = None + + +@unitypy_define +class SkeletonBoneLimit: + m_Length: float + m_Max: Vector3f + m_Min: Vector3f + m_Modified: bool + m_Value: Vector3f + m_PostQ: Optional[Quaternionf] = None + m_PreQ: Optional[Quaternionf] = None + + +@unitypy_define +class SkeletonMask: + m_Data: List[SkeletonMaskElement] + + +@unitypy_define +class SkeletonMaskElement: + m_Weight: float + m_Index: Optional[int] = None + m_PathHash: Optional[int] = None + + +@unitypy_define +class SkeletonPose: + m_X: List[xform] + + +@unitypy_define +class SketchUpImportCamera: + aspectRatio: float + fov: float + isPerspective: int + lookAt: Vector3f + orthoSize: float + position: Vector3f + up: Vector3f + farPlane: Optional[float] = None + nearPlane: Optional[float] = None + + +@unitypy_define +class SketchUpImportData: + defaultCamera: SketchUpImportCamera + scenes: List[SketchUpImportScene] + + +@unitypy_define +class SketchUpImportScene: + camera: SketchUpImportCamera + name: str + + +@unitypy_define +class SnapshotConstant: + nameHash: int + transitionIndices: List[int] + transitionTypes: List[int] + values: List[float] + + +@unitypy_define +class SoftJointLimit: + bounciness: float + limit: float + contactDistance: Optional[float] = None + damper: Optional[float] = None + spring: Optional[float] = None + + +@unitypy_define +class SoftJointLimitSpring: + damper: float + spring: float + + +@unitypy_define +class SortingLayerEntry: + name: str + uniqueID: int + userID: Optional[int] = None + + +@unitypy_define +class SourceAssetIdentifier: + assembly: str + name: str + type: str + + +@unitypy_define +class SourceLocation: + m_File: str + m_Position: int + + +@unitypy_define +class SourceTextureInformation: + doesTextureContainAlpha: bool + height: int + width: int + doesTextureContainColor: Optional[bool] = None + sourceWasHDR: Optional[bool] = None + + +@unitypy_define +class SpecializationConstantParameter: + m_Binding: Binding + m_NameIndex: int + + +@unitypy_define +class SpeedTreeWind: + BRANCH_DIRECTIONAL_1: bool + BRANCH_DIRECTIONAL_2: bool + BRANCH_DIRECTIONAL_FROND_1: bool + BRANCH_DIRECTIONAL_FROND_2: bool + BRANCH_OSC_COMPLEX_1: bool + BRANCH_OSC_COMPLEX_2: bool + BRANCH_SIMPLE_1: bool + BRANCH_SIMPLE_2: bool + BRANCH_TURBULENCE_1: bool + BRANCH_TURBULENCE_2: bool + BRANCH_WHIP_1: bool + BRANCH_WHIP_2: bool + BranchWindAnchor0: float + BranchWindAnchor1: float + BranchWindAnchor2: float + FROND_RIPPLE_ADJUST_LIGHTING: bool + FROND_RIPPLE_ONE_SIDED: bool + FROND_RIPPLE_TWO_SIDED: bool + GLOBAL_PRESERVE_SHAPE: bool + GLOBAL_WIND: bool + LEAF_OCCLUSION_1: bool + LEAF_OCCLUSION_2: bool + LEAF_RIPPLE_COMPUTED_1: bool + LEAF_RIPPLE_COMPUTED_2: bool + LEAF_RIPPLE_VERTEX_NORMAL_1: bool + LEAF_RIPPLE_VERTEX_NORMAL_2: bool + LEAF_TUMBLE_1: bool + LEAF_TUMBLE_2: bool + LEAF_TWITCH_1: bool + LEAF_TWITCH_2: bool + ROLLING: bool + m_fMaxBranchLevel1Length: float + m_sParams: SParams + + +@unitypy_define +class SpeedTreeWindConfig8: + BRANCH_DIRECTIONAL_1: bool + BRANCH_DIRECTIONAL_2: bool + BRANCH_DIRECTIONAL_FROND_1: bool + BRANCH_DIRECTIONAL_FROND_2: bool + BRANCH_OSC_COMPLEX_1: bool + BRANCH_OSC_COMPLEX_2: bool + BRANCH_SIMPLE_1: bool + BRANCH_SIMPLE_2: bool + BRANCH_TURBULENCE_1: bool + BRANCH_TURBULENCE_2: bool + BRANCH_WHIP_1: bool + BRANCH_WHIP_2: bool + BranchLevel1: SBranchWindLevel + BranchLevel2: SBranchWindLevel + BranchWindAnchor0: float + BranchWindAnchor1: float + BranchWindAnchor2: float + FROND_RIPPLE_ADJUST_LIGHTING: bool + FROND_RIPPLE_ONE_SIDED: bool + FROND_RIPPLE_TWO_SIDED: bool + GLOBAL_PRESERVE_SHAPE: bool + GLOBAL_WIND: bool + LEAF_OCCLUSION_1: bool + LEAF_OCCLUSION_2: bool + LEAF_RIPPLE_COMPUTED_1: bool + LEAF_RIPPLE_COMPUTED_2: bool + LEAF_RIPPLE_VERTEX_NORMAL_1: bool + LEAF_RIPPLE_VERTEX_NORMAL_2: bool + LEAF_TUMBLE_1: bool + LEAF_TUMBLE_2: bool + LEAF_TWITCH_1: bool + LEAF_TWITCH_2: bool + LeafGroup1: SWindGroup + LeafGroup2: SWindGroup + Oscillation0_0: float + Oscillation0_1: float + Oscillation0_2: float + Oscillation0_3: float + Oscillation0_4: float + Oscillation0_5: float + Oscillation0_6: float + Oscillation0_7: float + Oscillation0_8: float + Oscillation0_9: float + Oscillation1_0: float + Oscillation1_1: float + Oscillation1_2: float + Oscillation1_3: float + Oscillation1_4: float + Oscillation1_5: float + Oscillation1_6: float + Oscillation1_7: float + Oscillation1_8: float + Oscillation1_9: float + Oscillation2_0: float + Oscillation2_1: float + Oscillation2_2: float + Oscillation2_3: float + Oscillation2_4: float + Oscillation2_5: float + Oscillation2_6: float + Oscillation2_7: float + Oscillation2_8: float + Oscillation2_9: float + Oscillation3_0: float + Oscillation3_1: float + Oscillation3_2: float + Oscillation3_3: float + Oscillation3_4: float + Oscillation3_5: float + Oscillation3_6: float + Oscillation3_7: float + Oscillation3_8: float + Oscillation3_9: float + Oscillation4_0: float + Oscillation4_1: float + Oscillation4_2: float + Oscillation4_3: float + Oscillation4_4: float + Oscillation4_5: float + Oscillation4_6: float + Oscillation4_7: float + Oscillation4_8: float + Oscillation4_9: float + Oscillation5_0: float + Oscillation5_1: float + Oscillation5_2: float + Oscillation5_3: float + Oscillation5_4: float + Oscillation5_5: float + Oscillation5_6: float + Oscillation5_7: float + Oscillation5_8: float + Oscillation5_9: float + Oscillation6_0: float + Oscillation6_1: float + Oscillation6_2: float + Oscillation6_3: float + Oscillation6_4: float + Oscillation6_5: float + Oscillation6_6: float + Oscillation6_7: float + Oscillation6_8: float + Oscillation6_9: float + Oscillation7_0: float + Oscillation7_1: float + Oscillation7_2: float + Oscillation7_3: float + Oscillation7_4: float + Oscillation7_5: float + Oscillation7_6: float + Oscillation7_7: float + Oscillation7_8: float + Oscillation7_9: float + Oscillation8_0: float + Oscillation8_1: float + Oscillation8_2: float + Oscillation8_3: float + Oscillation8_4: float + Oscillation8_5: float + Oscillation8_6: float + Oscillation8_7: float + Oscillation8_8: float + Oscillation8_9: float + Oscillation9_0: float + Oscillation9_1: float + Oscillation9_2: float + Oscillation9_3: float + Oscillation9_4: float + Oscillation9_5: float + Oscillation9_6: float + Oscillation9_7: float + Oscillation9_8: float + Oscillation9_9: float + ROLLING: bool + m_afFrondRippleDistance_0: float + m_afFrondRippleDistance_1: float + m_afFrondRippleDistance_2: float + m_afFrondRippleDistance_3: float + m_afFrondRippleDistance_4: float + m_afFrondRippleDistance_5: float + m_afFrondRippleDistance_6: float + m_afFrondRippleDistance_7: float + m_afFrondRippleDistance_8: float + m_afFrondRippleDistance_9: float + m_afGlobalDirectionAdherence_0: float + m_afGlobalDirectionAdherence_1: float + m_afGlobalDirectionAdherence_2: float + m_afGlobalDirectionAdherence_3: float + m_afGlobalDirectionAdherence_4: float + m_afGlobalDirectionAdherence_5: float + m_afGlobalDirectionAdherence_6: float + m_afGlobalDirectionAdherence_7: float + m_afGlobalDirectionAdherence_8: float + m_afGlobalDirectionAdherence_9: float + m_afGlobalDistance_0: float + m_afGlobalDistance_1: float + m_afGlobalDistance_2: float + m_afGlobalDistance_3: float + m_afGlobalDistance_4: float + m_afGlobalDistance_5: float + m_afGlobalDistance_6: float + m_afGlobalDistance_7: float + m_afGlobalDistance_8: float + m_afGlobalDistance_9: float + m_fAnchorDistanceScale: float + m_fAnchorOffset: float + m_fDirectionResponse: float + m_fFrondRippleLightingScalar: float + m_fFrondRippleTile: float + m_fGlobalHeight: float + m_fGlobalHeightExponent: float + m_fGustDurationMax: float + m_fGustDurationMin: float + m_fGustFallScalar: float + m_fGustFrequency: float + m_fGustRiseScalar: float + m_fGustStrengthMax: float + m_fGustStrengthMin: float + m_fMaxBranchLevel1Length: float + m_fRollingBranchFieldMin: float + m_fRollingBranchLightingAdjust: float + m_fRollingBranchVerticalOffset: float + m_fRollingLeafRippleMin: float + m_fRollingLeafTumbleMin: float + m_fRollingNoisePeriod: float + m_fRollingNoiseSize: float + m_fRollingNoiseSpeed: float + m_fRollingNoiseTurbulence: float + m_fRollingNoiseTwist: float + m_fStrengthResponse: float + + +@unitypy_define +class SpeedTreeWindConfig9: + m_bDoBranch1: int + m_bDoBranch2: int + m_bDoRipple: int + m_bDoShared: int + m_bDoShimmer: int + m_bLodFade: int + m_fBranch1StretchLimit: float + m_fBranch2StretchLimit: float + m_fDirectionResponse: float + m_fGustDurationMax: float + m_fGustDurationMin: float + m_fGustFallScalar: float + m_fGustFrequency: float + m_fGustRiseScalar: float + m_fGustStrengthMax: float + m_fGustStrengthMin: float + m_fSharedHeightStart: float + m_fStrengthResponse: float + m_fWindIndependence: float + m_sBranch1: BranchWindLevel + m_sBranch2: BranchWindLevel + m_sRipple: RippleGroup + m_sShared: BranchWindLevel + m_vTreeExtents: Vector3f + m_fImportScaling: Optional[float] = None + pad: Optional[int] = None + + +@unitypy_define +class SphericalHarmonicsL2: + sh_10_: float + sh_11_: float + sh_12_: float + sh_13_: float + sh_14_: float + sh_15_: float + sh_16_: float + sh_17_: float + sh_18_: float + sh_19_: float + sh_20_: float + sh_21_: float + sh_22_: float + sh_23_: float + sh_24_: float + sh_25_: float + sh_26_: float + sh__0_: float + sh__1_: float + sh__2_: float + sh__3_: float + sh__4_: float + sh__5_: float + sh__6_: float + sh__7_: float + sh__8_: float + sh__9_: float + + +@unitypy_define +class SplashScreenLogo: + duration: float + logo: PPtr[Sprite] + + +@unitypy_define +class SplatDatabase: + m_AlphaTextures: List[PPtr[Texture2D]] + m_AlphamapResolution: int + m_BaseMapResolution: int + m_ColorSpace: Optional[int] = None + m_MaterialRequiresMetallic: Optional[bool] = None + m_MaterialRequiresSmoothness: Optional[bool] = None + m_Splats: Optional[List[SplatPrototype]] = None + m_TerrainLayers: Optional[List[PPtr[TerrainLayer]]] = None + + +@unitypy_define +class SplatPrototype: + texture: PPtr[Texture2D] + tileOffset: Vector2f + tileSize: Vector2f + normalMap: Optional[PPtr[Texture2D]] = None + smoothness: Optional[float] = None + specularMetallic: Optional[Vector4f] = None + + +@unitypy_define +class SpriteAtlasAssetData: + packables: List[PPtr[Object]] + + +@unitypy_define +class SpriteAtlasData: + alphaTexture: PPtr[Texture2D] + downscaleMultiplier: float + settingsRaw: int + texture: PPtr[Texture2D] + textureRect: Rectf + textureRectOffset: Vector2f + uvTransform: Vector4f + atlasRectOffset: Optional[Vector2f] = None + secondaryTextures: Optional[List[SecondarySpriteTexture]] = None + spriteInstanceData: Optional[SpriteInstanceData] = None + + +@unitypy_define +class SpriteAtlasEditorData: + bindAsDefault: bool + cachedData: PPtr[CachedSpriteAtlasRuntimeData] + isAtlasV2: bool + packables: List[PPtr[Object]] + packingSettings: PackingSettings + platformSettings: List[TextureImporterPlatformSettings] + textureSettings: TextureSettings + variantMultiplier: float + secondaryTextureSettings: Optional[List[Tuple[str, SecondaryTextureSettings]]] = None + storedHash: Optional[Hash128] = None + totalSpriteSurfaceArea: Optional[int] = None + + +@unitypy_define +class SpriteBone: + length: float + name: str + parentId: int + position: Vector3f + rotation: Quaternionf + color: Optional[ColorRGBA] = None + guid: Optional[str] = None + + +@unitypy_define +class SpriteCustomDataEntry: + m_Key: str + m_Value: str + + +@unitypy_define +class SpriteCustomMetadata: + m_Entries: List[SpriteCustomDataEntry] + + +@unitypy_define +class SpriteData: + sprite: PPtr[Object] + + +@unitypy_define +class SpriteInstanceData: + border: Vector4f + m_Bindpose: List[Matrix4x4f] + m_BlendShapes: BlendShapeData + m_IndexBuffer: List[int] + m_IndexFormat: int + m_SubMeshes: List[SubMesh] + m_VertexData: VertexData + physicsShape: List[List[Vector2f]] + pivot: Vector2f + pixelsToUnits: float + rect: Rectf + spriteBones: List[SpriteBone] + spriteName: str + + +@unitypy_define +class SpriteMetaData: + m_Alignment: int + m_Name: str + m_Pivot: Vector2f + m_Rect: Rectf + m_Bones: Optional[List[SpriteBone]] = None + m_Border: Optional[Vector4f] = None + m_CustomData: Optional[str] = None + m_Edges: Optional[List[int2_storage]] = None + m_Indices: Optional[List[int]] = None + m_InternalID: Optional[int] = None + m_Outline: Optional[List[List[Vector2f]]] = None + m_PhysicsShape: Optional[List[List[Vector2f]]] = None + m_SpriteID: Optional[str] = None + m_TessellationDetail: Optional[float] = None + m_Vertices: Optional[List[Vector2f]] = None + m_Weights: Optional[List[BoneWeights4]] = None + + +@unitypy_define +class SpriteRenderData: + settingsRaw: int + texture: PPtr[Texture2D] + textureRect: Rectf + textureRectOffset: Vector2f + alphaTexture: Optional[PPtr[Texture2D]] = None + atlasRectOffset: Optional[Vector2f] = None + downscaleMultiplier: Optional[float] = None + indices: Optional[List[int]] = None + m_Bindpose: Optional[List[Matrix4x4f]] = None + m_BlendShapes: Optional[BlendShapeData] = None + m_IndexBuffer: Optional[List[int]] = None + m_SourceSkin: Optional[List[BoneWeights4]] = None + m_SubMeshes: Optional[List[SubMesh]] = None + m_VertexData: Optional[VertexData] = None + secondaryTextures: Optional[List[SecondarySpriteTexture]] = None + uvTransform: Optional[Vector4f] = None + vertices: Optional[List[SpriteVertex]] = None + + +@unitypy_define +class SpriteSheetMetaData: + m_Sprites: List[SpriteMetaData] + m_Bones: Optional[List[SpriteBone]] = None + m_CustomData: Optional[str] = None + m_Edges: Optional[List[int2_storage]] = None + m_Indices: Optional[List[int]] = None + m_InternalID: Optional[int] = None + m_NameFileIdTable: Optional[List[Tuple[str, int]]] = None + m_Outline: Optional[List[List[Vector2f]]] = None + m_PhysicsShape: Optional[List[List[Vector2f]]] = None + m_SecondaryTextures: Optional[List[SecondarySpriteTexture]] = None + m_SpriteCustomMetadata: Optional[SpriteCustomMetadata] = None + m_SpriteID: Optional[str] = None + m_Vertices: Optional[List[Vector2f]] = None + m_Weights: Optional[List[BoneWeights4]] = None + + +@unitypy_define +class SpriteTilingProperty: + adaptiveTiling: bool + adaptiveTilingThreshold: float + border: Vector4f + drawMode: int + newSize: Vector2f + oldSize: Vector2f + pivot: Vector2f + + +@unitypy_define +class SpriteVertex: + pos: Vector3f + uv: Optional[Vector2f] = None + + +@unitypy_define +class State(NamedObject): + m_IKOnFeet: bool + m_Motions: List[PPtr[Motion]] + m_Name: str + m_ParentStateMachine: PPtr[StateMachine] + m_Position: Vector3f + m_Speed: float + m_Tag: str + m_CycleOffset: Optional[float] = None + m_Mirror: Optional[bool] = None + + +@unitypy_define +class StateConstant: + m_BlendTreeConstantArray: List[OffsetPtr] + m_BlendTreeConstantIndexArray: List[int] + m_IKOnFeet: bool + m_Loop: bool + m_Speed: float + m_TagID: int + m_TransitionConstantArray: List[OffsetPtr] + m_CycleOffset: Optional[float] = None + m_CycleOffsetParamID: Optional[int] = None + m_FullPathID: Optional[int] = None + m_ID: Optional[int] = None + m_LeafInfoArray: Optional[List[LeafInfoConstant]] = None + m_Mirror: Optional[bool] = None + m_MirrorParamID: Optional[int] = None + m_NameID: Optional[int] = None + m_PathID: Optional[int] = None + m_SpeedParamID: Optional[int] = None + m_TimeParamID: Optional[int] = None + m_WriteDefaultValues: Optional[bool] = None + + +@unitypy_define +class StateKey: + m_LayerIndex: int + m_StateID: int + + +@unitypy_define +class StateMachine(NamedObject): + m_AnyStatePosition: Vector3f + m_ChildStateMachine: List[PPtr[StateMachine]] + m_ChildStateMachinePosition: List[Vector3f] + m_DefaultState: PPtr[State] + m_MotionSetCount: int + m_Name: str + m_OrderedTransitions: List[Tuple[PPtr[State], List[PPtr[Transition]]]] + m_ParentStateMachinePosition: Vector3f + m_States: List[PPtr[State]] + m_LocalTransitions: Optional[List[Tuple[PPtr[State], List[PPtr[Transition]]]]] = None + + +@unitypy_define +class StateMachineBehaviourVectorDescription: + m_StateMachineBehaviourIndices: List[int] + m_StateMachineBehaviourRanges: List[Tuple[StateKey, StateRange]] + + +@unitypy_define +class StateMachineConstant: + m_AnyStateTransitionConstantArray: List[OffsetPtr] + m_DefaultState: int + m_StateConstantArray: List[OffsetPtr] + m_EvaluateTransitionsOnStart: Optional[bool] = None + m_MotionSetCount: Optional[int] = None + m_SelectorStateConstantArray: Optional[List[OffsetPtr]] = None + m_SynchronizedLayerCount: Optional[int] = None + + +@unitypy_define +class StateRange: + m_Count: int + m_StartIndex: int + + +@unitypy_define +class StaticBatchInfo: + firstSubMesh: int + subMeshCount: int + + +@unitypy_define +class StreamInfo: + channelMask: int + offset: int + stride: int + align: Optional[int] = None + dividerOp: Optional[int] = None + frequency: Optional[int] = None + + +@unitypy_define +class StreamedClip: + curveCount: int + data: List[int] + discreteCurveCount: Optional[int] = None + + +@unitypy_define +class StreamedResource: + m_Offset: int + m_Size: int + m_Source: str + + +@unitypy_define +class StreamingInfo: + offset: int + path: str + size: int + + +@unitypy_define +class StructParameter: + m_ArraySize: int + m_MatrixMembers: List[MatrixParameter] + m_NameIndex: int + m_StructSize: int + m_VectorMembers: List[VectorParameter] + m_Index: Optional[int] = None + m_OffsetInConstantBuffer: Optional[int] = None + + +@unitypy_define +class SubCollider: + m_Collider: PPtr[Collider2D] + m_ColliderPaths: List[List[IntPoint]] + + +@unitypy_define +class SubEmitterData: + emitter: PPtr[ParticleSystem] + properties: int + type: int + emitProbability: Optional[float] = None + + +@unitypy_define +class SubMesh: + firstByte: int + firstVertex: int + indexCount: int + localAABB: AABB + vertexCount: int + baseVertex: Optional[int] = None + isTriStrip: Optional[int] = None + topology: Optional[int] = None + triangleCount: Optional[int] = None + + +@unitypy_define +class SubModule: + enabled: bool + subEmitterBirth: Optional[PPtr[ParticleSystem]] = None + subEmitterBirth1: Optional[PPtr[ParticleSystem]] = None + subEmitterCollision: Optional[PPtr[ParticleSystem]] = None + subEmitterCollision1: Optional[PPtr[ParticleSystem]] = None + subEmitterDeath: Optional[PPtr[ParticleSystem]] = None + subEmitterDeath1: Optional[PPtr[ParticleSystem]] = None + subEmitters: Optional[List[SubEmitterData]] = None + + +@unitypy_define +class SubPassDescriptor: + colorOutputs: AttachmentIndexArray + flags: int + inputs: AttachmentIndexArray + + +@unitypy_define +class SubstanceEnumItem: + text: str + value: int + + +@unitypy_define +class SubstanceInput: + alteredTexturesUID: List[int] + enumValues: List[SubstanceEnumItem] + flags: int + internalIndex: int + internalType: int + maximum: float + minimum: float + name: str + step: float + type: int + value: SubstanceValue + componentLabels: Optional[List[str]] = None + group: Optional[str] = None + internalIdentifier: Optional[int] = None + label: Optional[str] = None + visibleIf: Optional[str] = None + + +@unitypy_define +class SubstanceValue: + scalar_0_: float + scalar_1_: float + scalar_2_: float + scalar_3_: float + texture: PPtr[Texture2D] + stringvalue: Optional[str] = None + + +@unitypy_define +class TakeInfo: + bakeStartTime: float + bakeStopTime: float + clip: PPtr[AnimationClip] + defaultClipName: str + name: str + sampleRate: float + startTime: float + stopTime: float + internalID: Optional[int] = None + + +@unitypy_define +class Tetrahedron: + indices_0_: int + indices_1_: int + indices_2_: int + indices_3_: int + matrix: Matrix3x4f + neighbors_0_: int + neighbors_1_: int + neighbors_2_: int + neighbors_3_: int + + +@unitypy_define +class TextureImportInstructions: + colorSpace: int + compressedFormat: int + compressionQuality: int + height: int + uncompressedFormat: int + usageMode: int + width: int + androidETC2FallbackDownscale: Optional[bool] = None + androidETC2FallbackFormat: Optional[int] = None + cubeIntermediateSize: Optional[int] = None + cubeLayout: Optional[int] = None + cubeMode: Optional[int] = None + depth: Optional[int] = None + desiredFormat: Optional[int] = None + recommendedFormat: Optional[int] = None + vtOnly: Optional[bool] = None + + +@unitypy_define +class TextureImportOutput: + sourceTextureInformation: SourceTextureInformation + textureImportInstructions: TextureImportInstructions + importInspectorWarnings: Optional[str] = None + + +@unitypy_define +class TextureImporterPlatformSettings: + m_AllowsAlphaSplitting: bool + m_AndroidETC2FallbackOverride: int + m_BuildTarget: str + m_CompressionQuality: int + m_CrunchedCompression: bool + m_MaxTextureSize: int + m_Overridden: bool + m_ResizeAlgorithm: int + m_TextureCompression: int + m_TextureFormat: int + m_ForceMaximumCompressionQuality_BC6H_BC7: Optional[bool] = None + m_IgnorePlatformSupport: Optional[bool] = None + + +@unitypy_define +class TextureParameter: + m_Dim: int + m_NameIndex: int + m_Binding: Optional[Binding] = None + m_Index: Optional[int] = None + m_MultiSampled: Optional[bool] = None + m_SamplerBinding: Optional[Binding] = None + m_SamplerIndex: Optional[int] = None + + +@unitypy_define +class TextureParameters: + height: int + mipLevels: int + textureFormat: int + width: int + + +@unitypy_define +class TextureSettings: + anisoLevel: int + compressionQuality: int + crunchedCompression: bool + filterMode: int + generateMipMaps: bool + maxTextureSize: int + readable: bool + sRGB: bool + textureCompression: int + + +@unitypy_define +class TicksPerSecond: + m_Denominator: int + m_Numerator: int + + +@unitypy_define +class TierGraphicsSettings: + renderingPath: int + useCascadedShadowMaps: bool + enableLPPV: Optional[bool] = None + hdrMode: Optional[int] = None + prefer32BitShadowMaps: Optional[bool] = None + realtimeGICPUUsage: Optional[int] = None + useHDR: Optional[bool] = None + + +@unitypy_define +class Tile: + m_TileColorIndex: int + m_TileIndex: int + m_TileMatrixIndex: int + m_TileSpriteIndex: int + dummyAlignment: Optional[int] = None + m_AllTileFlags: Optional[int] = None + m_ColliderType: Optional[int] = None + m_ObjectToInstantiate: Optional[PPtr[GameObject]] = None + m_TileFlags: Optional[int] = None + m_TileObjectToInstantiateIndex: Optional[int] = None + + +@unitypy_define +class TileAnimationData: + m_AnimatedSprites: List[PPtr[Sprite]] + m_AnimationSpeed: float + m_AnimationTimeOffset: float + m_Flags: Optional[int] = None + m_IsLooping: Optional[bool] = None + + +@unitypy_define +class TilemapRefCountedData: + m_Data: Union[ColorRGBA, Matrix4x4f, PPtr[GameObject], PPtr[Object], PPtr[Sprite]] + m_RefCount: int + + +@unitypy_define +class TrailModule: + colorOverLifetime: MinMaxGradient + colorOverTrail: MinMaxGradient + dieWithParticles: bool + enabled: bool + inheritParticleColor: bool + lifetime: MinMaxCurve + minVertexDistance: float + ratio: float + sizeAffectsLifetime: bool + sizeAffectsWidth: bool + textureMode: int + widthOverTrail: MinMaxCurve + worldSpace: bool + attachRibbonsToTransform: Optional[bool] = None + generateLightingData: Optional[bool] = None + mode: Optional[int] = None + ribbonCount: Optional[int] = None + shadowBias: Optional[float] = None + splitSubEmitterRibbons: Optional[bool] = None + textureScale: Optional[Vector2f] = None + + +@unitypy_define +class TransformMaskElement: + m_Path: str + m_Weight: float + + +@unitypy_define +class Transition(NamedObject): + m_Atomic: bool + m_Conditions: List[Condition] + m_DstState: PPtr[State] + m_Mute: bool + m_Name: str + m_Solo: bool + m_SrcState: PPtr[State] + m_TransitionDuration: float + m_TransitionOffset: float + m_CanTransitionToSelf: Optional[bool] = None + + +@unitypy_define +class TransitionConstant: + m_ConditionConstantArray: List[OffsetPtr] + m_DestinationState: int + m_ID: int + m_TransitionDuration: float + m_TransitionOffset: float + m_UserID: int + m_Atomic: Optional[bool] = None + m_CanTransitionToSelf: Optional[bool] = None + m_ExitTime: Optional[float] = None + m_FullPathID: Optional[int] = None + m_HasExitTime: Optional[bool] = None + m_HasFixedDuration: Optional[bool] = None + m_InterruptionSource: Optional[int] = None + m_OrderedInterruption: Optional[bool] = None + + +@unitypy_define +class TreeInstance: + color: ColorRGBA + heightScale: float + index: int + lightmapColor: ColorRGBA + position: Vector3f + widthScale: float + rotation: Optional[float] = None + + +@unitypy_define +class TreePrototype: + bendFactor: float + prefab: PPtr[GameObject] + navMeshLod: Optional[int] = None + + +@unitypy_define +class TriggerModule: + enabled: bool + enter: int + exit: int + inside: int + outside: int + radiusScale: float + colliderQueryMode: Optional[int] = None + collisionShape0: Optional[PPtr[Component]] = None + collisionShape1: Optional[PPtr[Component]] = None + collisionShape2: Optional[PPtr[Component]] = None + collisionShape3: Optional[PPtr[Component]] = None + collisionShape4: Optional[PPtr[Component]] = None + collisionShape5: Optional[PPtr[Component]] = None + primitives: Optional[List[PPtr[Component]]] = None + + +@unitypy_define +class TypeStats: + classID: int + objectCount: int + resourceCount: int + size: int + + +@unitypy_define +class UAVParameter: + m_NameIndex: int + m_Binding: Optional[Binding] = None + m_Index: Optional[int] = None + m_OriginalBinding: Optional[Binding] = None + m_OriginalIndex: Optional[int] = None + + +@unitypy_define +class UVAnimation: + cycles: float + x_Tile: int + y_Tile: int + + +@unitypy_define +class UVModule: + animationType: int + cycles: float + enabled: bool + frameOverTime: MinMaxCurve + rowIndex: int + tilesX: int + tilesY: int + flipU: Optional[float] = None + flipV: Optional[float] = None + fps: Optional[float] = None + mode: Optional[int] = None + randomRow: Optional[bool] = None + rowMode: Optional[int] = None + speedRange: Optional[Vector2f] = None + sprites: Optional[List[SpriteData]] = None + startFrame: Optional[MinMaxCurve] = None + timeMode: Optional[int] = None + uvChannelMask: Optional[int] = None + + +@unitypy_define +class UnityAdsSettings(GlobalGameManager): + m_Enabled: bool + m_InitializeOnStartup: bool + m_TestMode: bool + m_AndroidGameId: Optional[str] = None + m_EnabledPlatforms: Optional[int] = None + m_GameId: Optional[str] = None + m_IosGameId: Optional[str] = None + + +@unitypy_define +class UnityAnalyticsSettings: + m_Enabled: bool + m_TestMode: bool + m_InitializeOnStartup: Optional[bool] = None + m_PackageRequiringCoreStatsPresent: Optional[bool] = None + m_TestConfigUrl: Optional[str] = None + m_TestEventUrl: Optional[str] = None + + +@unitypy_define +class UnityPropertySheet: + m_Colors: Union[List[Tuple[FastPropertyName, ColorRGBA]], List[Tuple[str, ColorRGBA]]] + m_Floats: Union[List[Tuple[FastPropertyName, float]], List[Tuple[str, float]]] + m_TexEnvs: Union[List[Tuple[FastPropertyName, UnityTexEnv]], List[Tuple[str, UnityTexEnv]]] + m_Ints: Optional[List[Tuple[str, int]]] = None + + +@unitypy_define +class UnityPurchasingSettings: + m_Enabled: bool + m_TestMode: bool + + +@unitypy_define +class UnitySceneHandle: + value: EntityId + + +@unitypy_define +class UnityTexEnv: + m_Offset: Vector2f + m_Scale: Vector2f + m_Texture: PPtr[Texture] + + +@unitypy_define +class UpdateZoneInfo: + needSwap: bool + passIndex: int + rotation: float + updateZoneCenter: Vector3f + updateZoneSize: Vector3f + + +@unitypy_define +class VFXCPUBufferData: + data: List[int] + + +@unitypy_define +class VFXCPUBufferDesc: + capacity: int + initialData: VFXCPUBufferData + layout: List[VFXLayoutElementDesc] + stride: int + debugName: Optional[str] = None + + +@unitypy_define +class VFXEditorSystemDesc: + buffers: List[VFXMapping] + capacity: int + flags: int + layer: int + tasks: List[VFXEditorTaskDesc] + type: int + values: List[VFXMapping] + name: Optional[str] = None + + +@unitypy_define +class VFXEditorTaskDesc: + buffers: List[VFXMapping] + params: List[VFXMapping] + processor: PPtr[NamedObject] + shaderSourceIndex: int + type: int + values: List[VFXMapping] + temporaryBuffers: Optional[List[VFXMappingTemporary]] = None + + +@unitypy_define +class VFXEntryExposed: + m_Name: str + m_Overridden: bool + m_Value: Union[AnimationCurve, Gradient, Matrix4x4f, PPtr[NamedObject], PPtr[Object], Vector2f, Vector3f, Vector4f, bool, float, int] + + +@unitypy_define +class VFXEntryExpressionValue: + m_ExpressionIndex: int + m_Value: Union[AnimationCurve, Gradient, Matrix4x4f, PPtr[NamedObject], PPtr[Object], Vector2f, Vector3f, Vector4f, bool, float, int] + + +@unitypy_define +class VFXEventDesc: + name: str + playSystems: List[int] + stopSystems: List[int] + initSystems: Optional[List[int]] = None + + +@unitypy_define +class VFXExposedMapping: + mapping: VFXMapping + space: int + + +@unitypy_define +class VFXExpressionContainer: + m_Expressions: List[Expression] + m_NeedsLocalToWorld: bool + m_NeedsWorldToLocal: bool + m_ConstantBakeCurveCount: Optional[int] = None + m_ConstantBakeGradientCount: Optional[int] = None + m_DynamicBakeCurveCount: Optional[int] = None + m_DynamicBakeGradientCount: Optional[int] = None + m_MaxCommonExpressionsIndex: Optional[int] = None + m_NeededMainCameraBuffers: Optional[int] = None + m_NeedsMainCamera: Optional[bool] = None + + +@unitypy_define +class VFXField: + m_Array: Union[List[VFXEntryExposed], List[VFXEntryExpressionValue]] + + +@unitypy_define +class VFXGPUBufferDesc: + capacity: int + layout: List[VFXLayoutElementDesc] + size: int + stride: int + debugName: Optional[str] = None + mode: Optional[int] = None + target: Optional[int] = None + type: Optional[int] = None + + +@unitypy_define +class VFXInstanceSplitDesc: + values: List[int] + + +@unitypy_define +class VFXLayoutElementDesc: + name: str + offset: VFXLayoutOffset + type: int + + +@unitypy_define +class VFXLayoutOffset: + bucket: int + element: int + structure: int + + +@unitypy_define +class VFXMapping: + index: int + nameId: str + + +@unitypy_define +class VFXMappingTemporary: + mapping: VFXMapping + pastFrameIndex: int + perCameraBuffer: bool + + +@unitypy_define +class VFXPropertySheetSerializedBase: + m_AnimationCurve: VFXField + m_Bool: VFXField + m_Float: VFXField + m_Gradient: VFXField + m_Int: VFXField + m_Matrix4x4f: VFXField + m_NamedObject: VFXField + m_Uint: VFXField + m_Vector2f: VFXField + m_Vector3f: VFXField + m_Vector4f: VFXField + + +@unitypy_define +class VFXRendererSettings: + motionVectorGenerationMode: int + shadowCastingMode: int + lightProbeUsage: Optional[int] = None + rayTracingMode: Optional[int] = None + receiveShadows: Optional[bool] = None + reflectionProbeUsage: Optional[int] = None + transparencyPriority: Optional[int] = None + + +@unitypy_define +class VFXShaderSourceDesc: + compute: bool + name: str + source: str + + +@unitypy_define +class VFXSystemDesc: + buffers: List[VFXMapping] + capacity: int + flags: int + layer: int + tasks: List[VFXTaskDesc] + type: int + values: List[VFXMapping] + instanceSplitDescs: Optional[List[VFXInstanceSplitDesc]] = None + name: Optional[str] = None + + +@unitypy_define +class VFXTaskDesc: + buffers: List[VFXMapping] + params: List[VFXMapping] + processor: Union[PPtr[NamedObject], PPtr[Object]] + type: int + values: List[VFXMapping] + instanceSplitIndex: Optional[int] = None + temporaryBuffers: Optional[List[VFXMappingTemporary]] = None + + +@unitypy_define +class VFXTemplate: + category: str + description: str + icon: PPtr[Texture2D] + name: str + thumbnail: PPtr[Texture2D] + order: Optional[int] = None + + +@unitypy_define +class VFXTemporaryGPUBufferDesc: + desc: VFXGPUBufferDesc + frameCount: int + + +@unitypy_define +class VRSettings: + cardboard: Optional[Google] = None + daydream: Optional[Google] = None + enable360StereoCapture: Optional[bool] = None + hololens: Optional[HoloLens] = None + lumin: Optional[Lumin] = None + none: Optional[DeviceNone] = None + oculus: Optional[Oculus] = None + + +@unitypy_define +class ValueArray: + m_BoolValues: List[bool] + m_FloatValues: List[float] + m_IntValues: List[int] + m_EntityIdValues: Optional[List[EntityId]] = None + m_PositionValues: Optional[Union[List[float3], List[float4]]] = None + m_QuaternionValues: Optional[List[float4]] = None + m_ScaleValues: Optional[Union[List[float3], List[float4]]] = None + m_VectorValues: Optional[List[float4]] = None + + +@unitypy_define +class ValueArrayConstant: + m_ValueArray: List[ValueConstant] + + +@unitypy_define +class ValueConstant: + m_ID: int + m_Index: int + m_Type: int + m_TypeID: Optional[int] = None + + +@unitypy_define +class ValueDelta: + m_Start: float + m_Stop: float + + +@unitypy_define +class VariableBoneCountWeights: + m_Data: List[int] + + +@unitypy_define +class VariantInfo: + graphicsStateInfoSet: Optional[List[GraphicsStateInfo]] = None + keywordNames: Optional[str] = None + keywords: Optional[str] = None + passIndex: Optional[int] = None + passType: Optional[int] = None + shader: Optional[PPtr[Shader]] = None + shaderAssetGUID: Optional[str] = None + shaderAssetLocalIdentifierInFile: Optional[int] = None + shaderName: Optional[str] = None + subShaderIndex: Optional[int] = None + + +@unitypy_define +class Vector3Curve: + curve: AnimationCurve + path: str + + +@unitypy_define +class VectorParameter: + m_ArraySize: int + m_Dim: int + m_NameIndex: int + m_Type: int + m_Index: Optional[int] = None + m_OffsetInConstantBuffer: Optional[int] = None + + +@unitypy_define +class VelocityModule: + enabled: bool + inWorldSpace: bool + x: MinMaxCurve + y: MinMaxCurve + z: MinMaxCurve + orbitalOffsetX: Optional[MinMaxCurve] = None + orbitalOffsetY: Optional[MinMaxCurve] = None + orbitalOffsetZ: Optional[MinMaxCurve] = None + orbitalX: Optional[MinMaxCurve] = None + orbitalY: Optional[MinMaxCurve] = None + orbitalZ: Optional[MinMaxCurve] = None + radial: Optional[MinMaxCurve] = None + speedModifier: Optional[MinMaxCurve] = None + + +@unitypy_define +class VertexData: + m_DataSize: bytes + m_VertexCount: int + m_Channels: Optional[List[ChannelInfo]] = None + m_CurrentChannels: Optional[int] = None + m_Streams: Optional[List[StreamInfo]] = None + m_Streams_0_: Optional[StreamInfo] = None + m_Streams_1_: Optional[StreamInfo] = None + m_Streams_2_: Optional[StreamInfo] = None + m_Streams_3_: Optional[StreamInfo] = None + + +@unitypy_define +class VertexLayoutInfo: + vertexChannelsInfo: List[ChannelInfo] + vertexStreamCount: int + vertexStrides: List[int] + + +@unitypy_define +class VideoClipImporterOutput: + encodedEndFrame: Optional[int] = None + encodedHeight: Optional[int] = None + encodedSettings: Optional[VideoClipImporterTargetSettings] = None + encodedStartFrame: Optional[int] = None + encodedWidth: Optional[int] = None + format: Optional[int] = None + originalFrameCount: Optional[int] = None + originalHeight: Optional[int] = None + originalWidth: Optional[int] = None + settings: Optional[VideoClipImporterTargetSettings] = None + sourceAudioChannelCount: Optional[List[int]] = None + sourceAudioSampleRate: Optional[List[int]] = None + sourceFileSize: Optional[int] = None + sourceFrameRate: Optional[float] = None + sourceHasAlpha: Optional[bool] = None + sourcePixelAspectRatioDenominator: Optional[int] = None + sourcePixelAspectRatioNumerator: Optional[int] = None + streamedResource: Optional[StreamedResource] = None + transcodeSkipped: Optional[bool] = None + + +@unitypy_define +class VideoClipImporterTargetSettings: + aspectRatio: int + bitrateMode: int + codec: int + customHeight: int + customWidth: int + enableTranscoding: bool + resizeFormat: int + spatialQuality: int + + +@unitypy_define +class VisualEffectInfo: + m_Buffers: List[VFXGPUBufferDesc] + m_CPUBuffers: List[VFXCPUBufferDesc] + m_CullingFlags: int + m_Events: List[VFXEventDesc] + m_ExposedExpressions: Union[List[VFXExposedMapping], List[VFXMapping]] + m_Expressions: VFXExpressionContainer + m_PropertySheet: VFXPropertySheetSerializedBase + m_RendererSettings: VFXRendererSettings + m_UpdateMode: int + m_CompilationVersion: Optional[int] = None + m_InitialEventName: Optional[str] = None + m_InstancingCapacity: Optional[int] = None + m_InstancingDisabledReason: Optional[int] = None + m_InstancingMode: Optional[int] = None + m_PreWarmDeltaTime: Optional[float] = None + m_PreWarmStepCount: Optional[int] = None + m_RuntimeVersion: Optional[int] = None + m_TemporaryBuffers: Optional[List[VFXTemporaryGPUBufferDesc]] = None + + +@unitypy_define +class VisualEffectSettings: + m_CullingFlags: int + m_InitialEventName: str + m_PreWarmDeltaTime: float + m_PreWarmStepCount: int + m_RendererSettings: VFXRendererSettings + m_UpdateMode: int + m_InstancingCapacity: Optional[int] = None + m_InstancingDisabledReason: Optional[int] = None + m_InstancingMode: Optional[int] = None + + +@unitypy_define +class VulkanGraphicsJobsDeviceFilterData: + filter: AndroidDeviceFilterData + preferredMode: int + + +@unitypy_define +class WebGPUDeviceFilterData: + browserName: str + browserVersion: str + browserVersionComparator: int + deviceType: int + features: List[int] + limits: List[WebGPUDeviceFilterLimit] + + +@unitypy_define +class WebGPUDeviceFilterLimit: + comparator: int + limit: int + value: int + + +@unitypy_define +class WheelFrictionCurve: + asymptoteSlip: Optional[float] = None + asymptoteValue: Optional[float] = None + extremumSlip: Optional[float] = None + extremumValue: Optional[float] = None + m_AsymptoteSlip: Optional[float] = None + m_AsymptoteValue: Optional[float] = None + m_ExtremumSlip: Optional[float] = None + m_ExtremumValue: Optional[float] = None + m_Stiffness: Optional[float] = None + stiffnessFactor: Optional[float] = None + + +@unitypy_define +class bitset: + bitCount: int + bitblocks: bytes + + +@unitypy_define +class int2_storage: + x: int + y: int + + +@unitypy_define +class int3_storage: + x: int + y: int + z: int + + +@unitypy_define +class xform: + q: float4 + s: Union[float3, float4] + t: Union[float3, float4] diff --git a/UnityPy/classes/legacy_patch/AudioClip.py b/UnityPy/classes/legacy_patch/AudioClip.py new file mode 100644 index 000000000..7f74daba4 --- /dev/null +++ b/UnityPy/classes/legacy_patch/AudioClip.py @@ -0,0 +1,18 @@ +from ...enums import AUDIO_TYPE_EXTEMSION +from ..generated import AudioClip + + +def _AudioClip_extension(self: AudioClip) -> str: + return AUDIO_TYPE_EXTEMSION.get(self.m_CompressionFormat, ".audioclip") + + +def _AudioClip_samples(self: AudioClip) -> dict: + from ...export import AudioClipConverter + + return AudioClipConverter.extract_audioclip_samples(self) + + +AudioClip.extension = property(_AudioClip_extension) +AudioClip.samples = property(_AudioClip_samples) + +__all__ = ("AudioClip",) diff --git a/UnityPy/classes/legacy_patch/AudioClip.pyi b/UnityPy/classes/legacy_patch/AudioClip.pyi new file mode 100644 index 000000000..f93994b73 --- /dev/null +++ b/UnityPy/classes/legacy_patch/AudioClip.pyi @@ -0,0 +1,30 @@ +from typing import Dict, List, Optional + +from UnityPy.classes.generated import SampleClip, StreamedResource + +class AudioClip(SampleClip): + m_Name: str + m_3D: Optional[bool] = None + m_Ambisonic: Optional[bool] = None + m_AudioData: Optional[List[int]] = None + m_BitsPerSample: Optional[int] = None + m_Channels: Optional[int] = None + m_CompressionFormat: Optional[int] = None + m_Format: Optional[int] = None + m_Frequency: Optional[int] = None + m_IsTrackerFormat: Optional[bool] = None + m_Legacy3D: Optional[bool] = None + m_Length: Optional[float] = None + m_LoadInBackground: Optional[bool] = None + m_LoadType: Optional[int] = None + m_PreloadAudioData: Optional[bool] = None + m_Resource: Optional[StreamedResource] = None + m_Stream: Optional[int] = None + m_SubsoundIndex: Optional[int] = None + m_Type: Optional[int] = None + m_UseHardware: Optional[bool] = None + + @property + def extension(self) -> str: ... + @property + def samples(self) -> Dict[str, bytes]: ... diff --git a/UnityPy/classes/legacy_patch/GameObject.py b/UnityPy/classes/legacy_patch/GameObject.py new file mode 100644 index 000000000..3b727d0bf --- /dev/null +++ b/UnityPy/classes/legacy_patch/GameObject.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from typing import List, Union + +from ...enums import ClassIDType +from ..generated import Component, GameObject, PPtr + + +def _GameObject_Components(self) -> List[PPtr[Component]]: + if self.m_Component is None: + return [] + if isinstance(self.m_Component[0], tuple): + return [c.m_GameObject for i, c in self.m_Component] + else: + return [c.component for c in self.m_Component] + + +def _GameObject_GetComponent(self, type: ClassIDType) -> Union[PPtr[Component], None]: + for component in self.m_Components: + if component.type == type: + return component + return None + + +GameObject.m_Components = property(_GameObject_Components) +GameObject.m_Animator = property(lambda self: _GameObject_GetComponent(self, ClassIDType.Animator)) +GameObject.m_Animation = property(lambda self: _GameObject_GetComponent(self, ClassIDType.Animation)) +GameObject.m_Transform = property(lambda self: _GameObject_GetComponent(self, ClassIDType.Transform)) +GameObject.m_MeshRenderer = property(lambda self: _GameObject_GetComponent(self, ClassIDType.MeshRenderer)) +GameObject.m_SkinnedMeshRenderer = property( + lambda self: _GameObject_GetComponent(self, ClassIDType.SkinnedMeshRenderer) +) +GameObject.m_MeshFilter = property(lambda self: _GameObject_GetComponent(self, ClassIDType.MeshFilter)) diff --git a/UnityPy/classes/legacy_patch/GameObject.pyi b/UnityPy/classes/legacy_patch/GameObject.pyi new file mode 100644 index 000000000..15fa33aa3 --- /dev/null +++ b/UnityPy/classes/legacy_patch/GameObject.pyi @@ -0,0 +1,26 @@ +from typing import List, Tuple, Union + +from UnityPy.classes import Component, PPtr +from UnityPy.classes.generated import ComponentPair, EditorExtension + +class GameObject(EditorExtension): + m_Component: Union[List[ComponentPair], List[Tuple[int, PPtr[Component]]]] + m_IsActive: Union[bool, int] + m_Layer: int + m_Name: str + m_Tag: int + + @property + def m_Components(self) -> List[PPtr[Component]]: ... + @property + def m_Animator(self) -> Union[PPtr[Component], None]: ... + @property + def m_Animation(self) -> Union[PPtr[Component], None]: ... + @property + def m_Transform(self) -> Union[PPtr[Component], None]: ... + @property + def m_SkinnedMeshRenderer(self) -> Union[PPtr[Component], None]: ... + @property + def m_MeshRenderer(self) -> Union[PPtr[Component], None]: ... + @property + def m_MeshFilter(self) -> Union[PPtr[Component], None]: ... diff --git a/UnityPy/classes/legacy_patch/Mesh.py b/UnityPy/classes/legacy_patch/Mesh.py new file mode 100644 index 000000000..37512d372 --- /dev/null +++ b/UnityPy/classes/legacy_patch/Mesh.py @@ -0,0 +1,13 @@ +from ..generated import Mesh + + +def _Mesh_export(self: Mesh, format: str = "obj"): + from ...export.MeshExporter import export_mesh + + return export_mesh(self, format) + + +Mesh.export = _Mesh_export + + +__all__ = ("Mesh",) diff --git a/UnityPy/classes/legacy_patch/Mesh.pyi b/UnityPy/classes/legacy_patch/Mesh.pyi new file mode 100644 index 000000000..ee921b8c6 --- /dev/null +++ b/UnityPy/classes/legacy_patch/Mesh.pyi @@ -0,0 +1,58 @@ +from typing import List, Optional, Union + +from UnityPy.classes.generated import ( + AABB, + BlendShapeData, + BoneInfluence, + BoneWeights4, + CompressedMesh, + MeshBlendShape, + MeshBlendShapeVertex, + MinMaxAABB, + NamedObject, + StreamingInfo, + SubMesh, + VariableBoneCountWeights, + VertexData, +) +from UnityPy.classes.math import ColorRGBA, Matrix4x4f, Vector2f, Vector3f, Vector4f + +class Mesh(NamedObject): + m_BindPose: List[Matrix4x4f] + m_CompressedMesh: CompressedMesh + m_IndexBuffer: List[int] + m_LocalAABB: AABB + m_MeshCompression: int + m_MeshUsageFlags: int + m_Name: str + m_SubMeshes: List[SubMesh] + m_BakedConvexCollisionMesh: Optional[List[int]] = None + m_BakedTriangleCollisionMesh: Optional[List[int]] = None + m_BoneNameHashes: Optional[List[int]] = None + m_BonesAABB: Optional[List[MinMaxAABB]] = None + m_CollisionTriangles: Optional[List[int]] = None + m_CollisionVertexCount: Optional[int] = None + m_Colors: Optional[List[ColorRGBA]] = None + m_CookingOptions: Optional[int] = None + m_IndexFormat: Optional[int] = None + m_IsReadable: Optional[bool] = None + m_KeepIndices: Optional[bool] = None + m_KeepVertices: Optional[bool] = None + m_MeshMetrics_0_: Optional[float] = None + m_MeshMetrics_1_: Optional[float] = None + m_Normals: Optional[List[Vector3f]] = None + m_RootBoneNameHash: Optional[int] = None + m_ShapeVertices: Optional[List[MeshBlendShapeVertex]] = None + m_Shapes: Optional[Union[BlendShapeData, List[MeshBlendShape]]] = None + m_Skin: Optional[Union[List[BoneInfluence], List[BoneWeights4]]] = None + m_StreamCompression: Optional[int] = None + m_StreamData: Optional[StreamingInfo] = None + m_Tangents: Optional[List[Vector4f]] = None + m_UV: Optional[List[Vector2f]] = None + m_UV1: Optional[List[Vector2f]] = None + m_Use16BitIndices: Optional[int] = None + m_VariableBoneCountWeights: Optional[VariableBoneCountWeights] = None + m_VertexData: Optional[VertexData] = None + m_Vertices: Optional[List[Vector3f]] = None + + def export(self, format: str = "obj") -> str: ... diff --git a/UnityPy/classes/legacy_patch/Renderer.py b/UnityPy/classes/legacy_patch/Renderer.py new file mode 100644 index 000000000..3f6e93206 --- /dev/null +++ b/UnityPy/classes/legacy_patch/Renderer.py @@ -0,0 +1,12 @@ +from ..generated import Renderer + + +def export(self, export_dir: str) -> None: + from ...export import MeshRendererExporter + + MeshRendererExporter.export_mesh_renderer(self, export_dir) + + +Renderer.export = export + +__all__ = ("Renderer",) diff --git a/UnityPy/classes/legacy_patch/Renderer.pyi b/UnityPy/classes/legacy_patch/Renderer.pyi new file mode 100644 index 000000000..44ea8bf5a --- /dev/null +++ b/UnityPy/classes/legacy_patch/Renderer.pyi @@ -0,0 +1,8 @@ +from UnityPy.classes import PPtr +from UnityPy.classes.generated import Component +from UnityPy.classes.legacy_patch import GameObject + +class Renderer(Component): + m_GameObject: PPtr[GameObject] + + def export(self, export_dir: str) -> None: ... diff --git a/UnityPy/classes/legacy_patch/Shader.py b/UnityPy/classes/legacy_patch/Shader.py new file mode 100644 index 000000000..23b051b7c --- /dev/null +++ b/UnityPy/classes/legacy_patch/Shader.py @@ -0,0 +1,12 @@ +from ..generated import Shader + + +def _Shader_export(self: Shader) -> str: + from ...export.ShaderConverter import export_shader + + return export_shader(self) + + +Shader.export = _Shader_export + +__all__ = ("Shader",) diff --git a/UnityPy/classes/legacy_patch/Shader.pyi b/UnityPy/classes/legacy_patch/Shader.pyi new file mode 100644 index 000000000..08ac3f0fc --- /dev/null +++ b/UnityPy/classes/legacy_patch/Shader.pyi @@ -0,0 +1,24 @@ +from typing import List, Optional, Tuple, Union + +from UnityPy.classes import PPtr +from UnityPy.classes.generated import GUID, NamedObject, SerializedShader, Texture + +class Shader(NamedObject): + m_Name: str + compressedBlob: Optional[List[int]] = None + compressedLengths: Optional[Union[List[int], List[List[int]]]] = None + decompressedLengths: Optional[Union[List[int], List[List[int]]]] = None + decompressedSize: Optional[int] = None + m_AssetGUID: Optional[GUID] = None + m_Dependencies: Optional[List[PPtr[Shader]]] = None + m_NonModifiableTextures: Optional[List[Tuple[str, PPtr[Texture]]]] = None + m_ParsedForm: Optional[SerializedShader] = None + m_PathName: Optional[str] = None + m_Script: Optional[str] = None + m_ShaderIsBaked: Optional[bool] = None + m_SubProgramBlob: Optional[List[int]] = None + offsets: Optional[Union[List[int], List[List[int]]]] = None + platforms: Optional[List[int]] = None + stageCounts: Optional[List[int]] = None + + def export(self) -> str: ... diff --git a/UnityPy/classes/legacy_patch/Sprite.py b/UnityPy/classes/legacy_patch/Sprite.py new file mode 100644 index 000000000..e12f84776 --- /dev/null +++ b/UnityPy/classes/legacy_patch/Sprite.py @@ -0,0 +1,12 @@ +from ..generated import Sprite + + +def _Sprite_image(self: Sprite): + from ...export import SpriteHelper + + return SpriteHelper.get_image_from_sprite(self) + + +Sprite.image = property(_Sprite_image) + +__all__ = ("Sprite",) diff --git a/UnityPy/classes/legacy_patch/Sprite.pyi b/UnityPy/classes/legacy_patch/Sprite.pyi new file mode 100644 index 000000000..dc88a2dc0 --- /dev/null +++ b/UnityPy/classes/legacy_patch/Sprite.pyi @@ -0,0 +1,35 @@ +from typing import List, Optional, Tuple + +from PIL.Image import Image + +from UnityPy.classes import PPtr +from UnityPy.classes.generated import ( + GUID, + MonoBehaviour, + NamedObject, + Rectf, + SpriteAtlas, + SpriteBone, + SpriteRenderData, +) +from UnityPy.classes.math import Vector2f, Vector4f + +class Sprite(NamedObject): + m_Extrude: int + m_Name: str + m_Offset: Vector2f + m_PixelsToUnits: float + m_RD: SpriteRenderData + m_Rect: Rectf + m_AtlasTags: Optional[List[str]] = None + m_Bones: Optional[List[SpriteBone]] = None + m_Border: Optional[Vector4f] = None + m_IsPolygon: Optional[bool] = None + m_PhysicsShape: Optional[List[List[Vector2f]]] = None + m_Pivot: Optional[Vector2f] = None + m_RenderDataKey: Optional[Tuple[GUID, int]] = None + m_ScriptableObjects: Optional[List[PPtr[MonoBehaviour]]] = None + m_SpriteAtlas: Optional[PPtr[SpriteAtlas]] = None + + @property + def image(self) -> Image: ... diff --git a/UnityPy/classes/legacy_patch/Texture2D.py b/UnityPy/classes/legacy_patch/Texture2D.py new file mode 100644 index 000000000..680829afa --- /dev/null +++ b/UnityPy/classes/legacy_patch/Texture2D.py @@ -0,0 +1,83 @@ +from typing import BinaryIO, Optional, Union + +from PIL import Image + +from ..generated import Texture2D + + +def _Texture2d_get_image(self: Texture2D): + from ...export import Texture2DConverter + + return Texture2DConverter.get_image_from_texture2d(self) + + +def _Texture2d_set_image( + self: Texture2D, + img: Union["Image.Image", str, BinaryIO], + target_format: Optional[int] = None, + mipmap_count: int = 1, +): + from ...export import Texture2DConverter + + if not target_format: + target_format = self.m_TextureFormat + + if not isinstance(img, Image.Image): + img = Image.open(img) + + platform = self.object_reader.platform if self.object_reader is not None else 0 + img_data, tex_format = Texture2DConverter.image_to_texture2d(img, target_format, platform, self.m_PlatformBlob) + self.m_Width = img.width + self.m_Height = img.height + + if mipmap_count > 1: + width = self.m_Width + height = self.m_Height + re_img = img + for i in range(mipmap_count - 1): + width //= 2 + height //= 2 + if width < 4 or height < 4: + mipmap_count = i + 1 + break + re_img = re_img.resize((width, height), Image.Resampling.BICUBIC) + img_data += Texture2DConverter.image_to_texture2d(re_img, target_format)[0] + + # disable mipmaps as we don't store them ourselves by default + if self.m_MipMap is not None: + self.m_MipMap = mipmap_count > 1 + if self.m_MipCount is not None: + self.m_MipCount = mipmap_count + + self.image_data = img_data + # width * height * channel count + self.m_CompleteImageSize = len(img_data) # img.width * img.height * len(img.getbands()) + self.m_TextureFormat = tex_format + + if self.m_StreamData is not None: + self.m_StreamData.path = "" + self.m_StreamData.offset = 0 + self.m_StreamData.size = 0 + + +def _Texture2D_get_image_data(self: Texture2D): + if self.image_data: + return self.image_data + if self.m_StreamData: + from ...helpers.ResourceReader import get_resource_data + + return get_resource_data( + self.m_StreamData.path, + self.object_reader.assets_file, + self.m_StreamData.offset, + self.m_StreamData.size, + ) + raise ValueError("No image data found") + + +Texture2D.image = property(_Texture2d_get_image, _Texture2d_set_image) +Texture2D.set_image = _Texture2d_set_image +Texture2D.get_image_data = _Texture2D_get_image_data + + +__all__ = ("Texture2D",) diff --git a/UnityPy/classes/legacy_patch/Texture2D.pyi b/UnityPy/classes/legacy_patch/Texture2D.pyi new file mode 100644 index 000000000..bc8df836d --- /dev/null +++ b/UnityPy/classes/legacy_patch/Texture2D.pyi @@ -0,0 +1,44 @@ +from typing import BinaryIO, List, Optional, Union + +from PIL.Image import Image + +from UnityPy.classes.generated import GLTextureSettings, StreamingInfo, Texture + +class Texture2D(Texture): + image_data: bytes + m_CompleteImageSize: int + m_Height: int + m_ImageCount: int + m_IsReadable: bool + m_LightmapFormat: int + m_Name: str + m_TextureDimension: int + m_TextureFormat: int + m_TextureSettings: GLTextureSettings + m_Width: int + m_ColorSpace: Optional[int] = None + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IgnoreMasterTextureLimit: Optional[bool] = None + m_IgnoreMipmapLimit: Optional[bool] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_IsPreProcessed: Optional[bool] = None + m_MipCount: Optional[int] = None + m_MipMap: Optional[bool] = None + m_MipmapLimitGroupName: Optional[str] = None + m_MipsStripped: Optional[int] = None + m_PlatformBlob: Optional[List[int]] = None + m_ReadAllowed: Optional[bool] = None + m_StreamData: Optional[StreamingInfo] = None + m_StreamingMipmaps: Optional[bool] = None + m_StreamingMipmapsPriority: Optional[int] = None + + @property + def image(self) -> Image: ... + def set_image( + self, + img: Union[Image, str, BinaryIO], + target_format: Optional[int] = None, + mipmap_count: int = 1, + ) -> None: ... + def get_image_data(self) -> bytes: ... diff --git a/UnityPy/classes/legacy_patch/Texture2DArray.py b/UnityPy/classes/legacy_patch/Texture2DArray.py new file mode 100644 index 000000000..e43039ded --- /dev/null +++ b/UnityPy/classes/legacy_patch/Texture2DArray.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from ...enums.GraphicsFormat import GRAPHICS_TO_TEXTURE_MAP, GraphicsFormat +from ..generated import Texture2DArray + +if TYPE_CHECKING: + from PIL import Image + + +def _Texture2DArray_get_images(self: Texture2DArray) -> List[Image.Image]: + from ...export import Texture2DConverter + from ...helpers.ResourceReader import get_resource_data + + texture_format = GRAPHICS_TO_TEXTURE_MAP.get(GraphicsFormat(self.m_Format)) + if not texture_format: + raise NotImplementedError(f"GraphicsFormat {self.m_Format} not supported yet") + + image_data = self.image_data + if image_data is None: + image_data = get_resource_data( + self.m_StreamData.path, + self.object_reader.assets_file, + self.m_StreamData.offset, + self.m_StreamData.size, + ) + + # calculate the number of textures in the array + texture_size = self.m_DataSize // self.m_Depth + + return [ + Texture2DConverter.parse_image_data( + image_data[offset : offset + texture_size], + self.m_Width, + self.m_Height, + texture_format, + self.object_reader.version, + 0, + None, + ) + for offset in range(0, self.m_DataSize, texture_size) + ] + + +Texture2DArray.images = property(_Texture2DArray_get_images) + +__all__ = ("Texture2DArray",) diff --git a/UnityPy/classes/legacy_patch/Texture2DArray.pyi b/UnityPy/classes/legacy_patch/Texture2DArray.pyi new file mode 100644 index 000000000..d790ecd0a --- /dev/null +++ b/UnityPy/classes/legacy_patch/Texture2DArray.pyi @@ -0,0 +1,29 @@ +from typing import List, Optional + +from PIL.Image import Image + +from UnityPy.classes.generated import GLTextureSettings, StreamingInfo, Texture + +class Texture2DArray(Texture): + image_data: bytes + m_ColorSpace: int + m_DataSize: int + m_Depth: int + m_Format: int + m_Height: int + m_IsReadable: bool + m_MipCount: int + m_Name: str + m_TextureSettings: GLTextureSettings + m_Width: int + m_DownscaleFallback: Optional[bool] = None + m_ForcedFallbackFormat: Optional[int] = None + m_IgnoreMipmapLimit: Optional[bool] = None + m_IsAlphaChannelOptional: Optional[bool] = None + m_MipmapLimitGroupName: Optional[str] = None + m_MipsStripped: Optional[int] = None + m_StreamData: Optional[StreamingInfo] = None + m_UsageMode: Optional[int] = None + + @property + def images(self) -> List[Image]: ... diff --git a/UnityPy/classes/legacy_patch/__init__.py b/UnityPy/classes/legacy_patch/__init__.py new file mode 100644 index 000000000..4e9159b9e --- /dev/null +++ b/UnityPy/classes/legacy_patch/__init__.py @@ -0,0 +1,19 @@ +from .AudioClip import AudioClip +from .GameObject import GameObject +from .Mesh import Mesh +from .Renderer import Renderer +from .Shader import Shader +from .Sprite import Sprite +from .Texture2D import Texture2D +from .Texture2DArray import Texture2DArray + +__all__ = [ + "AudioClip", + "GameObject", + "Mesh", + "Renderer", + "Shader", + "Sprite", + "Texture2D", + "Texture2DArray", +] diff --git a/UnityPy/classes/math.py b/UnityPy/classes/math.py new file mode 100644 index 000000000..525710e0a --- /dev/null +++ b/UnityPy/classes/math.py @@ -0,0 +1,124 @@ +""" +Definitions for math related classes. +As most calculations involving them are done in numpy, +we define them here as subtypes of np.ndarray, so that casting won't be necessary. +""" + +from attrs import define + + +@define(slots=True) +class Vector2f: + x: float = 0 + y: float = 0 + + def __repr__(self) -> str: + return f"Vector2f({self.x}, {self.y})" + + +@define(slots=True) +class Vector3f: + x: float = 0 + y: float = 0 + z: float = 0 + + def __repr__(self) -> str: + return f"Vector3f({self.x}, {self.y}, {self.z})" + + +@define(slots=True) +class Vector4f: + x: float = 0 + y: float = 0 + z: float = 0 + w: float = 0 + + def __repr__(self) -> str: + return f"Vector4f({self.x}, {self.y}, {self.z}, {self.w})" + + +float3 = Vector3f +float4 = Vector4f + + +class Quaternionf(Vector4f): + # TODO: Implement quaternion operations + def __repr__(self) -> str: + return f"Quaternion({self.x}, {self.y}, {self.z}, {self.w})" + + +@define(slots=True) +class Matrix3x4f: + e00: float + e01: float + e02: float + e03: float + e10: float + e11: float + e12: float + e13: float + e20: float + e21: float + e22: float + e23: float + + +@define(slots=True) +class Matrix4x4f: + e00: float + e01: float + e02: float + e03: float + e10: float + e11: float + e12: float + e13: float + e20: float + e21: float + e22: float + e23: float + e30: float + e31: float + e32: float + e33: float + + +@define(slots=True) +class ColorRGBA: + r: float = 0 + g: float = 0 + b: float = 0 + a: float = 1 + + def __init__(self, r: float = 0, g: float = 0, b: float = 0, a: float = 1, rgba: int = -1): + if rgba != -1: + r = ((rgba >> 24) & 0xFF) / 255 + g = ((rgba >> 16) & 0xFF) / 255 + b = ((rgba >> 8) & 0xFF) / 255 + a = (rgba & 0xFF) / 255 + # defined by attrs + self.__attrs_init__(r, g, b, a) # type: ignore + + @property + def rgba(self) -> int: + return int(self.r * 255) << 24 | int(self.g * 255) << 16 | int(self.b * 255) << 8 | int(self.a * 255) + + @rgba.setter + def rgba(self, value: int): + self.r = ((value >> 24) & 0xFF) / 255 + self.g = ((value >> 16) & 0xFF) / 255 + self.b = ((value >> 8) & 0xFF) / 255 + self.a = (value & 0xFF) / 255 + + +__all__ = ( + "Vector2f", + "Vector3f", + "Vector4f", + "Quaternionf", + "Matrix3x4f", + "Matrix4x4f", + "ColorRGBA", + "float3", + "float4", +) diff --git a/UnityPy/cli/__init__.py b/UnityPy/cli/__init__.py new file mode 100644 index 000000000..500815a9f --- /dev/null +++ b/UnityPy/cli/__init__.py @@ -0,0 +1,28 @@ +from argparse import ArgumentParser + +from UnityPy.cli.update_tpk import update_tpk + + +def main(): + parser = ArgumentParser( + prog="UnityPy", + description="UnityPy cli utility", + ) + + subparsers = parser.add_subparsers(title="utils") + + update_tpk_parser = subparsers.add_parser("update_tpk", help="Updates TPK (typetree dump) file") + update_tpk_parser.set_defaults(func=update_tpk) + + args = parser.parse_args() + + if not hasattr(args, "func"): + parser.print_help() + return + + operands = getattr(args, "operands", []) + args.func(*operands) + + +if __name__ == "__main__": + main() diff --git a/UnityPy/cli/update_tpk.py b/UnityPy/cli/update_tpk.py new file mode 100644 index 000000000..f2a512731 --- /dev/null +++ b/UnityPy/cli/update_tpk.py @@ -0,0 +1,29 @@ +import os + +from tpk_ar.utils import download_tpk + +RESOURCE_PATH = os.path.join(os.path.dirname(__file__), "..", "resources") + + +def update_tpk(): + print("Updating TPK file...") + print("\tDownloading...") + tpk_data = download_tpk() + + print("\tSaving...") + with open(os.path.join(RESOURCE_PATH, "lzma.tpk"), "wb") as f: + f.write(tpk_data) + + print("\tGenerating classes...") + # import here to avoid loading a potentially broken or missing tpk file + + from UnityPy.tools.TpkClassGenerator import generate_classes + + generate_classes() + print("\tDone.") + + +__all__ = ["update_tpk"] + +if __name__ == "__main__": + update_tpk() diff --git a/UnityPy/config.py b/UnityPy/config.py new file mode 100644 index 000000000..98c5660b5 --- /dev/null +++ b/UnityPy/config.py @@ -0,0 +1,40 @@ +import warnings + +from .exceptions import UnityVersionFallbackError, UnityVersionFallbackWarning + +FALLBACK_UNITY_VERSION = None +"""The Unity version to use when no version is defined + by the SerializedFile or its BundleFile. + + You may manually configure this value to a version string, e.g. `2.5.0f5`. +""" + +SERIALIZED_FILE_PARSE_TYPETREE = True +"""Determines if the typetree structures for the Object types will be parsed. + + Disabling this will reduce the load time by a lot (half of the time is spend on parsing the typetrees), + but it will also prevent saving an edited file. +""" + + +# WARNINGS CONTROL +warnings.simplefilter("once", UnityVersionFallbackWarning) + + +# GET FUNCTIONS +def get_fallback_version(): + global FALLBACK_UNITY_VERSION + + if not isinstance(FALLBACK_UNITY_VERSION, str): + raise UnityVersionFallbackError( + "No valid Unity version found, and the fallback version is not correctly configured. " + + "Please explicitly set the value of UnityPy.config.FALLBACK_UNITY_VERSION." + ) + + warnings.warn( + f"No valid Unity version found, defaulting to UnityPy.config.FALLBACK_UNITY_VERSION ({FALLBACK_UNITY_VERSION})", # noqa: E501 + category=UnityVersionFallbackWarning, + stacklevel=2, + ) + + return FALLBACK_UNITY_VERSION diff --git a/UnityPy/enums/Audio.py b/UnityPy/enums/Audio.py index 726837ab8..e97655829 100644 --- a/UnityPy/enums/Audio.py +++ b/UnityPy/enums/Audio.py @@ -2,31 +2,31 @@ class AudioType(IntEnum): - UNKNOWN = (0,) - ACC = (1,) - AIFF = (2,) - IT = (10,) - MOD = (12,) - MPEG = (13,) - OGGVORBIS = (14,) - S3M = (17,) - WAV = (20,) - XM = (21,) - XMA = (22,) - VAG = (23,) + UNKNOWN = 0 + ACC = 1 + AIFF = 2 + IT = 10 + MOD = 12 + MPEG = 13 + OGGVORBIS = 14 + S3M = 17 + WAV = 20 + XM = 21 + XMA = 22 + VAG = 23 AUDIOQUEUE = 24 class AudioCompressionFormat(IntEnum): - PCM = (0,) - Vorbis = (1,) - ADPCM = (2,) - MP3 = (3,) - VAG = (4,) - HEVAG = (5,) - XMA = (6,) - AAC = (7,) - GCADPCM = (8,) + PCM = 0 + Vorbis = 1 + ADPCM = 2 + MP3 = 3 + VAG = 4 + HEVAG = 5 + XMA = 6 + AAC = 7 + GCADPCM = 8 ATRAC9 = 9 diff --git a/UnityPy/enums/BuildTarget.py b/UnityPy/enums/BuildTarget.py index bd45942d4..d3e092cdd 100644 --- a/UnityPy/enums/BuildTarget.py +++ b/UnityPy/enums/BuildTarget.py @@ -1,44 +1,40 @@ -from enum import IntEnum +from .ExtendableEnum import ExtendableEnum -class BuildTarget(IntEnum): - UnknownPlatform = (3716,) - DashboardWidget = (1,) - StandaloneOSX = (2,) - StandaloneOSXPPC = (3,) - StandaloneOSXIntel = (4,) - StandaloneWindows = (5,) - WebPlayer = (6,) - WebPlayerStreamed = (7,) - Wii = (8,) - iOS = (9,) - PS3 = (10,) - XBOX360 = (11,) - Android = (13,) - StandaloneGLESEmu = (14,) - NaCl = (16,) - StandaloneLinux = (17,) - FlashPlayer = (18,) - StandaloneWindows64 = (19,) - WebGL = (20,) - WSAPlayer = (21,) - StandaloneLinux64 = (24,) - StandaloneLinuxUniversal = (25,) - WP8Player = (26,) - StandaloneOSXIntel64 = (27,) - BlackBerry = (28,) - Tizen = (29,) - PSP2 = (30,) - PS4 = (31,) - PSM = (32,) - XboxOne = (33,) - SamsungTV = (34,) - N3DS = (35,) - WiiU = (36,) - tvOS = (37,) - Switch = (38,) +class BuildTarget(ExtendableEnum): + UnknownPlatform = 3716 + DashboardWidget = 1 + StandaloneOSX = 2 + StandaloneOSXPPC = 3 + StandaloneOSXIntel = 4 + StandaloneWindows = 5 + WebPlayer = 6 + WebPlayerStreamed = 7 + Wii = 8 + iOS = 9 + PS3 = 10 + XBOX360 = 11 + Android = 13 + StandaloneGLESEmu = 14 + NaCl = 16 + StandaloneLinux = 17 + FlashPlayer = 18 + StandaloneWindows64 = 19 + WebGL = 20 + WSAPlayer = 21 + StandaloneLinux64 = 24 + StandaloneLinuxUniversal = 25 + WP8Player = 26 + StandaloneOSXIntel64 = 27 + BlackBerry = 28 + Tizen = 29 + PSP2 = 30 + PS4 = 31 + PSM = 32 + XboxOne = 33 + SamsungTV = 34 + N3DS = 35 + WiiU = 36 + tvOS = 37 + Switch = 38 NoTarget = -2 - - @classmethod - def _missing_(cls, value): - return BuildTarget.UnknownPlatform diff --git a/UnityPy/enums/BundleFile.py b/UnityPy/enums/BundleFile.py new file mode 100644 index 000000000..d72fdb162 --- /dev/null +++ b/UnityPy/enums/BundleFile.py @@ -0,0 +1,26 @@ +from enum import IntFlag + + +class CompressionFlags(IntFlag): + NONE = 0 + LZMA = 1 + LZ4 = 2 + LZ4HC = 3 + LZHAM = 4 + + +class ArchiveFlagsOld(IntFlag): + CompressionTypeMask = 0x3F + BlocksAndDirectoryInfoCombined = 0x40 + BlocksInfoAtTheEnd = 0x80 + OldWebPluginCompatibility = 0x100 + UsesAssetBundleEncryption = 0x200 + + +class ArchiveFlags(IntFlag): + CompressionTypeMask = 0x3F + BlocksAndDirectoryInfoCombined = 0x40 + BlocksInfoAtTheEnd = 0x80 + OldWebPluginCompatibility = 0x100 + BlockInfoNeedPaddingAtStart = 0x200 + UsesAssetBundleEncryption = 0x1400 # old: 0x400, new: 0x1000 diff --git a/UnityPy/enums/ClassIDType.py b/UnityPy/enums/ClassIDType.py index 5b09c168b..9168fb8da 100644 --- a/UnityPy/enums/ClassIDType.py +++ b/UnityPy/enums/ClassIDType.py @@ -1,297 +1,373 @@ -from enum import IntEnum +# https://docs.unity3d.com/Manual/ClassIDReference.html +from .ExtendableEnum import ExtendableEnum -class ClassIDType(IntEnum): - UnknownType = (-1,) - Object = (0,) - GameObject = (1,) - Component = (2,) - LevelGameManager = (3,) - Transform = (4,) - TimeManager = (5,) - GlobalGameManager = (6,) - Behaviour = (8,) - GameManager = (9,) - AudioManager = (11,) - ParticleAnimator = (12,) - InputManager = (13,) - EllipsoidParticleEmitter = (15,) - Pipeline = (17,) - EditorExtension = (18,) - Physics2DSettings = (19,) - Camera = (20,) - Material = (21,) - MeshRenderer = (23,) - Renderer = (25,) - ParticleRenderer = (26,) - Texture = (27,) - Texture2D = (28,) - SceneSettings = (29,) - GraphicsSettings = (30,) - MeshFilter = (33,) - OcclusionPortal = (41,) - Mesh = (43,) - Skybox = (45,) - QualitySettings = (47,) - Shader = (48,) - TextAsset = (49,) - Rigidbody2D = (50,) - Physics2DManager = (51,) - Collider2D = (53,) - Rigidbody = (54,) - PhysicsManager = (55,) - Collider = (56,) - Joint = (57,) - CircleCollider2D = (58,) - HingeJoint = (59,) - PolygonCollider2D = (60,) - BoxCollider2D = (61,) - PhysicsMaterial2D = (62,) - MeshCollider = (64,) - BoxCollider = (65,) - SpriteCollider2D = (66,) - EdgeCollider2D = (68,) - CapsuleCollider2D = (70,) - ComputeShader = (72,) - AnimationClip = (74,) - ConstantForce = (75,) - WorldParticleCollider = (76,) - TagManager = (78,) - AudioListener = (81,) - AudioSource = (82,) - AudioClip = (83,) - RenderTexture = (84,) - CustomRenderTexture = (86,) - MeshParticleEmitter = (87,) - ParticleEmitter = (88,) - Cubemap = (89,) - Avatar = (90,) - AnimatorController = (91,) - GUILayer = (92,) - RuntimeAnimatorController = (93,) - ScriptMapper = (94,) - Animator = (95,) - TrailRenderer = (96,) - DelayedCallManager = (98,) - TextMesh = (102,) - RenderSettings = (104,) - Light = (108,) - CGProgram = (109,) - BaseAnimationTrack = (110,) - Animation = (111,) - MonoBehaviour = (114,) - MonoScript = (115,) - MonoManager = (116,) - Texture3D = (117,) - NewAnimationTrack = (118,) - Projector = (119,) - LineRenderer = (120,) - Flare = (121,) - Halo = (122,) - LensFlare = (123,) - FlareLayer = (124,) - HaloLayer = (125,) - NavMeshAreas = (126,) - HaloManager = (127,) - Font = (128,) - PlayerSettings = (129,) - NamedObject = (130,) - GUITexture = (131,) - GUIText = (132,) - GUIElement = (133,) - PhysicMaterial = (134,) - SphereCollider = (135,) - CapsuleCollider = (136,) - SkinnedMeshRenderer = (137,) - FixedJoint = (138,) - RaycastCollider = (140,) - BuildSettings = (141,) - AssetBundle = (142,) - CharacterController = (143,) - CharacterJoint = (144,) - SpringJoint = (145,) - WheelCollider = (146,) - ResourceManager = (147,) - NetworkView = (148,) - NetworkManager = (149,) - PreloadData = (150,) - MovieTexture = (152,) - ConfigurableJoint = (153,) - TerrainCollider = (154,) - MasterServerInterface = (155,) - TerrainData = (156,) - LightmapSettings = (157,) - WebCamTexture = (158,) - EditorSettings = (159,) - InteractiveCloth = (160,) - ClothRenderer = (161,) - EditorUserSettings = (162,) - SkinnedCloth = (163,) - AudioReverbFilter = (164,) - AudioHighPassFilter = (165,) - AudioChorusFilter = (166,) - AudioReverbZone = (167,) - AudioEchoFilter = (168,) - AudioLowPassFilter = (169,) - AudioDistortionFilter = (170,) - SparseTexture = (171,) - AudioBehaviour = (180,) - AudioFilter = (181,) - WindZone = (182,) - Cloth = (183,) - SubstanceArchive = (184,) - ProceduralMaterial = (185,) - ProceduralTexture = (186,) - Texture2DArray = (187,) - CubemapArray = (188,) - OffMeshLink = (191,) - OcclusionArea = (192,) - Tree = (193,) - NavMeshObsolete = (194,) - NavMeshAgent = (195,) - NavMeshSettings = (196,) - LightProbesLegacy = (197,) - ParticleSystem = (198,) - ParticleSystemRenderer = (199,) - ShaderVariantCollection = (200,) - LODGroup = (205,) - BlendTree = (206,) - Motion = (207,) - NavMeshObstacle = (208,) - TerrainInstance = (210,) - SpriteRenderer = (212,) - Sprite = (213,) - CachedSpriteAtlas = (214,) - ReflectionProbe = (215,) - ReflectionProbes = (216,) - Terrain = (218,) - LightProbeGroup = (220,) - AnimatorOverrideController = (221,) - CanvasRenderer = (222,) - Canvas = (223,) - RectTransform = (224,) - CanvasGroup = (225,) - BillboardAsset = (226,) - BillboardRenderer = (227,) - SpeedTreeWindAsset = (228,) - AnchoredJoint2D = (229,) - Joint2D = (230,) - SpringJoint2D = (231,) - DistanceJoint2D = (232,) - HingeJoint2D = (233,) - SliderJoint2D = (234,) - WheelJoint2D = (235,) - ClusterInputManager = (236,) - BaseVideoTexture = (237,) - NavMeshData = (238,) - AudioMixer = (240,) - AudioMixerController = (241,) - AudioMixerGroupController = (243,) - AudioMixerEffectController = (244,) - AudioMixerSnapshotController = (245,) - PhysicsUpdateBehaviour2D = (246,) - ConstantForce2D = (247,) - Effector2D = (248,) - AreaEffector2D = (249,) - PointEffector2D = (250,) - PlatformEffector2D = (251,) - SurfaceEffector2D = (252,) - BuoyancyEffector2D = (253,) - RelativeJoint2D = (254,) - FixedJoint2D = (255,) - FrictionJoint2D = (256,) - TargetJoint2D = (257,) - LightProbes = (258,) - LightProbeProxyVolume = (259,) - SampleClip = (271,) - AudioMixerSnapshot = (272,) - AudioMixerGroup = (273,) - NScreenBridge = (280,) - AssetBundleManifest = (290,) - UnityAdsManager = (292,) - RuntimeInitializeOnLoadManager = (300,) - CloudWebServicesManager = (301,) - UnityAnalyticsManager = (303,) - CrashReportManager = (304,) - PerformanceReportingManager = (305,) - UnityConnectSettings = (310,) - AvatarMask = (319,) - VideoPlayer = (328,) - VideoClip = (329,) - OcclusionCullingData = (363,) - # kLargestRuntimeClassID = 364 - SmallestEditorClassID = (1000,) - Prefab = (1001,) - EditorExtensionImpl = (1002,) - AssetImporter = (1003,) - AssetDatabase = (1004,) - Mesh3DSImporter = (1005,) - TextureImporter = (1006,) - ShaderImporter = (1007,) - ComputeShaderImporter = (1008,) - AudioImporter = (1020,) - HierarchyState = (1026,) - GUIDSerializer = (1027,) - AssetMetaData = (1028,) - DefaultAsset = (1029,) - DefaultImporter = (1030,) - TextScriptImporter = (1031,) - SceneAsset = (1032,) - NativeFormatImporter = (1034,) - MonoImporter = (1035,) - AssetServerCache = (1037,) - LibraryAssetImporter = (1038,) - ModelImporter = (1040,) - FBXImporter = (1041,) - TrueTypeFontImporter = (1042,) - MovieImporter = (1044,) - EditorBuildSettings = (1045,) - DDSImporter = (1046,) - InspectorExpandedState = (1048,) - AnnotationManager = (1049,) - PluginImporter = (1050,) - EditorUserBuildSettings = (1051,) - PVRImporter = (1052,) - ASTCImporter = (1053,) - KTXImporter = (1054,) - IHVImageFormatImporter = (1055,) - AnimatorStateTransition = (1101,) - AnimatorState = (1102,) - HumanTemplate = (1105,) - AnimatorStateMachine = (1107,) - PreviewAssetType = (1108,) - AnimatorTransition = (1109,) - SpeedTreeImporter = (1110,) - AnimatorTransitionBase = (1111,) - SubstanceImporter = (1112,) - LightmapParameters = (1113,) - LightmapSnapshot = (1120,) - GISRaster = (1121,) - GISRasterImporter = (1122,) - CadImporter = (1123,) - SketchUpImporter = (1124,) - BuildReport = (1125,) - PackedAssets = (1126,) - VideoClipImporter = (1127,) - ActivationLogComponent = (2000,) - # kLargestEditorClassID = 2001 - # kClassIdOutOfHierarchy = 100000 - SubDerived = (367388927,) - SiblingDerived = (334799969,) - SpriteAtlas = (687078895,) - Derived = (1091556383,) - LowerResBlitTexture = (1480428607,) +class ClassIDType(ExtendableEnum): + UnknownType = -1 + Object = 0 + GameObject = 1 + Component = 2 + LevelGameManager = 3 + Transform = 4 + TimeManager = 5 + GlobalGameManager = 6 + Behaviour = 8 + GameManager = 9 + AudioManager = 11 + ParticleAnimator = 12 + InputManager = 13 + EllipsoidParticleEmitter = 15 + Pipeline = 17 + EditorExtension = 18 + Physics2DSettings = 19 + Camera = 20 + Material = 21 + MeshRenderer = 23 + Renderer = 25 + ParticleRenderer = 26 + Texture = 27 + Texture2D = 28 + OcclusionCullingSettings = 29 + GraphicsSettings = 30 + MeshFilter = 33 + OcclusionPortal = 41 + Mesh = 43 + Skybox = 45 + QualitySettings = 47 + Shader = 48 + TextAsset = 49 + Rigidbody2D = 50 + Physics2DManager = 51 + Collider2D = 53 + Rigidbody = 54 + PhysicsManager = 55 + Collider = 56 + Joint = 57 + CircleCollider2D = 58 + HingeJoint = 59 + PolygonCollider2D = 60 + BoxCollider2D = 61 + PhysicsMaterial2D = 62 + MeshCollider = 64 + BoxCollider = 65 + CompositeCollider2D = 66 + EdgeCollider2D = 68 + CapsuleCollider2D = 70 + ComputeShader = 72 + AnimationClip = 74 + ConstantForce = 75 + WorldParticleCollider = 76 + TagManager = 78 + AudioListener = 81 + AudioSource = 82 + AudioClip = 83 + RenderTexture = 84 + CustomRenderTexture = 86 + MeshParticleEmitter = 87 + ParticleEmitter = 88 + Cubemap = 89 + Avatar = 90 + AnimatorController = 91 + GUILayer = 92 + RuntimeAnimatorController = 93 + ScriptMapper = 94 + Animator = 95 + TrailRenderer = 96 + DelayedCallManager = 98 + TextMesh = 102 + RenderSettings = 104 + Light = 108 + CGProgram = 109 + BaseAnimationTrack = 110 + Animation = 111 + MonoBehaviour = 114 + MonoScript = 115 + MonoManager = 116 + Texture3D = 117 + NewAnimationTrack = 118 + Projector = 119 + LineRenderer = 120 + Flare = 121 + Halo = 122 + LensFlare = 123 + FlareLayer = 124 + HaloLayer = 125 + NavMeshProjectSettings = 126 + HaloManager = 127 + Font = 128 + PlayerSettings = 129 + NamedObject = 130 + GUITexture = 131 + GUIText = 132 + GUIElement = 133 + PhysicMaterial = 134 + SphereCollider = 135 + CapsuleCollider = 136 + SkinnedMeshRenderer = 137 + FixedJoint = 138 + RaycastCollider = 140 + BuildSettings = 141 + AssetBundle = 142 + CharacterController = 143 + CharacterJoint = 144 + SpringJoint = 145 + WheelCollider = 146 + ResourceManager = 147 + NetworkView = 148 + NetworkManager = 149 + PreloadData = 150 + MovieTexture = 152 + ConfigurableJoint = 153 + TerrainCollider = 154 + MasterServerInterface = 155 + TerrainData = 156 + LightmapSettings = 157 + WebCamTexture = 158 + EditorSettings = 159 + InteractiveCloth = 160 + ClothRenderer = 161 + EditorUserSettings = 162 + SkinnedCloth = 163 + AudioReverbFilter = 164 + AudioHighPassFilter = 165 + AudioChorusFilter = 166 + AudioReverbZone = 167 + AudioEchoFilter = 168 + AudioLowPassFilter = 169 + AudioDistortionFilter = 170 + SparseTexture = 171 + AudioBehaviour = 180 + AudioFilter = 181 + WindZone = 182 + Cloth = 183 + SubstanceArchive = 184 + ProceduralMaterial = 185 + ProceduralTexture = 186 + Texture2DArray = 187 + CubemapArray = 188 + OffMeshLink = 191 + OcclusionArea = 192 + Tree = 193 + NavMeshObsolete = 194 + NavMeshAgent = 195 + NavMeshSettings = 196 + LightProbesLegacy = 197 + ParticleSystem = 198 + ParticleSystemRenderer = 199 + ShaderVariantCollection = 200 + LODGroup = 205 + BlendTree = 206 + Motion = 207 + NavMeshObstacle = 208 + SortingGroup = 210 + SpriteRenderer = 212 + Sprite = 213 + CachedSpriteAtlas = 214 + ReflectionProbe = 215 + ReflectionProbes = 216 + Terrain = 218 + LightProbeGroup = 220 + AnimatorOverrideController = 221 + CanvasRenderer = 222 + Canvas = 223 + RectTransform = 224 + CanvasGroup = 225 + BillboardAsset = 226 + BillboardRenderer = 227 + SpeedTreeWindAsset = 228 + AnchoredJoint2D = 229 + Joint2D = 230 + SpringJoint2D = 231 + DistanceJoint2D = 232 + HingeJoint2D = 233 + SliderJoint2D = 234 + WheelJoint2D = 235 + ClusterInputManager = 236 + BaseVideoTexture = 237 + NavMeshData = 238 + AudioMixer = 240 + AudioMixerController = 241 + AudioMixerGroupController = 243 + AudioMixerEffectController = 244 + AudioMixerSnapshotController = 245 + PhysicsUpdateBehaviour2D = 246 + ConstantForce2D = 247 + Effector2D = 248 + AreaEffector2D = 249 + PointEffector2D = 250 + PlatformEffector2D = 251 + SurfaceEffector2D = 252 + BuoyancyEffector2D = 253 + RelativeJoint2D = 254 + FixedJoint2D = 255 + FrictionJoint2D = 256 + TargetJoint2D = 257 + LightProbes = 258 + LightProbeProxyVolume = 259 + SampleClip = 271 + AudioMixerSnapshot = 272 + AudioMixerGroup = 273 + NScreenBridge = 280 + AssetBundleManifest = 290 + UnityAdsManager = 292 + RuntimeInitializeOnLoadManager = 300 + CloudWebServicesManager = 301 + UnityAnalyticsManager = 303 + CrashReportManager = 304 + PerformanceReportingManager = 305 + UnityConnectSettings = 310 + AvatarMask = 319 + PlayableDirector = 320 + VideoPlayer = 328 + VideoClip = 329 + ParticleSystemForceField = 330 + SpriteMask = 331 + WorldAnchor = 362 + OcclusionCullingData = 363 + SmallestEditorClassID = 1000 + PrefabInstance = 1001 + EditorExtensionImpl = 1002 + AssetImporter = 1003 + AssetDatabaseV1 = 1004 + Mesh3DSImporter = 1005 + TextureImporter = 1006 + ShaderImporter = 1007 + ComputeShaderImporter = 1008 + AudioImporter = 1020 + HierarchyState = 1026 + GUIDSerializer = 1027 + AssetMetaData = 1028 + DefaultAsset = 1029 + DefaultImporter = 1030 + TextScriptImporter = 1031 + SceneAsset = 1032 + NativeFormatImporter = 1034 + MonoImporter = 1035 + AssetServerCache = 1037 + LibraryAssetImporter = 1038 + ModelImporter = 1040 + FBXImporter = 1041 + TrueTypeFontImporter = 1042 + MovieImporter = 1044 + EditorBuildSettings = 1045 + DDSImporter = 1046 + InspectorExpandedState = 1048 + AnnotationManager = 1049 + PluginImporter = 1050 + EditorUserBuildSettings = 1051 + PVRImporter = 1052 + ASTCImporter = 1053 + KTXImporter = 1054 + IHVImageFormatImporter = 1055 + AnimatorStateTransition = 1101 + AnimatorState = 1102 + HumanTemplate = 1105 + AnimatorStateMachine = 1107 + PreviewAnimationClip = 1108 + AnimatorTransition = 1109 + SpeedTreeImporter = 1110 + AnimatorTransitionBase = 1111 + SubstanceImporter = 1112 + LightmapParameters = 1113 + LightingDataAsset = 1120 + GISRaster = 1121 + GISRasterImporter = 1122 + CadImporter = 1123 + SketchUpImporter = 1124 + BuildReport = 1125 + PackedAssets = 1126 + VideoClipImporter = 1127 + ActivationLogComponent = 2000 + int = 100000 + bool = 100001 + float = 100002 + MonoObject = 100003 + Collision = 100004 + Vector3f = 100005 + RootMotionData = 100006 + Collision2D = 100007 + AudioMixerLiveUpdateFloat = 100008 + AudioMixerLiveUpdateBool = 100009 + Polygon2D = 100010 + void = 100011 + TilemapCollider2D = 19719996 + AssetImporterLog = 41386430 + VFXRenderer = 73398921 + SerializableManagedRefTestClass = 76251197 + Grid = 156049354 + ScenesUsingAssets = 156483287 + ArticulationBody = 171741748 + Preset = 181963792 + EmptyObject = 277625683 + IConstraint = 285090594 + TestObjectWithSpecialLayoutOne = 293259124 + AssemblyDefinitionReferenceImporter = 294290339 + SiblingDerived = 334799969 + TestObjectWithSerializedMapStringNonAlignedStruct = 342846651 + SubDerived = 367388927 + AssetImportInProgressProxy = 369655926 + PluginBuildInfo = 382020655 + EditorProjectAccess = 426301858 + PrefabImporter = 468431735 + TestObjectWithSerializedArray = 478637458 + TestObjectWithSerializedAnimationCurve = 478637459 + TilemapRenderer = 483693784 + ScriptableCamera = 488575907 + SpriteAtlasAsset = 612988286 + SpriteAtlasDatabase = 638013454 + AudioBuildInfo = 641289076 + CachedSpriteAtlasRuntimeData = 644342135 + RendererFake = 646504946 + AssemblyDefinitionReferenceAsset = 662584278 + BuiltAssetBundleInfoSet = 668709126 + SpriteAtlas = 687078895 + RayTracingShaderImporter = 747330370 + RayTracingShader = 825902497 + LightingSettings = 850595691 + PlatformModuleSetup = 877146078 + VersionControlSettings = 890905787 + AimConstraint = 895512359 + VFXManager = 937362698 + VisualEffectSubgraph = 994735392 + VisualEffectSubgraphOperator = 994735403 + VisualEffectSubgraphBlock = 994735404 + LocalizationImporter = 1027052791 + Derived = 1091556383 + PropertyModificationsTargetTestObject = 1111377672 + ReferencesArtifactGenerator = 1114811875 + AssemblyDefinitionAsset = 1152215463 + SceneVisibilityState = 1154873562 + LookAtConstraint = 1183024399 + SpriteAtlasImporter = 1210832254 + MultiArtifactTestImporter = 1223240404 + GameObjectRecorder = 1268269756 + LightingDataAssetParent = 1325145578 + PresetManager = 1386491679 + TestObjectWithSpecialLayoutTwo = 1392443030 + StreamingManager = 1403656975 + LowerResBlitTexture = 1480428607 + StreamingController = 1542919678 RenderPassAttachment = 1571458007 - - def __str__(self): - return self.name - - def __eq__(self, value): - if isinstance(value, str): - return self.name == value - else: - return self.value == value - - @classmethod - def _missing_(cls, value): - return ClassIDType.UnknownType + TestObjectVectorPairStringBool = 1628831178 + GridLayout = 1742807556 + AssemblyDefinitionImporter = 1766753193 + ParentConstraint = 1773428102 + FakeComponent = 1803986026 + PositionConstraint = 1818360608 + RotationConstraint = 1818360609 + ScaleConstraint = 1818360610 + Tilemap = 1839735485 + PackageManifest = 1896753125 + PackageManifestImporter = 1896753126 + TerrainLayer = 1953259897 + SpriteShapeRenderer = 1971053207 + NativeObjectType = 1977754360 + TestObjectWithSerializedMapStringBool = 1981279845 + SerializableManagedHost = 1995898324 + VisualEffectAsset = 2058629509 + VisualEffectImporter = 2058629510 + VisualEffectResource = 2058629511 + VisualEffectObject = 2059678085 + VisualEffect = 2083052967 + LocalizationAsset = 2083778819 + ScriptedImporter = 2089858483 diff --git a/UnityPy/enums/ExtendableEnum.py b/UnityPy/enums/ExtendableEnum.py new file mode 100644 index 000000000..3e061cf4f --- /dev/null +++ b/UnityPy/enums/ExtendableEnum.py @@ -0,0 +1,19 @@ +from enum import IntEnum + +# based on https://stackoverflow.com/a/57179436 + + +class ExtendableEnum(IntEnum): + @classmethod + def _missing_(cls, value): + if isinstance(value, int): + pseudo_member = cls._value2member_map_.get(value, None) + if pseudo_member is None: + new_member = int.__new__(cls, value) + # I expect a name attribute to hold a string, hence str(value) + # However, new_member._name_ = value works, too + new_member._name_ = f"Unknown ({value})" + new_member._value_ = value + pseudo_member = cls._value2member_map_.setdefault(value, new_member) + return pseudo_member + return None # will raise the ValueError in Enum.__new__ diff --git a/UnityPy/enums/FileType.py b/UnityPy/enums/FileType.py index b5ae77afc..adfb2c965 100644 --- a/UnityPy/enums/FileType.py +++ b/UnityPy/enums/FileType.py @@ -1,7 +1,7 @@ -from enum import Enum +from enum import IntEnum -class FileType(Enum): +class FileType(IntEnum): AssetsFile = 0 BundleFile = 1 WebFile = 2 diff --git a/UnityPy/enums/GfxPrimitiveType.py b/UnityPy/enums/GfxPrimitiveType.py new file mode 100644 index 000000000..591a7a34a --- /dev/null +++ b/UnityPy/enums/GfxPrimitiveType.py @@ -0,0 +1,10 @@ +from enum import IntEnum + + +class GfxPrimitiveType(IntEnum): + kPrimitiveTriangles = 0 + kPrimitiveTriangleStrip = 1 + kPrimitiveQuads = 2 + kPrimitiveLines = 3 + kPrimitiveLineStrip = 4 + kPrimitivePoints = 5 diff --git a/UnityPy/enums/GraphicsFormat.py b/UnityPy/enums/GraphicsFormat.py new file mode 100644 index 000000000..07bb568c6 --- /dev/null +++ b/UnityPy/enums/GraphicsFormat.py @@ -0,0 +1,277 @@ +from enum import IntEnum + +from .TextureFormat import TextureFormat + + +class GraphicsFormat(IntEnum): + NONE = 0 + R8_SRGB = 1 + R8G8_SRGB = 2 + R8G8B8_SRGB = 3 + R8G8B8A8_SRGB = 4 + + R8_UNorm = 5 + R8G8_UNorm = 6 + R8G8B8_UNorm = 7 + R8G8B8A8_UNorm = 8 + + R8_SNorm = 9 + R8G8_SNorm = 10 + R8G8B8_SNorm = 11 + R8G8B8A8_SNorm = 12 + + R8_UInt = 13 + R8G8_UInt = 14 + R8G8B8_UInt = 15 + R8G8B8A8_UInt = 16 + + R8_SInt = 17 + R8G8_SInt = 18 + R8G8B8_SInt = 19 + R8G8B8A8_SInt = 20 + + R16_UNorm = 21 + R16G16_UNorm = 22 + R16G16B16_UNorm = 23 + R16G16B16A16_UNorm = 24 + + R16_SNorm = 25 + R16G16_SNorm = 26 + R16G16B16_SNorm = 27 + R16G16B16A16_SNorm = 28 + + R16_UInt = 29 + R16G16_UInt = 30 + R16G16B16_UInt = 31 + R16G16B16A16_UInt = 32 + + R16_SInt = 33 + R16G16_SInt = 34 + R16G16B16_SInt = 35 + R16G16B16A16_SInt = 36 + + R32_UInt = 37 + R32G32_UInt = 38 + R32G32B32_UInt = 39 + R32G32B32A32_UInt = 40 + + R32_SInt = 41 + R32G32_SInt = 42 + R32G32B32_SInt = 43 + R32G32B32A32_SInt = 44 + + R16_SFloat = 45 + R16G16_SFloat = 46 + R16G16B16_SFloat = 47 + R16G16B16A16_SFloat = 48 + R32_SFloat = 49 + R32G32_SFloat = 50 + R32G32B32_SFloat = 51 + R32G32B32A32_SFloat = 52 + + B8G8R8_SRGB = 56 + B8G8R8A8_SRGB = 57 + B8G8R8_UNorm = 58 + B8G8R8A8_UNorm = 59 + B8G8R8_SNorm = 60 + B8G8R8A8_SNorm = 61 + B8G8R8_UInt = 62 + B8G8R8A8_UInt = 63 + B8G8R8_SInt = 64 + B8G8R8A8_SInt = 65 + + R4G4B4A4_UNormPack16 = 66 + B4G4R4A4_UNormPack16 = 67 + R5G6B5_UNormPack16 = 68 + B5G6R5_UNormPack16 = 69 + R5G5B5A1_UNormPack16 = 70 + B5G5R5A1_UNormPack16 = 71 + A1R5G5B5_UNormPack16 = 72 + + E5B9G9R9_UFloatPack32 = 73 + B10G11R11_UFloatPack32 = 74 + + A2B10G10R10_UNormPack32 = 75 + A2B10G10R10_UIntPack32 = 76 + A2B10G10R10_SIntPack32 = 77 + A2R10G10B10_UNormPack32 = 78 + A2R10G10B10_UIntPack32 = 79 + A2R10G10B10_SIntPack32 = 80 + A2R10G10B10_XRSRGBPack32 = 81 + A2R10G10B10_XRUNormPack32 = 82 + R10G10B10_XRSRGBPack32 = 83 + R10G10B10_XRUNormPack32 = 84 + A10R10G10B10_XRSRGBPack32 = 85 + A10R10G10B10_XRUNormPack32 = 86 + + D16_UNorm = 90 + D24_UNorm = 91 + D24_UNorm_S8_UInt = 92 + D32_SFloat = 93 + D32_SFloat_S8_UInt = 94 + S8_UInt = 95 + + RGB_DXT1_SRGB = 96 + RGBA_DXT1_SRGB = 96 + RGB_DXT1_UNorm = 97 + RGBA_DXT1_UNorm = 97 + RGBA_DXT3_SRGB = 98 + RGBA_DXT3_UNorm = 99 + RGBA_DXT5_SRGB = 100 + RGBA_DXT5_UNorm = 101 + R_BC4_UNorm = 102 + R_BC4_SNorm = 103 + RG_BC5_UNorm = 104 + RG_BC5_SNorm = 105 + RGB_BC6H_UFloat = 106 + RGB_BC6H_SFloat = 107 + RGBA_BC7_SRGB = 108 + RGBA_BC7_UNorm = 109 + + RGB_PVRTC_2Bpp_SRGB = 110 + RGB_PVRTC_2Bpp_UNorm = 111 + RGB_PVRTC_4Bpp_SRGB = 112 + RGB_PVRTC_4Bpp_UNorm = 113 + RGBA_PVRTC_2Bpp_SRGB = 114 + RGBA_PVRTC_2Bpp_UNorm = 115 + RGBA_PVRTC_4Bpp_SRGB = 116 + RGBA_PVRTC_4Bpp_UNorm = 117 + + RGB_ETC_UNorm = 118 + RGB_ETC2_SRGB = 119 + RGB_ETC2_UNorm = 120 + RGB_A1_ETC2_SRGB = 121 + RGB_A1_ETC2_UNorm = 122 + RGBA_ETC2_SRGB = 123 + RGBA_ETC2_UNorm = 124 + + R_EAC_UNorm = 125 + R_EAC_SNorm = 126 + RG_EAC_UNorm = 127 + RG_EAC_SNorm = 128 + + RGBA_ASTC4X4_SRGB = 129 + RGBA_ASTC4X4_UNorm = 130 + RGBA_ASTC5X5_SRGB = 131 + RGBA_ASTC5X5_UNorm = 132 + RGBA_ASTC6X6_SRGB = 133 + RGBA_ASTC6X6_UNorm = 134 + RGBA_ASTC8X8_SRGB = 135 + RGBA_ASTC8X8_UNorm = 136 + RGBA_ASTC10X10_SRGB = 137 + RGBA_ASTC10X10_UNorm = 138 + RGBA_ASTC12X12_SRGB = 139 + RGBA_ASTC12X12_UNorm = 140 + + YUV2 = 141 + + RGBA_ASTC4X4_UFloat = 145 + RGBA_ASTC5X5_UFloat = 146 + RGBA_ASTC6X6_UFloat = 147 + RGBA_ASTC8X8_UFloat = 148 + RGBA_ASTC10X10_UFloat = 149 + RGBA_ASTC12X12_UFloat = 150 + + D16_UNorm_S8_UInt = 151 + + +# very experimental & untested +GRAPHICS_TO_TEXTURE_MAP = { + GraphicsFormat.R8_SRGB: TextureFormat.R8, + GraphicsFormat.R8G8_SRGB: TextureFormat.RG16, + GraphicsFormat.R8G8B8_SRGB: TextureFormat.RGB24, + GraphicsFormat.R8G8B8A8_SRGB: TextureFormat.RGBA32, + GraphicsFormat.R8_UNorm: TextureFormat.R8, + GraphicsFormat.R8G8_UNorm: TextureFormat.RG16, + GraphicsFormat.R8G8B8_UNorm: TextureFormat.RGB24, + GraphicsFormat.R8G8B8A8_UNorm: TextureFormat.RGBA32, + GraphicsFormat.R8_SNorm: TextureFormat.R8_SIGNED, + GraphicsFormat.R8G8_SNorm: TextureFormat.RG16_SIGNED, + GraphicsFormat.R8G8B8_SNorm: TextureFormat.RGB24_SIGNED, + GraphicsFormat.R8G8B8A8_SNorm: TextureFormat.RGBA32_SIGNED, + GraphicsFormat.R8_UInt: TextureFormat.R16, + GraphicsFormat.R8G8_UInt: TextureFormat.RG32, + GraphicsFormat.R8G8B8_UInt: TextureFormat.RGB48, + GraphicsFormat.R8G8B8A8_UInt: TextureFormat.RGBA64, + GraphicsFormat.R8_SInt: TextureFormat.R16_SIGNED, + GraphicsFormat.R8G8_SInt: TextureFormat.RG32_SIGNED, + GraphicsFormat.R8G8B8_SInt: TextureFormat.RGB48_SIGNED, + GraphicsFormat.R8G8B8A8_SInt: TextureFormat.RGBA64_SIGNED, + GraphicsFormat.R16_UNorm: TextureFormat.R16, + GraphicsFormat.R16G16_UNorm: TextureFormat.RG32, + GraphicsFormat.R16G16B16_UNorm: TextureFormat.RGB48, + GraphicsFormat.R16G16B16A16_UNorm: TextureFormat.RGBA64, + GraphicsFormat.R16_SNorm: TextureFormat.R16_SIGNED, + GraphicsFormat.R16G16_SNorm: TextureFormat.RG32_SIGNED, + GraphicsFormat.R16G16B16_SNorm: TextureFormat.RGB48_SIGNED, + GraphicsFormat.R16G16B16A16_SNorm: TextureFormat.RGBA64_SIGNED, + GraphicsFormat.R16_UInt: TextureFormat.R16, + GraphicsFormat.R16G16_UInt: TextureFormat.RG32, + GraphicsFormat.R16G16B16_UInt: TextureFormat.RGB48, + GraphicsFormat.R16G16B16A16_UInt: TextureFormat.RGBA64, + GraphicsFormat.R16_SInt: TextureFormat.R16_SIGNED, + GraphicsFormat.R16G16_SInt: TextureFormat.RG32_SIGNED, + GraphicsFormat.R16G16B16_SInt: TextureFormat.RGB48_SIGNED, + GraphicsFormat.R16G16B16A16_SInt: TextureFormat.RGBA64_SIGNED, + GraphicsFormat.B8G8R8_SRGB: TextureFormat.BGR24, + GraphicsFormat.B8G8R8A8_SRGB: TextureFormat.BGRA32, + GraphicsFormat.B8G8R8_UNorm: TextureFormat.BGR24, + GraphicsFormat.B8G8R8A8_UNorm: TextureFormat.BGRA32, + # GraphicsFormat.B8G8R8_SNorm: TextureFormat.BGR24_SIGNED, + # GraphicsFormat.B8G8R8A8_SNorm: TextureFormat.BGRA32_SIGNED, + # GraphicsFormat.B8G8R8_UInt: TextureFormat.BGR48, + GraphicsFormat.RGB_DXT1_SRGB: TextureFormat.DXT1, + GraphicsFormat.RGBA_DXT1_SRGB: TextureFormat.DXT1, + GraphicsFormat.RGB_DXT1_UNorm: TextureFormat.DXT1, + GraphicsFormat.RGBA_DXT1_UNorm: TextureFormat.DXT1, + GraphicsFormat.RGBA_DXT3_SRGB: TextureFormat.DXT3, + GraphicsFormat.RGBA_DXT3_UNorm: TextureFormat.DXT3, + GraphicsFormat.RGBA_DXT5_SRGB: TextureFormat.DXT5, + GraphicsFormat.RGBA_DXT5_UNorm: TextureFormat.DXT5, + GraphicsFormat.R_BC4_UNorm: TextureFormat.BC4, + # GraphicsFormat.R_BC4_SNorm: TextureFormat.BC4_SIGNED, + GraphicsFormat.RG_BC5_UNorm: TextureFormat.BC5, + # GraphicsFormat.RG_BC5_SNorm: TextureFormat.BC5_SIGNED, + GraphicsFormat.RGB_BC6H_UFloat: TextureFormat.BC6H, + # GraphicsFormat.RGB_BC6H_SFloat: TextureFormat.BC6H_SIGNED, + GraphicsFormat.RGBA_BC7_SRGB: TextureFormat.BC7, + GraphicsFormat.RGBA_BC7_UNorm: TextureFormat.BC7, + GraphicsFormat.RGB_PVRTC_2Bpp_SRGB: TextureFormat.PVRTC_RGB2, + GraphicsFormat.RGB_PVRTC_2Bpp_UNorm: TextureFormat.PVRTC_RGB2, + GraphicsFormat.RGB_PVRTC_4Bpp_SRGB: TextureFormat.PVRTC_RGB4, + GraphicsFormat.RGB_PVRTC_4Bpp_UNorm: TextureFormat.PVRTC_RGB4, + GraphicsFormat.RGBA_PVRTC_2Bpp_SRGB: TextureFormat.PVRTC_RGBA2, + GraphicsFormat.RGBA_PVRTC_2Bpp_UNorm: TextureFormat.PVRTC_RGBA2, + GraphicsFormat.RGBA_PVRTC_4Bpp_SRGB: TextureFormat.PVRTC_RGBA4, + GraphicsFormat.RGBA_PVRTC_4Bpp_UNorm: TextureFormat.PVRTC_RGBA4, + GraphicsFormat.RGB_ETC_UNorm: TextureFormat.ETC_RGB4, + GraphicsFormat.RGB_ETC2_SRGB: TextureFormat.ETC2_RGB, + GraphicsFormat.RGB_ETC2_UNorm: TextureFormat.ETC2_RGB, + GraphicsFormat.RGB_A1_ETC2_SRGB: TextureFormat.ETC2_RGBA1, + GraphicsFormat.RGB_A1_ETC2_UNorm: TextureFormat.ETC2_RGBA1, + GraphicsFormat.RGBA_ETC2_SRGB: TextureFormat.ETC2_RGBA8, + GraphicsFormat.RGBA_ETC2_UNorm: TextureFormat.ETC2_RGBA8, + GraphicsFormat.R_EAC_UNorm: TextureFormat.EAC_R, + GraphicsFormat.R_EAC_SNorm: TextureFormat.EAC_R_SIGNED, + GraphicsFormat.RG_EAC_UNorm: TextureFormat.EAC_RG, + GraphicsFormat.RG_EAC_SNorm: TextureFormat.EAC_RG_SIGNED, + GraphicsFormat.RGBA_ASTC4X4_SRGB: TextureFormat.ASTC_RGBA_4x4, + GraphicsFormat.RGBA_ASTC4X4_UNorm: TextureFormat.ASTC_RGBA_4x4, + GraphicsFormat.RGBA_ASTC5X5_SRGB: TextureFormat.ASTC_RGBA_5x5, + GraphicsFormat.RGBA_ASTC5X5_UNorm: TextureFormat.ASTC_RGBA_5x5, + GraphicsFormat.RGBA_ASTC6X6_SRGB: TextureFormat.ASTC_RGBA_6x6, + GraphicsFormat.RGBA_ASTC6X6_UNorm: TextureFormat.ASTC_RGBA_6x6, + GraphicsFormat.RGBA_ASTC8X8_SRGB: TextureFormat.ASTC_RGBA_8x8, + GraphicsFormat.RGBA_ASTC8X8_UNorm: TextureFormat.ASTC_RGBA_8x8, + GraphicsFormat.RGBA_ASTC10X10_SRGB: TextureFormat.ASTC_RGBA_10x10, + GraphicsFormat.RGBA_ASTC10X10_UNorm: TextureFormat.ASTC_RGBA_10x10, + GraphicsFormat.RGBA_ASTC12X12_SRGB: TextureFormat.ASTC_RGBA_12x12, + GraphicsFormat.RGBA_ASTC12X12_UNorm: TextureFormat.ASTC_RGBA_12x12, + GraphicsFormat.YUV2: TextureFormat.YUY2, + # GraphicsFormat.RGBA_ASTC4X4_UFloat: TextureFormat.ASTC_RGBA_4x4, + # GraphicsFormat.RGBA_ASTC5X5_UFloat: TextureFormat.ASTC_RGBA_5x5, + # GraphicsFormat.RGBA_ASTC6X6_UFloat: TextureFormat.ASTC_RGBA_6x6, + # GraphicsFormat.RGBA_ASTC8X8_UFloat: TextureFormat.ASTC_RGBA_8x8, + # GraphicsFormat.RGBA_ASTC10X10_UFloat: TextureFormat.ASTC_RGBA_10x10, + # GraphicsFormat.RGBA_ASTC12X12_UFloat: TextureFormat.ASTC_RGBA_12x12, +} diff --git a/UnityPy/enums/MeshTopology.py b/UnityPy/enums/MeshTopology.py new file mode 100644 index 000000000..0baaedd87 --- /dev/null +++ b/UnityPy/enums/MeshTopology.py @@ -0,0 +1,10 @@ +from enum import IntEnum + + +class MeshTopology(IntEnum): + Triangles = 0 + TriangleStrip = 1 # deprecated + Quads = 2 + Lines = 3 + LineStrip = 4 + Points = 5 diff --git a/UnityPy/enums/PassType.py b/UnityPy/enums/PassType.py new file mode 100644 index 000000000..1b2d1c2e4 --- /dev/null +++ b/UnityPy/enums/PassType.py @@ -0,0 +1,7 @@ +from enum import IntEnum + + +class PassType(IntEnum): + kPassTypeNormal = 0 + kPassTypeUse = 1 + kPassTypeGrab = 2 diff --git a/UnityPy/enums/SerializedPropertyType.py b/UnityPy/enums/SerializedPropertyType.py new file mode 100644 index 000000000..de7325b87 --- /dev/null +++ b/UnityPy/enums/SerializedPropertyType.py @@ -0,0 +1,9 @@ +from enum import IntEnum + + +class SerializedPropertyType(IntEnum): + kColor = 0 + kVector = 1 + kFloat = 2 + kRange = 3 + kTexture = 4 diff --git a/UnityPy/enums/ShaderCompilerPlatform.py b/UnityPy/enums/ShaderCompilerPlatform.py new file mode 100644 index 000000000..f1d075679 --- /dev/null +++ b/UnityPy/enums/ShaderCompilerPlatform.py @@ -0,0 +1,26 @@ +from enum import IntEnum + + +class ShaderCompilerPlatform(IntEnum): + kShaderCompPlatformNone = -1 + kShaderCompPlatformGL = 0 + kShaderCompPlatformD3D9 = 1 + kShaderCompPlatformXbox360 = 2 + kShaderCompPlatformPS3 = 3 + kShaderCompPlatformD3D11 = 4 + kShaderCompPlatformGLES20 = 5 + kShaderCompPlatformNaCl = 6 + kShaderCompPlatformFlash = 7 + kShaderCompPlatformD3D11_9x = 8 + kShaderCompPlatformGLES3Plus = 9 + kShaderCompPlatformPSP2 = 10 + kShaderCompPlatformPS4 = 11 + kShaderCompPlatformXboxOne = 12 + kShaderCompPlatformPSM = 13 + kShaderCompPlatformMetal = 14 + kShaderCompPlatformOpenGLCore = 15 + kShaderCompPlatformN3DS = 16 + kShaderCompPlatformWiiU = 17 + kShaderCompPlatformVulkan = 18 + kShaderCompPlatformSwitch = 19 + kShaderCompPlatformXboxOneD3D12 = 20 diff --git a/UnityPy/enums/ShaderGpuProgramType.py b/UnityPy/enums/ShaderGpuProgramType.py new file mode 100644 index 000000000..170bf307c --- /dev/null +++ b/UnityPy/enums/ShaderGpuProgramType.py @@ -0,0 +1,36 @@ +from enum import IntEnum + + +class ShaderGpuProgramType(IntEnum): + kShaderGpuProgramUnknown = 0 + kShaderGpuProgramGLLegacy = 1 + kShaderGpuProgramGLES31AEP = 2 + kShaderGpuProgramGLES31 = 3 + kShaderGpuProgramGLES3 = 4 + kShaderGpuProgramGLES = 5 + kShaderGpuProgramGLCore32 = 6 + kShaderGpuProgramGLCore41 = 7 + kShaderGpuProgramGLCore43 = 8 + kShaderGpuProgramDX9VertexSM20 = 9 + kShaderGpuProgramDX9VertexSM30 = 10 + kShaderGpuProgramDX9PixelSM20 = 11 + kShaderGpuProgramDX9PixelSM30 = 12 + kShaderGpuProgramDX10Level9Vertex = 13 + kShaderGpuProgramDX10Level9Pixel = 14 + kShaderGpuProgramDX11VertexSM40 = 15 + kShaderGpuProgramDX11VertexSM50 = 16 + kShaderGpuProgramDX11PixelSM40 = 17 + kShaderGpuProgramDX11PixelSM50 = 18 + kShaderGpuProgramDX11GeometrySM40 = 19 + kShaderGpuProgramDX11GeometrySM50 = 20 + kShaderGpuProgramDX11HullSM50 = 21 + kShaderGpuProgramDX11DomainSM50 = 22 + kShaderGpuProgramMetalVS = 23 + kShaderGpuProgramMetalFS = 24 + kShaderGpuProgramSPIRV = 25 + kShaderGpuProgramConsoleVS = 26 + kShaderGpuProgramConsoleFS = 27 + kShaderGpuProgramConsoleHS = 28 + kShaderGpuProgramConsoleDS = 29 + kShaderGpuProgramConsoleGS = 30 + kShaderGpuProgramRayTracing = 31 diff --git a/UnityPy/enums/SpriteMeshType.py b/UnityPy/enums/SpriteMeshType.py index 6c2720619..75c282c43 100644 --- a/UnityPy/enums/SpriteMeshType.py +++ b/UnityPy/enums/SpriteMeshType.py @@ -1,6 +1,6 @@ -from enum import Enum +from enum import IntEnum -class SpriteMeshType(Enum): +class SpriteMeshType(IntEnum): kSpriteMeshTypeFullRect = 0 kSpriteMeshTypeTight = 1 diff --git a/UnityPy/enums/SpritePackingMode.py b/UnityPy/enums/SpritePackingMode.py new file mode 100644 index 000000000..e640576e6 --- /dev/null +++ b/UnityPy/enums/SpritePackingMode.py @@ -0,0 +1,6 @@ +from enum import IntEnum + + +class SpritePackingMode(IntEnum): + kSPMTight = 0 + kSPMRectangle = 1 diff --git a/UnityPy/enums/SpritePackingRotation.py b/UnityPy/enums/SpritePackingRotation.py new file mode 100644 index 000000000..1a361de14 --- /dev/null +++ b/UnityPy/enums/SpritePackingRotation.py @@ -0,0 +1,9 @@ +from enum import IntEnum + + +class SpritePackingRotation(IntEnum): + kSPRNone = 0 + kSPRFlipHorizontal = 1 + kSPRFlipVertical = 2 + kSPRRotate180 = 3 + kSPRRotate90 = 4 diff --git a/UnityPy/enums/TextureDimension.py b/UnityPy/enums/TextureDimension.py new file mode 100644 index 000000000..044e26262 --- /dev/null +++ b/UnityPy/enums/TextureDimension.py @@ -0,0 +1,13 @@ +from enum import IntEnum + + +class TextureDimension(IntEnum): + kTexDimUnknown = -1 + kTexDimNone = 0 + kTexDimAny = 1 + kTexDim2D = 2 + kTexDim3D = 3 + kTexDimCUBE = 4 + kTexDim2DArray = 5 + kTexDimCubeArray = 6 + kTexDimForce32Bit = 2147483647 diff --git a/UnityPy/enums/TextureFormat.py b/UnityPy/enums/TextureFormat.py index 308856b0d..68d5dc992 100644 --- a/UnityPy/enums/TextureFormat.py +++ b/UnityPy/enums/TextureFormat.py @@ -2,66 +2,81 @@ class TextureFormat(IntEnum): - Alpha8 = (1,) - ARGB4444 = (2,) - RGB24 = (3,) - RGBA32 = (4,) - ARGB32 = (5,) - RGB565 = (7,) - R16 = (9,) - DXT1 = (10,) - DXT5 = (12,) - RGBA4444 = (13,) - BGRA32 = (14,) - RHalf = (15,) - RGHalf = (16,) - RGBAHalf = (17,) - RFloat = (18,) - RGFloat = (19,) - RGBAFloat = (20,) - YUY2 = (21,) - RGB9e5Float = (22,) - BC4 = (26,) - BC5 = (27,) - BC6H = (24,) - BC7 = (25,) - DXT1Crunched = (28,) - DXT5Crunched = (29,) - PVRTC_RGB2 = (30,) - PVRTC_RGBA2 = (31,) - PVRTC_RGB4 = (32,) - PVRTC_RGBA4 = (33,) - ETC_RGB4 = (34,) - ATC_RGB4 = (35,) - ATC_RGBA8 = (36,) - EAC_R = (41,) - EAC_R_SIGNED = (42,) - EAC_RG = (43,) - EAC_RG_SIGNED = (44,) - ETC2_RGB = (45,) - ETC2_RGBA1 = (46,) - ETC2_RGBA8 = (47,) - ASTC_RGB_4x4 = (48,) - ASTC_RGB_5x5 = (49,) - ASTC_RGB_6x6 = (50,) - ASTC_RGB_8x8 = (51,) - ASTC_RGB_10x10 = (52,) - ASTC_RGB_12x12 = (53,) - ASTC_RGBA_4x4 = (54,) - ASTC_RGBA_5x5 = (55,) - ASTC_RGBA_6x6 = (56,) - ASTC_RGBA_8x8 = (57,) - ASTC_RGBA_10x10 = (58,) - ASTC_RGBA_12x12 = (59,) - ETC_RGB4_3DS = (60,) - ETC_RGBA8_3DS = (61,) - RG16 = (62,) - R8 = (63,) - ETC_RGB4Crunched = (64,) - ETC2_RGBA8Crunched = (65,) - ASTC_HDR_4x4 = (66,) - ASTC_HDR_5x5 = (67,) - ASTC_HDR_6x6 = (68,) - ASTC_HDR_8x8 = (69,) - ASTC_HDR_10x10 = (70,) - ASTC_HDR_12x12 = (71,) + Alpha8 = 1 + ARGB4444 = 2 + RGB24 = 3 + RGBA32 = 4 + ARGB32 = 5 + ARGBFloat = 6 + RGB565 = 7 + BGR24 = 8 + R16 = 9 + DXT1 = 10 + DXT3 = 11 + DXT5 = 12 + RGBA4444 = 13 + BGRA32 = 14 + RHalf = 15 + RGHalf = 16 + RGBAHalf = 17 + RFloat = 18 + RGFloat = 19 + RGBAFloat = 20 + YUY2 = 21 + RGB9e5Float = 22 + RGBFloat = 23 + BC6H = 24 + BC7 = 25 + BC4 = 26 + BC5 = 27 + DXT1Crunched = 28 + DXT5Crunched = 29 + PVRTC_RGB2 = 30 + PVRTC_RGBA2 = 31 + PVRTC_RGB4 = 32 + PVRTC_RGBA4 = 33 + ETC_RGB4 = 34 + ATC_RGB4 = 35 + ATC_RGBA8 = 36 + EAC_R = 41 + EAC_R_SIGNED = 42 + EAC_RG = 43 + EAC_RG_SIGNED = 44 + ETC2_RGB = 45 + ETC2_RGBA1 = 46 + ETC2_RGBA8 = 47 + ASTC_RGB_4x4 = 48 + ASTC_RGB_5x5 = 49 + ASTC_RGB_6x6 = 50 + ASTC_RGB_8x8 = 51 + ASTC_RGB_10x10 = 52 + ASTC_RGB_12x12 = 53 + ASTC_RGBA_4x4 = 54 + ASTC_RGBA_5x5 = 55 + ASTC_RGBA_6x6 = 56 + ASTC_RGBA_8x8 = 57 + ASTC_RGBA_10x10 = 58 + ASTC_RGBA_12x12 = 59 + ETC_RGB4_3DS = 60 + ETC_RGBA8_3DS = 61 + RG16 = 62 + R8 = 63 + ETC_RGB4Crunched = 64 + ETC2_RGBA8Crunched = 65 + ASTC_HDR_4x4 = 66 + ASTC_HDR_5x5 = 67 + ASTC_HDR_6x6 = 68 + ASTC_HDR_8x8 = 69 + ASTC_HDR_10x10 = 70 + ASTC_HDR_12x12 = 71 + RG32 = 72 + RGB48 = 73 + RGBA64 = 74 + R8_SIGNED = 75 + RG16_SIGNED = 76 + RGB24_SIGNED = 77 + RGBA32_SIGNED = 78 + R16_SIGNED = 79 + RG32_SIGNED = 80 + RGB48_SIGNED = 81 + RGBA64_SIGNED = 82 diff --git a/UnityPy/enums/VertexFormat.py b/UnityPy/enums/VertexFormat.py new file mode 100644 index 000000000..ea3350a1a --- /dev/null +++ b/UnityPy/enums/VertexFormat.py @@ -0,0 +1,80 @@ +from enum import IntEnum + + +class VertexChannelFormat(IntEnum): + kChannelFormatFloat = 0 + kChannelFormatFloat16 = 1 + kChannelFormatColor = 2 + kChannelFormatByte = 3 + kChannelFormatUInt32 = 4 + + +class VertexFormat2017(IntEnum): + kVertexFormatFloat = 0 + kVertexFormatFloat16 = 1 + kVertexFormatColor = 2 + kVertexFormatUNorm8 = 3 + kVertexFormatSNorm8 = 4 + kVertexFormatUNorm16 = 5 + kVertexFormatSNorm16 = 6 + kVertexFormatUInt8 = 7 + kVertexFormatSInt8 = 8 + kVertexFormatUInt16 = 9 + kVertexFormatSInt16 = 10 + kVertexFormatUInt32 = 11 + kVertexFormatSInt32 = 12 + + +class VertexFormat(IntEnum): + kVertexFormatFloat = 0 + kVertexFormatFloat16 = 1 + kVertexFormatUNorm8 = 2 + kVertexFormatSNorm8 = 3 + kVertexFormatUNorm16 = 4 + kVertexFormatSNorm16 = 5 + kVertexFormatUInt8 = 6 + kVertexFormatSInt8 = 7 + kVertexFormatUInt16 = 8 + kVertexFormatSInt16 = 9 + kVertexFormatUInt32 = 10 + kVertexFormatSInt32 = 11 + + +VERTEX_CHANNEL_FORMAT_STRUCT_TYPE_MAP = { + VertexChannelFormat.kChannelFormatFloat: "f", + VertexChannelFormat.kChannelFormatFloat16: "e", + VertexChannelFormat.kChannelFormatColor: "B", + VertexChannelFormat.kChannelFormatByte: "B", + VertexChannelFormat.kChannelFormatUInt32: "I", +} + +VERTEX_FORMAT_2017_STRUCT_TYPE_MAP = { + VertexFormat2017.kVertexFormatFloat: "f", + VertexFormat2017.kVertexFormatFloat16: "e", + VertexFormat2017.kVertexFormatColor: "B", + VertexFormat2017.kVertexFormatUNorm8: "B", + VertexFormat2017.kVertexFormatSNorm8: "b", + VertexFormat2017.kVertexFormatUNorm16: "H", + VertexFormat2017.kVertexFormatSNorm16: "h", + VertexFormat2017.kVertexFormatUInt8: "B", + VertexFormat2017.kVertexFormatSInt8: "b", + VertexFormat2017.kVertexFormatUInt16: "H", + VertexFormat2017.kVertexFormatSInt16: "h", + VertexFormat2017.kVertexFormatUInt32: "I", + VertexFormat2017.kVertexFormatSInt32: "i", +} + +VERTEX_FORMAT_STRUCT_TYPE_MAP = { + VertexFormat.kVertexFormatFloat: "f", + VertexFormat.kVertexFormatFloat16: "e", + VertexFormat.kVertexFormatUNorm8: "B", + VertexFormat.kVertexFormatSNorm8: "b", + VertexFormat.kVertexFormatUNorm16: "H", + VertexFormat.kVertexFormatSNorm16: "h", + VertexFormat.kVertexFormatUInt8: "B", + VertexFormat.kVertexFormatSInt8: "b", + VertexFormat.kVertexFormatUInt16: "H", + VertexFormat.kVertexFormatSInt16: "h", + VertexFormat.kVertexFormatUInt32: "I", + VertexFormat.kVertexFormatSInt32: "i", +} diff --git a/UnityPy/enums/__init__.py b/UnityPy/enums/__init__.py index 9e78b43a4..c9a99d83e 100644 --- a/UnityPy/enums/__init__.py +++ b/UnityPy/enums/__init__.py @@ -1,6 +1,39 @@ -from .Audio import AudioType, AudioCompressionFormat, AUDIO_TYPE_EXTEMSION +from .Audio import AUDIO_TYPE_EXTEMSION, AudioCompressionFormat, AudioType from .BuildTarget import BuildTarget +from .BundleFile import ArchiveFlags, ArchiveFlagsOld, CompressionFlags from .ClassIDType import ClassIDType from .FileType import FileType -from .TextureFormat import TextureFormat +from .GfxPrimitiveType import GfxPrimitiveType +from .GraphicsFormat import GraphicsFormat +from .PassType import PassType +from .SerializedPropertyType import SerializedPropertyType +from .ShaderCompilerPlatform import ShaderCompilerPlatform +from .ShaderGpuProgramType import ShaderGpuProgramType from .SpriteMeshType import SpriteMeshType +from .SpritePackingMode import SpritePackingMode +from .SpritePackingRotation import SpritePackingRotation +from .TextureDimension import TextureDimension +from .TextureFormat import TextureFormat + +__all__ = [ + "AUDIO_TYPE_EXTEMSION", + "AudioCompressionFormat", + "AudioType", + "BuildTarget", + "ArchiveFlags", + "ArchiveFlagsOld", + "CompressionFlags", + "ClassIDType", + "FileType", + "GfxPrimitiveType", + "GraphicsFormat", + "PassType", + "SerializedPropertyType", + "ShaderCompilerPlatform", + "ShaderGpuProgramType", + "SpriteMeshType", + "SpritePackingMode", + "SpritePackingRotation", + "TextureDimension", + "TextureFormat", +] diff --git a/UnityPy/environment.py b/UnityPy/environment.py index 49b87b298..c4a60aea2 100644 --- a/UnityPy/environment.py +++ b/UnityPy/environment.py @@ -1,117 +1,363 @@ import io +import ntpath import os +import re +from typing import TYPE_CHECKING, BinaryIO, Callable, Dict, List, Optional, Union, cast from zipfile import ZipFile -from . import files +from fsspec import AbstractFileSystem +from fsspec.implementations.local import LocalFileSystem + from .enums import FileType -from .helpers import ImportHelper +from .files import BundleFile, File, ObjectReader, SerializedFile, WebFile +from .helpers.ContainerHelper import ContainerHelper +from .helpers.ImportHelper import ( + FileSourceType, + check_file_type, + find_sensitive_path, + parse_file, +) +from .streams import EndianBinaryReader + +if TYPE_CHECKING: + from UnityPy.helpers.TypeTreeGenerator import TypeTreeGenerator + +reSplit = re.compile(r"(.*?([^\/\\]+?))\.split\d+") class Environment: - files: dict - resources: dict - container: dict - assets: dict + files: Dict[str, Union[SerializedFile, BundleFile, WebFile, EndianBinaryReader]] + cabs: Dict[str, Union[SerializedFile, EndianBinaryReader]] path: str + local_files: List[str] + local_files_simple: List[str] + typetree_generator: Optional["TypeTreeGenerator"] = None + _container_index_built: bool = False - def __init__(self, *args): + def __init__(self, *args: FileSourceType, fs: Optional[AbstractFileSystem] = None, path: Optional[str] = None): self.files = {} - self.container = {} - self.assets = {} - self.resources = {} - self.path = "." + self.cabs = {} + self.fs = fs or LocalFileSystem() + self.local_files = [] + self.local_files_simple = [] + self._container_index_built = False + + if path is None: + # if no path is given, use the current working directory + if isinstance(self.fs, LocalFileSystem): + self.path = os.getcwd() + else: + self.path = "" + else: + self.path = path if args: for arg in args: if isinstance(arg, str): - if os.path.isfile(arg): - if os.path.splitext(arg)[-1] in [".apk", ".zip"]: + if self.fs.isfile(arg): + if ntpath.splitext(arg)[-1] in [".apk", ".zip"]: self.load_zip_file(arg) else: - self.path = os.path.dirname(arg) - self.load_file(arg) - elif os.path.isdir(arg): + self.path = ntpath.dirname(arg) or ntpath.curdir + if reSplit.match(arg): + self.load_files([arg]) + else: + self.load_file(arg) + elif self.fs.isdir(arg): self.path = arg self.load_folder(arg) else: - self.path = None - self.load_file(data=arg) + self.load_file(file=arg) if len(self.files) == 1: self.file = list(self.files.values())[0] - def load_files(self, files: list): - """Loads all files (list) into the AssetsManager and merges .split files for common usage.""" - path = os.path.dirname(files[0]) - ImportHelper.merge_split_assets(path) - to_read_file = ImportHelper.processing_split_files(files) - self.load(to_read_file) + def load_files(self, files: List[str]): + """Loads all files (list) into the Environment and merges .split files for common usage.""" + self.load_assets(files, lambda x: open(x, "rb")) def load_folder(self, path: str): - """Loads all files in the given path and its subdirs into the AssetsManager.""" - ImportHelper.merge_split_assets(path, True) - files = ImportHelper.list_all_files(path) - to_read_file = ImportHelper.processing_split_files(files) - self.load(to_read_file) - - def load(self, files: list): - """Loads all files into the AssetsManager.""" - # for f in files: - # self.import_files[os.path.basename(f)] = f - - # self.Progress.reset() - # use a for loop because list size can change - # for i, f in enumerate(self.import_files.values()): - for f in files: - self.load_file(f) - # self.Progress.report(i + 1, len(self.import_files)) - - def load_file(self, full_name: str = "", data=None): - typ, reader = ImportHelper.check_file_type(data if data else full_name) - if not full_name: - full_name = str(data[:256]) - if typ == FileType.AssetsFile: - self.files[full_name] = files.SerializedFile(reader, self) - self.assets[full_name] = self.files[full_name] - elif typ == FileType.BundleFile: - self.files[full_name] = files.BundleFile(reader, self) - elif typ == FileType.WebFile: - self.files[full_name] = files.WebFile(reader, self) - elif typ == FileType.ZIP: - self.load_zip_file(reader.stream) - elif typ == FileType.ResourceFile: - self.resources[os.path.basename(full_name)] = reader + """Loads all files in the given path and its subdirs into the Environment.""" + self.load_files([self.fs.sep.join([root, f]) for root, dirs, files in self.fs.walk(path) for f in files]) + + def load(self, files: List[str]): + """Loads all files into the Environment.""" + self.files.update( + {ntpath.basename(f): self.load_file(self.fs.open(f, "rb"), self, f) for f in files if self.fs.exists(f)} + ) + + def _load_split_file(self, basename: str) -> bytes: + file: List[bytes] = [] + for i in range(0, 999): + item = f"{basename}.split{i}" + if self.fs.exists(item): + with self.fs.open(item, "rb") as f: + file.append(f.read()) # type: ignore + elif i: + break + return b"".join(file) + + def load_file( + self, + file: FileSourceType, + parent: Optional[Union["Environment", File]] = None, + name: Optional[str] = None, + is_dependency: bool = False, + ): + if not parent: + parent = self + + if isinstance(file, str): + split_match = reSplit.match(file) + if split_match: + basepath, _basename = split_match.groups() + assert isinstance(basepath, str) + name = basepath + file = self._load_split_file(basepath) + else: + name = file + if not os.path.exists(file): + # relative paths are in the asset directory, not the cwd + if not os.path.isabs(file): + file = os.path.join(self.path, file) + # for dependency loading of split files + if os.path.exists(f"{file}.split0"): + file = self._load_split_file(file) + # Unity paths are case insensitive, + # so we need to find "Resources/Foo.asset" when the record says "resources/foo.asset" + elif not os.path.exists(file): + file_path = find_sensitive_path(self.path, file) + if file_path: + file = file_path + else: + return None + # raise FileNotFoundError(f"File {file} not found in {self.path}") + + if isinstance(file, str): + file = self.fs.open(file, "rb") + + typ, reader = check_file_type(file) + + stream_name = ( + name + if name + else getattr( + file, + "name", + str(file.__hash__()) if hasattr(file, "__hash__") else "", # type: ignore + ) + ) + + if typ == FileType.ZIP: + f = self.load_zip_file(file) + else: + f = parse_file(reader, self, name=stream_name, typ=typ, is_dependency=is_dependency) + + if isinstance(f, (SerializedFile, EndianBinaryReader)): + self.register_cab(stream_name, f) + + self.files[stream_name] = f + return f def load_zip_file(self, value): - buffer = None - if isinstance(value, str) and os.path.exists(value): - buffer = open(value, "rb") - elif isinstance(value, (bytes, bytearray)): - buffer = ZipFile(io.BytesIO(value)) + if isinstance(value, str) and self.fs.exists(value): + buffer = cast(io.BufferedReader, self.fs.open(value, "rb")) + elif isinstance(value, (bytes, bytearray, memoryview)): + buffer = io.BytesIO(value) elif isinstance(value, (io.BufferedReader, io.BufferedIOBase)): buffer = value + else: + raise TypeError("Unsupported type for loading zip file") z = ZipFile(buffer) + self.load_assets(z.namelist(), lambda x: z.open(x, "r")) # type: ignore + z.close() - for path in z.namelist(): - data = z.open(path).read() - if data: - self.load_file(path, data) + def save(self, pack="none", out_path="output"): + """Saves all changed assets. + Mark assets as changed using `.mark_changed()`. + pack = "none" (default) or "lz4" + """ + for fname, fitem in self.files.items(): + if getattr(fitem, "is_changed", False): + with open(self.fs.sep.join([out_path, ntpath.basename(fname)]), "wb") as out: + out.write(fitem.save(packer=pack)) @property - def objects(self): + def objects(self) -> List[ObjectReader]: + """Returns a list of all objects in the Environment.""" + def search(item): ret = [] if not isinstance(item, Environment) and getattr(item, "objects", None): # serialized file + if getattr(item, "is_dependency", False): + return [] return [val for val in item.objects.values()] elif getattr(item, "files", None): # WebBundle and BundleFile # bundle - for item in item.files.values(): - ret.extend(search(item)) + for sub_item in item.files.values(): + ret.extend(search(sub_item)) return ret return ret return search(self) + + def _build_container_index(self) -> None: + if self._container_index_built: + return + + self._container_index_built = True + for f in self.cabs.values(): + if isinstance(f, SerializedFile): + f.container.parse_preload_table() + + @property + def container(self) -> ContainerHelper: + """Returns a dictionary of all objects in the Environment.""" + self._build_container_index() + container = [] + for f in self.cabs.values(): + if isinstance(f, SerializedFile) and not f.is_dependency: + container.extend(f.container.container) + + return ContainerHelper(container) + + @property + def assets(self) -> list: + """ + Lists all assets / SerializedFiles within this environment. + """ + + def gen_all_asset_files(file, ret: Optional[list] = None): + if ret is None: + ret = [] + + for f in getattr(file, "files", {}).values(): + if getattr(f, "is_dependency", False): + continue + if isinstance(f, SerializedFile): + ret.append(f) + else: + gen_all_asset_files(f, ret) + return ret + + return gen_all_asset_files(self) + + def get(self, key: str, default=None): + return getattr(self, key, default) + + def register_cab(self, name: str, item: Union[SerializedFile, EndianBinaryReader]) -> None: + """ + Registers a cab file. + + Parameters + ---------- + name : str + The name of the cab file. + item : File + The file to register. + """ + self.cabs[simplify_name(name)] = item + self._container_index_built = False + + def get_cab(self, name: str) -> Union[SerializedFile, EndianBinaryReader, None]: + """ + Returns the cab file with the given name. + + Parameters + ---------- + name : str + The name of the cab file. + + Returns + ------- + File + The cab file. + """ + return self.cabs.get(simplify_name(name), None) + + def load_assets(self, assets: List[str], open_f: Callable[[str], BinaryIO]): + """ + Load all assets from a list of files via the given open_f function. + + Parameters + ---------- + assets : List[str] + List of files to load. + open_f : Callable[[str], io.IOBase] + Function to open the files. + The function takes a file path and returns an io.IOBase object. + """ + split_files = [] + for path in assets: + splitMatch = reSplit.match(path) + if splitMatch: + basepath, _basename = splitMatch.groups() + + if basepath in split_files: + continue + + split_files.append(basepath) + data = self._load_split_file(basepath) + path = basepath + else: + data = open_f(path) + self.load_file(data, name=path) + + def find_file(self, name: str, is_dependency: bool = True) -> Union[File, None]: + """ + Finds a file in the environment. + + Parameters + ---------- + name : str + The name of the file. + is_dependency : bool + Whether the file is a dependency. + + Returns + ------- + File | None + The file if it was found, otherwise None. + """ + simple_name = simplify_name(name) + cab = self.get_cab(simple_name) + if cab: + return cab + fp = self.fs.sep.join([self.path, name]) + if self.fs.exists(fp): + return self.load_file(fp, name=name, is_dependency=is_dependency) + + if len(self.local_files) == 0 and self.path: + for root, _, files in self.fs.walk(self.path): + for f in files: + self.local_files.append(self.fs.sep.join([root, f])) + self.local_files_simple.append(self.fs.sep.join([root, simplify_name(f)])) + + if name in self.local_files: + fp = name + elif simple_name in self.local_files_simple: + fp = self.local_files[self.local_files_simple.index(simple_name)] + else: + fp = next((f for f in self.local_files if f.endswith(name)), None) + if not fp: + fp = next( + (f for f in self.local_files_simple if f.endswith(simple_name)), + None, + ) + if not fp: + raise FileNotFoundError(f"File {name} not found in {self.path}") + + return self.load_file(fp, name=name, is_dependency=is_dependency) + + +def simplify_name(name: str) -> str: + """Simplifies a name by: + - removing the extension + - removing the path + - converting to lowercase + """ + return ntpath.basename(name).lower() diff --git a/UnityPy/exceptions.py b/UnityPy/exceptions.py new file mode 100644 index 000000000..86bd144d0 --- /dev/null +++ b/UnityPy/exceptions.py @@ -0,0 +1,14 @@ +class TypeTreeError(Exception): + def __init__(self, message, nodes): + super().__init__(message) + self.nodes = nodes + + +class UnityVersionFallbackError(Exception): + def __init__(self, message): + super().__init__(message) + + +class UnityVersionFallbackWarning(UserWarning): + def __init__(self, message): + super().__init__(message) diff --git a/UnityPy/export/AudioClipConverter.py b/UnityPy/export/AudioClipConverter.py index 59793242c..c381d9943 100644 --- a/UnityPy/export/AudioClipConverter.py +++ b/UnityPy/export/AudioClipConverter.py @@ -1,37 +1,49 @@ -try: - from fsb5 import FSB5 -except ImportError: - print("Couldn't import fsb5.\nfsb5 is required to process convert audio clips.") - - -def extract_audioclip_samples(audio) -> dict: - """extracts all the sample data from an AudioClip - Copied from unitypack - https://github.com/HearthSim/UnityPack/blob/d9ce99fac3c917fa44b0042c9114b7cd03aa9884/unitypack/utils.py#L14 - - - :param audio: AudioClip - :type audio: AudioClip - :return: {filename : sample(bytes)} - :rtype: dict - """ - ret = {} - - if not audio.m_AudioData: - # eg. StreamedResource not available - return {} - - af = FSB5(audio.m_AudioData) - for i, sample in enumerate(af.samples): - if i > 0: - filename = "%s-%i.%s" % (audio.name, i, af.get_sample_extension()) - else: - filename = "%s.%s" % (audio.name, af.get_sample_extension()) - try: - sample = af.rebuild_sample(sample) - except ValueError as e: - print("WARNING: Could not extract %r (%s)" % (audio, e)) - continue - ret[filename] = sample - - return ret +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict + +import fmod_toolkit + +from ..helpers.ResourceReader import get_resource_data + +if TYPE_CHECKING: + from ..classes import AudioClip + + +def extract_audioclip_samples(audio: AudioClip, convert_pcm_float: bool = True) -> Dict[str, bytes]: + """extracts all the samples from an AudioClip + :param audio: AudioClip + :type audio: AudioClip + :return: {filename : sample(bytes)} + :rtype: dict + """ + audio_data: bytes + if audio.m_AudioData: + audio_data = bytes(audio.m_AudioData) + elif audio.m_Resource: + assert audio.object_reader is not None, "AudioClip uses an external resource but object_reader is not set" + resource = audio.m_Resource + audio_data = get_resource_data( + resource.m_Source, + audio.object_reader.assets_file, + resource.m_Offset, + resource.m_Size, + ) + else: + raise ValueError("AudioClip with neither m_AudioData nor m_Resource") + + magic = memoryview(audio_data)[:8] + if magic[:4] == b"OggS": + return {f"{audio.m_Name}.ogg": audio_data} + elif magic[:4] == b"RIFF": + return {f"{audio.m_Name}.wav": audio_data} + elif magic[4:8] == b"ftyp": + return {f"{audio.m_Name}.m4a": audio_data} + + return fmod_toolkit.raw_to_wav( + audio_data, + audio.m_Name, + audio.m_Channels or 2, + audio.m_Frequency or 44100, + convert_pcm_float=convert_pcm_float, + ) diff --git a/UnityPy/export/MeshExporter.py b/UnityPy/export/MeshExporter.py new file mode 100644 index 000000000..f61e3e729 --- /dev/null +++ b/UnityPy/export/MeshExporter.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Optional + +from ..helpers.MeshHelper import MeshHandler + +if TYPE_CHECKING: + from ..classes.generated import Mesh + + +def export_mesh(m_Mesh: Mesh, format: str = "obj") -> str: + if format == "obj": + return export_mesh_obj(m_Mesh) + raise NotImplementedError(f"Export format {format} not implemented") + + +def export_mesh_obj(mesh: Mesh, material_names: Optional[List[str]] = None) -> str: + handler = MeshHandler(mesh) + handler.process() + + m_Mesh = handler + if m_Mesh.m_VertexCount <= 0: + return False + + sb = [f"g {mesh.m_Name}\n"] + if material_names: + sb.append(f"mtllib {mesh.m_Name}.mtl\n") + # region Vertices + if not m_Mesh.m_Vertices: + return False + + sb.extend( + "v {0:.9G} {1:.9G} {2:.9G}\n".format(-pos[0], pos[1], pos[2]).replace("nan", "0") for pos in m_Mesh.m_Vertices + ) + # endregion + + # region UV + if m_Mesh.m_UV0: + sb.extend("vt {0:.9G} {1:.9G}\n".format(uv[0], uv[1]).replace("nan", "0") for uv in m_Mesh.m_UV0) + # endregion + + # region Normals + if m_Mesh.m_Normals: + sb.extend( + "vn {0:.9G} {1:.9G} {2:.9G}\n".format(-n[0], n[1], n[2]).replace("nan", "0") for n in m_Mesh.m_Normals + ) + # endregion + + # region Face + for i, triangles in enumerate(m_Mesh.get_triangles()): + sb.append(f"g {mesh.m_Name}_{i}\n") + if material_names and i < len(material_names) and material_names[i]: + sb.append(f"usemtl {material_names[i]}\n") + sb.extend("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n".format(c + 1, b + 1, a + 1) for a, b, c in triangles) + # endregion + return "".join(sb) diff --git a/UnityPy/export/MeshRendererExporter.py b/UnityPy/export/MeshRendererExporter.py new file mode 100644 index 000000000..138d9cf2e --- /dev/null +++ b/UnityPy/export/MeshRendererExporter.py @@ -0,0 +1,189 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Optional + +from ..classes.generated import SkinnedMeshRenderer +from .MeshExporter import export_mesh_obj + +if TYPE_CHECKING: + from ..classes import ( + Material, + Mesh, + MeshFilter, + PPtr, + Renderer, + StaticBatchInfo, + Texture2D, + ) + + class Renderer(Renderer): + m_Materials: Optional[list[PPtr[Material]]] = None + m_StaticBatchInfo: Optional[StaticBatchInfo] = None + m_SubsetIndices: Optional[List[int]] = None + + +def get_mesh(meshR: Renderer) -> Optional[Mesh]: + if isinstance(meshR, SkinnedMeshRenderer): + if meshR.m_Mesh: + return meshR.m_Mesh.deref_parse_as_object() + else: + m_GameObject = meshR.m_GameObject.deref_parse_as_object() + for comp in m_GameObject.m_Component: + if isinstance(comp, tuple): + pptr = comp[1] + else: + pptr = comp.component + if not pptr: + continue + obj = pptr.deref() + if not obj: + continue + if obj.type.name == "MeshFilter": + filter: MeshFilter = pptr.deref_parse_as_object() + if filter.m_Mesh: + return filter.m_Mesh.deref_parse_as_object() + return None + + +def export_mesh_renderer(renderer: Renderer, export_dir: str) -> None: + env = renderer.object_reader.assets_file.environment + env.fs.makedirs(export_dir, exist_ok=True) + mesh = get_mesh(renderer) + if not mesh: + return + + firstSubMesh = 0 + if hasattr(renderer, "m_StaticBatchInfo") and renderer.m_StaticBatchInfo.subMeshCount > 0: + firstSubMesh = renderer.m_StaticBatchInfo.firstSubMesh + elif hasattr(renderer, "m_SubsetIndices"): + firstSubMesh = min(renderer.m_SubsetIndices) + + materials = [] + material_names = [] + for i in range(len(mesh.m_SubMeshes)): + mat_index = i - firstSubMesh + if mat_index < 0 or mat_index >= len(renderer.m_Materials): + continue + matPtr: Optional[PPtr[Material]] = renderer.m_Materials[i - firstSubMesh] + if matPtr: + mat: Material = matPtr.deref_parse_as_object() + else: + material_names.append(None) + continue + materials.append(export_material(mat)) + material_names.append(mat.m_Name) + # save material textures + for key, texEnv in mat.m_SavedProperties.m_TexEnvs: + if not texEnv.m_Texture: + continue + if not isinstance(key, str): + # FastPropertyName + key = key.name + tex: Texture2D = texEnv.m_Texture.deref_parse_as_object() + texName = f"{tex.m_Name if tex.m_Name else key}.png" + with env.fs.open(env.fs.sep.join([export_dir, texName]), "wb") as f: + tex.image.save(f) + + # save .obj + with env.fs.open( + env.fs.sep.join([export_dir, f"{mesh.m_Name}.obj"]), + "wt", + encoding="utf8", + newline="", + ) as f: + f.write(export_mesh_obj(mesh, material_names)) + + # save .mtl + with env.fs.open( + env.fs.sep.join([export_dir, f"{mesh.m_Name}.mtl"]), + "wt", + encoding="utf8", + newline="", + ) as f: + f.write("\n".join(materials)) + + +def export_material(mat: Material) -> str: + """Creates a material file (.mtl) for the given material.""" + + def clt(color): # color to tuple + return color if isinstance(color, tuple) else (color.R, color.G, color.B, color.A) + + def properties_to_dict(properties): + return {k if isinstance(k, str) else k.name: v for k, v in properties if v is not None} + + colors = properties_to_dict(mat.m_SavedProperties.m_Colors) + floats = properties_to_dict(mat.m_SavedProperties.m_Floats) + texEnvs = properties_to_dict(mat.m_SavedProperties.m_TexEnvs) + + diffuse = clt(colors.get("_Color", (0.8, 0.8, 0.8, 1))) + ambient = clt(colors.get("_SColor", (0.2, 0.2, 0.2, 1))) + # emissive = clt(colors.get("_EmissionColor", (0, 0, 0, 1))) + specular = clt(colors.get("_SpecularColor", (0.2, 0.2, 0.2, 1))) + # reflection = clt(colors.get("_ReflectColor", (0, 0, 0, 1))) + shininess = floats.get("_Shininess", 20.0) + transparency = floats.get("_Transparency", 0.0) + + sb: List[str] = [] + sb.append(f"newmtl {mat.m_Name}") + + # Ka r g b + # defines the ambient color of the material to be (r,g,b). The default is (0.2,0.2,0.2); + sb.append(f"Ka {ambient[0]:.4f} {ambient[1]:.4f} {ambient[2]:.4f}") + + # Kd r g b + # defines the diffuse color of the material to be (r,g,b). The default is (0.8,0.8,0.8); + sb.append(f"Kd {diffuse[0]:.4f} {diffuse[1]:.4f} {diffuse[2]:.4f}") + + # Ks r g b + # defines the specular color of the material to be (r,g,b). This color shows up in highlights. + # The default is (1.0,1.0,1.0); + sb.append(f"Ks {specular[0]:.4f} {specular[1]:.4f} {specular[2]:.4f}") + + # d alpha + # defines the non-transparency of the material to be alpha. + # The default is 1.0 (not transparent at all). The quantities d and Tr are the opposites of each other, + # and specifying transparency or nontransparency is simply a matter of user convenience. + + # Tr alpha + # defines the transparency of the material to be alpha. The default is 0.0 (not transparent at all). + # The quantities d and Tr are the opposites of each other, + # and specifying transparency or nontransparency is simply a matter of user convenience. + sb.append(f"Tr {transparency:.4f}") + + # Ns s + # defines the shininess of the material to be s. The default is 0.0; + sb.append(f"Ns {shininess:.4f}") + + # illum n + # denotes the illumination model used by the material. + # illum = 1 indicates a flat material with no specular highlights, + # so the value of Ks is not used. illum = 2 denotes the presence of specular highlights, + # and so a specification for Ks is required. + + # map_Ka filename + # names a file containing a texture map, which should just be an ASCII dump of RGB values; + texName = None + tex = None + for key, texEnv in texEnvs: + if not texEnv.m_Texture: + continue + if not isinstance(key, str): + # FastPropertyName + key = key.name + + tex: Texture2D = texEnv.m_Texture.deref_parse_as_object() + texName = f"{tex.m_Name if tex.m_Name else key}.png" + if key == "_MainTex": + sb.append(f"map_Kd {texName}") + elif key == "_BumpMap": + # TODO: bump is default, some use map_bump + sb.append(f"map_bump {texName}") + sb.append(f"bump {texName}") + elif "Specular" in key: + sb.append(f"map_Ks {texName}") + elif "Normal" in key: + # TODO: figure out the key + pass + ret = "\n".join(sb) + return ret diff --git a/UnityPy/export/ShaderConverter.py b/UnityPy/export/ShaderConverter.py new file mode 100644 index 000000000..dd3ac1c55 --- /dev/null +++ b/UnityPy/export/ShaderConverter.py @@ -0,0 +1,689 @@ +from __future__ import annotations + +import re +from itertools import groupby +from typing import TYPE_CHECKING, List, Optional, Tuple, TypeVar, Union + +from ..enums import ( + PassType, + SerializedPropertyType, + ShaderCompilerPlatform, + ShaderGpuProgramType, + TextureDimension, +) +from ..helpers import CompressionHelper +from ..streams import EndianBinaryReader + +if TYPE_CHECKING: + from ..classes import ( + SerializedPass, + SerializedProperties, + SerializedProperty, + SerializedShader, + SerializedShaderState, + SerializedSubProgram, + SerializedSubShader, + SerializedTagMap, + Shader, + ) + +HEADER = """ +////////////////////////////////////////// +// +// NOTE: This is *not* a valid shader file +// +/////////////////////////////////////////// +"""[1:] + +T = TypeVar("T") + + +def export_shader(m_Shader: Shader) -> str: + if m_Shader.m_SubProgramBlob: # 5.3 - 5.4 + decompressedBytes = CompressionHelper.decompress_lz4( + bytes(m_Shader.m_SubProgramBlob), m_Shader.decompressedSize + ) + + blobReader = EndianBinaryReader(decompressedBytes) + program = ShaderProgram(blobReader, m_Shader.object_reader.version) + + return HEADER + program.Export(bytes(m_Shader.m_Script).decode("utf8")) + + if m_Shader.compressedBlob: # 5.5 and up + return HEADER + ConvertSerializedShader(m_Shader) + + return HEADER + bytes(m_Shader.m_Script).decode("utf8") + + +def ConvertSerializedShader(m_Shader: Shader) -> str: + shaderPrograms = [] + + platformNumber = len(m_Shader.platforms) + compressed_blob = bytes(m_Shader.compressedBlob) + + def get_entry(array: Union[List[T], List[List[T]]], index: int) -> T: + item = array[index] + if isinstance(item, List): + return item[0] + return item + + for i in range(platformNumber): + if i >= len(m_Shader.compressedLengths) or i >= len(m_Shader.decompressedLengths): + # m_Shader.platforms shouldn't be longer than m_shader.[de]compressedLengths, but it is + break + + compressedSize = get_entry(m_Shader.compressedLengths, i) + decompressedSize = get_entry(m_Shader.decompressedLengths, i) + offset = get_entry(m_Shader.offsets, i) + + compressedBytes = compressed_blob[offset : offset + compressedSize] + decompressedBytes = CompressionHelper.decompress_lz4(compressedBytes, decompressedSize) + + shaderPrograms.append( + ShaderProgram( + EndianBinaryReader(decompressedBytes, endian="<"), + m_Shader.object_reader.version, + ) + ) + + return ConvertSerializedShaderParsedForm(m_Shader.m_ParsedForm, m_Shader.platforms, shaderPrograms) + + +def ConvertSerializedShaderParsedForm( + m_ParsedForm: SerializedShader, + platforms: List[int], + shaderPrograms: List[ShaderProgram], +) -> str: + sb: List[str] = [ + 'Shader "{0}" {{\n'.format(m_ParsedForm.m_Name), + ConvertSerializedProperties(m_ParsedForm.m_PropInfo), + *[ + ConvertSerializedSubShader(m_SubShader, platforms, shaderPrograms) + for m_SubShader in m_ParsedForm.m_SubShaders + ], + ] + + if m_ParsedForm.m_FallbackName: + sb.append('Fall back "{0}"\n'.format(m_ParsedForm.m_FallbackName)) + + if m_ParsedForm.m_CustomEditorName: + sb.append('CustomEditor "{0}"\n'.format(m_ParsedForm.m_CustomEditorName)) + + sb.append("}") + return "".join(sb) + + +def ConvertSerializedSubShader( + m_SubShader: SerializedSubShader, + platforms: List[int], + shaderPrograms: List[ShaderProgram], +) -> str: + sb = ["SubShader {\n"] + + if m_SubShader.m_LOD != 0: + sb.append(" LOD {0}\n".format(m_SubShader.m_LOD)) + + sb.append(ConvertSerializedTagMap(m_SubShader.m_Tags, 1)) + + sb.extend(ConvertSerializedPass(m_Passe, platforms, shaderPrograms) for m_Passe in m_SubShader.m_Passes) + sb.append("}\n") + return "".join(sb) + + +def ConvertSerializedPass(m_Passe: SerializedPass, platforms: List[int], shaderPrograms: List[ShaderProgram]) -> str: + sb = [] + if m_Passe.m_Type == PassType.kPassTypeNormal: + sb.append(" Pass ") + elif m_Passe.m_Type == PassType.kPassTypeUse: + sb.append(" UsePass ") + elif m_Passe.m_Type == PassType.kPassTypeGrab: + sb.append(" GrabPass ") + + if m_Passe.m_Type == PassType.kPassTypeUse: + sb.append('"{0}"\n'.format(m_Passe.m_UseName)) + else: + sb.append("{\n") + + if m_Passe.m_Type == PassType.kPassTypeGrab: + if m_Passe.m_TextureName: + sb.append(' "{0}"\n'.format(m_Passe.m_TextureName)) + + else: + sb.append(ConvertSerializedShaderState(m_Passe.m_State)) + + if len(m_Passe.progVertex.m_SubPrograms) > 0: + sb.append('Program "vp" {\n') + sb.append(ConvertSerializedSubPrograms(m_Passe.progVertex.m_SubPrograms, platforms, shaderPrograms)) + sb.append("}\n") + + if len(m_Passe.progFragment.m_SubPrograms) > 0: + sb.append('Program "fp" {\n') + sb.append(ConvertSerializedSubPrograms(m_Passe.progFragment.m_SubPrograms, platforms, shaderPrograms)) + sb.append("}\n") + + if len(m_Passe.progGeometry.m_SubPrograms) > 0: + sb.append('Program "gp" {\n') + sb.append(ConvertSerializedSubPrograms(m_Passe.progGeometry.m_SubPrograms, platforms, shaderPrograms)) + sb.append("}\n") + + if len(m_Passe.progHull.m_SubPrograms) > 0: + sb.append('Program "hp" {\n') + sb.append(ConvertSerializedSubPrograms(m_Passe.progHull.m_SubPrograms, platforms, shaderPrograms)) + sb.append("}\n") + + if len(m_Passe.progDomain.m_SubPrograms) > 0: + sb.append('Program "dp" {\n') + sb.append(ConvertSerializedSubPrograms(m_Passe.progDomain.m_SubPrograms, platforms, shaderPrograms)) + sb.append("}\n") + + sb.append("}\n") + + return "".join(sb) + + +def ConvertSerializedSubPrograms( + m_SubPrograms: List[SerializedSubProgram], + platforms: List[int], + shaderPrograms: List[ShaderProgram], +) -> str: + sb = [] + + def indexFunc(x: SerializedSubProgram): + return x.m_BlobIndex + + def typeFunc(x: SerializedSubProgram): + return x.m_GpuProgramType + + groups = groupby(sorted(m_SubPrograms, key=indexFunc), indexFunc) + + for _, _group in groups: + group = list(_group) + programs = groupby(sorted(group, key=typeFunc), typeFunc) + + for programKey, _programList in programs: + subPrograms = list(_programList) + isTier = len(subPrograms) > 1 + for i in range(len(platforms)): + if i >= len(shaderPrograms): + # platforms shouldn't be longer than shaderPrograms, but it is + break + + platform = platforms[i] + + if CheckGpuProgramUsable(platform, programKey): + for subProgram in subPrograms: + sb.append('SubProgram "{0} '.format(GetPlatformString(platform))) + + if isTier: + sb.append("hw_tier{0:02} ".format(subProgram.m_ShaderHardwareTier)) + + sb.append('" {\n') + sb.append(shaderPrograms[i].m_SubPrograms[subProgram.m_BlobIndex].Export()) + + sb.append("\n}\n") + + break + + return "".join(sb) + + +def ConvertSerializedShaderState(m_State: SerializedShaderState) -> str: + sb = [] + if m_State.m_Name: + sb.append(' Name "{0}"\n'.format(m_State.m_Name)) + + if m_State.m_LOD != 0: + sb.append(" LOD {0}\n".format(m_State.m_LOD)) + + sb.append(ConvertSerializedTagMap(m_State.m_Tags, 2)) + + # sb.append(ConvertSerializedShaderRTBlendState(m_State.rtBlend)) + + if m_State.alphaToMask.val > 0: + sb.append(" AlphaToMask On\n") + + if m_State.zClip and m_State.zClip.val != 1: + sb.append(" ZClip Off\n") + + if m_State.zTest.val != 4: + sb.append(" ZTest ") + if m_State.zTest.val == 0: # kFuncDisabled + sb.append("Off") + elif m_State.zTest.val == 1: # kFuncNever + sb.append("Never") + elif m_State.zTest.val == 2: # kFuncLess + sb.append("Less") + elif m_State.zTest.val == 3: # kFuncEqual + sb.append("Equal") + elif m_State.zTest.val == 5: # kFuncGreater + sb.append("Greater") + elif m_State.zTest.val == 6: # kFuncNotEqual + sb.append("NotEqual") + elif m_State.zTest.val == 7: # kFuncGEqual + sb.append("GEqual") + elif m_State.zTest.val == 8: # kFuncAlways + sb.append("Always") + + sb.append("\n") + + if m_State.zWrite.val != 1: # ZWrite On + sb.append(" ZWrite Off\n") + + if m_State.culling.val != 2: # Cull Back + sb.append(" Cull ") + if m_State.culling.val == 0: # kCullOff + sb.append("Off") + elif m_State.culling.val == 1: # kCullFront + sb.append("Front") + + sb.append("\n") + + if m_State.offsetFactor.val != 0 or m_State.offsetUnits.val != 0: + sb.append(" Offset {0}, {1}\n".format(m_State.offsetFactor.val, m_State.offsetUnits.val)) + + # TODO Stencil + + # TODO Fog + + if m_State.lighting: + sb.append(" Lighting {0}\n".format(m_State.lighting and "On" or "Off")) + + sb.append(" GpuProgramID {0}\n".format(m_State.gpuProgramID)) + return "".join(sb) + + +def ConvertSerializedShaderRTBlendState(rbBlend): + # TODO Blend + sb = [] + return "".join(sb) + + +def ConvertSerializedTagMap(m_Tags: SerializedTagMap, intent: int) -> str: + if m_Tags.tags: + return "".join( + [ + " " * intent, + "Tags { ", + *[f'"{key}" = "{value}" ' for key, value in m_Tags.tags], + "}\n", + ] + ) + return "" + + +def ConvertSerializedProperties(m_PropInfo: SerializedProperties) -> str: + return "\n".join( + [ + "Properties {\n", + *[ConvertSerializedProperty(m_Prop) for m_Prop in m_PropInfo.m_Props], + "}\n", + ] + ) + + +def ConvertSerializedProperty(m_Prop: SerializedProperty) -> str: + sb = ["[{0}] ".format(m_Attribute) for m_Attribute in m_Prop.m_Attributes] + sb.append('{0} ("{1}", '.format(m_Prop.m_Name, m_Prop.m_Description)) + + if m_Prop.m_Type == SerializedPropertyType.kColor: + sb.append("Color") + elif m_Prop.m_Type == SerializedPropertyType.kVector: + sb.append("Vector") + elif m_Prop.m_Type == SerializedPropertyType.kFloat: + sb.append("Float") + elif m_Prop.m_Type == SerializedPropertyType.kRange: + sb.append("Range({0:g}, {1:g})".format(m_Prop.m_DefValue_1_, m_Prop.m_DefValue_2_)) + elif m_Prop.m_Type == SerializedPropertyType.kTexture: + if m_Prop.m_DefTexture.m_TexDim == TextureDimension.kTexDimAny: + sb.append("any") + elif m_Prop.m_DefTexture.m_TexDim == TextureDimension.kTexDim2D: + sb.append("2D") + elif m_Prop.m_DefTexture.m_TexDim == TextureDimension.kTexDim3D: + sb.append("3D") + elif m_Prop.m_DefTexture.m_TexDim == TextureDimension.kTexDimCUBE: + sb.append("Cube") + elif m_Prop.m_DefTexture.m_TexDim == TextureDimension.kTexDim2DArray: + sb.append("2DArray") + elif m_Prop.m_DefTexture.m_TexDim == TextureDimension.kTexDimCubeArray: + sb.append("CubeArray") + + sb.append(") = ") + + if m_Prop.m_Type in [SerializedPropertyType.kColor, SerializedPropertyType.kVector]: + sb.append( + "({0:g},{1:g},{2:g},{3:g})".format( + m_Prop.m_DefValue_0_, + m_Prop.m_DefValue_1_, + m_Prop.m_DefValue_2_, + m_Prop.m_DefValue_3_, + ) + ) + elif m_Prop.m_Type in [ + SerializedPropertyType.kFloat, + SerializedPropertyType.kRange, + ]: + sb.append(m_Prop.m_DefValue_0_) + elif m_Prop.m_Type == SerializedPropertyType.kTexture: + sb.append('"{0}" {{ }}'.format(m_Prop.m_DefTexture.m_DefaultName)) + else: + raise ValueError(m_Prop.m_Type) + + sb.append("\n") + return "".join(map(str, sb)) + + +def CheckGpuProgramUsable(platform: int, programType: int) -> bool: + if platform == ShaderCompilerPlatform.kShaderCompPlatformGL: + return programType == ShaderGpuProgramType.kShaderGpuProgramGLLegacy + elif platform == ShaderCompilerPlatform.kShaderCompPlatformD3D9: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramDX9VertexSM20 + or programType == ShaderGpuProgramType.kShaderGpuProgramDX9VertexSM30 + or programType == ShaderGpuProgramType.kShaderGpuProgramDX9PixelSM20 + or programType == ShaderGpuProgramType.kShaderGpuProgramDX9PixelSM30 + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformXbox360: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformD3D11: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramDX11VertexSM40 + or programType == ShaderGpuProgramType.kShaderGpuProgramDX11VertexSM50 + or programType == ShaderGpuProgramType.kShaderGpuProgramDX11PixelSM40 + or programType == ShaderGpuProgramType.kShaderGpuProgramDX11PixelSM50 + or programType == ShaderGpuProgramType.kShaderGpuProgramDX11GeometrySM40 + or programType == ShaderGpuProgramType.kShaderGpuProgramDX11GeometrySM50 + or programType == ShaderGpuProgramType.kShaderGpuProgramDX11HullSM50 + or programType == ShaderGpuProgramType.kShaderGpuProgramDX11DomainSM50 + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformGLES20: + return programType == ShaderGpuProgramType.kShaderGpuProgramGLES + elif platform == ShaderCompilerPlatform.kShaderCompPlatformNaCl: + raise NotImplementedError + elif platform == ShaderCompilerPlatform.kShaderCompPlatformFlash: + raise NotImplementedError + elif platform == ShaderCompilerPlatform.kShaderCompPlatformD3D11_9x: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramDX10Level9Vertex + or programType == ShaderGpuProgramType.kShaderGpuProgramDX10Level9Pixel + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformGLES3Plus: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramGLES31AEP + or programType == ShaderGpuProgramType.kShaderGpuProgramGLES31 + or programType == ShaderGpuProgramType.kShaderGpuProgramGLES3 + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformPSP2: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformPS4: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformXboxOne: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformPSM: + raise NotImplementedError + elif platform == ShaderCompilerPlatform.kShaderCompPlatformMetal: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramMetalVS + or programType == ShaderGpuProgramType.kShaderGpuProgramMetalVS + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformOpenGLCore: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramGLCore32 + or programType == ShaderGpuProgramType.kShaderGpuProgramGLCore41 + or programType == ShaderGpuProgramType.kShaderGpuProgramGLCore43 + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformN3DS: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformWiiU: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformVulkan: + return programType == ShaderGpuProgramType.kShaderGpuProgramSPIRV + elif platform == ShaderCompilerPlatform.kShaderCompPlatformSwitch: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) + elif platform == ShaderCompilerPlatform.kShaderCompPlatformXboxOneD3D12: + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) + else: + raise NotImplementedError + + +def GetPlatformString(platform: int): + if platform == ShaderCompilerPlatform.kShaderCompPlatformGL: + return "openGL" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformD3D9: + return "d3d9" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformXbox360: + return "xbox360" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformPS3: + return "ps3" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformD3D11: + return "d3d11" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformGLES20: + return "gles" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformNaCl: + return "glesdesktop" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformFlash: + return "flash" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformD3D11_9x: + return "d3d11_9x" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformGLES3Plus: + return "gles3" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformPSP2: + return "psp2" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformPS4: + return "ps4" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformXboxOne: + return "xboxone" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformPSM: + return "psm" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformMetal: + return "metal" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformOpenGLCore: + return "glcore" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformN3DS: + return "n3ds" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformWiiU: + return "wiiu" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformVulkan: + return "vulkan" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformSwitch: + return "switch" + elif platform == ShaderCompilerPlatform.kShaderCompPlatformXboxOneD3D12: + return "xboxone+_d3d12" + else: + return "unknown" + + +class ShaderProgram: + m_SubPrograms: List[ShaderSubProgram] + + def __init__(self, reader: EndianBinaryReader, version: Tuple[int, int, int, int]): + subProgramCapacity = reader.read_int() + self.m_SubPrograms = [None] * subProgramCapacity + + if version >= (2019, 3): # 2019.3 and up + entrySize = 12 + else: + entrySize = 8 + + for i in range(subProgramCapacity): + reader.Position = 4 + i * entrySize + offset = reader.read_int() + reader.Position = offset + self.m_SubPrograms[i] = ShaderSubProgram(reader) + + def Export(self, shader: str) -> str: + shader = re.sub( + r"GpuProgramIndex (.+)", + lambda math: self.m_SubPrograms[int(math.group(1))].Export(), + shader, + ) + + return shader + + +class ShaderSubProgram: + m_Version: int + m_Keywords: List[str] + m_ProgramCode: bytes + m_LocalKeywords: Optional[List[str]] + + def __init__(self, reader: EndianBinaryReader): + # LoadGpuProgramFromData + # 201509030 - Unity 5.3 + # 201510240 - Unity 5.4 + # 201608170 - Unity 5.5 + # 201609010 - Unity 5.6, 2017.1 & 2017.2 + # 201708220 - Unity 2017.3, Unity 2017.4 & Unity 2018.1 + # 201802150 - Unity 2018.2 & Unity 2018.3 + # 201806140 - Unity 2019.1~2020.1 + # 202012090 - Unity 2021.2 + + self.m_Version = reader.read_int() + self.m_ProgramType = ShaderGpuProgramType(reader.read_int()) + reader.Position += 12 + + if self.m_Version >= 201608170: + reader.Position += 4 + + m_KeywordSize = reader.read_int() + self.m_Keywords = [reader.read_aligned_string() for _ in range(m_KeywordSize)] + + if 201806140 <= self.m_Version < 202012090: + m_LocalKeywordsSize = reader.read_int() + self.m_LocalKeywords = [reader.read_aligned_string() for _ in range(m_LocalKeywordsSize)] + else: + self.m_LocalKeywords = None + + self.m_ProgramCode = reader.read_byte_array() + reader.align_stream() + + def Export(self) -> str: + sb = [] + + if len(self.m_Keywords) > 0: + sb.append("Keywords { ") + for keyword in self.m_Keywords: + sb.append('"{0}" '.format(keyword)) + + sb.append("}\n") + + if ( + getattr(self, "m_LocalKeywords") is not None # noqa: B009 + and len(self.m_LocalKeywords) > 0 # type: ignore + ): + sb.append("Local Keywords { ") + for keyword in self.m_LocalKeywords: # type: ignore + sb.append('"{0}" '.format(keyword)) + + sb.append("}\n") + + sb.append('"') + + if len(self.m_ProgramCode) > 0: + if self.m_ProgramType in [ + ShaderGpuProgramType.kShaderGpuProgramGLLegacy, + ShaderGpuProgramType.kShaderGpuProgramGLES31AEP, + ShaderGpuProgramType.kShaderGpuProgramGLES31, + ShaderGpuProgramType.kShaderGpuProgramGLES3, + ShaderGpuProgramType.kShaderGpuProgramGLES, + ShaderGpuProgramType.kShaderGpuProgramGLCore32, + ShaderGpuProgramType.kShaderGpuProgramGLCore41, + ShaderGpuProgramType.kShaderGpuProgramGLCore43, + ]: + sb.append(bytes(self.m_ProgramCode).decode("utf8")) + elif self.m_ProgramType in [ + ShaderGpuProgramType.kShaderGpuProgramDX9VertexSM20, + ShaderGpuProgramType.kShaderGpuProgramDX9VertexSM30, + ShaderGpuProgramType.kShaderGpuProgramDX9PixelSM20, + ShaderGpuProgramType.kShaderGpuProgramDX9PixelSM30, + ]: + sb.append("// shader disassembly not supported on DXBC") + elif self.m_ProgramType in [ + ShaderGpuProgramType.kShaderGpuProgramDX10Level9Vertex, + ShaderGpuProgramType.kShaderGpuProgramDX10Level9Pixel, + ShaderGpuProgramType.kShaderGpuProgramDX11VertexSM40, + ShaderGpuProgramType.kShaderGpuProgramDX11VertexSM50, + ShaderGpuProgramType.kShaderGpuProgramDX11PixelSM40, + ShaderGpuProgramType.kShaderGpuProgramDX11PixelSM50, + ShaderGpuProgramType.kShaderGpuProgramDX11GeometrySM40, + ShaderGpuProgramType.kShaderGpuProgramDX11GeometrySM50, + ShaderGpuProgramType.kShaderGpuProgramDX11HullSM50, + ShaderGpuProgramType.kShaderGpuProgramDX11DomainSM50, + ]: + sb.append("// shader disassembly not supported on DXBC") + elif self.m_ProgramType in [ + ShaderGpuProgramType.kShaderGpuProgramMetalVS, + ShaderGpuProgramType.kShaderGpuProgramMetalFS, + ]: + reader = EndianBinaryReader(self.m_ProgramCode, endian="<") + fourCC = reader.read_u_int() + if fourCC == 0xF00DCAFE: + offset = reader.read_int() + reader.Position = offset + + _entryName = reader.read_string_to_null() + buff = reader.read_bytes(int(reader.Length - reader.Position)) + sb.append(bytes(buff).decode("utf8")) + elif self.m_ProgramType == ShaderGpuProgramType.kShaderGpuProgramSPIRV: + # TODO SpirVShaderConverter + pass + elif self.m_ProgramType in [ + ShaderGpuProgramType.kShaderGpuProgramConsoleVS, + ShaderGpuProgramType.kShaderGpuProgramConsoleFS, + ShaderGpuProgramType.kShaderGpuProgramConsoleHS, + ShaderGpuProgramType.kShaderGpuProgramConsoleDS, + ShaderGpuProgramType.kShaderGpuProgramConsoleGS, + ]: + sb.append(bytes(self.m_ProgramCode).decode("utf8")) + else: + sb.append("//shader disassembly not supported on {0}".format(self.m_ProgramType)) + + sb.append('"') + return "".join(sb) diff --git a/UnityPy/export/SpriteHelper.py b/UnityPy/export/SpriteHelper.py index 6fcd1069e..9ab5c8e45 100644 --- a/UnityPy/export/SpriteHelper.py +++ b/UnityPy/export/SpriteHelper.py @@ -1,154 +1,349 @@ -from enum import IntEnum +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Iterable, Union, cast from PIL import Image, ImageDraw +from PIL.Image import Transform, Transpose +from ..classes import SpriteAtlasData +from ..enums import ( + ClassIDType, + SpriteMeshType, + SpritePackingMode, + SpritePackingRotation, +) +from ..helpers.MeshHelper import MeshHandler from .Texture2DConverter import get_image_from_texture2d -from ..streams import EndianBinaryReader +if TYPE_CHECKING: + from typing import List, Optional, Tuple + + from ..classes import PPtr, Sprite, Texture2D -# should be imported from Sprite, but too lazy to fix the import issues caused by that -class SpritePackingRotation(IntEnum): - kSPRNone = (0,) - kSPRFlipHorizontal = (1,) - kSPRFlipVertical = (2,) - kSPRRotate180 = (3,) - kSPRRotate90 = 4 +try: + import numpy as np +except ImportError: + np = None -class SpritePackingMode(IntEnum): - kSPMTight = (0,) - kSPMRectangle = 1 +class SpriteSettings: + packed: bool + packingMode: SpritePackingMode + packingRotation: SpritePackingRotation + meshType: SpriteMeshType + def __init__(self, settings_raw): + self.settingsRaw = settings_raw + self.packed = bool(self.settingsRaw & 1) # 1 + self.packingMode = SpritePackingMode((self.settingsRaw >> 1) & 1) # 1 + self.packingRotation = SpritePackingRotation((self.settingsRaw >> 2) & 0xF) # 4 + self.meshType = SpriteMeshType((self.settingsRaw >> 6) & 1) # 1 + # rest of the bits are reserved -def get_image(sprite, texture, alpha_texture) -> Image: - if alpha_texture and getattr(alpha_texture, "type", "") == "Texture2D": + +def get_image(sprite: Sprite, texture: PPtr[Texture2D], alpha_texture: Optional[PPtr[Texture2D]]) -> Image.Image: + assert sprite.assets_file, "Sprite assets file is not set!" + cache = cast(Dict[Any, Any], sprite.assets_file._cache) # TODO: edit in SerializibleFile + if alpha_texture: cache_id = (texture.path_id, alpha_texture.path_id) - if cache_id not in sprite.assets_file._cache: - original_image = get_image_from_texture2d(texture.read(), False) - alpha_image = get_image_from_texture2d(alpha_texture.read(), False) - original_image = Image.merge( - "RGBA", (*original_image.split()[:3], alpha_image.split()[0]) - ) - sprite.assets_file._cache[cache_id] = original_image + if cache_id not in cache: + original_image = get_image_from_texture2d(texture.deref_parse_as_object(), False) + alpha_image = get_image_from_texture2d(alpha_texture.deref_parse_as_object(), False) + original_image = Image.merge("RGBA", (*original_image.split()[:3], alpha_image.split()[0])) + cache[cache_id] = original_image else: cache_id = texture.path_id - if cache_id not in sprite.assets_file._cache: - original_image = get_image_from_texture2d(texture.read(), False) - sprite.assets_file._cache[cache_id] = original_image - return sprite.assets_file._cache[cache_id] + if cache_id not in cache: + original_image = get_image_from_texture2d(texture.deref_parse_as_object(), False) + cache[cache_id] = original_image + return cache[cache_id] -def get_image_from_sprite(m_Sprite) -> Image: +def get_image_from_sprite(m_Sprite: Sprite) -> Image.Image: atlas = None if m_Sprite.m_SpriteAtlas: - atlas = m_Sprite.m_SpriteAtlas.read() + atlas = m_Sprite.m_SpriteAtlas.deref_parse_as_object() elif m_Sprite.m_AtlasTags: # looks like the direct pointer is empty, let's try to find the Atlas via its name + assert m_Sprite.assets_file, "Sprite assets file is not set!" for obj in m_Sprite.assets_file.objects.values(): - if obj.type == "SpriteAtlas": - atlas = obj.read() - if atlas.name == m_Sprite.m_AtlasTags[0]: + if obj.type == ClassIDType.SpriteAtlas: + name = obj.peek_name() + if name == m_Sprite.m_AtlasTags[0]: + atlas = obj.parse_as_object() break atlas = None if atlas: - sprite_atlas_data = atlas.render_data_map[m_Sprite.m_RenderDataKey] + sprite_atlas_data = next(value for key, value in atlas.m_RenderDataMap if key == m_Sprite.m_RenderDataKey) + assert isinstance(sprite_atlas_data, SpriteAtlasData), "SpriteAtlasData not found!" else: sprite_atlas_data = m_Sprite.m_RD m_Texture2D = sprite_atlas_data.texture alpha_texture = sprite_atlas_data.alphaTexture texture_rect = sprite_atlas_data.textureRect - texture_rect_offset = sprite_atlas_data.textureRectOffset settings_raw = sprite_atlas_data.settingsRaw original_image = get_image(m_Sprite, m_Texture2D, alpha_texture) sprite_image = original_image.crop( - (texture_rect.x, texture_rect.y, texture_rect.x + texture_rect.width, texture_rect.y + texture_rect.height) + ( + texture_rect.x, + texture_rect.y, + texture_rect.x + texture_rect.width, + texture_rect.y + texture_rect.height, + ) ) + settings_raw = SpriteSettings(settings_raw) if settings_raw.packed == 1: rotation = settings_raw.packingRotation if rotation == SpritePackingRotation.kSPRFlipHorizontal: - sprite_image = sprite_image.transpose(Image.FLIP_TOP_BOTTOM) + sprite_image = sprite_image.transpose(Transpose.FLIP_LEFT_RIGHT) # spriteImage = RotateFlip(RotateFlipType.RotateNoneFlipX) elif rotation == SpritePackingRotation.kSPRFlipVertical: - sprite_image = sprite_image.transpose(Image.FLIP_LEFT_RIGHT) + sprite_image = sprite_image.transpose(Transpose.FLIP_TOP_BOTTOM) # spriteImage.RotateFlip(RotateFlipType.RotateNoneFlipY) elif rotation == SpritePackingRotation.kSPRRotate180: - sprite_image = sprite_image.transpose(Image.ROTATE_180) + sprite_image = sprite_image.transpose(Transpose.ROTATE_180) # spriteImage.RotateFlip(RotateFlipType.Rotate180FlipNone) elif rotation == SpritePackingRotation.kSPRRotate90: - sprite_image = sprite_image.transpose(Image.ROTATE_270) - # spriteImage.RotateFlip(RotateFlipType.Rotate270FlipNone) + sprite_image = sprite_image.transpose(Transpose.ROTATE_270) + # spriteImage.RotateFlip(RotateFlipType.Rotate270FlipNone) if settings_raw.packingMode == SpritePackingMode.kSPMTight: - # Tight - - # create mask to keep only the polygon - mask = Image.new("1", sprite_image.size, color=0) - draw = ImageDraw.ImageDraw(mask) - for triangle in get_triangles(m_Sprite): - draw.polygon(triangle, fill=1) - - # apply the mask - if sprite_image.mode == "RGBA": - # the image already has an alpha channel, - # so we have to use composite to keep it - empty_img = Image.new(sprite_image.mode, sprite_image.size, color=0) - sprite_image = Image.composite(sprite_image, empty_img, mask) + assert m_Sprite.object_reader, "Sprite object reader is not set!" + mesh = MeshHandler(m_Sprite.m_RD, m_Sprite.object_reader.version) + mesh.process() + + if mesh.m_UV0 and any(u or v for u, v in mesh.m_UV0): + # copy triangles from mesh + sprite_image = render_sprite_mesh(m_Sprite, mesh, original_image) else: - # add mask as alpha-channel to keep the polygon clean - sprite_image.putalpha(mask) - - return sprite_image.transpose(Image.FLIP_TOP_BOTTOM) - - -def get_triangles(m_Sprite): - """ - returns the triangles of the sprite polygon - """ - m_RD = m_Sprite.m_RD - - # read the raw points - points = [] - if hasattr(m_RD, "vertices"): # 5.6 down - vertices = [v.pos for v in m_RD.vertices] - points = [vertices[index] for index in range(m_RD.indices)] - else: # 5.6 and up - m_Channel = m_RD.m_VertexData.m_Channels[0] # kShaderChannelVertex - m_Stream = m_RD.m_VertexData.m_Streams[m_Channel.stream] - - vertexReader = EndianBinaryReader(m_RD.m_VertexData.m_DataSize, endian="<") - indexReader = EndianBinaryReader(m_RD.m_IndexBuffer, endian="<") - - for subMesh in m_RD.m_SubMeshes: - vertexReader.Position = ( - m_Stream.offset - + subMesh.firstVertex * m_Stream.stride - + m_Channel.offset - ) + # create mask to keep only the polygon + sprite_image = mask_sprite(m_Sprite, mesh, sprite_image) - vertices = [] - for _ in range(subMesh.vertexCount): - vertices.append(vertexReader.read_vector3()) - vertexReader.Position = vertexReader.Position + m_Stream.stride - 12 + return sprite_image.transpose(Transpose.FLIP_TOP_BOTTOM) - indexReader.Position = subMesh.firstByte - for _ in range(subMesh.indexCount): - points.append( - vertices[indexReader.read_u_short() - subMesh.firstVertex] - ) +def mask_sprite(m_Sprite: Sprite, mesh: MeshHandler, sprite_image: Image.Image) -> Image.Image: + mask_img = Image.new("1", sprite_image.size, color=0) + draw = ImageDraw.ImageDraw(mask_img) # normalize the points # shift the whole point matrix into the positive space # multiply them with a factor to scale them to the image - min_x = min(p.X for p in points) - min_y = min(p.Y for p in points) + positions = mesh.m_Vertices + assert positions, "No vertices found in sprite mesh!" + # find the axis that has only one value - can be removed + # usually the z axis + min_x = min(x for x, _y, _z in positions) + min_y = min(y for _x, y, _z in positions) factor = m_Sprite.m_PixelsToUnits - points = [((p.X - min_x) * factor, (p.Y - min_y) * factor) for p in points] + positions_2d = [((x - min_x) * factor, (y - min_y) * factor) for x, y, _z in positions] # generate triangles from the given points - return [points[i : i + 3] for i in range(0, len(points), 3)] + triangles = [ + ( + positions_2d[a], + positions_2d[b], + positions_2d[c], + ) + for submesh in mesh.get_triangles() + for a, b, c in submesh + ] + + for triangle in triangles: + draw.polygon(triangle, fill=1) + + # apply the mask + if sprite_image.mode == "RGBA": + # the image already has an alpha channel, + # so we have to use composite to keep it + empty_img = Image.new(sprite_image.mode, sprite_image.size, color=0) + sprite_image = Image.composite(sprite_image, empty_img, mask_img) + else: + # add mask as alpha-channel to keep the polygon clean + sprite_image.putalpha(mask_img) + + return sprite_image + + +def render_sprite_mesh(m_Sprite: Sprite, mesh: MeshHandler, texture: Image.Image) -> Image.Image: + for triangles in mesh.get_triangles(): + positions = mesh.m_Vertices + if not positions: + continue + uv = mesh.m_UV0 + if not uv: + raise ValueError("No UV coordinates found in sprite mesh!") + + # 2. patch position data + # 2.1 make positions 2d + # find the axis that has only one value - can be removed + # usually the z axis + axis_values = [[pos[i] for pos in positions] for i in range(3)] + for i in range(2, -1, -1): + if len(set(axis_values[i])) == 1: + break + else: + raise ValueError("Can't process 3d sprites!") + axis_values = axis_values[:i] + axis_values[i + 1 :] + x_min = min(axis_values[0]) + y_min = min(axis_values[1]) + x_max = max(axis_values[0]) + y_max = max(axis_values[1]) + + # 2.2 map positions from middle to top left + # 2.3 convert relative positions to absolute + pixels_to_units = m_Sprite.m_PixelsToUnits + positions_abs = [ + (round((x - x_min) * pixels_to_units), round((y - y_min) * pixels_to_units)) for x, y in zip(*axis_values) + ] + width, height = texture.size + uv_abs = [(round(u * width), round(v * height)) for u, v in uv] + + # 2.4 generate final image size + size = ( + round((x_max - x_min) * pixels_to_units), + round((y_max - y_min) * pixels_to_units), + ) + sprite = Image.new(texture.mode, size) + + for tri in triangles: + copy_triangle( + texture, + tuple(uv_abs[i] for i in tri), # type: ignore + sprite, + tuple(positions_abs[i] for i in tri), # type: ignore + ) + + return sprite + else: + raise ValueError("No triangles found in mesh!") + + +def copy_triangle( + src_img: Image.Image, + src_tri: Tuple[Tuple[int, int], Tuple[int, int], Tuple[int, int]], + dst_img: Image.Image, + dst_tri: Tuple[Tuple[int, int], Tuple[int, int], Tuple[int, int]], +) -> None: + src_off = ( + (src_tri[1][0] - src_tri[0][0], src_tri[1][1] - src_tri[0][1]), + (src_tri[2][0] - src_tri[0][0], src_tri[2][1] - src_tri[0][1]), + ) + dst_off = ( + (dst_tri[1][0] - dst_tri[0][0], dst_tri[1][1] - dst_tri[0][1]), + (dst_tri[2][0] - dst_tri[0][0], dst_tri[2][1] - dst_tri[0][1]), + ) + + # check if transform is necessary by comparing the triangle sizes + if src_off[0] == dst_off[0] and src_off[1] == dst_off[1]: + # no transform necessary, just copy the triangle + + # make rectangle that contains the triangle + # upper_left, _, lower_right = sorted(src_tri) + upper_left = ( + min(src_tri[0][0], src_tri[1][0], src_tri[2][0]), + min(src_tri[0][1], src_tri[1][1], src_tri[2][1]), + ) + lower_right = ( + max(src_tri[0][0], src_tri[1][0], src_tri[2][0]), + max(src_tri[0][1], src_tri[1][1], src_tri[2][1]), + ) + src_part = src_img.crop((*upper_left, *lower_right)) + + # create mask for triangle + mask_box = [(x - upper_left[0], y - upper_left[1]) for x, y in src_tri] + mask = Image.new("1", src_part.size) + maskdraw = ImageDraw.Draw(mask) + maskdraw.polygon(mask_box, fill=255) + + # paste triangle into destination image + dst = ( + int(min(dst_tri[0][0], dst_tri[1][0], dst_tri[2][0])), + int(min(dst_tri[0][1], dst_tri[1][1], dst_tri[2][1])), + ) + dst_img.paste(src_part, dst, mask=mask) + else: + # transform is necessary, use affine transformation + # https://stackoverflow.com/a/6959111 + ((x11, x12), (x21, x22), (x31, x32)) = src_tri + ((y11, y12), (y21, y22), (y31, y32)) = dst_tri + + # Construct matrix M manually + M = [ + [y11, y12, 1, 0, 0, 0], + [y21, y22, 1, 0, 0, 0], + [y31, y32, 1, 0, 0, 0], + [0, 0, 0, y11, y12, 1], + [0, 0, 0, y21, y22, 1], + [0, 0, 0, y31, y32, 1], + ] + + # Vector y corresponds to the x coordinates in the source triangle + y = [x11, x21, x31, x12, x22, x32] + + if np: + A = np.linalg.solve(M, y) + else: + # np.lingal.solve - obviously way faster, but numpy will only come with 2.0 + A = linalg_solve(M, y) # type: ignore + + transformed = src_img.transform(dst_img.size, Transform.AFFINE, A) + + mask = Image.new("1", dst_img.size) + maskdraw = ImageDraw.Draw(mask) + maskdraw.polygon(dst_tri, fill=255) + + dst_img.paste(transformed, mask=mask) + + +def linalg_solve(M: List[List[Union[float, int]]], y: List[Union[float, int]]) -> List[float]: + # M^-1 * y + M_i = get_matrix_inverse(M) + return [sum(M_i[i][j] * y[j] for j in range(len(y))) for i in range(len(M_i))] + + +def transpose_matrix(m: List[List[float]]) -> Iterable[List[float]]: + # https://stackoverflow.com/a/39881366 + return map(list, zip(*m)) + + +def get_matrix_minor(m: List[List[float]], i: int, j: int) -> List[List[float]]: + # https://stackoverflow.com/a/39881366 + return [row[:j] + row[j + 1 :] for row in (m[:i] + m[i + 1 :])] + + +def get_matrix_determinant(m: List[List[float]]) -> float: + # https://stackoverflow.com/a/39881366 + # base case for 2x2 matrix + if len(m) == 2: + return m[0][0] * m[1][1] - m[0][1] * m[1][0] + + return sum(((-1) ** c) * m[0][c] * get_matrix_determinant(get_matrix_minor(m, 0, c)) for c in range(len(m))) + + +def get_matrix_inverse(m: List[List[float]]) -> List[List[float]]: + # https://stackoverflow.com/a/39881366 + determinant = get_matrix_determinant(m) + # special case for 2x2 matrix: + if len(m) == 2: + return [ + [m[1][1] / determinant, -1 * m[0][1] / determinant], + [-1 * m[1][0] / determinant, m[0][0] / determinant], + ] + + # find matrix of cofactors + cofactors = [ + [((-1) ** (r + c)) * get_matrix_determinant(get_matrix_minor(m, r, c)) for c in range(len(m))] + for r in range(len(m)) + ] + cofactors = list(transpose_matrix(cofactors)) + + return [[c / determinant for c in row] for row in cofactors] + + +__all__ = ["get_image_from_sprite"] diff --git a/UnityPy/export/Texture2DConverter.py b/UnityPy/export/Texture2DConverter.py index 4a4b511b2..cdb743eee 100644 --- a/UnityPy/export/Texture2DConverter.py +++ b/UnityPy/export/Texture2DConverter.py @@ -1,93 +1,350 @@ -from sys import platform +from __future__ import annotations +import struct +from functools import lru_cache +from io import BytesIO +from threading import get_ident +from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Tuple, Union + +import astc_encoder import texture2ddecoder from PIL import Image -from copy import copy -from io import BytesIO -import struct -from ..enums import TextureFormat, BuildTarget + +from ..enums import BuildTarget, TextureFormat +from ..helpers import TextureSwizzler + +if TYPE_CHECKING: + from ..classes import Texture2D + TF = TextureFormat -PREFER_BASISU = False + +TEXTURE_FORMAT_BLOCK_SIZE_TABLE: Dict[TF, Optional[Tuple[int, int]]] = {} +for tf in TF: + if tf.name.startswith("ASTC"): + split = tf.name.rsplit("_", 1)[1].split("x") + block_size = (int(split[0]), int(split[1])) + elif tf.name.startswith(("DXT", "BC", "ETC", "EAC")): + block_size = (4, 4) + elif tf.name.startswith("PVRTC"): + block_size = (8 if tf.name.endswith("2") else 4, 4) + else: + block_size = None + TEXTURE_FORMAT_BLOCK_SIZE_TABLE[tf] = block_size -def get_image_from_texture2d(texture_2d, flip=True) -> Image: - """converts the given texture into PIL.Image +def get_compressed_image_size(width: int, height: int, texture_format: TextureFormat): + block_size = TEXTURE_FORMAT_BLOCK_SIZE_TABLE[texture_format] + if block_size is None: + return (width, height) + block_width, block_height = block_size + + def pad(value: int, pad_by: int) -> int: + to_pad = value % pad_by + if to_pad: + value += pad_by - to_pad + return value + + width = pad(width, block_width) + height = pad(height, block_height) + return width, height + + +def pad_image(img: Image.Image, pad_width: int, pad_height: int) -> Image.Image: + ori_width, ori_height = img.size + if pad_width == ori_width and pad_height == ori_height: + return img + + # Paste the original image at the top-left corner + pad_img = Image.new(img.mode, (pad_width, pad_height)) + pad_img.paste(img, (0, 0)) + + # Fill the right border: duplicate the last column + if pad_width != ori_width: + right_strip = img.crop((ori_width - 1, 0, ori_width, ori_height)) + right_strip = right_strip.resize((pad_width - ori_width, ori_height), resample=Image.Resampling.NEAREST) + pad_img.paste(right_strip, (ori_width, 0)) + + # Fill the bottom border: duplicate the last row + if pad_height != ori_height: + bottom_strip = img.crop((0, ori_height - 1, ori_width, ori_height)) + bottom_strip = bottom_strip.resize((ori_width, pad_height - ori_height), resample=Image.Resampling.NEAREST) + pad_img.paste(bottom_strip, (0, ori_height)) + + # Fill the bottom-right corner with the bottom-right pixel + if pad_width != ori_width and pad_height != ori_height: + corner = img.getpixel((ori_width - 1, ori_height - 1)) + corner_img = Image.new(img.mode, (pad_width - ori_width, pad_height - ori_height), color=corner) + pad_img.paste(corner_img, (ori_width, ori_height)) + + return pad_img + + +def compress_etcpak(data: bytes, width: int, height: int, target_texture_format: TextureFormat) -> bytes: + import etcpak + + if target_texture_format in [TF.DXT1, TF.DXT1Crunched]: + return etcpak.compress_bc1(data, width, height) + elif target_texture_format in [TF.DXT5, TF.DXT5Crunched]: + return etcpak.compress_bc3(data, width, height) + elif target_texture_format == TF.BC4: + return etcpak.compress_bc4(data, width, height) + elif target_texture_format == TF.BC5: + return etcpak.compress_bc5(data, width, height) + elif target_texture_format == TF.BC7: + return etcpak.compress_bc7(data, width, height, None) + elif target_texture_format in [TF.ETC_RGB4, TF.ETC_RGB4Crunched, TF.ETC_RGB4_3DS]: + return etcpak.compress_etc1_rgb(data, width, height) + elif target_texture_format == TF.ETC2_RGB: + return etcpak.compress_etc2_rgb(data, width, height) + elif target_texture_format in [TF.ETC2_RGBA8, TF.ETC2_RGBA8Crunched, TF.ETC2_RGBA1]: + return etcpak.compress_etc2_rgba(data, width, height) + else: + raise NotImplementedError(f"etcpak has no compress function for {target_texture_format.name}") + + +def compress_astc(data: bytes, width: int, height: int, target_texture_format: TextureFormat) -> bytes: + astc_image = astc_encoder.ASTCImage(astc_encoder.ASTCType.U8, width, height, 1, data) + block_size = TEXTURE_FORMAT_BLOCK_SIZE_TABLE[target_texture_format] + assert block_size is not None, f"failed to get block size for {target_texture_format.name}" + swizzle = astc_encoder.ASTCSwizzle.from_str("RGBA") + + context = get_astc_context(block_size) + enc_img = context.compress(astc_image, swizzle) + + return enc_img + + +def image_to_texture2d( + img: Image.Image, + target_texture_format: Union[TextureFormat, int], + platform: Union[BuildTarget, int] = 0, + platform_blob: Optional[List[int]] = None, + flip: bool = True, +) -> Tuple[bytes, TextureFormat]: + """Converts a PIL Image to Texture2D bytes.""" + if not isinstance(target_texture_format, TextureFormat): + target_texture_format = TextureFormat(target_texture_format) + + if flip: + img = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + + # defaults + compress_func = None + tex_format = TF.RGBA32 + pil_mode = "RGBA" + + # DXT / BC + if target_texture_format in [TF.DXT1, TF.DXT1Crunched]: + tex_format = TF.DXT1 + compress_func = compress_etcpak + elif target_texture_format in [TF.DXT5, TF.DXT5Crunched]: + tex_format = TF.DXT5 + compress_func = compress_etcpak + elif target_texture_format in [TF.BC4, TF.BC5, TF.BC7]: + tex_format = target_texture_format + compress_func = compress_etcpak + # ASTC + elif target_texture_format.name.startswith("ASTC"): + if "_HDR_" in target_texture_format.name: + block_size = TEXTURE_FORMAT_BLOCK_SIZE_TABLE[target_texture_format] + assert block_size is not None + if img.mode == "RGB": + tex_format = getattr(TF, f"ASTC_RGB_{block_size[0]}x{block_size[1]}") + else: + tex_format = getattr(TF, f"ASTC_RGBA_{block_size[0]}x{block_size[1]}") + else: + tex_format = target_texture_format + compress_func = compress_astc + # ETC + elif target_texture_format in [TF.ETC_RGB4, TF.ETC_RGB4Crunched, TF.ETC_RGB4_3DS]: + if target_texture_format == TF.ETC_RGB4_3DS: + tex_format = TF.ETC_RGB4_3DS + else: + tex_format = target_texture_format + compress_func = compress_etcpak + elif target_texture_format == TF.ETC2_RGB: + tex_format = TF.ETC2_RGB + compress_func = compress_etcpak + elif target_texture_format in [TF.ETC2_RGBA8, TF.ETC2_RGBA8Crunched, TF.ETC2_RGBA1]: + tex_format = TF.ETC2_RGBA8 + compress_func = compress_etcpak + # A + elif target_texture_format == TF.Alpha8: + tex_format = TF.Alpha8 + pil_mode = "A" + # R - should probably be merged into #A, as pure R is used as Alpha + # but need test data for this first + elif target_texture_format in [ + TF.R8, + TF.R16, + TF.RHalf, + TF.RFloat, + TF.EAC_R, + TF.EAC_R_SIGNED, + ]: + tex_format = TF.R8 + pil_mode = "R" + # RGBA + elif target_texture_format in [ + TF.RGB565, + TF.RGB24, + TF.BGR24, + TF.RGB9e5Float, + TF.PVRTC_RGB2, + TF.PVRTC_RGB4, + TF.ATC_RGB4, + ]: + tex_format = TF.RGB24 + pil_mode = "RGB" + # everything else defaulted to RGBA + + width, height = img.width, img.height + switch_info = None + + if TextureSwizzler.is_switch_swizzled(platform, platform_blob): + s_tex_format = tex_format + if tex_format == TF.RGB24: + s_tex_format = TF.RGBA32 + pil_mode = "RGBA" + elif tex_format == TF.BGR24: + s_tex_format = TF.BGRA32 + pil_mode = "BGRA" + + assert platform_blob is not None + gobs_per_block = TextureSwizzler.get_switch_gobs_per_block(platform_blob) + block_size = TextureSwizzler.TEXTURE_FORMAT_BLOCK_SIZE_MAP[s_tex_format] + width, height = TextureSwizzler.get_padded_texture_size(img.width, img.height, *block_size, gobs_per_block) + switch_info = (block_size, gobs_per_block) + + if compress_func: + width, height = get_compressed_image_size(width, height, tex_format) + img = pad_image(img, width, height) + enc_img = compress_func(img.tobytes("raw", "RGBA"), width, height, tex_format) + else: + if switch_info: + img = pad_image(img, width, height) + + enc_img = img.tobytes("raw", pil_mode) + + if switch_info: + block_size, gobs_per_block = switch_info + enc_img = bytes(TextureSwizzler.swizzle(enc_img, width, height, *block_size, gobs_per_block)) + + return enc_img, tex_format + + +def get_image_from_texture2d( + texture_2d: Texture2D, + flip: bool = True, +) -> Image.Image: + """Converts the given Texture2D object to PIL Image.""" + return parse_image_data( + texture_2d.get_image_data(), + texture_2d.m_Width, + texture_2d.m_Height, + texture_2d.m_TextureFormat, + getattr(texture_2d.object_reader, "version", (0, 0, 0, 0)), + getattr(texture_2d.object_reader, "platform", BuildTarget.UnknownPlatform), + getattr(texture_2d, "m_PlatformBlob", None), + flip, + ) + + +def parse_image_data( + image_data: Union[bytes, bytearray, memoryview], + width: int, + height: int, + texture_format: Union[TextureFormat, int], + version: Tuple[int, int, int, int], + platform: Union[BuildTarget, int], + platform_blob: Optional[List[int]] = None, + flip: bool = True, +) -> Image.Image: + """Converts the given image data bytes to PIL Image.""" + if not width or not height: + return Image.new("RGBA", (0, 0)) - :param texture_2d: texture to be converterd - :type texture_2d: Texture2D - :param flip: flips the image back to the original (all Unity textures are flipped by default) - :type flip: bool - :return: PIL.Image object - :rtype: Image - """ - image_data = copy(bytes(texture_2d.image_data)) if not image_data: - return Image.new("RGB", (0,0)) - texture_format = texture_2d.m_TextureFormat if isinstance(texture_2d, TextureFormat) else TextureFormat(texture_2d.m_TextureFormat) + raise ValueError("Texture2D has no image data") + + if not isinstance(texture_format, TextureFormat): + texture_format = TextureFormat(texture_format) - selection = CONV_TABLE[texture_format] + if platform == BuildTarget.XBOX360 and texture_format in XBOX_SWAP_FORMATS: + image_data = swap_bytes_for_xbox(image_data) - if len(selection) == 0: - raise NotImplementedError( - f"Not implemented texture format: {texture_format.name}" - ) + original_width, original_height = (width, height) + + if TextureSwizzler.is_switch_swizzled(platform, platform_blob): + if texture_format == TF.RGB24: + texture_format = TF.RGBA32 + elif texture_format == TF.BGR24: + texture_format = TF.BGRA32 + + assert platform_blob is not None + gobs_per_block = TextureSwizzler.get_switch_gobs_per_block(platform_blob) + block_size = TextureSwizzler.TEXTURE_FORMAT_BLOCK_SIZE_MAP[texture_format] + width, height = TextureSwizzler.get_padded_texture_size(width, height, *block_size, gobs_per_block) + image_data = TextureSwizzler.deswizzle(image_data, width, height, *block_size, gobs_per_block) + else: + width, height = get_compressed_image_size(width, height, texture_format) - if texture_format in XBOX_SWAP_FORMATS: - image_data = swap_bytes_for_xbox(image_data, texture_2d.platform) + if not isinstance(image_data, bytes): + # bytes(bytes item) would cause an unnecessary copy + image_data = bytes(image_data) if "Crunched" in texture_format.name: - version = texture_2d.version - if (version[0] > 2017 or (version[0] == 2017 and version[1] >= 3) #2017.3 and up - or texture_format == TextureFormat.ETC_RGB4Crunched - or texture_format == TextureFormat.ETC2_RGBA8Crunched): + if ( + version[0] > 2017 + or (version[0] == 2017 and version[1] >= 3) # 2017.3 and up + or texture_format == TF.ETC_RGB4Crunched + or texture_format == TF.ETC2_RGBA8Crunched + ): image_data = texture2ddecoder.unpack_unity_crunch(image_data) else: image_data = texture2ddecoder.unpack_crunch(image_data) - img = selection[0]( - image_data, texture_2d.m_Width, texture_2d.m_Height, *selection[1:] - ) + if texture_format not in CONV_TABLE: + raise NotImplementedError(f"Not implemented texture format: {texture_format.name}") + conv_func, conv_args = CONV_TABLE[texture_format] + + img = conv_func(image_data, width, height, *conv_args) + + if original_width != width or original_height != height: + img = img.crop((0, 0, original_width, original_height)) if img and flip: - return img.transpose(Image.FLIP_TOP_BOTTOM) - return img + return img.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + return img -def swap_bytes_for_xbox(image_data: bytes, build_target: BuildTarget) -> bytes: - """swaps the texture bytes - This is required for textures deployed on XBOX360. - :param image_data: texture data - :type image_data: bytes - :param build_target: platform of the asset - :type build_target: BuildTarget - :return: swapped data if platform = XBOX360 else data - :rtype: bytes - """ - if ( - build_target == BuildTarget.XBOX360 - ): # swap bytes for Xbox confirmed,PS3 not encountered - for i in range(0, len(image_data), 2): - image_data[i : i + 2] = image_data[i : i + 2][::-1] +def swap_bytes_for_xbox(image_data: Union[bytes, bytearray, memoryview]) -> bytearray: + """Swaps the texture bytes for textures deployed on XBOX360.""" + image_data = bytearray(image_data) + for i in range(0, len(image_data), 2): + image_data[i : i + 2] = image_data[i : i + 2][::-1] return image_data def pillow( - image_data: bytes, width: int, height: int, mode: str, codec: str, args, swap=False -) -> Image: - img = ( - Image.frombytes(mode, (width, height), image_data, codec, args) - if width - else Image.new(mode, (width, height)) - ) + image_data: bytes, + width: int, + height: int, + mode: str, + codec: str, + args, + swap: Optional[Tuple[int, ...]] = None, +) -> Image.Image: + img = Image.frombytes(mode, (width, height), image_data, codec, args) if width else Image.new(mode, (width, height)) if swap: channels = img.split() img = Image.merge(mode, [channels[x] for x in swap]) return img -def atc(image_data: bytes, width: int, height: int, alpha: bool) -> Image: +def atc(image_data: bytes, width: int, height: int, alpha: bool) -> Image.Image: if alpha: image_data = texture2ddecoder.decode_atc_rgba8(image_data, width, height) else: @@ -96,31 +353,71 @@ def atc(image_data: bytes, width: int, height: int, alpha: bool) -> Image: return Image.frombytes("RGBA", (width, height), image_data, "raw", "BGRA") -def astc(image_data: bytes, width: int, height: int, block_size: tuple) -> Image: - image_data = texture2ddecoder.decode_astc(image_data, width, height, *block_size) - return Image.frombytes("RGBA", (width, height), image_data, "raw", "BGRA") +def astc(image_data: bytes, width: int, height: int, block_size: tuple) -> Image.Image: + image = astc_encoder.ASTCImage(astc_encoder.ASTCType.U8, width, height, 1) + texture_size = calculate_astc_compressed_size(width, height, block_size) + if len(image_data) < texture_size: + raise ValueError(f"Invalid ASTC data size: {len(image_data)} < {texture_size}") + + context = get_astc_context(block_size) + context.decompress(image_data[:texture_size], image, astc_encoder.ASTCSwizzle.from_str("RGBA")) + assert image.data is not None, "Decompression failed, image data is None" + + return Image.frombytes("RGBA", (width, height), image.data, "raw", "RGBA") + + +@lru_cache(maxsize=128) +def _get_astc_context(ident: int, block_size: tuple): + config = astc_encoder.ASTCConfig( + astc_encoder.ASTCProfile.LDR, + *block_size, + block_z=1, + quality=100, + flags=astc_encoder.ASTCConfigFlags.USE_DECODE_UNORM8, + ) + context = astc_encoder.ASTCContext(config) + return context + + +def get_astc_context(block_size: tuple): + """Get the ASTC context for the current thread using the given `block_size`. + Created contexts belong to and only to the calling thread, and may be cached. + This function is thread safe. + """ + return _get_astc_context(get_ident(), block_size) + +def calculate_astc_compressed_size(width: int, height: int, block_size: tuple) -> int: + """Calculate the size of the compressed data for ASTC.""" + # calculate the number of blocks + block_count_x = (width + block_size[0] - 1) // block_size[0] + block_count_y = (height + block_size[1] - 1) // block_size[1] + # ignore depth for 2D textures + # calculate the size of the compressed data + return block_count_x * block_count_y * 16 -def pvrtc(image_data: bytes, width: int, height: int, fmt: bool): + +def pvrtc(image_data: bytes, width: int, height: int, fmt: bool) -> Image.Image: image_data = texture2ddecoder.decode_pvrtc(image_data, width, height, fmt) return Image.frombytes("RGBA", (width, height), image_data, "raw", "BGRA") -def etc(image_data: bytes, width: int, height: int, fmt: list): - if fmt[0] == 1: +def etc(image_data: bytes, width: int, height: int, fmt: str) -> Image.Image: + if fmt == "ETC1": image_data = texture2ddecoder.decode_etc1(image_data, width, height) - elif fmt[0] == 2: - if fmt[1] == "RGB": - image_data = texture2ddecoder.decode_etc2(image_data, width, height) - elif fmt[1] == "A1": - image_data = texture2ddecoder.decode_etc2a1(image_data, width, height) - elif fmt[1] == "A8": - image_data = texture2ddecoder.decode_etc2a8(image_data, width, height) + elif fmt == "ETC2_RGB": + image_data = texture2ddecoder.decode_etc2(image_data, width, height) + elif fmt == "ETC2_A1": + image_data = texture2ddecoder.decode_etc2a1(image_data, width, height) + elif fmt == "ETC2_A8": + image_data = texture2ddecoder.decode_etc2a8(image_data, width, height) else: - raise NotImplementedError("unknown etc mode") + raise NotImplementedError(f"Unknown ETC mode: {fmt}") + return Image.frombytes("RGBA", (width, height), image_data, "raw", "BGRA") -def eac(image_data: bytes, width: int, height: int, fmt: list): + +def eac(image_data: bytes, width: int, height: int, fmt: str) -> Image.Image: if fmt == "EAC_R": image_data = texture2ddecoder.decode_eacr(image_data, width, height) elif fmt == "EAC_R_SIGNED": @@ -129,97 +426,144 @@ def eac(image_data: bytes, width: int, height: int, fmt: list): image_data = texture2ddecoder.decode_eacrg(image_data, width, height) elif fmt == "EAC_RG_SIGNED": image_data = texture2ddecoder.decode_eacrg_signed(image_data, width, height) + else: + raise NotImplementedError(f"Unknown EAC mode: {fmt}") + return Image.frombytes("RGBA", (width, height), image_data, "raw", "BGRA") + def half( - image_data: bytes, width: int, height: int, mode: str, codec: str, args, swap=False -) -> Image: + image_data: bytes, + width: int, + height: int, + mode: str, + codec: str, + args, + swap: Optional[tuple] = None, +) -> Image.Image: # convert half-float to int8 stream = BytesIO(image_data) - image_data = bytes( - int(struct.unpackb("e", stream.read(2))*256) - for i in range(width*height*len(codec)) - ) - img = ( - Image.frombytes(mode, (width, height), image_data, codec, args) - if width - else Image.new(mode, (width, height)) - ) - if swap: - channels = img.split() - img = Image.merge(mode, [channels[x] for x in swap]) - return img + image_data = bytes(int(struct.unpack("e", stream.read(2))[0] * 256) for _ in range(width * height * len(codec))) + return pillow(image_data, width, height, mode, codec, args, swap) -CONV_TABLE = { -# FORMAT FUNC #ARGS..... -#----------------------- -------- -------- ------------ ----------------- ------------ ---------- -( TF.Alpha8, pillow, "RGBA", "raw", "A" ), -( TF.ARGB4444, pillow, "RGBA", "raw", "RGBA;4B", (2,1,0,3) ), -( TF.RGB24, pillow, "RGB", "raw", "RGB" ), -( TF.RGBA32, pillow, "RGBA", "raw", "RGBA" ), -( TF.ARGB32, pillow, "RGBA", "raw", "ARGB" ), -( TF.RGB565, pillow, "RGB", "raw", "BGR;16" ), -( TF.R8, pillow, "RGB", "raw", "R" ), -( TF.R16, pillow, "RGB", "raw", "R;16" ), -( TF.RG16, ), -( TF.DXT1, pillow, "RGBA", "bcn", 1 ), -( TF.DXT5, pillow, "RGBA", "bcn", 3 ), -( TF.RGBA4444, pillow, "RGBA", "raw", 'RGBA;4B', (3,2,1,0) ), -( TF.BGRA32, pillow, "RGBA", "raw", "BGRA" ), -( TF.RHalf, half, "R", "raw", "R" ), -( TF.RGHalf, ), -( TF.RGBAHalf, half, "RGB", "raw", "RGB" ), -( TF.RFloat, pillow, "RGB", "raw", "RF" ), -( TF.RGFloat, ), -( TF.RGBAFloat, pillow, "RGBA", "raw", "RGBAF" ), -( TF.YUY2, ), -( TF.RGB9e5Float, ), -( TF.BC4, pillow, "L", "bcn", 4 ), -( TF.BC5, pillow, "RGBA", "bcn", 5 ), -( TF.BC6H, pillow, "RGBA", "bcn", 6 ), -( TF.BC7, pillow, "RGBA", "bcn", 7 ), -( TF.DXT1Crunched, pillow, "RGBA", "bcn", 1 ), -( TF.DXT5Crunched, pillow, "RGBA", "bcn", 3 ), -( TF.PVRTC_RGB2, pvrtc, False ), -( TF.PVRTC_RGBA2, pvrtc, True ), -( TF.PVRTC_RGB4, pvrtc, False ), -( TF.PVRTC_RGBA4, pvrtc, True ), -( TF.ETC_RGB4, etc, (1,) ), -( TF.ATC_RGB4, atc, False ), -( TF.ATC_RGBA8, atc, True ), -( TF.EAC_R, eac, "EAC_R" ), -( TF.EAC_R_SIGNED, eac, "EAC_R:SIGNED" ), -( TF.EAC_RG, eac, "EAC_RG" ), -( TF.EAC_RG_SIGNED, eac, "EAC_RG_SIGNED" ), -( TF.ETC2_RGB, etc, (2,"RGB") ), -( TF.ETC2_RGBA1, etc, (2, "A1") ), -( TF.ETC2_RGBA8, etc, (2, "A8") ), -( TF.ASTC_RGB_4x4, astc, (4,4) ), -( TF.ASTC_RGB_5x5, astc, (5,5) ), -( TF.ASTC_RGB_6x6, astc, (6,6) ), -( TF.ASTC_RGB_8x8, astc, (8,8) ), -( TF.ASTC_RGB_10x10, astc, (10,10) ), -( TF.ASTC_RGB_12x12, astc, (12,12) ), -( TF.ASTC_RGBA_4x4, astc, (4,4) ), -( TF.ASTC_RGBA_5x5, astc, (5,5) ), -( TF.ASTC_RGBA_6x6, astc, (6,6) ), -( TF.ASTC_RGBA_8x8, astc, (8,8) ), -( TF.ASTC_RGBA_10x10, astc, (10,10) ), -( TF.ASTC_RGBA_12x12, astc, (12,12) ), -( TF.ETC_RGB4_3DS, etc, (1,) ), -( TF.ETC_RGBA8_3DS, etc, (1,) ), -( TF.ETC_RGB4Crunched, etc, (1,) ), -( TF.ETC2_RGBA8Crunched, etc, (2, "A8") ), -( TF.ASTC_HDR_4x4, astc, (4,4) ), -( TF.ASTC_HDR_5x5, astc, (5,5) ), -( TF.ASTC_HDR_6x6, astc, (6,6) ), -( TF.ASTC_HDR_8x8, astc, (8,8) ), -( TF.ASTC_HDR_10x10, astc, (10,10) ), -( TF.ASTC_HDR_12x12, astc, (12,12) ), + +RG_PADDING_MAP = { + "RGE": 16, + "RGF": 32, + "RG;16": 16, + "RG;16s": 16, + "RG;8s": 8, } -# format conv_table to a dict -CONV_TABLE = {line[0]: line[1:] for line in CONV_TABLE} + +def rg(image_data: bytes, width: int, height: int, mode: str, codec: str, args) -> Image.Image: + # convert rg to rgb by adding in zeroes + padding_size = RG_PADDING_MAP[codec] + stream = BytesIO(image_data) + padding = bytes(padding_size) + rgb_data = b"".join(stream.read(padding_size * 2) + padding for _ in range(len(image_data) // (2 * padding_size))) + if codec == "RGE": + return half(rgb_data, width, height, mode, "RGB", args) + else: + return pillow(rgb_data, width, height, mode, codec.replace("RG", "RGB"), args) + + +def rgb9e5float(image_data: bytes, width: int, height: int) -> Image.Image: + rgb = bytearray(width * height * 3) + for i, (n,) in enumerate(struct.iter_unpack("> 27 & 0x1F + scalef = 2 ** (scale - 24) + scaleb = scalef * 255.0 + b = (n >> 18 & 0x1FF) * scaleb + g = (n >> 9 & 0x1FF) * scaleb + r = (n & 0x1FF) * scaleb + + offset = i * 3 + rgb[offset : offset + 3] = [r, g, b] + + return Image.frombytes("RGB", (width, height), rgb, "raw", "RGB") + + +# Mapping TextureFormat -> (converter function, (additional args, ...)) +CONV_TABLE: Dict[TF, Tuple[Callable[..., Image.Image], Tuple[Any, ...]]] = { + TF.Alpha8: (pillow, ("RGBA", "raw", "A")), + TF.ARGB4444: (pillow, ("RGBA", "raw", "RGBA;4B", (2, 1, 0, 3))), + TF.RGB24: (pillow, ("RGB", "raw", "RGB")), + TF.RGBA32: (pillow, ("RGBA", "raw", "RGBA")), + TF.ARGB32: (pillow, ("RGBA", "raw", "ARGB")), + TF.ARGBFloat: (pillow, ("RGBA", "raw", "RGBAF", (2, 1, 0, 3))), + TF.RGB565: (pillow, ("RGB", "raw", "BGR;16")), + TF.BGR24: (pillow, ("RGB", "raw", "BGR")), + TF.R8: (pillow, ("RGB", "raw", "R")), + TF.R16: (pillow, ("RGB", "raw", "R;16")), + TF.RG16: (rg, ("RGB", "raw", "RG")), + TF.DXT1: (pillow, ("RGBA", "bcn", 1)), + TF.DXT3: (pillow, ("RGBA", "bcn", 2)), + TF.DXT5: (pillow, ("RGBA", "bcn", 3)), + TF.RGBA4444: (pillow, ("RGBA", "raw", "RGBA;4B", (3, 2, 1, 0))), + TF.BGRA32: (pillow, ("RGBA", "raw", "BGRA")), + TF.RHalf: (half, ("R", "raw", "R")), + TF.RGHalf: (rg, ("RGB", "raw", "RGE")), + TF.RGBAHalf: (half, ("RGB", "raw", "RGB")), + TF.RFloat: (pillow, ("RGB", "raw", "RF")), + TF.RGFloat: (rg, ("RGB", "raw", "RGF")), + TF.RGBAFloat: (pillow, ("RGBA", "raw", "RGBAF")), + # TF.YUY2: NotImplementedError("YUY2 not implemented"), + TF.RGB9e5Float: (rgb9e5float, ()), + TF.BC4: (pillow, ("L", "bcn", 4)), + TF.BC5: (pillow, ("RGB", "bcn", 5)), + TF.BC6H: (pillow, ("RGB", "bcn", 6)), + TF.BC7: (pillow, ("RGBA", "bcn", 7)), + TF.DXT1Crunched: (pillow, ("RGBA", "bcn", 1)), + TF.DXT5Crunched: (pillow, ("RGBA", "bcn", 3)), + TF.PVRTC_RGB2: (pvrtc, (True,)), + TF.PVRTC_RGBA2: (pvrtc, (True,)), + TF.PVRTC_RGB4: (pvrtc, (False,)), + TF.PVRTC_RGBA4: (pvrtc, (False,)), + TF.ETC_RGB4: (etc, ("ETC1",)), + TF.ATC_RGB4: (atc, (False,)), + TF.ATC_RGBA8: (atc, (True,)), + TF.EAC_R: (eac, ("EAC_R",)), + TF.EAC_R_SIGNED: (eac, ("EAC_R_SIGNED",)), + TF.EAC_RG: (eac, ("EAC_RG",)), + TF.EAC_RG_SIGNED: (eac, ("EAC_RG_SIGNED",)), + TF.ETC2_RGB: (etc, ("ETC2_RGB",)), + TF.ETC2_RGBA1: (etc, ("ETC2_A1",)), + TF.ETC2_RGBA8: (etc, ("ETC2_A8",)), + TF.ASTC_RGB_4x4: (astc, ((4, 4),)), + TF.ASTC_RGB_5x5: (astc, ((5, 5),)), + TF.ASTC_RGB_6x6: (astc, ((6, 6),)), + TF.ASTC_RGB_8x8: (astc, ((8, 8),)), + TF.ASTC_RGB_10x10: (astc, ((10, 10),)), + TF.ASTC_RGB_12x12: (astc, ((12, 12),)), + TF.ASTC_RGBA_4x4: (astc, ((4, 4),)), + TF.ASTC_RGBA_5x5: (astc, ((5, 5),)), + TF.ASTC_RGBA_6x6: (astc, ((6, 6),)), + TF.ASTC_RGBA_8x8: (astc, ((8, 8),)), + TF.ASTC_RGBA_10x10: (astc, ((10, 10),)), + TF.ASTC_RGBA_12x12: (astc, ((12, 12),)), + TF.ETC_RGB4_3DS: (etc, ("ETC1",)), + TF.ETC_RGBA8_3DS: (etc, ("ETC1",)), + TF.ETC_RGB4Crunched: (etc, ("ETC1",)), + TF.ETC2_RGBA8Crunched: (etc, ("ETC2_A8",)), + TF.ASTC_HDR_4x4: (astc, ((4, 4),)), + TF.ASTC_HDR_5x5: (astc, ((5, 5),)), + TF.ASTC_HDR_6x6: (astc, ((6, 6),)), + TF.ASTC_HDR_8x8: (astc, ((8, 8),)), + TF.ASTC_HDR_10x10: (astc, ((10, 10),)), + TF.ASTC_HDR_12x12: (astc, ((12, 12),)), + TF.RG32: (rg, ("RGB", "raw", "RG;16")), + TF.RGB48: (pillow, ("RGB", "raw", "RGB;16")), + TF.RGBA64: (pillow, ("RGBA", "raw", "RGBA;16")), + TF.R8_SIGNED: (pillow, ("R", "raw", "R;8s")), + TF.RG16_SIGNED: (rg, ("RGB", "raw", "RG;8s")), + TF.RGB24_SIGNED: (pillow, ("RGB", "raw", "RGB;8s")), + TF.RGBA32_SIGNED: (pillow, ("RGBA", "raw", "RGBA;8s")), + TF.R16_SIGNED: (pillow, ("R", "raw", "R;16s")), + TF.RG32_SIGNED: (rg, ("RGB", "raw", "RG;16s")), + TF.RGB48_SIGNED: (pillow, ("RGB", "raw", "RGB;16s")), + TF.RGBA64_SIGNED: (pillow, ("RGBA", "raw", "RGBA;16s")), +} # XBOX Swap Formats XBOX_SWAP_FORMATS = [TF.RGB565, TF.DXT1, TF.DXT1Crunched, TF.DXT5, TF.DXT5Crunched] diff --git a/UnityPy/export/__init__.py b/UnityPy/export/__init__.py index 8d64f5db7..0381efb02 100644 --- a/UnityPy/export/__init__.py +++ b/UnityPy/export/__init__.py @@ -1 +1,15 @@ -from . import SpriteHelper, Texture2DConverter, AudioClipConverter +from . import ( + AudioClipConverter, + MeshExporter, + MeshRendererExporter, + SpriteHelper, + Texture2DConverter, +) + +__all__ = [ + "AudioClipConverter", + "MeshExporter", + "MeshRendererExporter", + "SpriteHelper", + "Texture2DConverter", +] diff --git a/UnityPy/files/BundleFile.py b/UnityPy/files/BundleFile.py index 154924d00..3e17d7dea 100644 --- a/UnityPy/files/BundleFile.py +++ b/UnityPy/files/BundleFile.py @@ -1,145 +1,147 @@ -from .File import File -from .SerializedFile import SerializedFile -from ..enums import FileType -from ..helpers import CompressionHelper, ImportHelper -from ..streams import EndianBinaryReader, EndianBinaryWriter - +# TODO: implement encryption for saving files +import re from collections import namedtuple +from typing import Optional, Union, cast + +from .. import config +from ..enums import ArchiveFlags, ArchiveFlagsOld, CompressionFlags +from ..helpers import ArchiveStorageManager, CompressionHelper +from ..helpers.UnityVersion import UnityVersion +from ..streams import EndianBinaryReader, EndianBinaryWriter +from . import File -DirectoryInfo = namedtuple("DirectoryInfo", "path offset size") -DirectoryInfoFS = namedtuple("DirectoryInfoFS", "offset size flags path") BlockInfo = namedtuple("BlockInfo", "uncompressedSize compressedSize flags") +DirectoryInfoFS = namedtuple("DirectoryInfoFS", "offset size flags path") +reVersion = re.compile(r"(\d+)\.(\d+)\.(\d+)\w.+") -class BundleFile(File): +class BundleFile(File.File): format: int + is_changed: bool signature: str version_engine: str version_player: str - - def __init__(self, reader: EndianBinaryReader, environment=None): - self.environment = environment - self.files = {} + dataflags: Union[ArchiveFlags, ArchiveFlagsOld] + decryptor: Optional[ArchiveStorageManager.ArchiveStorageDecryptor] = None + _uses_block_alignment: bool = False + + def __init__( + self, + reader: EndianBinaryReader, + parent: File, + name: Optional[str] = None, + **kwargs, + ): + super().__init__(parent=parent, name=name, **kwargs) signature = self.signature = reader.read_string_to_null() + self.version = reader.read_u_int() + self.version_player = reader.read_string_to_null() + self.version_engine = reader.read_string_to_null() if signature == "UnityArchive": - raise NotImplemented("BundleFile - UnityArchive") + raise NotImplementedError("BundleFile - UnityArchive") elif signature in ["UnityWeb", "UnityRaw"]: - m_DirectoryInfo, blocksReader = self.read_web_raw(reader) - # self.read_header_and_blocks_info(reader) - # blocks_stream, directory = self.read_block_and_directory(reader) - # self.read_files(blocks_stream, directory) + if self.version == 6: + m_DirectoryInfo, blocksReader = self.read_fs(reader) + else: + m_DirectoryInfo, blocksReader = self.read_web_raw(reader) elif signature == "UnityFS": m_DirectoryInfo, blocksReader = self.read_fs(reader) - # self.read_header(reader) - # directory = self.read_blocks_info_and_directory(reader) - # blocks_stream = self.read_blocks(reader) - # self.read_files(blocks_stream, directory) + else: + raise NotImplementedError(f"Unknown Bundle signature: {signature}") self.read_files(blocksReader, m_DirectoryInfo) - def read_files(self, blocksStream: EndianBinaryReader, m_DirectoryInfo): - for node in m_DirectoryInfo: - blocksStream.Position = node.offset - path = node.path - f = EndianBinaryReader(blocksStream.read(node.size)) - f._flag = getattr(node, "flags", None) # required for save - # check for serialized files - if path.endswith((".resS", ".resource", ".config", ".xml", ".dat")): - self.environment.resources[path] = f - else: - typ, _ = ImportHelper.check_file_type(f) - if typ == FileType.AssetsFile: - f.Position = 0 - f = SerializedFile(f, self.environment, self) - self.environment.assets[path] = f - else: - self.environment.resources[path] = f - - self.files[path] = f - def read_web_raw(self, reader: EndianBinaryReader): # def read_header_and_blocks_info(self, reader:EndianBinaryReader): - isCompressed = self.signature == "UnityWeb" - version = self.version = reader.read_u_int() - self.version_player = reader.read_string_to_null() - self.version_engine = reader.read_string_to_null() + version = self.version if version >= 4: - _hash = reader.read_bytes(16) - crc = reader.read_u_int() + self._hash = reader.read_bytes(16) + self.crc = reader.read_u_int() - minimumStreamedBytes = reader.read_u_int() + minimumStreamedBytes = reader.read_u_int() # noqa: F841 headerSize = reader.read_u_int() - numberOfLevelsToDownloadBeforeStreaming = reader.read_u_int() + numberOfLevelsToDownloadBeforeStreaming = reader.read_u_int() # noqa: F841 levelCount = reader.read_int() reader.Position += 4 * 2 * (levelCount - 1) compressedSize = reader.read_u_int() - uncompressedSize = reader.read_u_int() - flags = True + uncompressedSize = reader.read_u_int() # noqa: F841 if version >= 2: - completeFileSize = reader.read_u_int() + completeFileSize = reader.read_u_int() # noqa: F841 if version >= 3: - fileInfoHeaderSize = reader.read_u_int() + fileInfoHeaderSize = reader.read_u_int() # noqa: F841 reader.Position = headerSize - # def read_blocks_and_directory(self, reader : EndianBinaryReader): uncompressedBytes = reader.read_bytes(compressedSize) - if flags: - uncompressedBytes = CompressionHelper.decompress_lzma(uncompressedBytes) + if self.signature == "UnityWeb": + uncompressedBytes = CompressionHelper.decompress_lzma(uncompressedBytes, True) - blocksReader = EndianBinaryReader(uncompressedBytes) + blocksReader = EndianBinaryReader(uncompressedBytes, offset=headerSize) nodesCount = blocksReader.read_int() m_DirectoryInfo = [ - DirectoryInfo( + File.DirectoryInfo( blocksReader.read_string_to_null(), # path blocksReader.read_u_int(), # offset blocksReader.read_u_int(), # size ) - for i in range(nodesCount) + for _ in range(nodesCount) ] return m_DirectoryInfo, blocksReader def read_fs(self, reader: EndianBinaryReader): - # def read_header(self, reader : EndianBinaryReader): - self.version = reader.read_u_int() - self.version_player = reader.read_string_to_null() - self.version_engine = reader.read_string_to_null() - size = reader.read_long() + size = reader.read_long() # noqa: F841 # header compressedSize = reader.read_u_int() uncompressedSize = reader.read_u_int() - flags = reader.read_u_int() + dataflagsValue = reader.read_u_int() + + # UnityWeb version 6 + if self.signature != "UnityFS": + reader.read_byte() + + version = self.parse_version() + # https://issuetracker.unity3d.com/issues/files-within-assetbundles-do-not-start-on-aligned-boundaries-breaking-patching-on-nintendo-switch + # Unity CN introduced encryption before the alignment fix was introduced. + # Unity CN used the same flag for the encryption as later on the alignment fix, + # so we have to check the version to determine the correct flag set. + if ( + version < (2020,) + or (version[0] == 2020 and version < (2020, 3, 34)) + or (version[0] == 2021 and version < (2021, 3, 2)) + or (version[0] == 2022 and version < (2022, 1, 1)) + ): + self.dataflags = ArchiveFlagsOld(dataflagsValue) + else: + self.dataflags = ArchiveFlags(dataflagsValue) + + if self.dataflags & self.dataflags.UsesAssetBundleEncryption: + self.decryptor = ArchiveStorageManager.ArchiveStorageDecryptor(reader) + + # if header version is 7 or later we need to align the reader + # for 2019.4.15 and later, version should be 7 and aligned + # but some games in these versions somehow has version 6 while aligned + if self.version >= 7 or (version[0] == 2019 and version >= (2019, 4, 15)): + reader.align_stream(16) + self._uses_block_alignment = True - # def read_blocks_info_and_directory(self, reader : EndianBinaryReader): - if flags & 0x80 != 0: # kArchiveBlocksInfoAtTheEnd - position = reader.Position + start = reader.Position + if self.dataflags & ArchiveFlags.BlocksInfoAtTheEnd: # kArchiveBlocksInfoAtTheEnd reader.Position = reader.Length - compressedSize blocksInfoBytes = reader.read_bytes(compressedSize) - reader.Position = position + reader.Position = start else: # 0x40 kArchiveBlocksAndDirectoryInfoCombined - if self.version >= 7: - reader.allign_stream(16) blocksInfoBytes = reader.read_bytes(compressedSize) - switch = flags & 0x3F + blocksInfoBytes = self.decompress_data(blocksInfoBytes, uncompressedSize, self.dataflags) + blocksInfoReader = EndianBinaryReader(blocksInfoBytes, offset=start) - if switch == 1: # LZMA - blocksInfoBytes = CompressionHelper.decompress_lzma(blocksInfoBytes) - elif switch in [2, 3]: # LZ4, LZ4HC - blocksInfoBytes = CompressionHelper.decompress_lz4( - blocksInfoBytes, uncompressedSize - ) - # elif switch == 4: #LZHAM: - - blocksInfoReader = EndianBinaryReader(blocksInfoBytes) - - uncompressedDataHash = blocksInfoReader.read_bytes(16) + uncompressedDataHash = blocksInfoReader.read_bytes(16) # noqa: F841 blocksInfoCount = blocksInfoReader.read_int() m_BlocksInfo = [ @@ -148,7 +150,7 @@ def read_fs(self, reader: EndianBinaryReader): blocksInfoReader.read_u_int(), # compressedSize blocksInfoReader.read_u_short(), # flags ) - for i in range(blocksInfoCount) + for _ in range(blocksInfoCount) ] nodesCount = blocksInfoReader.read_int() @@ -159,73 +161,102 @@ def read_fs(self, reader: EndianBinaryReader): blocksInfoReader.read_u_int(), # flags blocksInfoReader.read_string_to_null(), # path ) - for i in range(nodesCount) + for _ in range(nodesCount) ] - # def read_blocks(reader : EndianBinaryReader, blocksStream): - def decompress_block(blockInfo): - switch = blockInfo.flags & 0x3F # kStorageBlockCompressionTypeMask - if switch == 1: # LZMA - return CompressionHelper.decompress_lzma( - reader.read_bytes(blockInfo.compressedSize) - ) - elif switch in [2, 3]: # LZ4, LZ4HC - return CompressionHelper.decompress_lz4( + if m_BlocksInfo: + self._block_info_flags = m_BlocksInfo[0].flags + + if isinstance(self.dataflags, ArchiveFlags) and self.dataflags & ArchiveFlags.BlockInfoNeedPaddingAtStart: + reader.align_stream(16) + + blocksReader = EndianBinaryReader( + b"".join( + self.decompress_data( reader.read_bytes(blockInfo.compressedSize), blockInfo.uncompressedSize, + blockInfo.flags, + i, ) - # elif switch == 4: #LZHAM: - else: # no compression - return reader.read_bytes(blockInfo.uncompressedSize) - - blocksReader = EndianBinaryReader( - b"".join(decompress_block(blockInfo) for blockInfo in m_BlocksInfo) + for i, blockInfo in enumerate(m_BlocksInfo) + ), + offset=(blocksInfoReader.real_offset()), ) return m_DirectoryInfo, blocksReader - def save(self): + def save(self, packer=None): + """ + Rewrites the BundleFile and returns it as bytes object. + + packer: + can be either one of the following strings + or tuple consisting of (block_info_flag, data_flag) + allowed strings: + none - no compression, default, safest bet + lz4 - lz4 compression + original - uses the original flags + """ # file_header - # signature (string_to_null) - # format (int) - # version_player (string_to_null) - # version_engine (string_to_null) + # signature (string_to_null) + # format (int) + # version_player (string_to_null) + # version_engine (string_to_null) writer = EndianBinaryWriter() writer.write_string_to_null(self.signature) - writer.write_int(self.format) + writer.write_u_int(self.version) writer.write_string_to_null(self.version_player) writer.write_string_to_null(self.version_engine) if self.signature == "UnityArchive": - raise NotImplemented("BundleFile - UnityArchive") + raise NotImplementedError("BundleFile - UnityArchive") elif self.signature in ["UnityWeb", "UnityRaw"]: - raise NotImplemented("Saving Unity Web and Raw bundles isn't supported yet") - # self.save_web_raw(writer) - elif signature == "UnityFS": - self.save_fs(writer) + if self.version == 6: + self.save_fs(writer, 64, 64) + else: + self.save_web_raw(writer) + elif self.signature == "UnityFS": + if not packer or packer == "none": + self.save_fs(writer, 64, 64) + elif packer == "original": + self.save_fs( + writer, + data_flag=self.dataflags, + block_info_flag=self._block_info_flags, + ) + elif packer == "lz4": + self.save_fs(writer, data_flag=194, block_info_flag=2) + elif packer == "lzma": + self.save_fs(writer, data_flag=65, block_info_flag=1) + elif isinstance(packer, tuple): + self.save_fs(writer, *packer) + else: + raise NotImplementedError("UnityFS - Packer:", packer) return writer.bytes - def save_fs(self, writer: EndianBinaryWriter, padding=False): + def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: int): # header # compressed blockinfo (block details & directionary) # compressed assets - # 192 - uncompressed - # 194 - lz4 - block_info_flag = 64 - # 0/64 - uncompressed - # 1 - lzma - # 2 - lz4 - # 3 - lz4hc - not implemented - # 4 - lzham - not implemented - data_flag = 64 + # 0b1000000 / 0b11000000 | 64 / 192 - uncompressed + # 0b11000010 | 194 - lz4 + # block_info_flag + + # 0 / 0b1000000 | 0 / 64 - uncompressed + # 0b1 | 1 - lzma + # 0b10 | 2 - lz4 + # 0b11 | 3 - lz4hc [not implemented] + # 0b100 | 4 - lzham [not implemented] + # data_flag + # header: - # bundle_size (long) - # compressed_size (int) - # uncompressed_size (int) - # flag (int) - # ?padding? (bool) + # bundle_size (long) + # compressed_size (int) + # uncompressed_size (int) + # flag (int) + # ?padding? (bool) # This will be written at the end, # because the size can only be calculated after the data compression, @@ -235,31 +266,32 @@ def save_fs(self, writer: EndianBinaryWriter, padding=False): # *read compressed_size -> uncompressed_size # 0x10 offset # *read blocks infos of the data stream - # count (int) + # count (int) # ( - # uncompressed_size(uint) - # compressed_size (uint) - # flag(short) + # uncompressed_size (uint) + # compressed_size (uint) + # flag (short) # ) # *decompression via info.flag & 0x3F # *afterwards the file positions - # file_count (int) + # file_count (int) # ( - # offset (long) - # size (long) - # flag (int) - # name (string_to_null) + # offset (long) + # size (long) + # flag (int) + # name (string_to_null) # ) # file list & file data + # prep nodes and build up block data data_writer = EndianBinaryWriter() files = [ ( name, - f.flag, + f.flags, data_writer.write_bytes( - f.bytes if isinstance(f, EndianBinaryReader) else f.save() + f.bytes if isinstance(f, (EndianBinaryReader, EndianBinaryWriter)) else f.save() ), ) for name, f in self.files.items() @@ -267,32 +299,31 @@ def save_fs(self, writer: EndianBinaryWriter, padding=False): file_data = data_writer.bytes data_writer.dispose() - uncompressed_data_size = len(file_data) - # compress the data - switch = data_flag & 0x3F - if switch == 1: # LZMA - file_data = CompressionHelper.compress_lzma(file_data) - elif switch in [2, 3]: # LZ4, LZ4HC - file_data = CompressionHelper.compress_lz4(file_data) - elif switch == 4: # LZHAM - raise NotImplementedError - # else no compression - data stays the same - compressed_data_size = len(file_data) + # remove encryption flag, as encryption isn't done + if block_info_flag & self.dataflags.UsesAssetBundleEncryption: + block_info_flag ^= self.dataflags.UsesAssetBundleEncryption + if data_flag & self.dataflags.UsesAssetBundleEncryption: + data_flag ^= self.dataflags.UsesAssetBundleEncryption + + file_data, block_info = CompressionHelper.chunk_based_compress(file_data, block_info_flag) # write the block_info + # uncompressedDataHash block_writer = EndianBinaryWriter(b"\x00" * 0x10) # data block info - # block count - block_writer.write_int(1) - # uncompressed size - block_writer.write_u_int(uncompressed_data_size) - # compressed size - block_writer.write_u_int(compressed_data_size) - # flag - block_writer.write_short(data_flag) + block_writer.write_int(len(block_info)) + for block_uncompressed_size, block_compressed_size, block_flag in block_info: + # uncompressed size + block_writer.write_u_int(block_uncompressed_size) + # compressed size + block_writer.write_u_int(block_compressed_size) + # flag + block_writer.write_u_short(block_flag) # file block info + if not data_flag & 0x40: + raise NotImplementedError("UnityPy always writes DirectoryInfo, so data_flag must include 0x40") # file count block_writer.write_int(len(files)) offset = 0 @@ -303,7 +334,7 @@ def save_fs(self, writer: EndianBinaryWriter, padding=False): block_writer.write_long(f_len) offset += f_len # flag - block_writer.write_int(f_flag) + block_writer.write_u_int(f_flag) # name block_writer.write_string_to_null(f_name) @@ -313,36 +344,193 @@ def save_fs(self, writer: EndianBinaryWriter, padding=False): uncompressed_block_data_size = len(block_data) - switch = block_info_flag & 0x3F - if switch == 1: # LZMA - block_data = CompressionHelper.compress_lzma(block_data) - elif switch in [2, 3]: # LZ4, LZ4HC - block_data = CompressionHelper.compress_lz4(block_data) - elif switch == 4: # LZHAM - raise NotImplementedError + switch = data_flag & 0x3F + if switch in CompressionHelper.COMPRESSION_MAP: + block_data = CompressionHelper.COMPRESSION_MAP[switch](block_data) + else: + raise NotImplementedError(f"No compression function in the CompressionHelper.COMPRESSION_MAP for {switch}") compressed_block_data_size = len(block_data) # write the header info - writer.write_long( - writer.Length - + 8 - + 4 - + 4 - + 4 - + (1 if padding else 0) - + compressed_block_data_size - + compressed_data_size - ) - writer.write_int(compressed_block_data_size) - writer.write_int(uncompressed_block_data_size) - writer.write_int(block_info_flag) - if padding: - writer.write_boolean(padding) - - if (block_info_flag & 0x80) != 0: # at end of file + ## file size - 0 for now, will be set at the end + writer_header_pos = writer.Position + writer.write_long(0) + # compressed blockInfoBytes size + writer.write_u_int(compressed_block_data_size) + # uncompressed size + writer.write_u_int(uncompressed_block_data_size) + # compression and file layout flag + writer.write_u_int(data_flag) + + # UnityWeb version 6 + if self.signature != "UnityFS": + writer.write_byte(0) + + if self._uses_block_alignment: + # UnityFS\x00 - 8 + # size 8 + # comp sizes 4+4 + # flag 4 + # sum : 28 -> +8 alignment + writer.align_stream(16) + + if data_flag & 0x80: # at end of file + if data_flag & 0x200: + writer.align_stream(16) writer.write(file_data) writer.write(block_data) else: writer.write(block_data) + if data_flag & 0x200: + writer.align_stream(16) writer.write(file_data) + + writer_end_pos = writer.Position + writer.Position = writer_header_pos + # correct file size + writer.write_long(writer_end_pos) + writer.Position = writer_end_pos + + def save_web_raw(self, writer: EndianBinaryWriter): + # (version >= 4) hash + # (version >= 4) crc + # minimumStreamedBytes + # headerSize + # numberOfLevelsToDownloadBeforeStreaming + # levelCount + # compressedSize * levelCount + # uncompressedSize * levelCount + # (version >= 2) completeFileSize + # (version >= 3) file_info_header_size + # compressed assets + + if self.version > 3: + raise NotImplementedError("Saving Unity Web bundles with version > 3 is not supported") + + # Calculate fileInfoHeaderSize for set offsets + file_info_header_size = 4 # for nodesCount + + for file_name in self.files.keys(): + file_info_header_size += len(file_name.encode()) + 1 # +1 for null terminator + file_info_header_size += 4 * 2 # 4 bytes each for offset and size + + file_info_header_padding_size = 4 - (file_info_header_size % 4) if file_info_header_size % 4 != 0 else 0 + file_info_header_size += file_info_header_padding_size + + # Prepare directory info + directory_info_writer = EndianBinaryWriter() + directory_info_writer.write_int(len(self.files)) # nodesCount + + file_content_writer = EndianBinaryWriter() + current_offset = file_info_header_size + + for file_name, f in self.files.items(): + directory_info_writer.write_string_to_null(file_name) + directory_info_writer.write_u_int(current_offset) + + # Get file content + if isinstance(f, (EndianBinaryReader, EndianBinaryWriter)): + file_data = f.bytes + else: + file_data = f.save() + + file_size = len(file_data) + directory_info_writer.write_u_int(file_size) + + file_content_writer.write_bytes(file_data) + current_offset += file_size + + directory_info_writer.write(b"\x00" * file_info_header_padding_size) + uncompressed_directory_info = directory_info_writer.bytes + uncompressed_file_content = file_content_writer.bytes + + # Combine directory info and file content + uncompressed_content = uncompressed_directory_info + uncompressed_file_content + compressed_content = uncompressed_content + if self.signature == "UnityWeb": + compressed_content = CompressionHelper.compress_lzma(uncompressed_content, True) + + # Write header + header_size = writer.Position + 24 # assuming levelCount = 1 + if self.version >= 2: + header_size += 4 + if self.version >= 3: + header_size += 4 + if self.version >= 4: + header_size += 20 + # pad to multiple of 4 + header_size = (header_size + 3) & ~3 + + if self.version >= 4: + writer.write_bytes(self._hash) + writer.write_u_int(self.crc) + + writer.write_u_int(header_size + len(compressed_content)) # minimumStreamedBytes (same as completeFileSize) + writer.write_u_int(header_size) # headerSize + writer.write_u_int(1) # numberOfLevelsToDownloadBeforeStreaming (always 1) + writer.write_int(1) # levelCount (always 1) + + writer.write_u_int(len(compressed_content)) # compressedSize + writer.write_u_int(len(uncompressed_content)) # uncompressedSize + + if self.version >= 2: + writer.write_u_int(header_size + len(compressed_content)) # completeFileSize + + if self.version >= 3: + writer.write_u_int(file_info_header_size) # file_info_header_size + + # align header + writer.align_stream(4) + + # Write compressed content + writer.write(compressed_content) + + def decompress_data( + self, + compressed_data: bytes, + uncompressed_size: int, + flags: Union[int, ArchiveFlags, ArchiveFlagsOld], + index: int = 0, + ) -> bytes: + """ + Parameters + ---------- + compressed_data : bytes + The compressed data. + uncompressed_size : int + The uncompressed size of the data. + flags : int + The flags of the data. + + Returns + ------- + bytes + The decompressed data.""" + comp_flag = CompressionFlags(flags & ArchiveFlags.CompressionTypeMask) + + if self.decryptor is not None and flags & 0x100 and comp_flag != CompressionFlags.NONE: + compressed_data = self.decryptor.decrypt_block(compressed_data, index) + + if comp_flag in CompressionHelper.DECOMPRESSION_MAP: + return cast( + bytes, + CompressionHelper.DECOMPRESSION_MAP[comp_flag](compressed_data, uncompressed_size), + ) + else: + raise ValueError(f"Unknown compression! flag: {flags}, compression flag: {comp_flag.value}") + + def parse_version(self) -> UnityVersion: + """Returns the version as a tuple.""" + version = None + version_str = self.version_engine + try: + version = UnityVersion.from_str(version_str) + except ValueError: + pass + + if version is None or version.major == 0: + version_str = config.get_fallback_version() + version = UnityVersion.from_str(version_str) + + return version diff --git a/UnityPy/files/File.py b/UnityPy/files/File.py index 372b5621e..e672f197c 100644 --- a/UnityPy/files/File.py +++ b/UnityPy/files/File.py @@ -1,9 +1,131 @@ -class File(object): +from __future__ import annotations + +from collections import namedtuple +from os.path import basename +from typing import TYPE_CHECKING, Dict, Optional + +from ..helpers import ImportHelper +from ..streams import EndianBinaryReader, EndianBinaryWriter + +if TYPE_CHECKING: + from ..environment import Environment + +DirectoryInfo = namedtuple("DirectoryInfo", "path offset size") + + +class File: name: str - files: dict + files: Dict[str, File] + environment: Environment + cab_file: str + is_changed: bool signature: str - compression: str - # parent: File + packer: str + is_dependency: bool + parent: Optional[File] + + def __init__( + self, + parent: Optional[File] = None, + name: Optional[str] = None, + is_dependency: bool = False, + ): + self.files = {} + self.is_changed = False + self.cab_file = "CAB-UnityPy_Mod.resS" + self.parent = parent + self.environment = getattr(parent, "environment", parent) if parent else None + self.name = basename(name) if isinstance(name, str) else "" + self.is_dependency = is_dependency + + def get_assets(self): + if isinstance(self, SerializedFile.SerializedFile): + return self + + for f in self.files.values(): + if isinstance(f, (BundleFile.BundleFile, WebFile.WebFile)): + for asset in f.get_assets(): + yield asset + elif isinstance(f, SerializedFile.SerializedFile): + yield f + + def get_filtered_objects(self, obj_types: Optional[list] = None): + if obj_types is None or len(obj_types) == 0: + return self.get_objects() + for f in self.files.values(): + if isinstance(f, (BundleFile.BundleFile, WebFile.WebFile)): + for obj in f.objects: + if obj.type in obj_types: + yield obj + elif isinstance(f, SerializedFile.SerializedFile): + for obj in f.objects.values(): + if obj.type in obj_types: + yield obj + + def get_objects(self): + for f in self.files.values(): + if isinstance(f, (BundleFile.BundleFile, WebFile.WebFile)): + for obj in f.objects: + yield obj + elif isinstance(f, SerializedFile.SerializedFile): + for obj in f.objects.values(): + yield obj + elif isinstance(f, ObjectReader.ObjectReader): + yield f + + def read_files(self, reader: EndianBinaryReader, files: list): + # read file data and convert it + for node in files: + reader.Position = node.offset + name = node.path + node_reader = EndianBinaryReader(reader.read(node.size), offset=(reader.BaseOffset + node.offset)) + f = ImportHelper.parse_file(node_reader, self, name, is_dependency=self.is_dependency) + + if isinstance(f, (EndianBinaryReader, SerializedFile.SerializedFile)): + if self.environment: + self.environment.register_cab(name, f) + + # required for BundleFiles + f.flags = getattr(node, "flags", 0) + self.files[name] = f + + def get_writeable_cab(self, name: Optional[str] = None): + """ + Creates a new cab file in the bundle that contains the given data. + This is useful for asset types that use resource files. + """ + + if not name: + name = self.cab_file + + if not name: + return None + + if name in self.files: + if isinstance(self.files[name], EndianBinaryWriter): + return self.files[name] + else: + raise ValueError("This cab already exists and isn't an EndianBinaryWriter") + + writer = EndianBinaryWriter() + # try to find another resource file to copy the flags from + for fname, f in self.files.items(): + if fname.endswith(".resS"): + writer.flags = f.flags + writer.endian = f.endian + break + else: + writer.flags = 0 + writer.name = name + self.files[name] = writer + return writer + + @property + def container(self): + return {path: obj for f in self.files.values() if isinstance(f, File) for path, obj in f.container.items()} + + def get(self, key, default=None): + return getattr(self, key, default) def keys(self): return self.files.keys() @@ -19,3 +141,12 @@ def __getitem__(self, item): def __repr__(self): return f"<{self.__class__.__name__}>" + + def mark_changed(self): + if isinstance(self.parent, File): + self.parent.mark_changed() + self.is_changed = True + + +# recursive import requires the import down here +from . import BundleFile, ObjectReader, SerializedFile, WebFile # noqa: E402 diff --git a/UnityPy/files/ObjectReader.py b/UnityPy/files/ObjectReader.py new file mode 100644 index 000000000..b5fe1dbd0 --- /dev/null +++ b/UnityPy/files/ObjectReader.py @@ -0,0 +1,355 @@ +from __future__ import annotations + +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Generic, + List, + Optional, + Type, + TypeVar, + Union, + cast, +) + +from attrs import define + +from ..classes import MonoBehaviour +from ..classes.ClassIDTypeToClassMap import ClassIDTypeToClassMap +from ..enums import ClassIDType +from ..exceptions import TypeTreeError +from ..helpers import TypeTreeHelper +from ..helpers.Tpk import get_typetree_node +from ..helpers.TypeTreeNode import TypeTreeNode +from ..streams import EndianBinaryReader, EndianBinaryWriter + +if TYPE_CHECKING: + from ..files.SerializedFile import SerializedFile, SerializedType + +T = TypeVar("T") +NodeInput = Union[TypeTreeNode, List[Dict[str, Union[str, int]]]] + + +@define( + slots=True, +) +class ObjectReader(Generic[T]): + assets_file: SerializedFile + reader: EndianBinaryReader + path_id: int + type_id: int + serialized_type: Optional[SerializedType] + class_id: int + type: ClassIDType + byte_start: int + byte_size: int + is_destroyed: Optional[int] + is_stripped: Optional[int] + data: Optional[bytes] = None + _read_until: Optional[int] = None + + @property + def version(self): + return self.assets_file.version + + @property + def version2(self): + return self.assets_file.header.version + + @property + def platform(self): + return self.assets_file.target_platform + + # saves where the parser stopped + # in case that not all data is read + # and the obj.data is changed, the unknown data can be added again + @classmethod + def from_reader(cls, assets_file: SerializedFile, reader: EndianBinaryReader) -> ObjectReader[Any]: + header = assets_file.header + types = assets_file.types + + # AssetStudio ObjectInfo init + if assets_file.big_id_enabled: + path_id = reader.read_long() + elif header.version < 14: + path_id = reader.read_int() + else: + reader.align_stream() + path_id = reader.read_long() + + if header.version >= 22: + byte_start = reader.read_long() + else: + byte_start = reader.read_u_int() + + byte_start += header.data_offset + byte_size = reader.read_u_int() + + type_id = reader.read_int() + + serialized_type = None + if header.version < 16: + class_id = reader.read_u_short() + for typ in types: + if typ.class_id == type_id: + serialized_type = typ + break + else: + typ = types[type_id] + serialized_type = typ + class_id = typ.class_id + + clz_type = ClassIDType(class_id) + + is_destroyed = None + if header.version < 11: + is_destroyed = reader.read_u_short() + + if 11 <= header.version < 17: + script_type_index = reader.read_short() + if serialized_type: + serialized_type.script_type_index = script_type_index + + is_stripped = None + if header.version == 15 or header.version == 16: + is_stripped = reader.read_byte() + + return cls( + assets_file, + reader, + path_id, + type_id, + serialized_type, + class_id, + clz_type, + byte_start, + byte_size, + is_destroyed, + is_stripped, + ) + + def write(self, header, writer: EndianBinaryWriter, data_writer: EndianBinaryWriter): + if self.assets_file.big_id_enabled: + writer.write_long(self.path_id) + elif header.version < 14: + writer.write_int(self.path_id) + else: + writer.align_stream() + writer.write_long(self.path_id) + + if self.data is not None: + data = self.data + # in some cases the parser doesn't read all of the object data + # games might still require the missing data + # so following code appends the missing data back to edited objects + # -> following solution has let to some problems, so it will be removed for now + # if self.type != ClassIDType.MonoBehaviour: + # end_pos = self.byte_start + self.byte_size + # if self._read_until and self._read_until != end_pos: + # self.reader.Position = self._read_until + # data += self.reader.read_bytes(end_pos - self._read_until) + else: + self.reset() + data = self.reader.read(self.byte_size) + + if header.version >= 22: + writer.write_long(data_writer.Position) + else: + writer.write_u_int(data_writer.Position) + + writer.write_u_int(len(data)) + data_writer.write(data) + + writer.write_int(self.type_id) + + if header.version < 16: + writer.write_u_short(self.class_id) + + if header.version < 11: + assert self.is_destroyed is not None + writer.write_u_short(self.is_destroyed) + + if 11 <= header.version < 17: + assert self.serialized_type is not None + writer.write_short(self.serialized_type.script_type_index) + + if header.version == 15 or header.version == 16: + assert self.is_stripped is not None + writer.write_byte(self.is_stripped) + + def set_raw_data(self, data: bytes): + self.data = data + if self.assets_file: + self.assets_file.mark_changed() + + def get_class(self) -> Union[Type[T], None]: + return ClassIDTypeToClassMap.get(self.type) # type: ignore + + def peek_name(self) -> Union[str, None]: + """Peeks the name of the object without reading/parsing the whole object.""" + node = self._get_typetree_node() + peek_node = node.get_name_peek_node() + if peek_node: + node, key = peek_node + return self.parse_as_dict(node, check_read=False)[key] + else: + return None + + @property + def container(self): + env = self.assets_file.environment + if env is not None: + env._build_container_index() + return self.assets_file._container.path_dict.get(self.path_id) + + @property + def Position(self): + return self.reader.Position + + @Position.setter + def Position(self, pos): + self.reader.Position = pos + + def reset(self): + self.reader.Position = self.byte_start + + def read(self, check_read: bool = True) -> T: + return self.read_typetree(wrap=True, check_read=check_read) # type: ignore + + def get(self, key, default=None): + return getattr(self, key, default) + + def __getattr__(self, item: str): + if hasattr(self.reader, item): + return getattr(self.reader, item) + + def __repr__(self): + return "<%s %s>" % (self.__class__.__name__, self.type.name) + + ################################################### + # + # Typetree Stuff + # + ################################################### + + def dump_typetree_structure( + self, + nodes: Optional[NodeInput] = None, + indent: str = " ", + ) -> str: + node = self._get_typetree_node(nodes) + return node.dump_structure(indent=indent) + + def read_typetree( + self, + nodes: Optional[NodeInput] = None, + wrap: bool = False, + check_read: bool = True, + ) -> Union[dict, T]: + node = self._get_typetree_node(nodes) + self.reset() + ret = TypeTreeHelper.read_typetree( + node, + self.reader, + as_dict=not wrap, + assetsfile=self.assets_file, + byte_size=self.byte_size, + check_read=check_read, + ) + if wrap: + ret.set_object_reader(self) # type: ignore + return ret # type: ignore + + def save_typetree( + self, + tree: Union[dict, T], + nodes: Optional[NodeInput] = None, + writer: Optional[EndianBinaryWriter] = None, + ): + node = self._get_typetree_node(nodes) + if not writer: + writer = EndianBinaryWriter(endian=self.reader.endian) + TypeTreeHelper.write_typetree(tree, node, writer, self.assets_file) + data = writer.bytes + self.set_raw_data(data) + return data + + def get_raw_data(self) -> bytes: + pos = self.Position + self.reset() + ret = self.reader.read_bytes(self.byte_size) + self.Position = pos + return ret + + def _get_typetree_node( + self, + node: Optional[NodeInput] = None, + ) -> TypeTreeNode: + if isinstance(node, TypeTreeNode): + return node + elif isinstance(node, list): + return TypeTreeNode.from_list(node) + elif node is not None: + raise ValueError("nodes must be a list[dict] or TypeTreeNode") + + if self.serialized_type: + node = self.serialized_type.node + if not node: + node = get_typetree_node(self.class_id, self.version) + if node.m_Type == "MonoBehaviour": + try: + node = self.generate_monobehaviour_node(node) + except ValueError: + pass + if not node: + raise TypeTreeError("There are no TypeTree nodes for this object.") + return node + + # UnityPy 2 syntax early implementation + def parse_as_object(self, node: Optional[NodeInput] = None, check_read: bool = True) -> T: + return self.read_typetree(nodes=node, wrap=True, check_read=check_read) # type: ignore + + def parse_as_dict(self, node: Optional[NodeInput] = None, check_read: bool = True) -> dict[str, Any]: + return self.read_typetree(nodes=node, wrap=False, check_read=check_read) # type: ignore + + def patch( + self, + obj: Union[dict, T], + nodes: Optional[NodeInput] = None, + writer: Optional[EndianBinaryWriter] = None, + ): + return self.save_typetree(obj, nodes=nodes, writer=writer) + + # MonoBehaviour specific methods + def parse_monobehaviour_head(self, mb_node: Optional[TypeTreeNode] = None) -> MonoBehaviour: + if mb_node is None: + mb_node = get_typetree_node(ClassIDType.MonoBehaviour, self.version) + + mb = self.read_typetree(nodes=mb_node, wrap=True, check_read=False) + return cast(MonoBehaviour, mb) + + def generate_monobehaviour_node(self, mb_node: Optional[TypeTreeNode] = None) -> TypeTreeNode: + env = self.assets_file.environment + generator = env.typetree_generator + if generator is None: + raise ValueError("MonoBehaviour detected, but no typetree_generator set to the environment!") + + monobehaviour = self.parse_monobehaviour_head(mb_node) + script = monobehaviour.m_Script.deref_parse_as_object() + + if script.m_Namespace != "": + fullname = f"{script.m_Namespace}.{script.m_ClassName}" + else: + fullname = script.m_ClassName + + node = generator.get_nodes_up(script.m_AssemblyName, fullname) + if node: + return node + else: + raise ValueError(f"Failed to generate MonoBehaviour node for {fullname} of {script.m_AssemblyName}!") + + +__all__ = [ + "ObjectReader", +] diff --git a/UnityPy/files/SerializedFile.py b/UnityPy/files/SerializedFile.py index 7246d4c99..d8853378b 100644 --- a/UnityPy/files/SerializedFile.py +++ b/UnityPy/files/SerializedFile.py @@ -1,31 +1,45 @@ -import os -import re -import sys +from __future__ import annotations -from .File import File -from .. import classes -from ..CommonString import COMMON_STRING +from ntpath import basename +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple + +from attrs import define + +from .. import config from ..enums import BuildTarget, ClassIDType -from ..streams import EndianBinaryReader, EndianBinaryWriter +from ..helpers.ContainerHelper import ContainerHelper +from ..helpers.Tpk import get_common_strings +from ..helpers.TypeTreeHelper import TypeTreeNode +from ..helpers.UnityVersion import UnityVersion +from ..streams import EndianBinaryWriter +from . import BundleFile, File +from .ObjectReader import ObjectReader -RECURSION_LIMIT = sys.getrecursionlimit() +if TYPE_CHECKING: + from ..classes import AssetBundle, Object + from ..files import ObjectReader + from ..streams.EndianBinaryReader import EndianBinaryReader +@define(slots=True) class SerializedFileHeader: metadata_size: int file_size: int version: int data_offset: int - endian: bytes + endian: str reserved: bytes def __init__(self, reader: EndianBinaryReader): - self.metadata_size = reader.read_u_int() - self.file_size = reader.read_u_int() - self.version = reader.read_u_int() - self.data_offset = reader.read_u_int() + ( + self.metadata_size, + self.file_size, + self.version, + self.data_offset, + ) = reader.read_u_int_array(4) +@define(slots=True) class LocalSerializedObjectIdentifier: # script type local_serialized_file_index: int local_identifier_in_file: int @@ -47,15 +61,20 @@ def write(self, header: SerializedFileHeader, writer: EndianBinaryWriter): writer.write_long(self.local_identifier_in_file) +@define(slots=True) class FileIdentifier: # external - guid: bytes - type: int - # enum { kNonAssetType = 0, kDeprecatedCachedAssetType = 1, kSerializedAssetType = 2, kMetaAssetType = 3 }; path: str + temp_empty: Optional[str] = None + guid: Optional[bytes] = None + type: Optional[int] = None + # enum { kNonAssetType = 0, kDeprecatedCachedAssetType = 1, kSerializedAssetType = 2, kMetaAssetType = 3 }; @property def name(self): - return os.path.basename(self.path) + return basename(self.path) + + def __repr__(self): + return f"<{self.__class__.__name__}({self.path})>" def __init__(self, header: SerializedFileHeader, reader: EndianBinaryReader): if header.version >= 6: @@ -67,91 +86,170 @@ def __init__(self, header: SerializedFileHeader, reader: EndianBinaryReader): def write(self, header: SerializedFileHeader, writer: EndianBinaryWriter): if header.version >= 6: + assert self.temp_empty is not None writer.write_string_to_null(self.temp_empty) if header.version >= 5: + assert self.guid is not None and self.type is not None writer.write_bytes(self.guid) writer.write_int(self.type) writer.write_string_to_null(self.path) -class TypeTreeNode: - type: str - name: str - byte_size: int - index: int - is_array: int - version: int - meta_flag: int - level: int - type_str_offset: int - name_str_offset: int +@define(slots=True, init=False) +class SerializedType: + class_id: int + is_stripped_type: Optional[bool] = None + script_type_index: int = -1 + script_id: Optional[bytes] = None # Hash128 + old_type_hash: Optional[bytes] = None # Hash128 + node: Optional[TypeTreeNode] = None + # ref type + m_ClassName: Optional[str] = None + m_NameSpace: Optional[str] = None + m_AssemblyName: Optional[str] = None + # 21+ + type_dependencies: Optional[Tuple[int, ...]] = None + + def __init__( + self, + reader: EndianBinaryReader, + serialized_file: SerializedFile, + is_ref_type: bool, + ): + version = serialized_file.header.version + self.class_id = reader.read_int() + self.__attrs_init__(self.class_id) + if version >= 16: + self.is_stripped_type = reader.read_boolean() -class BuildType: - build_type: str + if version >= 17: + self.script_type_index = reader.read_short() - def __init__(self, build_type): - self.build_type = build_type + if version >= 13: + if ( + (is_ref_type and self.script_type_index >= 0) + or (version < 16 and self.class_id < 0) + or (version >= 16 and self.class_id == 114) + ): + self.script_id = reader.read_bytes(16) + self.old_type_hash = reader.read_bytes(16) - @property - def is_alpha(self): - return self.build_type == "a" + if serialized_file._enable_type_tree: + if version >= 12 or version == 10: + self.node = TypeTreeNode.parse_blob(reader, version) + else: + self.node = TypeTreeNode.parse(reader, version) - @property - def is_path(self): - return self.build_type == "p" + if version >= 21: + if is_ref_type: + self.m_ClassName = reader.read_string_to_null() + self.m_NameSpace = reader.read_string_to_null() + self.m_AssemblyName = reader.read_string_to_null() + else: + self.type_dependencies = reader.read_int_array() + def write( + self, + serialized_file: SerializedFile, + writer: EndianBinaryWriter, + is_ref_type: bool, + ): + version = serialized_file.header.version + writer.write_int(self.class_id) + + if version >= 16: + assert self.is_stripped_type is not None + writer.write_boolean(self.is_stripped_type) + + if version >= 17: + assert self.script_type_index is not None + writer.write_short(self.script_type_index) + + if version >= 13: + if ( + (is_ref_type and self.script_type_index >= 0) + or (version < 16 and self.class_id < 0) + or (version >= 16 and self.class_id == 114) + ): + assert self.script_id is not None + writer.write_bytes(self.script_id) # Hash128 + assert self.old_type_hash is not None + writer.write_bytes(self.old_type_hash) # Hash128 + + if serialized_file._enable_type_tree: + assert self.node is not None + if version >= 12 or version == 10: + self.node.dump_blob(writer, version) + else: + serialized_file.dump(writer, version) + + if version >= 21: + if is_ref_type: + assert ( + self.m_ClassName is not None + and self.m_NameSpace is not None + and self.m_AssemblyName is not None + ) + writer.write_string_to_null(self.m_ClassName) + writer.write_string_to_null(self.m_NameSpace) + writer.write_string_to_null(self.m_AssemblyName) + else: + assert self.type_dependencies is not None + writer.write_int_array(self.type_dependencies, True) -class SerializedType: - class_id: ClassIDType - is_stripped_type: bool - script_type_index = -1 - nodes: list = [] # TypeTreeNode - script_id: bytes # Hash128 - old_type_hash: bytes # Hash128} + @property + def nodes(self) -> Optional[TypeTreeNode]: + # for compatibility with old versions + return self.node -class SerializedFile(File): +class SerializedFile(File.File): reader: EndianBinaryReader + version: UnityVersion unity_version: str - version: list - build_type: BuildType target_platform: BuildTarget - types: list - script_types: list - externals: list - _container: dict - objects: dict - container: dict - _cache: dict + _enable_type_tree: bool + types: List[SerializedType] + script_types: List[LocalSerializedObjectIdentifier] + externals: List[FileIdentifier] + ref_types: Optional[List[SerializedType]] + objects: Dict[int, ObjectReader] + unknown: int header: SerializedFileHeader + _m_target_platform: int + big_id_enabled: int + userInformation: Optional[str] + assetbundle: Optional[AssetBundle] + _cache: Dict[str, Object] @property def files(self): - return self.objects + if self.objects: + return self.objects + return {} + + @files.setter + def files(self, value): + self.objects = value - def __init__(self, reader: EndianBinaryReader, environment=None, parent=None): - self.environment = environment - self.parent = parent + def __init__(self, reader: EndianBinaryReader, parent=None, name=None, **kwargs): + super().__init__(parent=parent, name=name, **kwargs) self.reader = reader self.unity_version = "2.5.0f5" - self.version = [0, 0, 0, 0] - self.build_type = BuildType("") self.target_platform = BuildTarget.UnknownPlatform self._enable_type_tree = True self.types = [] self.script_types = [] self.externals = [] - self._container = {} - self.objects = {} - self.container = {} # used to speed up mass asset extraction # some assets refer to each other, so by keeping the result # of specific assets cached the extraction can be speed up by a lot. # used by: Sprite (Texture2D (with alpha) cached), self._cache = {} + self.unknown = 0 # ReadHeader header = SerializedFileHeader(reader) @@ -160,16 +258,15 @@ def __init__(self, reader: EndianBinaryReader, environment=None, parent=None): if header.version >= 9: header.endian = ">" if reader.read_boolean() else "<" header.reserved = reader.read_bytes(3) + if header.version >= 22: + header.metadata_size = reader.read_u_int() + header.file_size = reader.read_long() + header.data_offset = reader.read_long() + self.unknown = reader.read_long() # unknown else: reader.Position = header.file_size - header.metadata_size header.endian = ">" if reader.read_boolean() else "<" - if header.version >= 22: - header.metadata_size = reader.read_u_int() - header.file_size = reader.read_long() - header.data_offset = reader.read_long() - reader.read_long() # unknown - reader.endian = header.endian if header.version >= 7: @@ -178,17 +275,14 @@ def __init__(self, reader: EndianBinaryReader, environment=None, parent=None): if header.version >= 8: self._m_target_platform = reader.read_int() - try: - self.target_platform = BuildTarget(self._m_target_platform) - except KeyError: - self.target_platform = BuildTarget.UnknownPlatform + self.target_platform = BuildTarget(self._m_target_platform) if header.version >= 13: self._enable_type_tree = reader.read_boolean() # ReadTypes type_count = reader.read_int() - self.types = [self.read_serialized_type() for _ in range(type_count)] + self.types = [SerializedType(reader, self, False) for _ in range(type_count)] self.big_id_enabled = 0 if 7 <= header.version < 14: @@ -198,28 +292,24 @@ def __init__(self, reader: EndianBinaryReader, environment=None, parent=None): object_count = reader.read_int() self.objects = {} for _ in range(object_count): - obj = ObjectReader(self, reader) + obj = ObjectReader.from_reader(self, reader) self.objects[obj.path_id] = obj # Read Scripts if header.version >= 11: script_count = reader.read_int() - self.script_types = [ - LocalSerializedObjectIdentifier(header, reader) - for _ in range(script_count) - ] + self.script_types = [LocalSerializedObjectIdentifier(header, reader) for _ in range(script_count)] # Read Externals externals_count = reader.read_int() - self.externals = [ - FileIdentifier(header, reader) for _ in range(externals_count) - ] + self.externals = [FileIdentifier(header, reader) for _ in range(externals_count)] if header.version >= 20: ref_type_count = reader.read_int() - self.ref_types = [ - self.read_serialized_type() for _ in range(ref_type_count) - ] + self.ref_types = [SerializedType(reader, self, True) for _ in range(ref_type_count)] + + if config.SERIALIZED_FILE_PARSE_TYPETREE is False: + self._enable_type_tree = False if header.version >= 5: self.userInformation = reader.read_string_to_null() @@ -227,351 +317,181 @@ def __init__(self, reader: EndianBinaryReader, environment=None, parent=None): # read the asset_bundles to get the containers for obj in self.objects.values(): if obj.type == ClassIDType.AssetBundle: - data = obj.read() - for container, asset_info in data.container.items(): - asset = asset_info.asset - self.container[container] = asset - if hasattr(asset, "path_id"): - self._container[asset.path_id] = container - environment.container = {**environment.container, **self.container} - - def set_version(self, string_version): + self.assetbundle = obj.parse_as_object() + self._container = ContainerHelper(self.assetbundle) + break + else: + self.assetbundle = None + self._container = ContainerHelper([]) + + @property + def container(self): + return self._container + + def load_dependencies(self, possible_dependencies: Optional[list] = None): + """Load all external dependencies. + + Parameters + ---------- + possible_dependencies : list + List of possible dependencies for cases + where the target file is not listed as external. + """ + for file_id in self.externals: + self.environment.load_file(file_id.path, True) + + if possible_dependencies is None: + return + + for dependency in possible_dependencies: + try: + self.environment.load_file(dependency, True) + except FileNotFoundError: + pass + + def set_version(self, string_version: str): self.unity_version = string_version - build_type = re.findall(r"([^\d.])", string_version) - self.build_type = BuildType(build_type[0] if build_type else "") - version_split = re.split(r"\D", string_version) - self.version = tuple(int(x) for x in version_split) + if not string_version or string_version == "0.0.0": + # weird case, but apparently can happen? + # check "cant read Texture2D by 2020.3.13 f1 AssetBundle #77" for details + if isinstance(self.parent, BundleFile.BundleFile): + string_version = self.parent.version_engine + if not string_version or string_version == "0.0.0": + string_version = config.get_fallback_version() + self.version = UnityVersion.from_str(string_version) + + def get_writeable_cab(self, name: str = "CAB-UnityPy_Mod.resS"): + """ + Creates a new cab file in the bundle that contains the given data. + This is usefull for asset types that use resource files. + """ + if not isinstance(self.parent, (File.BundleFile.BundleFile, File.WebFile.WebFile)): + return None - def read_serialized_type(self): - type_ = SerializedType() - type_.class_id = self.reader.read_int() + cab = self.parent.get_writeable_cab(name) + cab.path = f"archive:/{self.name}/{name}" + if not any(cab.path == x.path for x in self.externals): + # register as external + class FileIdentifierFake: + pass - if self.header.version >= 16: - type_.is_stripped_type = self.reader.read_boolean() + file_identifier = FileIdentifierFake() + file_identifier.__class__ = FileIdentifier + file_identifier.temp_empty = "" + import uuid - if self.header.version >= 17: - type_.script_type_index = self.reader.read_short() + file_identifier.guid = uuid.uuid1().urn[-16:].encode("ascii") + file_identifier.path = cab.path + file_identifier.type = 0 + self.externals.append(file_identifier) - if self.header.version >= 13: - if (self.header.version < 16 and type_.class_id < 0) or ( - self.header.version >= 16 and type_.class_id == 114 - ): - type_.script_id = self.reader.read_bytes(16) # Hash128 - type_.old_type_hash = self.reader.read_bytes(16) # Hash128 + return cab - if self._enable_type_tree: - type_tree = [] - if self.header.version >= 12 or self.header.version == 10: - type_.string_data = self.read_type_tree5(type_tree) - else: - self.read_type_tree(type_tree) - - if self.header.version >= 21: - type_.type_dependencies = reader.read_int_array() - - type_.nodes = type_tree - - return type_ - - def read_type_tree(self, type_tree, level=0): - if level == RECURSION_LIMIT - 1: - raise RecursionError - - type_tree_node = TypeTreeNode() - type_tree.append(type_tree_node) - type_tree_node.level = level - type_tree_node.type = self.reader.read_string_to_null() - type_tree_node.name = self.reader.read_string_to_null() - type_tree_node.byte_size = self.reader.read_int() - if self.header.version == 2: - type_tree_node.variable_count = self.reader.read_int() - - if self.header.version != 3: - type_tree_node.index = self.reader.read_int() - - type_tree_node.is_array = self.reader.read_int() - type_tree_node.version = self.reader.read_int() - if self.header.version != 3: - type_tree_node.meta_flag = self.reader.read_int() - - children_count = self.reader.read_int() - for i in range(children_count): - self.read_type_tree(type_tree, level + 1) - - def read_type_tree5(self, type_tree): - number_of_nodes = self.reader.read_int() - string_buffer_size = self.reader.read_int() - - node_size = 24 - if self.header.version > 19: - node_size = 32 - - self.reader.Position += number_of_nodes * node_size - string_buffer_reader = EndianBinaryReader(self.reader.read(string_buffer_size)) - self.reader.Position -= number_of_nodes * node_size + string_buffer_size - - for i in range(number_of_nodes): - type_tree_node = TypeTreeNode() - type_tree.append(type_tree_node) - type_tree_node.version = self.reader.read_u_short() - type_tree_node.level = self.reader.read_byte() - type_tree_node.is_array = self.reader.read_boolean() - type_tree_node.type_str_offset = self.reader.read_u_int() - type_tree_node.name_str_offset = self.reader.read_u_int() - type_tree_node.byte_size = self.reader.read_int() - type_tree_node.index = self.reader.read_int() - type_tree_node.meta_flag = self.reader.read_int() - - if self.header.version > 19: - type_tree_node.ref_type_hash = self.reader.read_u_long() - - type_tree_node.type = read_string( - string_buffer_reader, type_tree_node.type_str_offset - ) - type_tree_node.name = read_string( - string_buffer_reader, type_tree_node.name_str_offset - ) - - self.reader.Position += string_buffer_size - if self.header.version >= 21: - self.reader.Position += 4 - return string_buffer_reader.bytes - - def save(self) -> bytes: - # Structure: - # 1. header - # file header - # types - # objects - # scripts - # externals - # 2. small 0es offset - align stream - # 3. objects data - - # adjust metadata_size, data_offst and file_size + def save(self, packer: Optional[str] = None) -> bytes: + # 1. header -> has to be delayed until the very end + # 2. data -> types, objects, scripts, ... + + # so write the data first header = self.header - types = self.types - objects = self.objects - script_types = self.script_types - externals = self.externals - - # the data-offset is required for the header and is kinda hard to calculate, - # so we split the asset building up into multiple parts - fileheader_writer = EndianBinaryWriter() - header_writer = EndianBinaryWriter(endian=header.endian) + meta_writer = EndianBinaryWriter(endian=header.endian) data_writer = EndianBinaryWriter(endian=header.endian) - # 1. building the header without file header - - # reader.Position = header.file_size - header.metadata_size - # header.endian = '>' if reader.read_boolean() else '<' - if header.version < 9: - header_writer.write_boolean(header.endian == ">") - if header.version >= 7: - header_writer.write_string_to_null(self.unity_version) + meta_writer.write_string_to_null(self.unity_version) if header.version >= 8: - header_writer.write_int(self._m_target_platform) + meta_writer.write_int(self._m_target_platform) if header.version >= 13: - header_writer.write_boolean(self._enable_type_tree) + meta_writer.write_boolean(self._enable_type_tree) - # types - header_writer.write_int(len(types)) - for typ in types: - self.save_serialized_type(typ, header, header_writer) + # ReadTypes + meta_writer.write_int(len(self.types)) + for typ in self.types: + typ.write(self, meta_writer, False) if 7 <= header.version < 14: - header_writer.write_int(self.big_id_enabled) + meta_writer.write_int(self.big_id_enabled) - # objects - header_writer.write_int(len(objects)) - for i, obj in enumerate(objects.values()): - obj.write(header, header_writer, data_writer) - if i < len(objects) - 1: - data_writer.align_stream(8) + # ReadObjects + meta_writer.write_int(len(self.objects)) + for obj in sorted(self.objects.values(), key=lambda x: x.path_id): + obj.write(header, meta_writer, data_writer) + data_writer.align_stream(8) - # scripts + # Read Scripts if header.version >= 11: - header_writer.write_int(len(script_types)) - for script_type in script_types: - script_type.write(header, header_writer) + meta_writer.write_int(len(self.script_types)) + for script_type in self.script_types: + script_type.write(header, meta_writer) - # externals - header_writer.write_int(len(externals)) - for external in externals: - external.write(header, header_writer) + # Read Externals + meta_writer.write_int(len(self.externals)) + for external in self.externals: + external.write(header, meta_writer) - header_writer.write_string_to_null(self.userInformation) - if header.version >= 21: - header_writer.write_int(self.unknown) + if header.version >= 20: + assert self.ref_types is not None + meta_writer.write_int(len(self.ref_types)) + for ref_type in self.ref_types: + ref_type.write(self, meta_writer, True) + if header.version >= 5: + assert self.userInformation is not None + meta_writer.write_string_to_null(self.userInformation) + + # prepare header + writer = EndianBinaryWriter() + header_size = 16 # 4*4 + metadata_size = meta_writer.Length + data_size = data_writer.Length if header.version >= 9: - # file header - fileheader_size = 4 * 4 + 1 + 3 # following + endian + reserved - metadata_size = header_writer.Length - # metadata size - fileheader_writer.write_u_int(header_writer.Length) - # align between metadata and data - mod = (fileheader_size + metadata_size) % 16 - align_length = 16 - mod if mod else 0 - - # file size - fileheader_writer.write_u_int( - header_writer.Length - + data_writer.Length - + fileheader_size - + align_length - ) - # version - fileheader_writer.write_u_int(header.version) - # data offset - fileheader_writer.write_u_int( - header_writer.Length + fileheader_size + align_length - ) - # endian - fileheader_writer.write_boolean(header.endian == ">") - fileheader_writer.write_bytes(header.reserved) - - # self.reader.bytes[fileheader_size:fileheader_size+header_writer.Length] == header_writer.bytes - - return ( - fileheader_writer.bytes - + header_writer.bytes - + b"\x00" * align_length - + data_writer.bytes - ) - - else: - # file header - fileheader_size = 4 * 4 - metadata_size = header_writer.Length - # metadata size - fileheader_writer.write_u_int(metadata_size) - # file size - fileheader_writer.write_u_int( - metadata_size + data_writer.Length + fileheader_size - ) - # version - fileheader_writer.write_u_int(header.version) - # data offset - unity seems to align the stream .... but it's apparently not necessary - fileheader_writer.write_u_int(fileheader_size) - - return fileheader_writer.bytes + data_writer.bytes + header_writer.bytes - - def save_serialized_type( - self, - typ: SerializedType, - header: SerializedFileHeader, - writer: EndianBinaryWriter, - ): - writer.write_int(typ.class_id) - - if header.version >= 16: - writer.write_boolean(typ.is_stripped_type) - - if header.version >= 17: - writer.write_short(typ.script_type_index) - - if header.version >= 13: - if (header.version < 16 and typ.class_id < 0) or ( - header.version >= 16 and typ.class_id == 114 - ): - writer.write_bytes(typ.script_id) # Hash128 - writer.write_bytes(typ.old_type_hash) # Hash128 - - if self._enable_type_tree: - if header.version >= 12 or header.version == 10: - self.save_type_tree5(typ.nodes, writer, typ.string_data) - else: - self.save_type_tree(typ.nodes, writer) - - def save_type_tree(self, nodes: list, writer: EndianBinaryWriter): - for i, node in nodes: - writer.write_string_to_null(node.type) - writer.write_string_to_null(node.name) - writer.write_int(node.byte_size) - if self.header.version == 2: - writer.write_int(node.variable_count) - - if self.header.version != 3: - writer.write_int(node.index) - - writer.write_int(node.is_array) - writer.write_int(node.version) - if self.header.version != 3: - writer.write_int(node.meta_flag) - - # calc children count - children_count = 0 - for node2 in nodes[i + 1 :]: - if node2.level == node.level: - break - if node2.level == node.level - 1: - children_count += 1 - writer.write_int(children_count) - - def save_type_tree5(self, nodes: list, writer: EndianBinaryWriter, str_data=b""): - # node count - # stream buffer size - # node data - # string buffer - - string_buffer = EndianBinaryWriter() - """ - saved_strings = {} - def save_string(value: str) -> int: - import re - if value in saved_strings: - return saved_strings[value] - - for key, val in COMMON_STRING.items(): - if val == value: - return key | 0x80000000 - if re.match(r"\d+", value): - return int(value) + # 1 bool + 3 reserved + extra header 4 + 3*8 + header_size += 4 if header.version < 22 else 4 + 28 + data_offset = header_size + metadata_size + # align data_offset + data_offset += (16 - data_offset % 16) % 16 + file_size = data_offset + data_size + if header.version < 22: + writer.write_u_int(metadata_size) + writer.write_u_int(file_size) + writer.write_u_int(header.version) + # reader.Position = header.file_size - header.metadata_size + # so data follows right after this header -> after 32 + writer.write_u_int(data_offset) + writer.write_boolean(">" == header.endian) + writer.write_bytes(header.reserved) else: - # normal string - offset = string_buffer.Position - saved_strings[value] = offset - string_buffer.write_string_to_null(value) - return offset - """ - string_buffer.write(str_data) - strings_values = [ - (node.type_str_offset, node.name_str_offset) for node in nodes - ] - - # number of nodes - writer.write_int(len(nodes)) - # string buffer size - writer.write_int(string_buffer.Length) - - # nodes - for i, node in enumerate(nodes): - # version - writer.write_u_short(node.version) - # level - writer.write_byte(node.level) - # is array - writer.write_boolean(node.is_array) - - writer.write_u_int(strings_values[i][0]) - writer.write_u_int(strings_values[i][1]) - - writer.write_int(node.byte_size) - writer.write_int(node.index) - writer.write_int(node.meta_flag) - - if self.header.version > 17: - writer.write(node.extra) + # old header + writer.write_u_int(0) + writer.write_u_int(0) + writer.write_u_int(header.version) + writer.write_u_int(0) + writer.write_boolean(">" == header.endian) + writer.write_bytes(header.reserved) + writer.write_u_int(metadata_size) + writer.write_long(file_size) + writer.write_long(data_offset) + writer.write_long(self.unknown) + + writer.write_bytes(meta_writer.bytes) + writer.align_stream(16) + writer.write_bytes(data_writer.bytes) - # string buffer - writer.write(string_buffer.bytes) + else: + metadata_size += 1 # endian boolean + file_size = header_size + metadata_size + data_size + writer.write_u_int(metadata_size) + writer.write_u_int(file_size) + writer.write_u_int(header.version) + # reader.Position = header.file_size - header.metadata_size + # so data follows right after this header -> after 32 + writer.write_u_int(32) + writer.write_bytes(data_writer.bytes) + writer.write_boolean(">" == header.endian) + writer.write_bytes(meta_writer.bytes) - if self.header.version >= 21: - self.reader.write_bytes(b"\x00" * 4) + return writer.bytes def read_string(string_buffer_reader: EndianBinaryReader, value: int) -> str: @@ -581,130 +501,4 @@ def read_string(string_buffer_reader: EndianBinaryReader, value: int) -> str: return string_buffer_reader.read_string_to_null() offset = value & 0x7FFFFFFF - if offset in COMMON_STRING: - return COMMON_STRING[offset] - - return str(offset) - - -class ObjectReader: - byte_start: int - byte_size: int - type_id: int - class_id: ClassIDType - path_id: int - serialized_type: SerializedType - - def __init__(self, assets_file, reader): - self.assets_file = assets_file - self.reader = reader - self.data = b"" - self.version = assets_file.version - self.version2 = assets_file.header.version - self.platform = assets_file.target_platform - self.build_type = assets_file.build_type - - header = assets_file.header - types = assets_file.types - - if assets_file.big_id_enabled: - self.path_id = reader.read_long() - elif header.version < 14: - self.path_id = reader.read_int() - else: - reader.align_stream() - self.path_id = reader.read_long() - - if header.version >= 22: - self.byte_start = reader.read_long() - else: - self.byte_start = reader.read_u_int() - - self.byte_start += header.data_offset - self.byte_size = reader.read_u_int() - self.type_id = reader.read_int() - if header.version < 16: - self.class_id = reader.read_u_short() - if types: - self.serialized_type = [x for x in types if x.class_id == self.type_id][ - 0 - ] - else: - self.serialized_type = SerializedType() - else: - typ = types[self.type_id] - self.serialized_type = typ - self.class_id = typ.class_id - - self.type = ClassIDType(self.class_id) - - if header.version < 11: - self.is_destroyed = reader.read_u_short() - - if 11 <= header.version < 17: - script_type_index = reader.read_short() - if self.serialized_type: - self.serialized_type.script_type_index = script_type_index - - if header.version == 15 or header.version == 16: - self.stripped = reader.read_byte() - - def write(self, header, writer, data_writer): - # validated - if header.version < 14: - writer.write_int(self.path_id) - else: - writer.align_stream() - writer.write_long(self.path_id) - - if self.data: - data = self.data - else: - self.reset() - data = self.reader.read(self.byte_size) - writer.write_u_int(data_writer.Position) - writer.write_u_int(len(data)) - - data_writer.write(data) - - writer.write_int(self.type_id) - - if header.version < 16: - # WARNING - CLASSIDTYPE MIGHT CHANGE THE NUMBER IF IT'S UNNOWN - writer.write_u_short(self.class_id) - writer.write_u_short(self.is_destroyed) - - if header.version == 15 or header.version == 16: - writer.write_byte(self.stripped) - - @property - def container(self): - return ( - self.assets_file._container[self.path_id] - if self.path_id in self.assets_file._container - else None - ) - - def reset(self): - self.reader.Position = self.byte_start - - def read(self): - try: - return getattr(classes, self.type.name, classes.Object)(self) - except: - # hacky solution in case the parsing via the class fails - # this solution uses the type tree to set the variables and then changes the class - obj = classes.Object(self) - obj.__class__ = getattr(classes, self.type.name, classes.Object) - for key, val in obj.__dict__.items(): - if " " in key: - obj.__dict__[key.replace(" ", "_")] = val - delattr(obj, key) - return obj - - def __getattr__(self, item: str): - if hasattr(self.reader, item): - return getattr(self.reader, item) - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.type.name) + return get_common_strings().get(offset, str(offset)) diff --git a/UnityPy/files/WebFile.py b/UnityPy/files/WebFile.py index 7b2aba7dd..5521fbd7c 100644 --- a/UnityPy/files/WebFile.py +++ b/UnityPy/files/WebFile.py @@ -1,29 +1,27 @@ -from .BundleFile import BundleFile -from .File import File -from .SerializedFile import SerializedFile -from ..enums import FileType -from ..helpers import CompressionHelper, ImportHelper +from typing import Optional + +from ..helpers import CompressionHelper from ..streams import EndianBinaryReader, EndianBinaryWriter +from . import File -class WebFile(File): +class WebFile(File.File): """A package which can hold other WebFiles, Bundles and SerialiedFiles. It may be compressed via gzip or brotli. files -- list of all files in the WebFile """ - def __init__(self, reader: EndianBinaryReader, environment=None): - """Constructor Method - """ - self.files = {} + def __init__(self, reader: EndianBinaryReader, parent: File, name=None, **kwargs): + """Constructor Method""" + super().__init__(parent=parent, name=name, **kwargs) # check compression magic = reader.read_bytes(2) reader.Position = 0 if magic == CompressionHelper.GZIP_MAGIC: - self.compression = "gzip" + self.packer = "gzip" data = CompressionHelper.decompress_gzip(reader.bytes) reader = EndianBinaryReader(data, endian="<") else: @@ -31,17 +29,17 @@ def __init__(self, reader: EndianBinaryReader, environment=None): magic = reader.read_bytes(6) reader.Position = 0 if CompressionHelper.BROTLI_MAGIC == magic: - self.compression = "brotli" + self.packer = "brotli" data = CompressionHelper.decompress_brotli(reader.bytes) reader = EndianBinaryReader(data, endian="<") else: - self.compression = "None" + self.packer = "none" reader.endian = "<" # signature check signature = reader.read_string_to_null() - if signature != "UnityWebData1.0": - return + if not signature.startswith(("UnityWebData", "TuanjieWebData")): + raise ValueError(f"Invalid WebFile signature: {signature!r}. Expected 'UnityWebData' or 'TuanjieWebData'.") self.signature = signature # read header -> contains file headers @@ -52,52 +50,25 @@ def __init__(self, reader: EndianBinaryReader, environment=None): offset = reader.read_int() length = reader.read_int() path_length = reader.read_int() - name = reader.read_bytes(path_length).decode("utf8") - files.append((name, offset, length)) - - # read file data and convert it - for name, offset, length in files: - reader.Position = offset - data = reader.read(length) - typ, item = ImportHelper.check_file_type(data) - - if typ == FileType.BundleFile: - item = BundleFile(item, environment) - elif typ == FileType.WebFile: - item = WebFile(item, environment) - - if typ == FileType.AssetsFile: - # pre-check if resource file - if name.endswith((".resS", ".resource", ".config", ".xml", ".dat")): - environment.resources[name] = item - else: - # try to load the file as serialized file - try: - item = SerializedFile(item, environment) - environment.assets[name] = item - except ValueError: - environment.resources[name] = item - - self.files[name] = item - item.parent = self + name = bytes(reader.read_bytes(path_length)).decode("utf-8") + files.append(File.DirectoryInfo(name, offset, length)) + + self.read_files(reader, files) def save( self, - files: dict = None, - compression: str = None, + files: Optional[dict] = None, + packer: str = "none", signature: str = "UnityWebData1.0", ) -> bytes: # solve defaults if not files: files = self.files - if not compression: - compression = self.compression + if not packer: + packer = self.packer # get raw data - files = { - name: f.bytes if isinstance(f, EndianBinaryReader) else f.save() - for name, f in files.items() - } + files = {name: f.bytes if isinstance(f, EndianBinaryReader) else f.save() for name, f in files.items()} # create writer writer = EndianBinaryWriter(endian="<") @@ -108,9 +79,7 @@ def save( offset = sum( [ writer.Position, # signature - sum( - len(path.encode("utf8")) for path in files.keys() - ), # path of each file + sum(len(path.encode("utf-8")) for path in files.keys()), # path of each file 4 * 3 * len(files), # 3 ints per file 4, # offset int ] @@ -127,7 +96,7 @@ def save( writer.write_int(length) offset += length # path - enc_path = name.encode("utf8") + enc_path = name.encode("utf-8") writer.write_int(len(enc_path)) writer.write(enc_path) @@ -135,9 +104,9 @@ def save( for data in files.values(): writer.write(data) - if compression == "gzip": + if packer == "gzip": return CompressionHelper.compress_gzip(writer.bytes) - elif compression == "brotli": + elif packer == "brotli": return CompressionHelper.compress_brotli(writer.bytes) else: return writer.bytes diff --git a/UnityPy/files/__init__.py b/UnityPy/files/__init__.py index 2db8747a5..076605d16 100644 --- a/UnityPy/files/__init__.py +++ b/UnityPy/files/__init__.py @@ -1,3 +1,14 @@ -from .SerializedFile import SerializedFile from .BundleFile import BundleFile +from .File import DirectoryInfo, File +from .ObjectReader import ObjectReader +from .SerializedFile import SerializedFile from .WebFile import WebFile + +__all__ = [ + "BundleFile", + "DirectoryInfo", + "File", + "ObjectReader", + "SerializedFile", + "WebFile", +] diff --git a/UnityPy/helpers/ArchiveStorageManager.py b/UnityPy/helpers/ArchiveStorageManager.py new file mode 100644 index 000000000..13c59c842 --- /dev/null +++ b/UnityPy/helpers/ArchiveStorageManager.py @@ -0,0 +1,146 @@ +# based on: https://github.com/Razmoth/PGRStudio/blob/master/AssetStudio/PGR/PGR.cs +import re +from typing import Optional, Tuple, Union + +from ..streams import EndianBinaryReader + +try: + from UnityPy import UnityPyBoost +except ImportError: + UnityPyBoost = None + +UNITY3D_SIGNATURE = b"#$unity3dchina!@" +DECRYPT_KEY: Optional[bytes] = None + + +def set_assetbundle_decrypt_key(key: Union[bytes, str]): + if isinstance(key, str): + key = key.encode("utf-8", "surrogateescape") + if len(key) != 16: + raise ValueError(f"AssetBundle Key length is wrong. It should be 16 bytes and now is {len(key)} bytes.") + global DECRYPT_KEY + DECRYPT_KEY = key + + +def read_vector(reader: EndianBinaryReader) -> Tuple[bytes, bytes]: + data = reader.read_bytes(0x10) + key = reader.read_bytes(0x10) + reader.Position += 1 + + return data, key + + +def decrypt_key(key: bytes, data: bytes, keybytes: bytes): + from Crypto.Cipher import AES + + key = AES.new(keybytes, AES.MODE_ECB).encrypt(key) + return bytes(x ^ y for x, y in zip(data, key)) + + +def brute_force_key( + fp: str, + key_sig: bytes, + data_sig: bytes, + pattern: re.Pattern = re.compile(rb"(?=(\w{16}))"), + verbose: bool = False, +): + with open(fp, "rb") as f: + data = f.read() + + matches = pattern.findall(data) + for i, key in enumerate(matches): + if verbose: + print(f"Trying {i + 1}/{len(matches)} - {key}") + signature = decrypt_key(key_sig, data_sig, key) + if signature == UNITY3D_SIGNATURE: + if verbose: + print(f"Found key: {key}") + return key + return None + + +class ArchiveStorageDecryptor: + unknown_1: int + index: bytes + substitute: bytes = bytes(0x10) + + def __init__(self, reader: EndianBinaryReader): + self.unknown_1 = reader.read_u_int() + + # read vector data/key vectors + self.data, self.key = read_vector(reader) + self.data_sig, self.key_sig = read_vector(reader) + + if DECRYPT_KEY is None: + raise LookupError( + "\n".join( + [ + "The BundleFile is encrypted, but no key was provided!", + "You can set the key via UnityPy.set_assetbundle_decrypt_key(key).", + "To try brute-forcing the key, use UnityPy.helpers.ArchiveStorageManager.brute_force_key(fp, key_sig, data_sig)", # noqa: E501 + f"with key_sig = {self.key_sig}, data_sig = {self.data_sig}," + "and fp being the path to global-metadata.dat or a memory dump.", + ] + ) + ) + + signature = decrypt_key(self.key_sig, self.data_sig, DECRYPT_KEY) + if signature != UNITY3D_SIGNATURE: + raise Exception(f"Invalid signature {signature} != {UNITY3D_SIGNATURE}") + + data = decrypt_key(self.key, self.data, DECRYPT_KEY) + data = bytes(nibble for byte in data for nibble in (byte >> 4, byte & 0xF)) + self.index = data[:0x10] + self.substitute = bytes(data[0x10 + i * 4 + j] for j in range(4) for i in range(4)) + + def decrypt_block(self, data: bytes, index: int): + if UnityPyBoost: + return UnityPyBoost.decrypt_block(self.index, self.substitute, data, index) + + offset = 0 + size = len(data) + data = bytearray(data) + view = memoryview(data) + while offset < len(data): + offset += self.decrypt(view[offset:], index, size - offset) + index += 1 + return data + + def decrypt_byte(self, view: Union[bytearray, memoryview], offset: int, index: int): + b = ( + self.substitute[((index >> 2) & 3) + 4] + + self.substitute[index & 3] + + self.substitute[((index >> 4) & 3) + 8] + + self.substitute[(index % 256 >> 6) + 12] + ) + view[offset] = ((self.index[view[offset] & 0xF] - b) & 0xF | 0x10 * (self.index[view[offset] >> 4] - b)) % 256 + b = view[offset] + return b, offset + 1, index + 1 + + def decrypt(self, data: Union[bytearray, memoryview], index: int, remaining: int): + offset = 0 + + curByte, offset, index = self.decrypt_byte(data, offset, index) + byteHigh = curByte >> 4 + byteLow = curByte & 0xF + + if byteHigh == 0xF: + b = 0xFF + while b == 0xFF: + b, offset, index = self.decrypt_byte(data, offset, index) + byteHigh += b + + offset += byteHigh + + if offset < remaining: + _, offset, index = self.decrypt_byte(data, offset, index) + _, offset, index = self.decrypt_byte(data, offset, index) + if byteLow == 0xF: + b = 0xFF + while b == 0xFF: + b, offset, index = self.decrypt_byte(data, offset, index) + + return offset + + # def encrypt(self, data: bytes): + # # TODO: patch BundleFile encryption flag to keep either 0x1000 or 0x400 diff --git a/UnityPy/helpers/CompressionHelper.py b/UnityPy/helpers/CompressionHelper.py index 234bef86e..a318959e7 100644 --- a/UnityPy/helpers/CompressionHelper.py +++ b/UnityPy/helpers/CompressionHelper.py @@ -1,29 +1,33 @@ import gzip import lzma import struct +from typing import Callable, Dict, Tuple, Union import brotli import lz4.block +from ..enums.BundleFile import CompressionFlags + +ByteString = Union[bytes, bytearray, memoryview] GZIP_MAGIC: bytes = b"\x1f\x8b" BROTLI_MAGIC: bytes = b"brotli" # LZMA -def decompress_lzma(data: bytes) -> bytes: +def decompress_lzma(data: ByteString, read_decompressed_size: bool = False) -> bytes: """decompresses lzma-compressed data - :param data: compressed data - :type data: bytes - :raises _lzma.LZMAError: Compressed data ended before the end-of-stream marker was reached - :return: uncompressed data - :rtype: bytes - """ + :param data: compressed data + :type data: ByteString + :raises _lzma.LZMAError: Compressed data ended before the end-of-stream marker was reached + :return: uncompressed data + :rtype: bytes + """ props, dict_size = struct.unpack(" bytes: } ], ) - return dec.decompress(data[5:]) + data_offset = 13 if read_decompressed_size else 5 + return dec.decompress(data[data_offset:]) -def compress_lzma(data: bytes) -> bytes: +def compress_lzma(data: ByteString, write_decompressed_size: bool = False) -> bytes: """compresses data via lzma (unity specific) - The current static settings may not be the best solution, - but they are the most commonly used values and should therefore be enough for the time being. - - :param data: uncompressed data - :type data: bytes - :return: compressed data - :rtype: bytes - """ - ec = lzma.LZMACompressor( + The current static settings may not be the best solution, + but they are the most commonly used values and should therefore be enough for the time being. + + :param data: uncompressed data + :type data: ByteString + :return: compressed data + :rtype: bytes + """ + dict_size = 0x800000 # 1 << 23 + compressor = lzma.LZMACompressor( format=lzma.FORMAT_RAW, filters=[ - {"id": lzma.FILTER_LZMA1, "dict_size": 524288, "lc": 3, "lp": 0, "pb": 2,} + { + "id": lzma.FILTER_LZMA1, + "dict_size": dict_size, + "lc": 3, + "lp": 0, + "pb": 2, + "mode": lzma.MODE_NORMAL, + "mf": lzma.MF_BT4, + "nice_len": 123, + } ], ) - ec.compress(data) - return b"]\x00\x00\x08\x00" + ec.flush() + + compressed_data = compressor.compress(data) + compressor.flush() + cdl = len(compressed_data) + if write_decompressed_size: + return struct.pack(f" bytes: # LZ4M/LZ4HC +def decompress_lz4(data: ByteString, uncompressed_size: int) -> bytes: # LZ4M/LZ4HC """decompresses lz4-compressed data - :param data: compressed data - :type data: bytes - :param uncompressed_size: size of the uncompressed data - :type uncompressed_size: int - :raises _block.LZ4BlockError: Decompression failed: corrupt input or insufficient space in destination buffer. - :return: uncompressed data - :rtype: bytes - """ + :param data: compressed data + :type data: ByteString + :param uncompressed_size: size of the uncompressed data + :type uncompressed_size: int + :raises _block.LZ4BlockError: Decompression failed: corrupt input or insufficient space in destination buffer. + :return: uncompressed data + :rtype: bytes + """ return lz4.block.decompress(data, uncompressed_size) -def compress_lz4(data: bytes) -> bytes: # LZ4M/LZ4HC +def compress_lz4(data: ByteString) -> bytes: # LZ4M/LZ4HC """compresses data via lz4.block - :param data: uncompressed data - :type data: bytes - :return: compressed data - :rtype: bytes - """ - return lz4.block.compress( - data, mode="high_compression", compression=9, store_size=False - ) + :param data: uncompressed data + :type data: ByteString + :return: compressed data + :rtype: bytes + """ + return lz4.block.compress(data, mode="high_compression", compression=9, store_size=False) # Brotli -def decompress_brotli(data: bytes) -> bytes: +def decompress_brotli(data: ByteString) -> bytes: """decompresses brotli-compressed data - :param data: compressed data - :type data: bytes - :raises brotli.error: BrotliDecompress failed - :return: uncompressed data - :rtype: bytes - """ + :param data: compressed data + :type data: ByteString + :raises brotli.error: BrotliDecompress failed + :return: uncompressed data + :rtype: bytes + """ return brotli.decompress(data) -def compress_brotli(data: bytes) -> bytes: +def compress_brotli(data: ByteString) -> bytes: """compresses data via brotli - :param data: uncompressed data - :type data: bytes - :return: compressed data - :rtype: bytes - """ + :param data: uncompressed data + :type data: ByteString + :return: compressed data + :rtype: bytes + """ return brotli.compress(data) # GZIP -def decompress_gzip(data: bytes) -> bytes: +def decompress_gzip(data: ByteString) -> bytes: """decompresses gzip-compressed data - :param data: compressed data - :type data: bytes - :raises OSError: Not a gzipped file - :return: uncompressed data - :rtype: bytes - """ + :param data: compressed data + :type data: ByteString + :raises OSError: Not a gzipped file + :return: uncompressed data + :rtype: bytes + """ return gzip.decompress(data) -def compress_gzip(data: bytes) -> bytes: +def compress_gzip(data: ByteString) -> bytes: """compresses data via gzip - The current static settings may not be the best solution, - but they are the most commonly used values and should therefore be enough for the time being. - - :param data: uncompressed data - :type data: bytes - :return: compressed data - :rtype: bytes - """ + The current static settings may not be the best solution, + but they are the most commonly used values and should therefore be enough for the time being. + + :param data: uncompressed data + :type data: ByteString + :return: compressed data + :rtype: bytes + """ return gzip.compress(data) + + +def chunk_based_compress(data: ByteString, block_info_flag: int) -> Tuple[ByteString, list]: + """compresses AssetBundle data based on the block_info_flag + LZ4/LZ4HC will be chunk-based compression + + :param data: uncompressed data + :type data: ByteString + :param block_info_flag: block info flag + :type block_info_flag: int + :return: compressed data and block info + :rtype: tuple + """ + switch = block_info_flag & 0x3F + chunk_size = None + compress_func = None + if switch == 0: # NONE + return data, [(len(data), len(data), block_info_flag)] + + if switch in COMPRESSION_MAP: + compress_func = COMPRESSION_MAP[switch] + else: + raise NotImplementedError(f"No compression function in the CompressionHelper.COMPRESSION_MAP for {switch}") + + if switch in COMPRESSION_CHUNK_SIZE_MAP: + chunk_size = COMPRESSION_CHUNK_SIZE_MAP[switch] + else: + raise NotImplementedError(f"No chunk size in the CompressionHelper.COMPRESSION_CHUNK_SIZE_MAP for {switch}") + + block_info = [] + uncompressed_data_size = len(data) + compressed_file_data = bytearray() + p = 0 + while uncompressed_data_size > chunk_size: + compressed_data = compress_func(data[p : p + chunk_size]) + if len(compressed_data) > chunk_size: + compressed_file_data.extend(data[p : p + chunk_size]) + block_info.append( + ( + chunk_size, + chunk_size, + block_info_flag ^ switch, + ) + ) + else: + compressed_file_data.extend(compressed_data) + block_info.append( + ( + chunk_size, + len(compressed_data), + block_info_flag, + ) + ) + p += chunk_size + uncompressed_data_size -= chunk_size + if uncompressed_data_size > 0: + compressed_data = compress_func(data[p:]) + if len(compressed_data) > uncompressed_data_size: + compressed_file_data.extend(data[p:]) + block_info.append( + ( + uncompressed_data_size, + uncompressed_data_size, + block_info_flag ^ switch, + ) + ) + else: + compressed_file_data.extend(compressed_data) + block_info.append( + ( + uncompressed_data_size, + len(compressed_data), + block_info_flag, + ) + ) + return bytes(compressed_file_data), block_info + + +def decompress_lzham(data: ByteString, uncompressed_size: int) -> bytes: + raise NotImplementedError("Custom compression or unimplemented LZHAM (removed by Unity) encountered!") + + +DECOMPRESSION_MAP: Dict[Union[int, CompressionFlags], Callable[[ByteString, int], ByteString]] = { + CompressionFlags.NONE: lambda cd, _ucs: cd, + CompressionFlags.LZMA: lambda cd, _ucs: decompress_lzma(cd), + CompressionFlags.LZ4: decompress_lz4, + CompressionFlags.LZ4HC: decompress_lz4, + CompressionFlags.LZHAM: decompress_lzham, +} + +COMPRESSION_MAP: Dict[Union[int, CompressionFlags], Callable[[ByteString], ByteString]] = { + CompressionFlags.NONE: lambda cd: cd, + CompressionFlags.LZMA: compress_lzma, + CompressionFlags.LZ4: compress_lz4, + CompressionFlags.LZ4HC: compress_lz4, +} + +COMPRESSION_CHUNK_SIZE_MAP: Dict[Union[int, CompressionFlags], int] = { + CompressionFlags.NONE: 0xFFFFFFFF, + CompressionFlags.LZMA: 0xFFFFFFFF, + CompressionFlags.LZ4: 0x00020000, + CompressionFlags.LZ4HC: 0x00020000, +} + + +__all__ = ( + "compress_brotli", + "compress_gzip", + "compress_lz4", + "compress_lzma", + "decompress_brotli", + "decompress_gzip", + "decompress_lz4", + "decompress_lzma", + "decompress_lzham", + "chunk_based_compress", + "COMPRESSION_MAP", + "DECOMPRESSION_MAP", + "COMPRESSION_CHUNK_SIZE_MAP", +) diff --git a/UnityPy/helpers/ContainerHelper.py b/UnityPy/helpers/ContainerHelper.py new file mode 100644 index 000000000..f2ad977ca --- /dev/null +++ b/UnityPy/helpers/ContainerHelper.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Dict, Generator, Iterator, List, Optional, Tuple, Union + +from attrs import define + +if TYPE_CHECKING: + from ..classes import AssetBundle, AssetInfo, Object, PPtr + + +@define(slots=True) +class ContainerHelper: + """Helper class to allow multidict containers + without breaking compatibility with old versions""" + + container: List[Tuple[str, AssetInfo]] + container_dict: Dict[str, PPtr[Object]] + path_dict: Dict[int, str] + _preload_table: Optional[List[PPtr[Object]]] = None + + def __init__(self, container: Union[List[Tuple[str, AssetInfo]], AssetBundle]) -> None: + preload_table: Optional[List[PPtr[Object]]] = None + if not isinstance(container, (list)): + preload_table = container.m_PreloadTable + container = container.m_Container + self.container = container + self.container_dict = {key: value.asset for key, value in container} + self.path_dict = {value.asset.path_id: key for key, value in container} + self._preload_table = preload_table + + def parse_preload_table(self) -> None: + if self._preload_table is None: + return + + for path, info in self.container: + start = info.preloadIndex + size = info.preloadSize + if start < 0 or size <= 0 or start + size > len(self._preload_table): + continue + for pptr in self._preload_table[start : start + size]: + if not pptr: + continue + try: + target = pptr.deref() + except (FileNotFoundError, KeyError): + continue + target.assets_file._container.path_dict.setdefault(pptr.path_id, path) + + self._preload_table = None + + def items(self) -> Generator[Tuple[str, PPtr[Object]], None, None]: + return ((key, value.asset) for key, value in self.container) + + def keys(self) -> list[str]: + return list({key for key, value in self.container}) + + def values(self) -> list[PPtr[Object]]: + return list({value.asset for key, value in self.container}) + + def __getitem__(self, key) -> PPtr[Object]: + return self.container_dict[key] + + def __setitem__(self, key, value) -> None: + raise NotImplementedError("Assigning to container is not allowed!") + + def __delitem__(self, key) -> None: + raise NotImplementedError("Deleting from the container is not allowed!") + + def __iter__(self) -> Iterator[str]: + return iter(self.keys()) + + def __len__(self) -> int: + return len(self.container) + + def __getattr__(self, name: str) -> PPtr[Object]: + return self.container_dict[name] + + def __str__(self) -> str: + return f"{{{', '.join(f'{key}: {value}' for key, value in self.items())}}}" + + def __dict__(self) -> Dict[str, PPtr[Object]]: + return self.container_dict + + def __contains__(self, key: str) -> bool: + return key in self.container_dict diff --git a/UnityPy/helpers/ImportHelper.py b/UnityPy/helpers/ImportHelper.py index c5c3c7a46..2717c6f68 100644 --- a/UnityPy/helpers/ImportHelper.py +++ b/UnityPy/helpers/ImportHelper.py @@ -1,159 +1,173 @@ -import os -import re +from __future__ import annotations -from .CompressionHelper import BROTLI_MAGIC, GZIP_MAGIC +import io +import os +from gzip import GzipFile +from typing import BinaryIO, List, Optional, Tuple, Union + +from .. import files from ..enums import FileType from ..streams import EndianBinaryReader +from .CompressionHelper import BROTLI_MAGIC, GZIP_MAGIC + +FileSourceType = Union[str, bytes, bytearray, io.IOBase, EndianBinaryReader, BinaryIO] def file_name_without_extension(file_name: str) -> str: - return os.path.splitext(os.path.basename(file_name))[0] + return os.path.join(os.path.dirname(file_name), os.path.splitext(os.path.basename(file_name))[0]) -def list_all_files(directory: str) -> list: +def list_all_files(directory: str) -> List[str]: return [ val for sublist in [ [os.path.join(dir_path, filename) for filename in filenames] - for (dir_path, dirn_ames, filenames) in os.walk(directory) + for (dir_path, dirnames, filenames) in os.walk(directory) if ".git" not in dir_path ] for val in sublist ] -def find_all_files(directory: str, search_str: str) -> list: +def find_all_files(directory: str, search_str: str) -> List[str]: return [ val for sublist in [ - [ - os.path.join(dir_path, filename) - for filename in filenames - if search_str in filename - ] - for (dir_path, dirn_ames, filenames) in os.walk(directory) + [os.path.join(dir_path, filename) for filename in filenames if search_str in filename] + for (dir_path, dirnames, filenames) in os.walk(directory) if ".git" not in dir_path ] for val in sublist ] -def merge_split_assets(path: str, all_directories=False): - if all_directories: - split_files = [fp for fp in list_all_files(path) if fp[-7:] == ".split0"] - else: - split_files = [ - os.path.join(path, fp) for fp in os.listdir(path) if fp[-7:] == ".split0" - ] - - for split_file in split_files: - dest_file = file_name_without_extension(split_file) - dest_path = os.path.dirname(split_file) - dest_full = os.path.join(dest_file, dest_path) - - if not os.path.exists(dest_full): - with open(dest_full, "wb") as f: - i = 0 - while True: - split_part = "".join([dest_full, ".split", str(i)]) - if not os.path.isfile(split_part): - break - f.write(open(split_part, "rb").read()) - - -def processing_split_files(select_file: list) -> list: - split_files = [fp for fp in select_file if ".split" in fp] - select_file = [f for f in select_file if f not in split_files] - - split_files = set([file_name_without_extension(fp) for fp in split_files]) - for splitFile in split_files: - if os.path.isfile: - select_file.append(splitFile) - return select_file - - -def check_file_type(input_) -> (FileType, EndianBinaryReader): +def check_file_type( + input_: FileSourceType, +) -> Tuple[FileType, EndianBinaryReader]: if isinstance(input_, str) and os.path.isfile(input_): reader = EndianBinaryReader(open(input_, "rb")) elif isinstance(input_, EndianBinaryReader): reader = input_ else: - try: - reader = EndianBinaryReader(input_) - except: - return None, None - + reader = EndianBinaryReader(input_) + if reader.Length < 20: return FileType.ResourceFile, reader signature = reader.read_string_to_null(20) + reader.Position = 0 if signature in [ "UnityWeb", "UnityRaw", - "\xFA\xFA\xFA\xFA\xFA\xFA\xFA\xFA", + "\xfa\xfa\xfa\xfa\xfa\xfa\xfa\xfa", "UnityFS", ]: return FileType.BundleFile, reader - elif signature == "UnityWebData1.0": + elif signature.startswith(("UnityWebData", "TuanjieWebData")): return FileType.WebFile, reader - elif signature == "PK\x03\x04\x14": + elif signature == "PK\x03\x04": return FileType.ZIP, reader else: if reader.Length < 128: return FileType.ResourceFile, reader - magic = reader.read_bytes(2) + magic = bytes(reader.read_bytes(2)) reader.Position = 0 if GZIP_MAGIC == magic: - return FileType.WebFile, reader + g_stream = GzipFile(fileobj=reader) + g_reader = EndianBinaryReader(g_stream, endian="<") + signature = g_reader.read_string_to_null(20) + g_stream.close() + if signature.startswith(("UnityWebData", "TuanjieWebData")): + return FileType.WebFile, reader reader.Position = 0x20 - magic = reader.read_bytes(6) + magic = bytes(reader.read_bytes(6)) reader.Position = 0 if BROTLI_MAGIC == magic: return FileType.WebFile, reader # check if AssetsFile old_endian = reader.endian - assets_file = True # read as if assetsfile and check version + # ReadHeader reader.Position = 0 metadata_size = reader.read_u_int() file_size = reader.read_u_int() version = reader.read_u_int() data_offset = reader.read_u_int() - - if ( - version < 0 - or version > 100 - or any( - [ - x < 0 or x > reader.Length - for x in [metadata_size, version, data_offset] - ] + + if version >= 22: + raw_endian = reader.read_u_byte() + _endian = ">" if raw_endian else "<" + _reserved = reader.read_bytes(3) + metadata_size = reader.read_u_int() + file_size = reader.read_long() + data_offset = reader.read_long() + _unknown = reader.read_long() # unknown + + # reset + reader.endian = old_endian + reader.Position = 0 + # check info + if any( + ( + version < 0, + version > 100, + *[x < 0 or x > reader.Length for x in [file_size, metadata_size, version, data_offset]], + file_size < metadata_size, + file_size < data_offset, ) - or file_size < metadata_size ): return FileType.ResourceFile, reader - - if version >= 9: - endian = ">" if reader.read_boolean() else "<" - reserved = reader.read_bytes(3) else: - reader.Position = file_size - metadata_size - endian = ">" if reader.read_boolean() else "<" - - reader.endian = endian + return FileType.AssetsFile, reader - if version >= 7: - unity_version = reader.read_string_to_null() - if len([x for x in re.split(r"\D", unity_version) if x != ""]) < 2: - assets_file = False - # check end - reader.endian = old_endian - reader.Position = 0 - if assets_file: - return FileType.AssetsFile, reader - else: - return FileType.ResourceFile, reader +def parse_file( + reader: EndianBinaryReader, + parent: files.File, + name: str, + typ: Optional[FileType] = None, + is_dependency: bool = False, +) -> Union[files.File, EndianBinaryReader]: + if typ is None: + typ, _ = check_file_type(reader) + f = reader + try: + if typ == FileType.AssetsFile and not name.endswith( + ( + ".resS", + ".resource", + ".config", + ".xml", + ".dat", + ) + ): + f = files.SerializedFile(reader, parent, name=name, is_dependency=is_dependency) + elif typ == FileType.BundleFile: + f = files.BundleFile(reader, parent, name=name, is_dependency=is_dependency) + elif typ == FileType.WebFile: + f = files.WebFile(reader, parent, name=name, is_dependency=is_dependency) + except Exception as e: + reader.seek(0) + print(f"Error parsing file {name!r} as {typ}: {e}") + raise e + return f + + +def find_sensitive_path(dir: str, insensitive_path: str) -> Union[str, None]: + parts = os.path.split(insensitive_path.strip(os.path.sep)) + + sensitive_path = dir + for part in parts: + part_lower = part.lower() + part = next( + (name for name in os.listdir(sensitive_path) if name.lower() == part_lower), + None, + ) + if part is None: + return None + sensitive_path = os.path.join(sensitive_path, part) + + return sensitive_path diff --git a/UnityPy/helpers/MeshHelper.py b/UnityPy/helpers/MeshHelper.py new file mode 100644 index 000000000..371e8baf9 --- /dev/null +++ b/UnityPy/helpers/MeshHelper.py @@ -0,0 +1,709 @@ +from __future__ import annotations + +import math +import struct +from typing import TYPE_CHECKING, List, Optional, Tuple, Union, cast + +from ..classes.generated import ( + ChannelInfo, + Mesh, + SpriteRenderData, + StreamInfo, + Vector2f, + Vector3f, + Vector4f, +) +from ..enums.MeshTopology import MeshTopology +from ..enums.VertexFormat import ( + VERTEX_CHANNEL_FORMAT_STRUCT_TYPE_MAP, + VERTEX_FORMAT_2017_STRUCT_TYPE_MAP, + VERTEX_FORMAT_STRUCT_TYPE_MAP, + VertexChannelFormat, + VertexFormat, + VertexFormat2017, +) +from .PackedBitVector import unpack_floats, unpack_ints +from .ResourceReader import get_resource_data + +try: + from UnityPy import UnityPyBoost +except ImportError: + UnityPyBoost = None + +Tuple2f = Tuple[float, float] +Tuple3f = Tuple[float, float, float] +Tuple4f = Tuple[float, float, float, float] + + +def vector_list_to_tuples( + data: Union[List[Vector2f], List[Vector3f], List[Vector4f]], +) -> List[tuple]: + if isinstance(data[0], Vector2f): + return [(v.x, v.y) for v in data] + elif isinstance(data[0], Vector3f): + if TYPE_CHECKING: + data = cast(List[Vector3f], data) + return [(v.x, v.y, v.z) for v in data] + elif isinstance(data[0], Vector4f): + if TYPE_CHECKING: + data = cast(List[Vector4f], data) + return [(v.x, v.y, v.z, v.w) for v in data] + else: + raise ValueError("Unknown vector type") + + +def lists_to_tuples(data: List[list]) -> List[tuple]: + return [tuple(v) for v in data] + + +def zeros(m: int, n: int) -> List[list]: + return [[0] * n for _ in range(m)] + + +def normalize(*vector: float) -> Tuple[float, ...]: + length = math.sqrt(sum(v**2 for v in vector)) + if length > 0.00001: + inv_norm = 1.0 / length + return tuple(v * inv_norm for v in vector) + return (0,) * len(vector) + + +class MeshHandler: + src: Union[Mesh, SpriteRenderData] + endianess: str = "<" + version: Tuple[int, int, int, int] + m_VertexCount: int = 0 + m_Vertices: Optional[List[Tuple3f]] = None + # normals can be stored as Tuple4f, + # in such cases the 4th dimension is always 0 and can be discarded + m_Normals: Optional[Union[List[Tuple3f], List[Tuple4f]]] = None + m_Colors: Optional[List[Tuple4f]] = None + m_UV0: Optional[List[Tuple2f]] = None + m_UV1: Optional[List[Tuple2f]] = None + m_UV2: Optional[List[Tuple2f]] = None + m_UV3: Optional[List[Tuple2f]] = None + m_UV4: Optional[List[Tuple2f]] = None + m_UV5: Optional[List[Tuple2f]] = None + m_UV6: Optional[List[Tuple2f]] = None + m_UV7: Optional[List[Tuple2f]] = None + m_Tangents: Optional[List[Tuple4f]] = None + m_BoneIndices: Optional[List[Tuple[int, int, int, int]]] = None + m_BoneWeights: Optional[List[Tuple4f]] = None + m_IndexBuffer: Optional[List[int]] = None + m_Use16BitIndices: bool = True + + def __init__( + self, + src: Union[Mesh, SpriteRenderData], + version: Optional[Tuple[int, int, int, int]] = None, + endianess: str = "<", + ): + self.src = src + self.endianess = endianess + if version is not None: + self.version = version + elif not isinstance(src, SpriteRenderData) and src.object_reader is not None: + self.version = src.object_reader.version + else: + raise ValueError("No version provided and no object reader found") + + def process(self): + mesh = self.src + vertex_data = mesh.m_VertexData + assert vertex_data is not None + + m_Channels: list[ChannelInfo] + m_Streams: list[StreamInfo] + + if self.version[0] < 4: + assert ( + vertex_data.m_Streams_0_ is not None + and vertex_data.m_Streams_1_ is not None + and vertex_data.m_Streams_2_ is not None + and vertex_data.m_Streams_3_ is not None + ) + m_Streams = [ + vertex_data.m_Streams_0_, + vertex_data.m_Streams_1_, + vertex_data.m_Streams_2_, + vertex_data.m_Streams_3_, + ] + assert all(stream is not None for stream in m_Streams) + m_Channels = self.get_channels(m_Streams) + elif self.version[0] == 4: + assert vertex_data.m_Streams is not None and vertex_data.m_Channels is not None + m_Streams = vertex_data.m_Streams + m_Channels = vertex_data.m_Channels + else: + assert vertex_data.m_Channels is not None + m_Channels = vertex_data.m_Channels + m_Streams = self.get_streams(m_Channels, vertex_data.m_VertexCount) + + if ( + isinstance(mesh, Mesh) and mesh.m_StreamData and mesh.m_StreamData.path + # and mesh.m_VertexData + # and mesh.m_VertexData.m_VertexCount + ): + stream_data = mesh.m_StreamData + assert mesh.object_reader, "No object reader assigned to the input Mesh!" + data = get_resource_data( + stream_data.path, + mesh.object_reader.assets_file, + stream_data.offset, + stream_data.size, + ) + vertex_data.m_DataSize = data + + # try to copy data directly from mesh + if isinstance(mesh, Mesh): + if mesh.m_Use16BitIndices is not None: + self.m_Use16BitIndices = bool(mesh.m_Use16BitIndices) + elif ( + (self.version >= (2017, 4)) + or + # version == (2017, 3, 1) & patched - px string + (self.version[:2] == (2017, 3) and mesh.m_MeshCompression == 0) + ): + self.m_Use16BitIndices = mesh.m_IndexFormat == 0 + self.copy_from_mesh() + elif isinstance(mesh, SpriteRenderData): + self.copy_from_spriterenderdata() + else: + raise ValueError(f"Unknown mesh type {type(mesh)}") + + if self.m_IndexBuffer: + raw_indices = bytes(self.m_IndexBuffer) + if self.m_Use16BitIndices: + char = "H" + index_size = 2 + else: + char = "I" + index_size = 4 + + self.m_IndexBuffer = cast( + List[int], + struct.unpack(f"<{len(raw_indices) // index_size}{char}", raw_indices), + ) + + if self.version >= (3, 5): + self.read_vertex_data(m_Channels, m_Streams) + + if isinstance(mesh, Mesh) and self.version >= (2, 6): + self.decompress_compressed_mesh() + + if self.m_VertexCount == 0 and self.m_Vertices: + self.m_VertexCount = len(self.m_Vertices) + + def copy_from_mesh(self): + """Copy data from mesh to handler if it's not already set.""" + mesh = self.src + if TYPE_CHECKING: + assert isinstance(mesh, Mesh) + + if self.m_IndexBuffer is None and mesh.m_IndexBuffer: + self.m_IndexBuffer = mesh.m_IndexBuffer + + if self.m_Vertices is None and mesh.m_Vertices: + self.m_Vertices = vector_list_to_tuples(mesh.m_Vertices) + + if self.m_Normals is None and mesh.m_Normals: + self.m_Normals = vector_list_to_tuples(mesh.m_Normals) + + if self.m_Tangents is None and mesh.m_Tangents: + self.m_Tangents = vector_list_to_tuples(mesh.m_Tangents) + + if self.m_UV0 is None and mesh.m_UV: + self.m_UV0 = vector_list_to_tuples(mesh.m_UV) + + if self.m_UV1 is None and mesh.m_UV1: + self.m_UV1 = vector_list_to_tuples(mesh.m_UV1) + + if self.m_Colors is None and mesh.m_Colors: + self.m_Colors = [ + (color.r / 255.0, color.g / 255.0, color.b / 255.0, color.a / 255.0) for color in mesh.m_Colors + ] + + if self.m_BoneWeights is None and mesh.m_Skin: + # BoneInfluence == BoneWeight in terms of usage in UnityPy due to int simplification + self.m_BoneIndices = [ + (skin.boneIndex_0_, skin.boneIndex_1_, skin.boneIndex_2_, skin.boneIndex_3_) for skin in mesh.m_Skin + ] + self.m_BoneWeights = [ + (skin.weight_0_, skin.weight_1_, skin.weight_2_, skin.weight_3_) for skin in mesh.m_Skin + ] + + def copy_from_spriterenderdata(self): + rd = self.src + if TYPE_CHECKING: + assert isinstance(rd, SpriteRenderData) + + if self.m_IndexBuffer is None: + if rd.m_IndexBuffer: + self.m_IndexBuffer = rd.m_IndexBuffer + elif rd.indices: + self.m_IndexBuffer = rd.indices + + if self.m_Vertices is None and rd.vertices: + vertices = rd.vertices + self.m_Vertices = [(v.pos.x, v.pos.y, v.pos.z) for v in vertices] + + if vertices[0].uv is not None: + self.m_UV0 = [(v.uv.x, v.uv.y) for v in vertices] # type: ignore + + # if self.m_BindPose is None and rd.m_BindPose: + # self.m_BindPose = rd.m_BindPose + + def get_streams(self, m_Channels: list[ChannelInfo], m_VertexCount: int) -> list[StreamInfo]: + streamCount = 1 + max(x.stream for x in m_Channels) + m_Streams: list[StreamInfo] = [] + offset = 0 + for s in range(streamCount): + chnMask = 0 + stride = 0 + for chn, m_Channel in enumerate(m_Channels): + if m_Channel.stream == s: + if m_Channel.dimension > 0: + chnMask |= 1 << chn + component_size = self.get_channel_component_size(m_Channel) + stride += (m_Channel.dimension & 0xF) * component_size + + m_Streams.append( + StreamInfo( + channelMask=chnMask, + offset=offset, + stride=stride, + dividerOp=0, + frequency=0, + ) + ) + offset += m_VertexCount * stride + offset = (offset + (16 - 1)) & ~(16 - 1) + return m_Streams + + def get_channels(self, m_Streams: list[StreamInfo]) -> list[ChannelInfo]: + m_Channels = [ + ChannelInfo( + dimension=0, + format=0, + offset=0, + stream=0, + ) + for _ in range(6) + ] + for s, m_Stream in enumerate(m_Streams): + channelMask = m_Stream.channelMask # uint + offset = 0 + for i in range(6): + if channelMask & (1 << i): + m_Channel = m_Channels[i] + m_Channel.stream = s + m_Channel.offset = offset + if i in [0, 1]: + # 0 - kShaderChannelVertex + # 1 - kShaderChannelNormal + m_Channel.format = 0 # kChannelFormatFloat + m_Channel.dimension = 3 + elif i == 2: # kShaderChannelColor + m_Channel.format = 2 # kChannelFormatColor + m_Channel.dimension = 4 + elif i in [3, 4]: + # 3 - kShaderChannelTexCoord0 + # 4 - kShaderChannelTexCoord1 + m_Channel.format = 0 # kChannelFormatFloat + m_Channel.dimension = 2 + elif i == 5: # kShaderChannelTangent + m_Channel.format = 0 # kChannelFormatFloat + m_Channel.dimension = 4 + + component_size = self.get_channel_component_size(m_Channel) + offset += m_Channel.dimension * component_size + + return m_Channels + + def read_vertex_data(self, m_Channels: list[ChannelInfo], m_Streams: list[StreamInfo]) -> None: + m_VertexData = self.src.m_VertexData + if m_VertexData is None: + return + + # could be empty for fully compressed meshes + # in that case data will be read from CompressedMesh via decompress_compressed_mesh + # also avoids a crash in UnityPyBoost.unpack_vertexdata with empty data + if m_VertexData.m_VertexCount == 0 or not m_VertexData.m_DataSize: + return + + self.m_VertexCount = m_VertexCount = m_VertexData.m_VertexCount + # m_VertexDataRaw = m_VertexData.m_DataSize + + for chn, m_Channel in enumerate(m_Channels): + if m_Channel.dimension == 0: + continue + + m_Stream = m_Streams[m_Channel.stream] + # m_StreamData = m_VertexDataRaw[ + # m_Stream.offset : m_Stream.offset + m_VertexCount * m_Stream.stride + # ] + + channelMask = bin(m_Stream.channelMask)[::-1] + if channelMask[chn] == "1": + if ( + self.version[0] < 2018 and chn == 2 and m_Channel.format == 2 + ): # kShaderChannelColor && kChannelFormatColor + # new instance to not modify the original + m_Channel = ChannelInfo( + dimension=4, + format=2, + offset=m_Channel.offset, + stream=m_Channel.stream, + ) + + component_dtype = self.get_channel_dtype(m_Channel) + component_byte_size = self.get_channel_component_size(m_Channel) + # channel_byte_size = m_Channel.dimension * component_byte_size + + swap = self.endianess == "<" and component_byte_size > 1 + channel_dimension = m_Channel.dimension & 0xF + + if UnityPyBoost: + componentBytes = UnityPyBoost.unpack_vertexdata( + m_VertexData.m_DataSize, + component_byte_size, + m_VertexCount, + m_Stream.offset, + m_Stream.stride, + m_Channel.offset, + channel_dimension, + swap, + ) + else: + channelSize = channel_dimension * component_byte_size + + componentBytes = bytearray(m_VertexCount * channel_dimension * component_byte_size) + vertexData = m_VertexData.m_DataSize + + componentOffset = 0 + vertexOffset = m_Stream.offset + m_Channel.offset + + for _ in range(m_VertexCount): + componentBytes[componentOffset : componentOffset + channelSize] = vertexData[ + vertexOffset : vertexOffset + channelSize + ] + componentOffset += channelSize + vertexOffset += m_Stream.stride + + if swap: + for offset in range(0, len(componentBytes), component_byte_size): + item = componentBytes[offset : offset + component_byte_size] + item.reverse() + componentBytes[offset : offset + component_byte_size] = item + + component_data = list(struct.iter_unpack(f">{channel_dimension}{component_dtype}", componentBytes)) + self.assign_channel_vertex_data(chn, component_data) + + def assign_channel_vertex_data(self, channel: int, component_data: list): + if self.version[0] >= 2018: + if channel == 0: # kShaderChannelVertex + self.m_Vertices = component_data + elif channel == 1: # kShaderChannelNormal + self.m_Normals = component_data + elif channel == 2: # kShaderChannelTangent + self.m_Tangents = component_data + elif channel == 3: # kShaderChannelColor + self.m_Colors = component_data + elif channel == 4: # kShaderChannelTexCoord0 + self.m_UV0 = component_data + elif channel == 5: # kShaderChannelTexCoord1 + self.m_UV1 = component_data + elif channel == 6: # kShaderChannelTexCoord2 + self.m_UV2 = component_data + elif channel == 7: # kShaderChannelTexCoord3 + self.m_UV3 = component_data + elif channel == 8: # kShaderChannelTexCoord4 + self.m_UV4 = component_data + elif channel == 9: # kShaderChannelTexCoord5 + self.m_UV5 = component_data + elif channel == 10: # kShaderChannelTexCoord6 + self.m_UV6 = component_data + elif channel == 11: # kShaderChannelTexCoord7 + self.m_UV7 = component_data + # 2018.2 and up + elif channel == 12: # kShaderChannelBlendWeight + self.m_BoneWeights = component_data + elif channel == 13: # kShaderChannelBlendIndices + self.m_BoneIndices = component_data + else: + raise ValueError(f"Unknown channel {channel}") + else: + if channel == 0: # kShaderChannelVertex + self.m_Vertices = component_data + elif channel == 1: # kShaderChannelNormal + self.m_Normals = component_data + elif channel == 2: # kShaderChannelColor + self.m_Colors = component_data + elif channel == 3: # kShaderChannelTexCoord0 + self.m_UV0 = component_data + elif channel == 4: # kShaderChannelTexCoord1 + self.m_UV1 = component_data + elif channel == 5: + if self.version[0] >= 5: # kShaderChannelTexCoord2 + self.m_UV2 = component_data + else: # kShaderChannelTangent + self.m_Tangents = component_data + elif channel == 6: # kShaderChannelTexCoord3 + self.m_UV3 = component_data + elif channel == 7: # kShaderChannelTangent + self.m_Tangents = component_data + else: + raise ValueError(f"Unknown channel {channel}") + + def get_channel_dtype(self, m_Channel: ChannelInfo): + if self.version[0] < 2017: + format = VertexChannelFormat(m_Channel.format) + component_dtype = VERTEX_CHANNEL_FORMAT_STRUCT_TYPE_MAP[format] + elif self.version[0] < 2019: + format = VertexFormat2017(m_Channel.format) + component_dtype = VERTEX_FORMAT_2017_STRUCT_TYPE_MAP[format] + else: + format = VertexFormat(m_Channel.format) + component_dtype = VERTEX_FORMAT_STRUCT_TYPE_MAP[format] + + return component_dtype + + def get_channel_component_size(self, m_Channel: ChannelInfo): + dtype = self.get_channel_dtype(m_Channel) + return struct.Struct(dtype).size + + def decompress_compressed_mesh(self): + # TODO: m_Triangles???? + + version = self.version + assert isinstance(self.src, Mesh) + m_CompressedMesh = self.src.m_CompressedMesh + + # Vertex + self.m_VertexCount = m_VertexCount = m_CompressedMesh.m_Vertices.m_NumItems // 3 + + if m_CompressedMesh.m_Vertices.m_NumItems > 0: + self.m_Vertices = unpack_floats(m_CompressedMesh.m_Vertices, shape=(3,)) + + # UV + if m_CompressedMesh.m_UV.m_NumItems > 0: + m_UVInfo = m_CompressedMesh.m_UVInfo + if m_UVInfo is not None and m_UVInfo != 0: + kInfoBitsPerUV = 4 + kUVDimensionMask = 3 + kUVChannelExists = 4 + kMaxTexCoordShaderChannels = 8 + + uvSrcOffset = 0 + + for uv_channel in range(kMaxTexCoordShaderChannels): + texCoordBits = m_UVInfo >> (uv_channel * kInfoBitsPerUV) + texCoordBits &= (1 << kInfoBitsPerUV) - 1 + if (texCoordBits & kUVChannelExists) != 0: + uvDim = 1 + int(texCoordBits & kUVDimensionMask) + m_UV = unpack_floats( + m_CompressedMesh.m_UV, + uvSrcOffset, + m_VertexCount * uvDim, + shape=(uvDim,), + ) + setattr(self, f"m_UV{uv_channel}", m_UV) + uvSrcOffset = uvDim * m_VertexCount + else: + self.m_UV0 = unpack_floats(m_CompressedMesh.m_UV, 0, m_VertexCount * 2, shape=(2,)) + if m_CompressedMesh.m_UV.m_NumItems >= m_VertexCount * 4: + self.m_UV1 = unpack_floats( + m_CompressedMesh.m_UV, + m_VertexCount * 2, + m_VertexCount * 2, + shape=(2,), + ) + + # BindPose + if version[0] < 5: # 5.0 down + m_BindPoses = m_CompressedMesh.m_BindPoses + if m_BindPoses and m_BindPoses.m_NumItems > 0: + self.m_BindPose = unpack_floats( + m_BindPoses, + shape=( + 4, + 4, + ), + ) + + # Normal + if m_CompressedMesh.m_Normals.m_NumItems > 0: + normalData = unpack_floats(m_CompressedMesh.m_Normals, shape=(2,)) + signs = unpack_ints(m_CompressedMesh.m_NormalSigns) + + normals = zeros(self.m_VertexCount, 3) + for srcNrm, sign, dstNrm in zip(normalData, signs, normals): + x, y = srcNrm + zsqr = 1 - x * x - y * y + if zsqr >= 0: + z = math.sqrt(zsqr) + dstNrm[:] = x, y, z + else: + z = 0 + dstNrm[:] = normalize(x, y, z) + if sign == 0: + dstNrm[2] *= -1 + self.m_Normals = lists_to_tuples(normals) + + # Tangent + if m_CompressedMesh.m_Tangents.m_NumItems > 0: + tangentData = unpack_floats(m_CompressedMesh.m_Tangents, shape=(2,)) + signs = unpack_ints(m_CompressedMesh.m_TangentSigns, shape=(2,)) + + tangents = zeros(self.m_VertexCount, 4) + for srcTan, (sign_z, sign_w), dstTan in zip(tangentData, signs, tangents): + x, y = srcTan + zsqr = 1 - x * x - y * y + z = 0 + w = 0 + if zsqr >= 0: + z = math.sqrt(zsqr) + else: + x, y, z = normalize(x, y, z) + if sign_z == 0: + z = -z + w = 1.0 if sign_w > 0 else -1.0 + dstTan[:] = x, y, z, w + self.m_Tangents = lists_to_tuples(tangents) + + # FloatColor + if version[0] >= 5: # 5.0 and up + m_FloatColors = m_CompressedMesh.m_FloatColors + if m_FloatColors and m_FloatColors.m_NumItems > 0: + self.m_Colors = unpack_floats(m_FloatColors, shape=(4,)) + # Skin + if m_CompressedMesh.m_Weights.m_NumItems > 0: + weightsData = unpack_ints(m_CompressedMesh.m_Weights) + boneIndicesData = unpack_ints(m_CompressedMesh.m_BoneIndices) + + vertexIndex = 0 + j = 0 + sum = 0 + + boneWeights = zeros(self.m_VertexCount, 4) + boneIndices = zeros(self.m_VertexCount, 4) + + boneIndicesIterator = iter(boneIndicesData) + for weight, boneIndex in zip(weightsData, boneIndicesIterator): + # read bone index and weight + boneWeights[vertexIndex][j] = weight / 31 + boneIndices[vertexIndex][j] = boneIndex + + j += 1 + sum += weight + + # the weights add up to one, continue with the next vertex. + if sum >= 31: + j = 4 + # set weights and boneIndices to 0, + # already done on init + vertexIndex += 1 + j = 0 + sum = 0 + # we read three weights, but they don't add up to one. calculate the fourth one, and read + # missing bone index. continue with next vertex. + elif j == 3: # + boneWeights[vertexIndex][j] = 1 - sum + boneIndices[vertexIndex][j] = next(boneIndicesIterator) + + vertexIndex += 1 + j = 0 + sum = 0 + + self.m_BoneWeights = lists_to_tuples(boneWeights) + self.m_BoneIndices = lists_to_tuples(boneIndices) + + # IndexBuffer + if m_CompressedMesh.m_Triangles.m_NumItems > 0: # + self.m_IndexBuffer = unpack_ints(m_CompressedMesh.m_Triangles) + # Color + if m_CompressedMesh.m_Colors and m_CompressedMesh.m_Colors.m_NumItems > 0: + rgba_colors = unpack_ints(m_CompressedMesh.m_Colors) + self.m_Colors = [ + ( + ((rgba >> 24) & 0xFF) / 255, + ((rgba >> 16) & 0xFF) / 255, + ((rgba >> 8) & 0xFF) / 255, + (rgba & 0xFF) / 255, + ) + for rgba in rgba_colors + ] + + def get_triangles(self) -> List[List[Tuple[int, ...]]]: + assert self.m_IndexBuffer is not None + assert self.src.m_SubMeshes is not None + + submeshes: List[List[Tuple[int, ...]]] = [] + + for m_SubMesh in self.src.m_SubMeshes: + firstIndex = m_SubMesh.firstByte // 2 + if not self.m_Use16BitIndices: + firstIndex //= 2 + + indexCount = m_SubMesh.indexCount + topology = m_SubMesh.topology + + triangles: List[Tuple[int, ...]] + + if topology == MeshTopology.Triangles: + triangles = [ + tuple(self.m_IndexBuffer[i : i + 3]) for i in range(firstIndex, firstIndex + indexCount, 3) + ] + + elif self.version[0] < 4 or topology == MeshTopology.TriangleStrip: + triangles = [()] * (indexCount - 2) + triIndex = 0 + for i in range(firstIndex, firstIndex + indexCount - 2): + a, b, c = self.m_IndexBuffer[i : i + 3] + # skip degenerates + if a == b or a == c or b == c: + continue + # do the winding flip-flop of strips + if (i - firstIndex) & 1: + triangles[triIndex] = (b, a, c) + else: + triangles[triIndex] = (a, b, c) + triIndex += 1 + triangles = triangles[:triIndex] + m_SubMesh.indexCount = len(triangles) * 3 + + elif topology == MeshTopology.Quads: + # one quad is two triangles, so // 4 * 2 = // 2 + triangles = [()] * (indexCount // 2) + triIndex = 0 + for i in range(firstIndex, firstIndex + indexCount, 4): + a, b, c, d = self.m_IndexBuffer[i : i + 4] + triangles[triIndex] = (a, b, c) + triangles[triIndex + 1] = (a, c, d) + triIndex += 2 + + else: + raise ValueError("Failed getting triangles. Submesh topology is lines or points.") + + submeshes.append(triangles) + + return submeshes + + +# COMPRESSION_BIT_SIZES = { +# "high": { +# "vertex": 10, +# "uv": 8, +# "normal": 6, +# }, +# "medium": { +# "vertex": 16, +# "uv": 10, +# "normal": 8 +# }, +# "low": { +# "vertex": 20, +# "uv": 16, +# "normal": 8 +# }, +# } diff --git a/UnityPy/helpers/PackedBitVector.py b/UnityPy/helpers/PackedBitVector.py new file mode 100644 index 000000000..57d53fe10 --- /dev/null +++ b/UnityPy/helpers/PackedBitVector.py @@ -0,0 +1,143 @@ +from typing import TYPE_CHECKING, Any, List, Optional, Tuple + +if TYPE_CHECKING: + from ..classes.generated import PackedBitVector + + +def reshape(data: list, shape: Optional[Tuple[int, ...]] = None) -> List[Any]: + if shape is None: + return data + if len(shape) == 1: + m = shape[0] + return [data[i : i + m] for i in range(0, len(data), m)] + elif len(shape) == 2: + m, n = shape + return [[data[i + j : i + j + n] for j in range(0, m * n, n)] for i in range(0, len(data), m * n)] + else: + raise ValueError("Invalid shape") + + +def unpack_ints( + packed: "PackedBitVector", + start: int = 0, + count: Optional[int] = None, + shape: Optional[Tuple[int, ...]] = None, +) -> List[Any]: + assert packed.m_BitSize is not None + + m_BitSize = packed.m_BitSize + m_Data = packed.m_Data + + bitPos = m_BitSize * start + indexPos = bitPos // 8 + bitPos %= 8 + + if count is None: + count = packed.m_NumItems + + # if m_BitSize <= 8: + # dtype = np.uint8 + # elif m_BitSize <= 16: + # dtype = np.uint16 + # elif m_BitSize <= 32: + # dtype = np.uint32 + # elif m_BitSize <= 64: + # dtype = np.uint64 + # else: + # raise ValueError("Invalid bit size") + + # data = np.zeros(packed.m_NumItems, dtype=dtype) + data = [0] * count + + for i in range(count): + bits = 0 + value = 0 + while bits < m_BitSize: + value |= (m_Data[indexPos] >> bitPos) << bits + num = min(m_BitSize - bits, 8 - bitPos) + bitPos += num + bits += num + if bitPos == 8: + indexPos += 1 + bitPos = 0 + data[i] = value & ((1 << m_BitSize) - 1) + + return reshape(data, shape) + + +def unpack_floats( + packed: "PackedBitVector", + start: int = 0, + count: Optional[int] = None, + shape: Optional[Tuple[int, ...]] = None, +) -> List[Any]: + assert packed.m_BitSize is not None and packed.m_Range is not None and packed.m_Start is not None + + # avoid zero division of scale + if packed.m_BitSize == 0: + quantized = [packed.m_Start] * (packed.m_NumItems if count is None else count) + else: + # read as int and cast up to double to prevent loss of precision + quantized_f64 = unpack_ints(packed, start, count) + scale = packed.m_Range / ((1 << packed.m_BitSize) - 1) + quantized = [x * scale + packed.m_Start for x in quantized_f64] + + return reshape(quantized, shape) + + +# def pack_ints( +# data: npt.NDArray[np.uint], bitsize: Optional[int] = 0 +# ) -> PackedBitVector: +# # ensure that the data type is unsigned +# assert "uint" in data.dtype.name + +# m_NumItems = data.size + +# maxi = data.max() +# # Prevent overflow +# if bitsize: +# m_BitSize = bitsize +# else: +# m_BitSize = (32 if maxi == 0xFFFFFFFF else np.ceil(np.log2(maxi + 1))) % 256 +# m_Data = np.zeros((m_NumItems * m_BitSize + 7) // 8, dtype=np.uint8) + +# indexPos = 0 +# bitPos = 0 +# for x in data: +# bits = 0 +# while bits < m_BitSize: +# m_Data[indexPos] |= (x >> bits) << bitPos +# num = min(m_BitSize - bits, 8 - bitPos) +# bitPos += num +# bits += num +# if bitPos == 8: +# indexPos += 1 +# bitPos = 0 + +# return PackedBitVector(m_NumItems=m_NumItems, m_BitSize=m_BitSize, m_Data=m_Data) + + +# def pack_floats( +# data: npt.NDArray[np.floating[Any]], +# bitsize: Optional[int] = None, +# ) -> PackedBitVector: +# min = data.min() +# max = data.max() +# range = max - min +# data_f64 = data.astype(np.float64) +# # rebase to 0 +# data_f64 -= min +# # scale to [0, 1] +# data_f64 /= range +# # quantize to [0, 2^bit_size - 1] +# bitsize = bitsize or max(data.itemsize, 32) +# assert bitsize is not None + +# data_f64 *= (1 << bitsize) - 1 +# # pack the data +# packed = pack_ints(data_f64.astype(np.uint32), bitsize) +# packed.m_Start = min +# packed.m_Range = range +# return packed + +__all__ = ("unpack_ints", "unpack_floats") diff --git a/UnityPy/helpers/ResourceReader.py b/UnityPy/helpers/ResourceReader.py index d1b2acb32..62e9800a8 100644 --- a/UnityPy/helpers/ResourceReader.py +++ b/UnityPy/helpers/ResourceReader.py @@ -1,66 +1,38 @@ -import os - -from .ImportHelper import find_all_files - - -def get_resource_data(*args): - """ - Input: - Option 1: - path - file path - assets_file - SerializedFile - offset - - size - - Option 2: - reader - EndianBinaryReader - offset - - size - - """ - if len(args) == 4: - need_search = True - path = args[0] - assets_file = args[1] - elif len(args) == 3: - need_search = False - reader = args[0] - offset = args[-2] - size = args[-1] - - if need_search: - resource_file_name = path - - reader = assets_file.environment.resources.get(resource_file_name) - if not reader: - reader = assets_file.environment.resources.get( - os.path.basename(resource_file_name) - ) - if not reader: - reader = assets_file.environment.resources.get( - os.path.basename(resource_file_name.replace('.assets.resS', '.resource')) - ) - +import ntpath +from typing import TYPE_CHECKING + +from ..streams import EndianBinaryReader + +if TYPE_CHECKING: + from ..files.SerializedFile import SerializedFile + + +def get_resource_data(res_path: str, assets_file: "SerializedFile", offset: int, size: int): + basename = ntpath.basename(res_path) + name, ext = ntpath.splitext(basename) + possible_names = [ + basename, + f"{name}.resource", + f"{name}.assets.resS", + f"{name}.resS", + ] + environment = assets_file.environment + reader = None + for possible_name in possible_names: + reader = environment.get_cab(possible_name) if reader: - reader.Position = offset - return reader.read_bytes(size) - - if not assets_file.environment.path: - return - current_directory = os.path.dirname(assets_file.environment.path) - resource_file_path = os.path.join(current_directory, resource_file_name) - if not os.path.isfile(resource_file_path): - find_files = find_all_files(current_directory, resource_file_name) - if find_files: - resource_file_path = find_files[0] + break + if not reader: + assets_file.load_dependencies(possible_names) + for possible_name in possible_names: + reader = environment.get_cab(possible_name) + if reader: + break + if not reader: + raise FileNotFoundError(f"Resource file {basename} not found") + return _get_resource_data(reader, offset, size) - if os.path.isfile(resource_file_path): - with open(resource_file_path, "rb") as f: - f.seek(offset) - return f.read(size) - else: - raise FileNotFoundError( - f"Can't find the resource file {resource_file_name}" - ) - else: - reader.Position = offset - return reader.read_bytes(size) +def _get_resource_data(reader: EndianBinaryReader, offset: int, size: int): + reader.Position = offset + return reader.read_bytes(size) diff --git a/UnityPy/helpers/TextureSwizzler.py b/UnityPy/helpers/TextureSwizzler.py new file mode 100644 index 000000000..fb3edd2b4 --- /dev/null +++ b/UnityPy/helpers/TextureSwizzler.py @@ -0,0 +1,127 @@ +# based on https://github.com/nesrak1/AssetsTools.NET/blob/dev/AssetsTools.NET.Texture/Swizzle/SwitchSwizzle.cs +from typing import Dict, List, Optional, Tuple, Union + +from ..enums import BuildTarget, TextureFormat + +GOB_X_TEXEL_COUNT = 4 +GOB_Y_TEXEL_COUNT = 8 +TEXEL_BYTE_SIZE = 16 +TEXELS_IN_GOB = GOB_X_TEXEL_COUNT * GOB_Y_TEXEL_COUNT +GOB_MAP = [(((v >> 3) & 0b10) | ((v >> 1) & 0b1), ((v >> 1) & 0b110) | (v & 0b1)) for v in range(TEXELS_IN_GOB)] + + +def ceil_divide(a: int, b: int) -> int: + return (a + b - 1) // b + + +def deswizzle( + data: Union[bytes, bytearray, memoryview], + width: int, + height: int, + block_width: int, + block_height: int, + texels_per_block: int, +) -> bytearray: + block_count_x = ceil_divide(width, block_width) + block_count_y = ceil_divide(height, block_height) + gob_count_x = block_count_x // GOB_X_TEXEL_COUNT + gob_count_y = block_count_y // GOB_Y_TEXEL_COUNT + new_data = bytearray(len(data)) + data_view = memoryview(data) + + for i in range(gob_count_y // texels_per_block): + for j in range(gob_count_x): + base_gob_dst_x = j * 4 + for k in range(texels_per_block): + base_gob_dst_y = (i * texels_per_block + k) * GOB_Y_TEXEL_COUNT + for gob_x, gob_y in GOB_MAP: + dst_offset = ((base_gob_dst_y + gob_y) * block_count_x + (base_gob_dst_x + gob_x)) * TEXEL_BYTE_SIZE + new_data[dst_offset : dst_offset + TEXEL_BYTE_SIZE] = data_view[:TEXEL_BYTE_SIZE] + data_view = data_view[TEXEL_BYTE_SIZE:] + return new_data + + +def swizzle( + data: Union[bytes, bytearray, memoryview], + width: int, + height: int, + block_width: int, + block_height: int, + texels_per_block: int, +) -> bytearray: + block_count_x = ceil_divide(width, block_width) + block_count_y = ceil_divide(height, block_height) + gob_count_x = block_count_x // GOB_X_TEXEL_COUNT + gob_count_y = block_count_y // GOB_Y_TEXEL_COUNT + new_data = bytearray(len(data)) + data_view = memoryview(new_data) + + for i in range(gob_count_y // texels_per_block): + for j in range(gob_count_x): + base_gob_dst_x = j * 4 + for k in range(texels_per_block): + base_gob_dst_y = (i * texels_per_block + k) * GOB_Y_TEXEL_COUNT + for gob_x, gob_y in GOB_MAP: + src_offset = ((base_gob_dst_y + gob_y) * block_count_x + (base_gob_dst_x + gob_x)) * TEXEL_BYTE_SIZE + data_view[:TEXEL_BYTE_SIZE] = data[src_offset : src_offset + TEXEL_BYTE_SIZE] + data_view = data_view[TEXEL_BYTE_SIZE:] + + return new_data + + +# this should be the amount of pixels that can fit 16 bytes +TEXTURE_FORMAT_BLOCK_SIZE_MAP: Dict[TextureFormat, Tuple[int, int]] = { + TextureFormat.Alpha8: (16, 1), # 1 byte per pixel + TextureFormat.ARGB4444: (8, 1), # 2 bytes per pixel + TextureFormat.RGBA32: (4, 1), # 4 bytes per pixel + TextureFormat.ARGB32: (4, 1), # 4 bytes per pixel + TextureFormat.ARGBFloat: (1, 1), # 16 bytes per pixel (?) + TextureFormat.RGB565: (8, 1), # 2 bytes per pixel + TextureFormat.R16: (8, 1), # 2 bytes per pixel + TextureFormat.DXT1: (8, 4), # 8 bytes per 4x4=16 pixels + TextureFormat.DXT5: (4, 4), # 16 bytes per 4x4=16 pixels + TextureFormat.RGBA4444: (8, 1), # 2 bytes per pixel + TextureFormat.BGRA32: (4, 1), # 4 bytes per pixel + TextureFormat.BC6H: (4, 4), # 16 bytes per 4x4=16 pixels + TextureFormat.BC7: (4, 4), # 16 bytes per 4x4=16 pixels + TextureFormat.BC4: (8, 4), # 8 bytes per 4x4=16 pixels + TextureFormat.BC5: (4, 4), # 16 bytes per 4x4=16 pixels + TextureFormat.ASTC_RGB_4x4: (4, 4), # 16 bytes per 4x4=16 pixels + TextureFormat.ASTC_RGB_5x5: (5, 5), # 16 bytes per 5x5=25 pixels + TextureFormat.ASTC_RGB_6x6: (6, 6), # 16 bytes per 6x6=36 pixels + TextureFormat.ASTC_RGB_8x8: (8, 8), # 16 bytes per 8x8=64 pixels + TextureFormat.ASTC_RGB_10x10: (10, 10), # 16 bytes per 10x10=100 pixels + TextureFormat.ASTC_RGB_12x12: (12, 12), # 16 bytes per 12x12=144 pixels + TextureFormat.ASTC_RGBA_4x4: (4, 4), # 16 bytes per 4x4=16 pixels + TextureFormat.ASTC_RGBA_5x5: (5, 5), # 16 bytes per 5x5=25 pixels + TextureFormat.ASTC_RGBA_6x6: (6, 6), # 16 bytes per 6x6=36 pixels + TextureFormat.ASTC_RGBA_8x8: (8, 8), # 16 bytes per 8x8=64 pixels + TextureFormat.ASTC_RGBA_10x10: (10, 10), # 16 bytes per 10x10=100 pixels + TextureFormat.ASTC_RGBA_12x12: (12, 12), # 16 bytes per 12x12=144 pixels + TextureFormat.RG16: (8, 1), # 2 bytes per pixel + TextureFormat.R8: (16, 1), # 1 byte per pixel +} + + +def get_padded_texture_size(width: int, height: int, block_width: int, block_height: int, texels_per_block: int): + width = ceil_divide(width, block_width * GOB_X_TEXEL_COUNT) * block_width * GOB_X_TEXEL_COUNT + height = ( + ceil_divide(height, block_height * GOB_Y_TEXEL_COUNT * texels_per_block) + * block_height + * GOB_Y_TEXEL_COUNT + * texels_per_block + ) + return width, height + + +def get_switch_gobs_per_block(platform_blob: List[int]) -> int: + return 1 << int.from_bytes(platform_blob[8:12], "little") + + +def is_switch_swizzled(platform: Union[BuildTarget, int], platform_blob: Optional[List[int]]) -> bool: + if platform != BuildTarget.Switch: + return False + if not platform_blob or len(platform_blob) < 12: + return False + gobs_per_block = get_switch_gobs_per_block(platform_blob) + return gobs_per_block > 1 diff --git a/UnityPy/helpers/Tpk.py b/UnityPy/helpers/Tpk.py new file mode 100644 index 000000000..3ccdfbef9 --- /dev/null +++ b/UnityPy/helpers/Tpk.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +import sys +from functools import cache +from io import BytesIO +from typing import TYPE_CHECKING, Dict, Optional, cast + +from tpk_ar import TpkFile, TpkTypeTreeBlob, TpkUnityClass, TpkUnityNode +from tpk_ar import UnityVersion as TpkUnityVersion + +from . import TypeTreeHelper +from .UnityVersion import UnityVersion + +if TYPE_CHECKING: + from .TypeTreeHelper import TypeTreeNode + + +@cache +def get_typetree() -> TpkTypeTreeBlob: + package = "UnityPy.resources" + resource = "lzma.tpk" + + tpk_data: bytes + if sys.version_info >= (3, 9): + from importlib.resources import files + + tpk_data = files(package).joinpath(resource).read_bytes() + + else: + from importlib.resources import open_binary + + tpk_data = open_binary(package, resource).read() + + with BytesIO(tpk_data) as stream: + tree = TpkFile.parse(stream).GetDataBlob() + assert isinstance(tree, TpkTypeTreeBlob) + return tree + + +@cache +def get_typetree_node(class_id: int, version: UnityVersion): + tpk_version = cast(TpkUnityVersion, version) + class_info = get_typetree().ClassInformation[class_id].getVersionedClass(tpk_version) + if class_info is None: + raise ValueError("Could not find class info for class id {}".format(class_id)) + + node = generate_node(class_info) + return node + + +@cache +def generate_node(class_info: TpkUnityClass) -> "TypeTreeNode": + assert class_info.ReleaseRootNode is not None, "Class {} has no ReleaseRootNode".format(class_info) + + TypeTreeNode = TypeTreeHelper.TypeTreeNode + + nodes = [] + NODES = get_typetree().NodeBuffer + STRINGBUFFER = get_typetree().StringBuffer + stack = [(class_info.ReleaseRootNode, 0)] + index = 0 + while stack: + node_id, level = stack.pop(0) + node: TpkUnityNode = NODES[node_id] + nodes.append( + TypeTreeNode( + m_ByteSize=node.ByteSize, + m_Index=index, + m_Version=node.Version, + m_MetaFlag=node.MetaFlag, + m_Level=level, + m_Type=STRINGBUFFER[node.TypeName], + m_Name=STRINGBUFFER[node.Name], + ) + ) + stack = [(node_id, level + 1) for node_id in node.SubNodes] + stack + index += 1 + return TypeTreeNode.from_list(nodes) + + +@cache +def get_common_strings(version: Optional[UnityVersion] = None) -> Dict[int, str]: + tpk_version: TpkUnityVersion | None = cast(TpkUnityVersion, version) if version is not None else None + return get_typetree().CommonString.BuildMap(get_typetree().StringBuffer, tpk_version) diff --git a/UnityPy/helpers/TypeTreeGenerator.py b/UnityPy/helpers/TypeTreeGenerator.py new file mode 100644 index 000000000..4f313784e --- /dev/null +++ b/UnityPy/helpers/TypeTreeGenerator.py @@ -0,0 +1,78 @@ +import os +from typing import Dict, List, Tuple + +from .TypeTreeNode import TypeTreeNode + +try: + from TypeTreeGeneratorAPI import TypeTreeGenerator as TypeTreeGeneratorBase # pyright: ignore[reportAssignmentType] +except ImportError: + + class TypeTreeGeneratorBase: + def __init__(self, unity_version: str): + raise ImportError("TypeTreeGeneratorAPI isn't installed!") + + def load_dll(self, dll: bytes): ... + def load_il2cpp(self, il2cpp: bytes, metadata: bytes): ... + def get_nodes_as_json(self, assembly: str, fullname: str) -> str: ... + def get_nodes(self, assembly: str, fullname: str) -> List[TypeTreeNode]: ... + + +class TypeTreeGenerator(TypeTreeGeneratorBase): + cache: Dict[Tuple[str, str], TypeTreeNode] + + def __init__(self, unity_version: str, *args, **kwargs): + super().__init__(unity_version, *args, **kwargs) + self.cache = {} + + def load_local_game(self, root_dir: str): + root_files = os.listdir(root_dir) + data_dir = os.path.join(root_dir, next(f for f in root_files if f.endswith("_Data"))) + if "GameAssembly.dll" in root_files: + ga_fp = os.path.join(root_dir, "GameAssembly.dll") + gm_fp = os.path.join(data_dir, "il2cpp_data", "Metadata", "global-metadata.dat") + with open(ga_fp, "rb") as f: + ga_raw = f.read() + with open(gm_fp, "rb") as f: + gm_raw = f.read() + self.load_il2cpp(ga_raw, gm_raw) + else: + self.load_local_dll_folder(os.path.join(data_dir, "Managed")) + + def load_local_dll_folder(self, dll_dir: str): + for f in os.listdir(dll_dir): + if not f.endswith(".dll"): + continue + fp = os.path.join(dll_dir, f) + with open(fp, "rb") as f: + data = f.read() + self.load_dll(data) + + def get_nodes_up(self, assembly: str, fullname: str) -> TypeTreeNode: + key = (assembly, fullname) + if key in self.cache: + return self.cache[key] + + if not assembly.endswith(".dll"): + assembly = f"{assembly}.dll" + + base_nodes = self.get_nodes(assembly, fullname) + + node = TypeTreeNode.from_list( + [ + TypeTreeNode( + base_node.m_Level, + base_node.m_Type, + base_node.m_Name, + 0, + 0, + m_MetaFlag=base_node.m_MetaFlag, + ) + for base_node in base_nodes + ] + ) + + self.cache[key] = node + return node + + +__all__ = ("TypeTreeGenerator",) diff --git a/UnityPy/helpers/TypeTreeHelper.py b/UnityPy/helpers/TypeTreeHelper.py index 7ae750e0c..7eb916eb6 100644 --- a/UnityPy/helpers/TypeTreeHelper.py +++ b/UnityPy/helpers/TypeTreeHelper.py @@ -1,388 +1,444 @@ -from ..streams import EndianBinaryReader - - -class RefInt: - v: int - - def __init__(self, value): - self._value = value - - def __add__(self, other): - return self._value + other - - def __sub__(self, other): - self._value -= other - return self._value - - def __int__(self): - return self._value - - def __getattr__(self, item): - return self._value - - def __getitem__(self, item): - return self._value - - def __setattr__(self, key, value): - self.__dict__["_value"] = value - - def __setitem__(self, key, value): - self._value = value - - def __mod__(self, other): - return self._value % other - - def __ge__(self, other): - return self._value >= other - - def __gt__(self, other): - return self._value > other - - def __le__(self, other): - return self._value <= other - - def __lt__(self, other): - return self._value < other - - def __eq__(self, other): - return self._value == other - - -def get_members(members: list, level: int, index: int) -> list: - member2 = [members[0]] - for i in range(index + 1, len(members)): - member = members[i] - if member.level <= level: - return member2 - member2.append(member) - return member2 - - -class TypeTreeHelper: - def __init__(self, reader: EndianBinaryReader): - self.reader = reader - self.READ = { - "SInt8": self.reader.read_byte, - "UInt8": self.reader.read_u_byte, - "short": self.reader.read_short, - "SInt16": self.reader.read_short, - "unsigned short": self.reader.read_u_short, - "UInt16": self.reader.read_u_short, - "int": self.reader.read_int, - "SInt32": self.reader.read_int, - "unsigned int": self.reader.read_u_int, - "UInt32": self.reader.read_u_int, - "Type*": self.reader.read_u_int, - "long long": self.reader.read_long, - "SInt64": self.reader.read_long, - "unsigned long long": self.reader.read_u_long, - "UInt64": self.reader.read_u_long, - "float": self.reader.read_float, - "double": self.reader.read_double, - "bool": self.reader.read_boolean, - } - - self.READ2 = { - "string": self.read_string, - "map": self.read_map, - "TypelessData": self.read_typeless_data, - } - - def read_u_type(self, members: list) -> dict: - i = RefInt(0) - obj = {} - while i.v < len(members): - member = members[i.v] - obj[member.name] = self.read_value(members, i) - i.v += 1 - return obj - - def read_string(self, i, align, *args): - value = self.reader.read_aligned_string() - i.v += 3 - return value, align - - def read_map(self, i, align, members, level): - if (members[i + 1].meta_flag & 0x4000) != 0: - align = True - size = self.reader.read_int() - map_ = get_members(members, level, i)[4:] - i.v += len(map_) + 3 - first = get_members(map_, map_[0].level, 0) - second = map_[len(first) :] +from __future__ import annotations + +from sys import version_info as py_version_info +from typing import TYPE_CHECKING, Any, Optional, Union + +from attrs import define + +from .. import classes +from ..streams.EndianBinaryReader import EndianBinaryReader +from ..streams.EndianBinaryWriter import EndianBinaryWriter +from .TypeTreeNode import TypeTreeNode + +Object = classes.Object +UnknownObject = classes.UnknownObject +PPtr = classes.PPtr + +if TYPE_CHECKING: + from ..files.SerializedFile import SerializedFile + + +try: + from ..UnityPyBoost import read_typetree as read_typetree_boost +except ImportError: + read_typetree_boost = None + +kAlignBytes = 0x4000 + +FUNCTION_READ_MAP = { + "SInt8": EndianBinaryReader.read_byte, + "UInt8": EndianBinaryReader.read_u_byte, + "char": EndianBinaryReader.read_u_byte, + "short": EndianBinaryReader.read_short, + "SInt16": EndianBinaryReader.read_short, + "unsigned short": EndianBinaryReader.read_u_short, + "UInt16": EndianBinaryReader.read_u_short, + "int": EndianBinaryReader.read_int, + "SInt32": EndianBinaryReader.read_int, + "unsigned int": EndianBinaryReader.read_u_int, + "UInt32": EndianBinaryReader.read_u_int, + "Type*": EndianBinaryReader.read_u_int, + "long long": EndianBinaryReader.read_long, + "SInt64": EndianBinaryReader.read_long, + "unsigned long long": EndianBinaryReader.read_u_long, + "UInt64": EndianBinaryReader.read_u_long, + "FileSize": EndianBinaryReader.read_u_long, + "float": EndianBinaryReader.read_float, + "double": EndianBinaryReader.read_double, + "bool": EndianBinaryReader.read_boolean, + "string": EndianBinaryReader.read_aligned_string, + "TypelessData": EndianBinaryReader.read_byte_array, +} + +FUNCTION_READ_MAP_ARRAY = { + "SInt8": EndianBinaryReader.read_byte_array, + "UInt8": EndianBinaryReader.read_u_byte_array, + "char": EndianBinaryReader.read_u_byte_array, + "short": EndianBinaryReader.read_short_array, + "SInt16": EndianBinaryReader.read_short_array, + "unsigned short": EndianBinaryReader.read_u_short_array, + "UInt16": EndianBinaryReader.read_u_short_array, + "int": EndianBinaryReader.read_int_array, + "SInt32": EndianBinaryReader.read_int_array, + "unsigned int": EndianBinaryReader.read_u_int_array, + "UInt32": EndianBinaryReader.read_u_int_array, + "Type*": EndianBinaryReader.read_u_int_array, + "long long": EndianBinaryReader.read_long_array, + "SInt64": EndianBinaryReader.read_long_array, + "unsigned long long": EndianBinaryReader.read_u_long_array, + "UInt64": EndianBinaryReader.read_u_long_array, + "FileSize": EndianBinaryReader.read_u_long_array, + "float": EndianBinaryReader.read_float_array, + "double": EndianBinaryReader.read_double_array, + "bool": EndianBinaryReader.read_boolean_array, +} + + +@define(slots=True) +class TypeTreeConfig: + as_dict: bool + assetsfile: "Optional[SerializedFile]" = None + has_registry: bool = False + + def copy(self) -> TypeTreeConfig: + return TypeTreeConfig(self.as_dict, self.assetsfile, self.has_registry) + + +def get_ref_type_node(ref_object: dict, assetfile: SerializedFile) -> Optional[TypeTreeNode]: + typ = ref_object["type"] + if isinstance(typ, dict): + cls = typ["class"] + ns = typ["ns"] + asm = typ["asm"] + else: + cls = getattr(typ, "class") + ns = typ.ns + asm = typ.asm + + if not assetfile or not assetfile.ref_types: + raise ValueError("SerializedFile has no ref_types") + + if cls == "": + return None + + for ref_type in assetfile.ref_types: + if cls == ref_type.m_ClassName and ns == ref_type.m_NameSpace and asm == ref_type.m_AssemblyName: + return ref_type.node + else: + raise ValueError(f"Referenced type not found: {cls} {ns} {asm}") + + +if py_version_info >= (3, 14): + from annotationlib import get_annotations as annotationlib_get_annotations + + def get_annotation_keys(clz) -> set[str]: + return set(annotationlib_get_annotations(clz).keys()) +else: + + def get_annotation_keys(clz) -> set[str]: + return set(clz.__annotations__) + + +def read_typetree( + root_node: TypeTreeNode, + reader: EndianBinaryReader, + as_dict: bool = True, + byte_size: Optional[int] = None, + check_read: bool = True, + assetsfile: Optional[SerializedFile] = None, +) -> Union[dict[str, Any], Object]: + """Reads the typetree of the object contained in the reader via the node list. + + Parameters + ---------- + nodes : list + List of nodes/nodes + reader : EndianBinaryReader + Reader of the object to be parsed + + Returns + ------- + dict | objects.Object + The parsed typtree + """ + bytes_read: int + if byte_size and read_typetree_boost: + data = reader.read_bytes(byte_size) + obj, bytes_read = read_typetree_boost(data, root_node, reader.endian, as_dict, assetsfile, classes) + else: + pos = reader.Position + config = TypeTreeConfig(as_dict, assetsfile, False) + obj = read_value(root_node, reader, config) + bytes_read = reader.Position - pos + + if check_read and bytes_read != byte_size: + raise ValueError(f"Expected to read {byte_size} bytes, but only read {bytes_read} bytes") + + return obj + + +def write_typetree( + value: Union[dict[str, Any], Object], + root_node: TypeTreeNode, + writer: EndianBinaryWriter, + assetsfile: Optional[SerializedFile] = None, +) -> None: + """Writes the typetree of the object contained in the reader via the node list. + + Parameters + ---------- + value: dict | objects.Object + The object to be written + nodes : list + List of nodes/nodes + writer : EndianBinaryWriter + Writer of the object to be parsed + """ + config = TypeTreeConfig(isinstance(value, dict), assetsfile, False) + return write_value(value, root_node, writer, config) + + +def read_value( + node: TypeTreeNode, + reader: EndianBinaryReader, + config: TypeTreeConfig, +) -> Any: + # print(reader.Position, node.m_Name, node.m_Type, node.m_MetaFlag) + align = metaflag_is_aligned(node.m_MetaFlag) + + func = FUNCTION_READ_MAP.get(node.m_Type) + if func: + value = func(reader) + elif node.m_Type == "pair": + first = read_value(node.m_Children[0], reader, config) + second = read_value(node.m_Children[1], reader, config) + value = (first, second) + elif node.m_Type == "ReferencedObject": value = {} - for j in range(size): - tmp1 = RefInt(0) - tmp2 = RefInt(0) - v1 = self.read_value( - first, tmp1 - ) # python reads the value first and then the key, so it has to be this way - if isinstance(v1, dict): - value[first] = tmp1 - value["values"] = self.read_value(second, tmp2) + for child in node.m_Children: + if child.m_Type == "ReferencedObjectData": + ref_type_nodes = get_ref_type_node(value, config.assetsfile) + if ref_type_nodes is None: + continue + value[child.m_Name] = read_value(ref_type_nodes, reader, config) else: - value[v1] = self.read_value(second, tmp2) - return value, align - - def read_typeless_data(self, i, align, *args): - size = self.reader.read_int() - value = self.reader.read_bytes(size) - i.v += 2 - return value, align - - def read_value(self, members: list, i) -> object: - if type(i) != RefInt: - i = RefInt(i) - member = members[i.v] - level = member.level - var_type_str = member.type - align = (member.meta_flag & 0x4000) != 0 - - value = self.READ.get(var_type_str) - if value: - value = value() + value[child.m_Name] = read_value(child, reader, config) + # Vector + elif node.m_Children and node.m_Children[0].m_Type == "Array": + if metaflag_is_aligned(node.m_Children[0].m_MetaFlag): + align = True + + # size = read_value(node.m_Children[0].m_Children[0], reader, as_dict) + size = reader.read_int() + if size < 0: + raise ValueError("Negative length read from TypeTree") + subtype = node.m_Children[0].m_Children[1] + if metaflag_is_aligned(subtype.m_MetaFlag): + value = read_value_array(subtype, reader, config, size) else: - value = self.READ2.get(var_type_str) - if value: - value, align = value(i, align, members, level) - else: - if i != len(members) and members[i.v + 1].type == "Array": - if (members[i.v + 1].meta_flag & 0x4000) != 0: - align = True - size = self.reader.read_int() - vector = get_members(members, level, i)[3:] - i.v += len(vector) + 2 - value = [self.read_value(vector, 0) for j in range(size)] + value = [read_value(subtype, reader, config) for _ in range(size)] + + else: # Class + value = {} + for child in node.m_Children: + if child.m_Type == "ManagedReferencesRegistry": + if config.has_registry: + continue else: - eclass = get_members(members, level, i) - eclass.pop(0) - i.v += len(eclass) - j = RefInt(0) - value = {} - while j < len(eclass): - classmember = eclass[j.v] - name = classmember.name - value[name] = self.read_value(eclass, j) - j.v += 1 - if align: - self.reader.align_stream() - return value - - def read_type_string(self, sb: list, members: list): - i = RefInt(0) - while i < len(members): - self.read_string_value(sb, members, i) - i.v += 1 - return sb - - def read_string_value(self, sb: list, members, i: RefInt): - if type(i) != RefInt: - i = RefInt(i) - member = members[i.v] - level = member.level - var_type_str = member.type - var_name_str = member.name - append = True - align = (member.meta_flag & 0x4000) != 0 - value = self.READ.get(var_type_str) - if value: - value = value() - - elif var_type_str == "string": - append = False - string = self.reader.read_aligned_string() - sb.append( - '{0}{1} {2} = "{3}"\r\n'.format( - "\t" * level, var_type_str, var_name_str, string + config = config.copy() + config.has_registry = True + value[child.m_Name if config.as_dict else child._clean_name] = read_value(child, reader, config) + + if not config.as_dict: + if node.m_Type.startswith("PPtr<"): + value = PPtr[Any]( + assetsfile=config.assetsfile, + m_FileID=value["m_FileID"], + m_PathID=value["m_PathID"], ) - ) - i.v += 3 - - elif var_type_str == "vector": - if (members[i + 1].meta_flag & 0x4000) != 0: - align = True - append = False - sb.append("{0}{1} {2}\r\n".format("\t" * level, var_type_str, var_name_str)) - sb.append("{0}{1} {2}\r\n".format("\t" * (level + 1), "Array", "Array")) - size = self.reader.read_int() - sb.append( - "{0}{1} {2} = {3}\r\n".format("\t" * (level + 1), "int", "size", size) - ) - vector = get_members(members, level, i.v)[3:] - i.v += len(vector) + 2 - for j in range(size): - sb.append("{0}[{1}]\r\n".format("\t" * (level + 2), j)) - tmp = RefInt(0) - self.read_string_value(sb, vector, tmp) - - elif var_type_str == "map": - if (members[i + 1].meta_flag & 0x4000) != 0: - align = True - append = False - sb.append("{0}{1} {2}\r\n".format("\t" * level, var_type_str, var_name_str)) - sb.append("{0}{1} {2}\r\n".format("\t" * (level + 1), "Array", "Array")) - size = self.reader.read_int() - sb.append( - "{0}{1} {2} = {3}\r\n".format("\t" * (level + 1), "int", "size", size) - ) - map_ = get_members(members, level, i.v)[4:] - i.v += len(map_) + 3 - first = get_members(map_, map_[0].level, 0) - second = map_[len(first) :] - for j in range(size): - sb.append("{0}[{1}]\r\n".format("\t" * (level + 2), j)) - sb.append("{0}{1} {2}\r\n".format("\t" * (level + 2), "pair", "data")) - tmp1 = RefInt(0) - tmp2 = RefInt(0) - self.read_string_value(sb, first, tmp1) - self.read_string_value(sb, second, tmp2) - - elif var_type_str == "TypelessData": - append = False - size = self.reader.read_int() - value = self.reader.read_bytes(size) - i.v += 2 - sb.append("{0}{1} {2}\r\n".format("\t" * level, var_type_str, var_name_str)) - sb.append("{0}{1} {2} = {3}\r\n".format("\t" * level, "int", "size", size)) - + else: + clz = getattr(classes, node.m_Type, UnknownObject) + try: + value = clz(**value) + except TypeError: + keys = set(value.keys()) + annotation_keys = get_annotation_keys(clz) + missing_keys = annotation_keys - keys + if clz is UnknownObject or missing_keys: + value = UnknownObject(node, **value) + else: + extra_keys = keys - annotation_keys + if extra_keys: + instance = clz(**{key: value[key] for key in annotation_keys}) + for key in extra_keys: + setattr(instance, key, value[key]) + value = instance + else: + value = UnknownObject(**value) + + if align: + reader.align_stream() + + return value + + +def read_value_array( + node: TypeTreeNode, + reader: EndianBinaryReader, + config: TypeTreeConfig, + size: int, +) -> Any: + align = metaflag_is_aligned(node.m_MetaFlag) + + func = FUNCTION_READ_MAP_ARRAY.get(node.m_Type) + if func: + value = func(reader, size) + elif node.m_Type == "string": + value = [reader.read_aligned_string() for _ in range(size)] + elif node.m_Type == "TypelessData": + value = [reader.read_byte_array() for _ in range(size)] + elif node.m_Type == "pair": + key_node = node.m_Children[0] + value_node = node.m_Children[1] + + key_func = FUNCTION_READ_MAP.get( + key_node.m_Type, + lambda reader: read_value(key_node, reader, config), + ) + value_func = FUNCTION_READ_MAP.get( + value_node.m_Type, + lambda reader: read_value(value_node, reader, config), + ) + value = [(key_func(reader), value_func(reader)) for _ in range(size)] + elif node.m_Type == "ReferencedObject": + value = [None] * size + for i in range(size): + item = {} + for child in node.m_Children: + if child.m_Type == "ReferencedObjectData": + ref_type_nodes = get_ref_type_node(item, config.assetsfile) + item[child.m_Name] = read_value(ref_type_nodes, reader, config) + else: + item[child.m_Name] = read_value(child, reader, config) + value[i] = item + # Vector + elif node.m_Children and node.m_Children[0].m_Type == "Array": + if metaflag_is_aligned(node.m_Children[0].m_MetaFlag): + align = True + subtype = node.m_Children[0].m_Children[1] + if metaflag_is_aligned(subtype.m_MetaFlag): + value = [read_value_array(subtype, reader, config, reader.read_int()) for _ in range(size)] else: - if i != len(members) and members[i + 1].type == "Array": - if (members[i + 1].meta_flag & 0x4000) != 0: - align = True - append = False - sb.append( - "{0}{1} {2}\r\n".format("\t" * level, var_type_str, var_name_str) + value = [[read_value(subtype, reader, config) for _ in range(reader.read_int())] for _ in range(size)] + else: # Class + if config.as_dict: + value = [ + {child.m_Name: read_value(child, reader, config) for child in node.m_Children} for _ in range(size) + ] + elif node.m_Type.startswith("PPtr<"): + value = [ + PPtr[Any]( + assetsfile=config.assetsfile, + **{child.m_Name: read_value(child, reader, config) for child in node.m_Children}, ) - sb.append("{0}{1} {2}\r\n".format("\t" * (level + 1), "Array", "Array")) - size = self.reader.read_int() - sb.append( - "{0}{1} {2} = {3}\r\n".format( - "\t" * (level + 1), "int", "size", size + for _ in range(size) + ] + else: + clz = getattr( + classes, + node.m_Type, + UnknownObject, + ) + keys = set(child._clean_name for child in node.m_Children) + annotation_keys = get_annotation_keys(clz) + missing_keys = annotation_keys - keys + extra_keys = keys - annotation_keys + if missing_keys or clz is UnknownObject: + value = [ + UnknownObject( + node, + **{child._clean_name: read_value(child, reader, config) for child in node.m_Children}, ) - ) - vector = get_members(members, level, i.v) - i.v += len(vector) - 1 - vector = vector[3:] # vector.RemoveRange(0, 3) - for j in range(size): - sb.append("{0}[{1}]\r\n".format("\t" * (level + 2), j)) - tmp = RefInt(0) - self.read_string_value(sb, vector, tmp) + for _ in range(size) + ] + elif extra_keys: + value = [None] * size + for i in range(size): + value_i_d = {child._clean_name: read_value(child, reader, config) for child in node.m_Children} + value_i = clz(**{key: value for key, value in value_i_d.items() if key in annotation_keys}) + for key in extra_keys: + setattr(value_i, key, value_i_d[key]) + value[i] = value_i else: - append = False - sb.append( - "{0}{1} {2}\r\n".format("\t" * level, var_type_str, var_name_str) - ) - eclass = get_members(members, level, i.v) - eclass.pop(0) # .RemoveAt(0) - i.v += len(eclass) - j = RefInt(0) - while j < len(eclass): - self.read_string_value(sb, eclass, j) - j.v += 1 - - if append: - sb.append( - "{0}{1} {2} = {3}\r\n".format( - "\t" * level, var_type_str, var_name_str, value - ) - ) - if align: - self.reader.align_stream() - - return sb - - -""" - def write_u_type(self, obj: dict, members: list) -> bytes: - stream = io.BytesIO() - write = EndianBinaryReader(stream) - for i, member in enumerate(members): - var_name_str = member.name - write_value(obj[var_name_str], members, write, RefInt(i)) - pos = stream.tell() - stream.seek(0) - ret = stream.read() - stream.seek(pos) - return ret - - def write_value(value: dict, members: list, write: EndianBinaryReader, i): - member = members[i.v] - level = member.level - var_type_str = member.type - align = (member.meta_flag & 0x4000) != 0 - if var_type_str == "SInt8": - write.write_s_byte(value) - elif var_type_str == "UInt8": - write.write_byte(value) - elif var_type_str in ["short", "SInt16"]: - write.write_short(value) - elif var_type_str in ["UInt16", "unsigned short"]: - write.write_u_short(value) - elif var_type_str in ["int", "SInt32"]: - write.write_int(value) - elif var_type_str in ["UInt32", "unsigned int", "Type*"]: - write.write_u_int(value) - elif var_type_str in ["long long", "SInt64"]: - write.write_long(value) - elif var_type_str in ["UInt64", "unsigned long long"]: - write.write_u_long(value) - elif var_type_str == "float": - write.write_float(value) - elif var_type_str == "double": - write.write_double(value) - elif var_type_str == "bool": - write.write_bool(value) - elif var_type_str == "string": - write.write_aligned_string(value) - i.v += 3 - elif var_type_str == "map": - if (members[i + 1].meta_flag & 0x4000) != 0: - align = True - dic = list(value.items()) - size = len(dic) - write.Write(size) - map_ = get_members(members, level, i) - i.v += len(map_) - 1 - map_ = map_[4:] # map.RemoveRange(0, 4) - first = get_members(map_, map_[0].level, 0) - map_ = map_[len(first):] # .RemoveRange(0, len(first)) - second = map_ - for j in range(size): - tmp1 = RefInt(0) - tmp2 = RefInt(0) - write_value(dic[j][0], first, write, tmp1) # key - write_value(dic[j][1], second, write, tmp2) # value - elif var_type_str == "TypelessData": - byts = bytes(value) - size = len(byts) - write.Write(size) - write.Write(bytes) - i.v += 2 - else: - if i != len(members) and members[i + 1].type == "Array": # Array - if (members[i.v + 1].meta_flag & 0x4000) != 0: - align = True - list_ = value - size = len(list_) - write.Write(size) - vector = get_members(members, level, i) - i.v += len(vector) - 1 - vector = vector[3:] # vector.RemoveRange(0, 3) - for j in range(size): - tmp = RefInt(0) - write_value(list_[j], vector, write, tmp) - else: # Class - eclass = get_members(members, level, i) - eclass.pop(0) # .RemoveAt(0) - i.v += len(eclass) - obj = value - j = RefInt(0) - while j < len(eclass): - classmember = eclass[j.v] - name = classmember.name - write_value(obj[name], eclass, write, j) - if align: - write.align_stream(4) - - -""" + value = [ + clz(**{child._clean_name: read_value(child, reader, config) for child in node.m_Children}) + for _ in range(size) + ] + + if align: + reader.align_stream() + return value + + +def metaflag_is_aligned(meta_flag: int | None) -> bool: + return ((meta_flag or 0) & kAlignBytes) != 0 + + +FUNCTION_WRITE_MAP = { + "SInt8": EndianBinaryWriter.write_byte, + "UInt8": EndianBinaryWriter.write_u_byte, + "char": EndianBinaryWriter.write_u_byte, + "short": EndianBinaryWriter.write_short, + "SInt16": EndianBinaryWriter.write_short, + "unsigned short": EndianBinaryWriter.write_u_short, + "UInt16": EndianBinaryWriter.write_u_short, + "int": EndianBinaryWriter.write_int, + "SInt32": EndianBinaryWriter.write_int, + "unsigned int": EndianBinaryWriter.write_u_int, + "UInt32": EndianBinaryWriter.write_u_int, + "Type*": EndianBinaryWriter.write_u_int, + "long long": EndianBinaryWriter.write_long, + "SInt64": EndianBinaryWriter.write_long, + "unsigned long long": EndianBinaryWriter.write_u_long, + "UInt64": EndianBinaryWriter.write_u_long, + "FileSize": EndianBinaryWriter.write_u_long, + "float": EndianBinaryWriter.write_float, + "double": EndianBinaryWriter.write_double, + "bool": EndianBinaryWriter.write_boolean, + "string": EndianBinaryWriter.write_aligned_string, + "TypelessData": EndianBinaryWriter.write_byte_array, +} + + +def write_value( + value: Union[dict[str, Any], Object], + node: TypeTreeNode, + writer: EndianBinaryWriter, + config: TypeTreeConfig, +) -> None: + # print(reader.Position, node.m_Name, node.m_Type, node.m_MetaFlag) + align = metaflag_is_aligned(node.m_MetaFlag) + + func = FUNCTION_WRITE_MAP.get(node.m_Type) + if func: + value = func(writer, value) + elif node.m_Type == "pair": + write_value(value[0], node.m_Children[0], writer, config) + write_value(value[1], node.m_Children[1], writer, config) + elif node.m_Type == "ReferencedObject": + for child in node.m_Children: + if child.m_Type == "ReferencedObjectData": + ref_type_nodes = get_ref_type_node(value, config.assetsfile) + write_value(value[child.m_Name], ref_type_nodes, writer, config) + else: + write_value(value[child.m_Name], child, writer, config) + elif node.m_Children and node.m_Children[0].m_Type == "Array": + if metaflag_is_aligned(node.m_Children[0].m_MetaFlag): + align = True + + writer.write_int(len(value)) + subtype = node.m_Children[0].m_Children[1] + [write_value(sub_value, subtype, writer, config) for sub_value in value] + + else: # Class + if isinstance(value, dict): + for child in node.m_Children: + if child.m_Type == "ManagedReferencesRegistry": + if config.has_registry: + continue + else: + config = config.copy() + config.has_registry = True + write_value(value[child.m_Name], child, writer, config) + else: + for child in node.m_Children: + if child.m_Type == "ManagedReferencesRegistry": + if config.has_registry: + continue + else: + config = config.copy() + config.has_registry = True + write_value(getattr(value, child._clean_name), child, writer, config) + + if align: + writer.align_stream() diff --git a/UnityPy/helpers/TypeTreeNode.py b/UnityPy/helpers/TypeTreeNode.py new file mode 100644 index 000000000..a0a095711 --- /dev/null +++ b/UnityPy/helpers/TypeTreeNode.py @@ -0,0 +1,345 @@ +from __future__ import annotations + +import re +from struct import Struct +from threading import Lock +from typing import ( + TYPE_CHECKING, + Dict, + Iterator, + List, + Optional, + Tuple, + Union, + cast, +) + +from attrs import define, field + +from ..helpers.Tpk import get_common_strings +from ..streams.EndianBinaryReader import EndianBinaryReader +from ..streams.EndianBinaryWriter import EndianBinaryWriter + +try: + from ..UnityPyBoost import TypeTreeNode as TypeTreeNodeC # type: ignore +except ImportError: + + @define(slots=True) + class TypeTreeNodeC: + m_Level: int + m_Type: str + m_Name: str + m_ByteSize: int + m_Version: int + m_Children: List[TypeTreeNode] = field(factory=list) + m_TypeFlags: Optional[int] = None + m_VariableCount: Optional[int] = None + m_Index: Optional[int] = None + m_MetaFlag: Optional[int] = None + m_RefTypeHash: Optional[int] = None + _clean_name: str = field(init=False) + + def __attrs_post_init__(self): + self._clean_name = clean_name(self.m_Name) + + def __repr__(self): + return f"TypeTreeNode(m_Level={self.m_Level}, m_Type='{self.m_Type}', \ + m_Name='{self.m_Name}', m_MetaFlag={self.m_MetaFlag})" + + +TYPETREENODE_KEYS = [ + "m_Level", + "m_Type", + "m_Name", + "m_ByteSize", + "m_Version", + "m_Children", + "m_TypeFlags", + "m_VariableCount", + "m_Index", + "m_MetaFlag", + "m_RefTypeHash", +] + +SYSTEM_GLOBAL_LOCK = Lock() +NAME_PEEK_NODE_CACHE: dict[Tuple[str, str, int], Union[Tuple[TypeTreeNode, str], None]] = {} + + +class TypeTreeNode(TypeTreeNodeC): + def traverse(self) -> Iterator[TypeTreeNode]: + stack: list[TypeTreeNode] = [self] + while stack: + node = stack.pop() + yield node + stack.extend(reversed(node.m_Children)) + + @classmethod + def parse(cls, reader: EndianBinaryReader, version: int) -> TypeTreeNode: + # stack approach is way faster than recursion + # using a fake root node to avoid special case for root node + dummy_node = cls(-1, "", "", 0, 0, []) + dummy_root = cls(-1, "", "", 0, 0, [dummy_node]) + + stack: List[Tuple[TypeTreeNode, int]] = [(dummy_root, 1)] + while stack: + parent, count = stack[-1] + if count == 1: + stack.pop() + else: + stack[-1] = (parent, count - 1) + + node = cls( + m_Level=parent.m_Level + 1, + m_Type=reader.read_string_to_null(), + m_Name=reader.read_string_to_null(), + m_ByteSize=reader.read_int(), + m_VariableCount=reader.read_int() if version == 2 else None, + m_Index=reader.read_int() if version != 3 else None, + m_TypeFlags=reader.read_int(), + m_Version=reader.read_int(), + m_MetaFlag=reader.read_int() if version != 3 else None, + ) + parent.m_Children[-count] = node + children_count = reader.read_int() + if children_count > 0: + node.m_Children = [dummy_node] * children_count + stack.append((node, children_count)) + return dummy_root.m_Children[0] + + @classmethod + def parse_blob(cls, reader: EndianBinaryReader, version: int) -> TypeTreeNode: + node_count = reader.read_int() + stringbuffer_size = reader.read_int() + + node_struct, keys = _get_blob_node_struct(reader.endian, version) + struct_data = reader.read(node_struct.size * node_count) + stringbuffer_reader = EndianBinaryReader(reader.read(stringbuffer_size), reader.endian) + + CommonString = get_common_strings() + + def read_string(reader: EndianBinaryReader, value: int) -> str: + is_offset = (value & 0x80000000) == 0 + if is_offset: + reader.Position = value + return reader.read_string_to_null() + + offset = value & 0x7FFFFFFF + return CommonString.get(offset, str(offset)) + + fake_root: TypeTreeNode = cls(-1, "", "", 0, 0, []) + stack: List[TypeTreeNode] = [fake_root] + parent = fake_root + prev = fake_root + + for raw_node in node_struct.iter_unpack(struct_data): + node = cls( + **dict(zip(keys[:3], raw_node[:3])), + **dict(zip(keys[5:], raw_node[5:])), + m_Type=read_string(stringbuffer_reader, raw_node[3]), + m_Name=read_string(stringbuffer_reader, raw_node[4]), + ) + + if node.m_Level > prev.m_Level: + stack.append(parent) + parent = prev + elif node.m_Level < prev.m_Level: + while node.m_Level <= parent.m_Level: + parent = stack.pop() + + parent.m_Children.append(node) + prev = node + + return fake_root.m_Children[0] + + @classmethod + def from_list(cls, nodes: Union[List[Dict[str, Union[str, int]]], List[TypeTreeNode]]) -> TypeTreeNode: + fake_root: TypeTreeNode = cls(-1, "", "", 0, 0, []) + stack: List[TypeTreeNode] = [fake_root] + parent = fake_root + prev = fake_root + + # check if the nodes contain all required fields + if isinstance(nodes[0], dict): + if "m_Level" not in nodes[0] or "m_Type" not in nodes[0] or "m_Name" not in nodes[0]: + raise ValueError("Nodes must contain at least m_Level, m_Type and m_Name") + patch_dict = {} + if "m_ByteSize" not in nodes[0]: + patch_dict["m_ByteSize"] = 0 + if "m_Version" not in nodes[0]: + patch_dict["m_Version"] = 0 + nodes = [cls(**node, **patch_dict) for node in nodes] # type: ignore + + if TYPE_CHECKING: + nodes = cast(List[TypeTreeNode], nodes) + + for node in nodes: + if node.m_Level > prev.m_Level: + stack.append(parent) + parent = prev + elif node.m_Level < prev.m_Level: + while node.m_Level <= parent.m_Level: + parent = stack.pop() + + parent.m_Children.append(node) + prev = node + + return fake_root.m_Children[0] + + def get_name_peek_node(self) -> Union[Tuple[TypeTreeNode, str], None]: + global SYSTEM_GLOBAL_LOCK + with SYSTEM_GLOBAL_LOCK: + key = (self.m_Name, self.m_Type, self.m_Version) + if key in NAME_PEEK_NODE_CACHE: + return NAME_PEEK_NODE_CACHE[key] + + result: Union[Tuple[TypeTreeNode, str], None] = None + for i, child in enumerate(self.m_Children): + if child.m_Name in ("m_Name", "name"): + peek_node = TypeTreeNode( + self.m_Level, + self.m_Type, + self.m_Name, + self.m_ByteSize, + self.m_Version, + self.m_Children[: i + 1], + ) + result = peek_node, child.m_Name + break + NAME_PEEK_NODE_CACHE[key] = result + return result + + def dump(self, writer: EndianBinaryWriter, version: int): + stack: list[TypeTreeNode] = [self] + while stack: + node = stack.pop() + + writer.write_string_to_null(self.m_Type) + writer.write_string_to_null(self.m_Name) + writer.write_int(self.m_ByteSize) + if version == 2: + assert self.m_VariableCount is not None + writer.write_int(self.m_VariableCount) + if version != 3: + assert self.m_Index is not None + writer.write_int(self.m_Index) + writer.write_int(self.m_TypeFlags or 0) + writer.write_int(self.m_Version) + if version != 3: + assert self.m_MetaFlag is not None + writer.write_int(self.m_MetaFlag) + + writer.write_int(len(self.m_Children)) + + stack.extend(reversed(node.m_Children)) + + def dump_blob(self, writer: EndianBinaryWriter, version: int): + node_writer = EndianBinaryWriter(endian=writer.endian) + string_writer = EndianBinaryWriter() + + # string buffer setup + CommonStringOffsetMap = {string: offset for offset, string in get_common_strings().items()} + + string_offsets: dict[str, int] = {} + + def write_string(string: str) -> int: + offset = string_offsets.get(string) + if offset is None: + common_offset = CommonStringOffsetMap.get(string) + if common_offset: + offset = common_offset | 0x80000000 + else: + offset = string_writer.Position + string_writer.write_string_to_null(string) + string_offsets[string] = offset + return offset + + # node buffer setup + node_struct, keys = _get_blob_node_struct(writer.endian, version) + + def write_node(node: TypeTreeNode): + node_writer.write( + node_struct.pack( + *[getattr(node, key) for key in keys[:3]], + write_string(node.m_Type), + write_string(node.m_Name), + *[getattr(node, key) for key in keys[5:]], + ) + ) + + # write nodes + node_count = len([write_node(node) for node in self.traverse()]) + + # write blob + writer.write_int(node_count) + writer.write_int(string_writer.Position) + writer.write(node_writer.bytes) + writer.write(string_writer.bytes) + + def dump_structure(self, indent: str = " ") -> str: + # dump structure similar to https://github.com/AssetRipper/TypeTreeDumps/blob/main/StructsDump + sb = [ + f"{indent}{self.m_Type} {self.m_Name} // ByteSize{{{self.m_ByteSize:X}}}, Index{{{self.m_Index}}}, \ + Version{{{self.m_Version}}}, TypeFlags{{{self.m_TypeFlags}}}, MetaFlag{{{self.m_MetaFlag}}}" + ] + for child in self.m_Children: + sb.append(child.dump_structure(indent + " ")) + return "\n".join(sb) + + def to_dict(self) -> dict: + return { + key: value for key, value in ((key, getattr(self, key)) for key in TYPETREENODE_KEYS) if value is not None + } + + def to_dict_list(self) -> List[dict]: + return [ + self.to_dict(), + *(item for child in self.m_Children for item in child.to_dict_list()), + ] + + def __eq__(self, other: TypeTreeNode) -> bool: # type: ignore + return self.to_dict() == other.to_dict() and self.m_Children == other.m_Children + + +def _get_blob_node_struct(endian: str, version: int) -> tuple[Struct, list[str]]: + struct_type = f"{endian}hBBIIiii" + keys = [ + "m_Version", + "m_Level", + "m_TypeFlags", + "m_TypeStrOffset", + "m_NameStrOffset", + "m_ByteSize", + "m_Index", + "m_MetaFlag", + ] + if version >= 19: + struct_type += "Q" + keys.append("m_RefTypeHash") + + return Struct(struct_type), keys + + +CLEAN_NAME_REMOVE_RE = re.compile(r"[\?\*]") +CLEAN_NAME_REPLACE_RE = re.compile(r"[ \.:\-\[\]]") + + +def clean_name(name: str) -> str: + # keep in sync with TypeTreeHelper.cpp + if len(name) == 0: + return name + if name.startswith("(int&)"): + name = name[6:] + name = CLEAN_NAME_REMOVE_RE.sub("", name) + name = CLEAN_NAME_REPLACE_RE.sub("_", name) + if name in ["pass", "from"]: + name += "_" + if name[0].isdigit(): + name = f"x{name}" + return name + + +__all__ = ( + "TypeTreeNode", + "get_common_strings", + "clean_name", +) diff --git a/UnityPy/helpers/UnityVersion.py b/UnityPy/helpers/UnityVersion.py new file mode 100644 index 000000000..3f001f6f1 --- /dev/null +++ b/UnityPy/helpers/UnityVersion.py @@ -0,0 +1,146 @@ +from __future__ import annotations + +import re +from enum import IntEnum +from typing import Optional, Tuple, Union + +VersionPattern = re.compile( + r"^(?P\d+)\.(?P\d+)\.(?P\d+)(?P.+?)?(?P\d+)?(?P.*)$", + flags=re.DOTALL, +) + + +class UnityVersionType(IntEnum): + a = 0 # Alpha + b = 1 # Beta + c = 2 # China + f = 3 # Final + p = 4 # Patch + x = 5 # Experimental + u = 255 # Unknown + + +class UnityVersion(int): + # https://github.com/AssetRipper/VersionUtilities/blob/master/VersionUtilities/UnityVersion.cs + _type_str: Optional[str] + _postfix: Optional[str] + + @property + def major(self): + return (self >> 48) & 0xFFFF + + @property + def minor(self): + return (self >> 32) & 0xFFFF + + @property + def build(self): + return (self >> 16) & 0xFFFF + + @property + def type(self): + return UnityVersionType((self >> 8) & 0xFF) + + @property + def type_str(self): + return getattr(self, "_type_str", self.type.name) + + @property + def postfix(self): + return getattr(self, "_postfix", "") + + @property + def type_number(self): + return self & 0xFF + + @classmethod + def from_list( + cls, major: int = 0, minor: int = 0, build: int = 0, type: int = UnityVersionType.f, type_number: int = 0 + ): + return cls((major << 48) | (minor << 32) | (build << 16) | (type << 8) | type_number) + + @classmethod + def from_str(cls, version: str): + # formats: + # old: 5.0.0, .. + # new: 2018.1.1f2 .. + # the new format string can be followed by a custom postfix + match = VersionPattern.match(version) + if not match: + raise ValueError(f"Invalid version string: {version}") + major = int(match.group("major")) + minor = int(match.group("minor")) + build = int(match.group("build")) + type_str = match.group("type_str") + type_number = int(match.group("type_number") or 0) + postfix = match.group("postfix") + + if type_str is None: + return cls.from_list(major, minor, build) + + type = getattr(UnityVersionType, type_str.lower(), UnityVersionType.u) + obj = cls.from_list(major, minor, build, type, type_number) + if type is UnityVersionType.u: + obj._type_str = type_str + if postfix: + obj._postfix = postfix + + return obj + + def __str__(self) -> str: + if self.major <= 5: + return f"{self.major}.{self.minor}.{self.build}" + else: + return f"{self.major}.{self.minor}{self.type_str}{self.type_number}{self.postfix}" + + def __repr__(self) -> str: + return f"UnityVersion {self.__str__()}" + + def __getitem__(self, idx: Union[int, slice]) -> Union[int, Tuple[int, ...]]: + values = ( + self.major, + self.minor, + self.build, + self.type.value, + self.type_number, + ) + return values[idx] + + def as_tuple(self) -> Tuple[int, int, int, int, int]: + return (self.major, self.minor, self.build, self.type.value, self.type_number) + + def __eq__(self, other: Union[int, UnityVersion, Tuple[int, ...]]) -> bool: + if isinstance(other, int): + return super().__eq__(other) + elif isinstance(other, tuple): + return self.as_tuple() == other + raise NotImplementedError("Unsupported comparison") + + def __ne__(self, other: Union[int, UnityVersion, Tuple[int, ...]]) -> bool: + return not self.__eq__(other) + + def __lt__(self, other: Union[int, UnityVersion, Tuple[int, ...]]) -> bool: + if isinstance(other, int): + return super().__lt__(other) + elif isinstance(other, tuple): + return self.as_tuple() < other + raise NotImplementedError("Unsupported comparison") + + def __le__(self, other: Union[int, UnityVersion, Tuple[int, ...]]) -> bool: + return self.__lt__(other) or self.__eq__(other) + + def __gt__(self, other: Union[int, UnityVersion, Tuple[int, ...]]) -> bool: + if isinstance(other, int): + return super().__gt__(other) + elif isinstance(other, tuple): + return self.as_tuple() > other + raise NotImplementedError("Unsupported comparison") + + def __ge__(self, other: Union[int, UnityVersion, Tuple[int, ...]]) -> bool: + return self.__gt__(other) or self.__eq__(other) + + def __hash__(self) -> int: + return super().__hash__() + + +__all__ = ["UnityVersion", "UnityVersionType"] diff --git a/UnityPy/helpers/__init__.py b/UnityPy/helpers/__init__.py index 23b7fa461..6dba06cb0 100644 --- a/UnityPy/helpers/__init__.py +++ b/UnityPy/helpers/__init__.py @@ -1,2 +1,10 @@ -from . import CompressionHelper, ImportHelper -from .TypeTreeHelper import TypeTreeHelper +from . import ArchiveStorageManager, CompressionHelper, ContainerHelper, ImportHelper, TypeTreeHelper, UnityVersion + +__all__ = [ + "ArchiveStorageManager", + "CompressionHelper", + "ImportHelper", + "TypeTreeHelper", + "ContainerHelper", + "UnityVersion", +] diff --git a/UnityPy/math/Color.py b/UnityPy/math/Color.py deleted file mode 100644 index f4a4dfa4d..000000000 --- a/UnityPy/math/Color.py +++ /dev/null @@ -1,58 +0,0 @@ -from .Vector4 import Vector4 - - -class Color: - R: float - G: float - B: float - A: float - - def __init__(self, r: float, g: float, b: float, a: float): - self.R = r - self.G = g - self.B = b - self.A = a - - def GetHashCode(self): - return Vector4(self).GetHashCode() - - def __eq__(self, other): - if isinstance(other, Color): - return self.__dict__ == other.__dict__ - else: - return False - - def __add__(self, other): - return Color( - self.R + other.R, self.G + other.G, self.B + other.B, self.A + other.A - ) - - def __sub__(self, other): - return Color( - self.R - other.R, self.G - other.G, self.B - other.B, self.A - other.A - ) - - def __mul__(self, other): - if isinstance(other, Color): - return Color( - self.R * other.R, self.G * other.G, self.B * other.B, self.A * other.A - ) - else: - return Color(self.R * other, self.G * other, self.B * other, self.A * other) - - def __div__(self, other): - if isinstance(other, Color): - return Color( - self.R / other.R, self.G / other.G, self.B / other.B, self.A / other.A - ) - else: - return Color(self.R / other, self.G / other, self.B / other, self.A / other) - - def __eq__(self, other): - return self.__dict__ == other.__dict__ - - def __ne__(self, other): - return self.__dict__ != other.__dict__ - - def Vector4(self): - return Vector4(self.R, self.G, self.B, self.A) diff --git a/UnityPy/math/Half.py b/UnityPy/math/Half.py deleted file mode 100644 index 8702e1781..000000000 --- a/UnityPy/math/Half.py +++ /dev/null @@ -1,105 +0,0 @@ -import struct - -from UnityPy import math - -# import binascii - -# import numpy - -MaxValue = 65504.0 -MinValue = -65504.0 - - -def ToHalf(*args) -> float: - """ - Converts the input into a half-float. - Inputs: - unsigned integer - or - buffer (bytes, buffer) - offset - """ - # int input -> pack as UInt16 - if len(args) == 1: - data = struct.pack("H", args[0]) - val = struct.unpack("e", data)[0] - # buffer input - elif len(args) == 2: - val = struct.unpack_from("e", args[0], args[1])[0] - - if math.isnan(val): - # print('Nan') - return 0 - elif math.isinf(val): - return MaxValue - - return val - - -# #CONSTANTS -# Epsilon = ToHalf(0x0001) -# MaxValue = ToHalf(0x7bff) -# MinValue = ToHalf(0xfbff) -# NaN = ToHalf(0xfe00) -# NegativeInfinity = ToHalf(0xfc00) -# PositiveInfinity = ToHalf(0x7c00) - -# class Float16Compressor: -# def __init__(self): -# self.temp = 0 - -# def compress(self, float32): -# F16_EXPONENT_BITS = 0x1F -# F16_EXPONENT_SHIFT = 10 -# F16_EXPONENT_BIAS = 15 -# F16_MANTISSA_BITS = 0x3ff -# F16_MANTISSA_SHIFT = (23 - F16_EXPONENT_SHIFT) -# F16_MAX_EXPONENT = (F16_EXPONENT_BITS << F16_EXPONENT_SHIFT) - -# a = struct.pack('>f', float32) -# b = binascii.hexlify(a) - -# f32 = int(b, 16) -# f16 = 0 -# sign = (f32 >> 16) & 0x8000 -# exponent = ((f32 >> 23) & 0xff) - 127 -# mantissa = f32 & 0x007fffff - -# if exponent == 128: -# f16 = sign | F16_MAX_EXPONENT -# if mantissa: -# f16 |= (mantissa & F16_MANTISSA_BITS) -# elif exponent > 15: -# f16 = sign | F16_MAX_EXPONENT -# elif exponent > -15: -# exponent += F16_EXPONENT_BIAS -# mantissa >>= F16_MANTISSA_SHIFT -# f16 = sign | exponent << F16_EXPONENT_SHIFT | mantissa -# else: -# f16 = sign -# return f16 - -# def decompress(self, float16): -# s = int((float16 >> 15) & 0x00000001) # sign -# e = int((float16 >> 10) & 0x0000001f) # exponent -# f = int(float16 & 0x000003ff) # fraction - -# if e == 0: -# if f == 0: -# return int(s << 31) -# else: -# while not (f & 0x00000400): -# f = f << 1 -# e -= 1 -# e += 1 -# f &= ~0x00000400 -# # print(s,e,f) -# elif e == 31: -# if f == 0: -# return int((s << 31) | 0x7f800000) -# else: -# return int((s << 31) | 0x7f800000 | (f << 13)) - -# e = e + (127 - 15) -# f = f << 13 -# return int((s << 31) | (e << 23) | f) diff --git a/UnityPy/math/Matrix4x4.py b/UnityPy/math/Matrix4x4.py deleted file mode 100644 index de142c48c..000000000 --- a/UnityPy/math/Matrix4x4.py +++ /dev/null @@ -1,255 +0,0 @@ -class Matrix4x4: - M: list - - def __init__(self, values): - if len(values) != 16: - raise ValueError( - "There must be sixteen and only sixteen input values for Matrix." - ) - self.M = [values[i * 4 : i * 4 + 4] for i in range(0, 4)] - - -""" -using System; -using System.Runtime.InteropServices; - -namespace AssetStudio -{ - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct Matrix4x4 : IEquatable - { - public float M00; - public float M10; - public float M20; - public float M30; - - public float M01; - public float M11; - public float M21; - public float M31; - - public float M02; - public float M12; - public float M22; - public float M32; - - public float M03; - public float M13; - public float M23; - public float M33; - - public Matrix4x4(float[] values) - { - if (values == null) - throw new ArgumentNullException(nameof(values)); - if (values.Length != 16) - throw new ArgumentOutOfRangeException(nameof(values), "There must be sixteen and only sixteen input values for Matrix."); - - M00 = values[0]; - M10 = values[1]; - M20 = values[2]; - M30 = values[3]; - - M01 = values[4]; - M11 = values[5]; - M21 = values[6]; - M31 = values[7]; - - M02 = values[8]; - M12 = values[9]; - M22 = values[10]; - M32 = values[11]; - - M03 = values[12]; - M13 = values[13]; - M23 = values[14]; - M33 = values[15]; - } - - public float this[int row, int column] - { - get => this[row + column * 4]; - - set => this[row + column * 4] = value; - } - - public float this[int index] - { - get - { - switch (index) - { - case 0: return M00; - case 1: return M10; - case 2: return M20; - case 3: return M30; - case 4: return M01; - case 5: return M11; - case 6: return M21; - case 7: return M31; - case 8: return M02; - case 9: return M12; - case 10: return M22; - case 11: return M32; - case 12: return M03; - case 13: return M13; - case 14: return M23; - case 15: return M33; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Matrix4x4 index!"); - } - } - - set - { - switch (index) - { - case 0: M00 = value; break; - case 1: M10 = value; break; - case 2: M20 = value; break; - case 3: M30 = value; break; - case 4: M01 = value; break; - case 5: M11 = value; break; - case 6: M21 = value; break; - case 7: M31 = value; break; - case 8: M02 = value; break; - case 9: M12 = value; break; - case 10: M22 = value; break; - case 11: M32 = value; break; - case 12: M03 = value; break; - case 13: M13 = value; break; - case 14: M23 = value; break; - case 15: M33 = value; break; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Matrix4x4 index!"); - } - } - } - - public override int GetHashCode() - { - return GetColumn(0).GetHashCode() ^ (GetColumn(1).GetHashCode() << 2) ^ (GetColumn(2).GetHashCode() >> 2) ^ (GetColumn(3).GetHashCode() >> 1); - } - - public override bool Equals(object other) - { - if (!(other is Matrix4x4)) - return false; - return Equals((Matrix4x4)other); - } - - public bool Equals(Matrix4x4 other) - { - return GetColumn(0).Equals(other.GetColumn(0)) - && GetColumn(1).Equals(other.GetColumn(1)) - && GetColumn(2).Equals(other.GetColumn(2)) - && GetColumn(3).Equals(other.GetColumn(3)); - } - - public Vector4 GetColumn(int index) - { - switch (index) - { - case 0: return new Vector4(M00, M10, M20, M30); - case 1: return new Vector4(M01, M11, M21, M31); - case 2: return new Vector4(M02, M12, M22, M32); - case 3: return new Vector4(M03, M13, M23, M33); - default: throw new IndexOutOfRangeException("Invalid column index!"); - } - } - - public Vector4 GetRow(int index) - { - switch (index) - { - case 0: return new Vector4(M00, M01, M02, M03); - case 1: return new Vector4(M10, M11, M12, M13); - case 2: return new Vector4(M20, M21, M22, M23); - case 3: return new Vector4(M30, M31, M32, M33); - default: throw new IndexOutOfRangeException("Invalid row index!"); - } - } - - public static Matrix4x4 operator *(Matrix4x4 lhs, Matrix4x4 rhs) - { - Matrix4x4 res; - res.M00 = lhs.M00 * rhs.M00 + lhs.M01 * rhs.M10 + lhs.M02 * rhs.M20 + lhs.M03 * rhs.M30; - res.M01 = lhs.M00 * rhs.M01 + lhs.M01 * rhs.M11 + lhs.M02 * rhs.M21 + lhs.M03 * rhs.M31; - res.M02 = lhs.M00 * rhs.M02 + lhs.M01 * rhs.M12 + lhs.M02 * rhs.M22 + lhs.M03 * rhs.M32; - res.M03 = lhs.M00 * rhs.M03 + lhs.M01 * rhs.M13 + lhs.M02 * rhs.M23 + lhs.M03 * rhs.M33; - - res.M10 = lhs.M10 * rhs.M00 + lhs.M11 * rhs.M10 + lhs.M12 * rhs.M20 + lhs.M13 * rhs.M30; - res.M11 = lhs.M10 * rhs.M01 + lhs.M11 * rhs.M11 + lhs.M12 * rhs.M21 + lhs.M13 * rhs.M31; - res.M12 = lhs.M10 * rhs.M02 + lhs.M11 * rhs.M12 + lhs.M12 * rhs.M22 + lhs.M13 * rhs.M32; - res.M13 = lhs.M10 * rhs.M03 + lhs.M11 * rhs.M13 + lhs.M12 * rhs.M23 + lhs.M13 * rhs.M33; - - res.M20 = lhs.M20 * rhs.M00 + lhs.M21 * rhs.M10 + lhs.M22 * rhs.M20 + lhs.M23 * rhs.M30; - res.M21 = lhs.M20 * rhs.M01 + lhs.M21 * rhs.M11 + lhs.M22 * rhs.M21 + lhs.M23 * rhs.M31; - res.M22 = lhs.M20 * rhs.M02 + lhs.M21 * rhs.M12 + lhs.M22 * rhs.M22 + lhs.M23 * rhs.M32; - res.M23 = lhs.M20 * rhs.M03 + lhs.M21 * rhs.M13 + lhs.M22 * rhs.M23 + lhs.M23 * rhs.M33; - - res.M30 = lhs.M30 * rhs.M00 + lhs.M31 * rhs.M10 + lhs.M32 * rhs.M20 + lhs.M33 * rhs.M30; - res.M31 = lhs.M30 * rhs.M01 + lhs.M31 * rhs.M11 + lhs.M32 * rhs.M21 + lhs.M33 * rhs.M31; - res.M32 = lhs.M30 * rhs.M02 + lhs.M31 * rhs.M12 + lhs.M32 * rhs.M22 + lhs.M33 * rhs.M32; - res.M33 = lhs.M30 * rhs.M03 + lhs.M31 * rhs.M13 + lhs.M32 * rhs.M23 + lhs.M33 * rhs.M33; - - return res; - } - - public static bool operator ==(Matrix4x4 lhs, Matrix4x4 rhs) - { - return lhs.GetColumn(0) == rhs.GetColumn(0) - && lhs.GetColumn(1) == rhs.GetColumn(1) - && lhs.GetColumn(2) == rhs.GetColumn(2) - && lhs.GetColumn(3) == rhs.GetColumn(3); - } - - public static bool operator !=(Matrix4x4 lhs, Matrix4x4 rhs) - { - return !(lhs == rhs); - } - - public static Matrix4x4 Scale(Vector3 vector) - { - Matrix4x4 m; - m.M00 = vector.X; m.M01 = 0F; m.M02 = 0F; m.M03 = 0F; - m.M10 = 0F; m.M11 = vector.Y; m.M12 = 0F; m.M13 = 0F; - m.M20 = 0F; m.M21 = 0F; m.M22 = vector.Z; m.M23 = 0F; - m.M30 = 0F; m.M31 = 0F; m.M32 = 0F; m.M33 = 1F; - return m; - } - - public static Matrix4x4 Translate(Vector3 vector) - { - Matrix4x4 m; - m.M00 = 1F; m.M01 = 0F; m.M02 = 0F; m.M03 = vector.X; - m.M10 = 0F; m.M11 = 1F; m.M12 = 0F; m.M13 = vector.Y; - m.M20 = 0F; m.M21 = 0F; m.M22 = 1F; m.M23 = vector.Z; - m.M30 = 0F; m.M31 = 0F; m.M32 = 0F; m.M33 = 1F; - return m; - } - - public static Matrix4x4 Rotate(Quaternion q) - { - float x = q.X * 2.0F; - float y = q.Y * 2.0F; - float z = q.Z * 2.0F; - float xx = q.X * x; - float yy = q.Y * y; - float zz = q.Z * z; - float xy = q.X * y; - float xz = q.X * z; - float yz = q.Y * z; - float wx = q.W * x; - float wy = q.W * y; - float wz = q.W * z; - - Matrix4x4 m; - m.M00 = 1.0f - (yy + zz); m.M10 = xy + wz; m.M20 = xz - wy; m.M30 = 0.0F; - m.M01 = xy - wz; m.M11 = 1.0f - (xx + zz); m.M21 = yz + wx; m.M31 = 0.0F; - m.M02 = xz + wy; m.M12 = yz - wx; m.M22 = 1.0f - (xx + yy); m.M32 = 0.0F; - m.M03 = 0.0F; m.M13 = 0.0F; m.M23 = 0.0F; m.M33 = 1.0F; - return m; - } - } -} - -""" diff --git a/UnityPy/math/Quaternion.py b/UnityPy/math/Quaternion.py deleted file mode 100644 index ceed30df6..000000000 --- a/UnityPy/math/Quaternion.py +++ /dev/null @@ -1,104 +0,0 @@ -class Quaternion: - X: float - Y: float - Z: float - W: float - - def __init__(self, x: float, y: float, z: float, w: float): - self.X = x - self.Y = y - self.Z = z - self.W = w - - -""" -using System; -using System.Runtime.InteropServices; - -namespace AssetStudio -{ - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct Quaternion : IEquatable - { - public float X; - public float Y; - public float Z; - public float W; - - public Quaternion(float x, float y, float z, float w) - { - X = x; - Y = y; - Z = z; - W = w; - } - - public float this[int index] - { - get - { - switch (index) - { - case 0: return X; - case 1: return Y; - case 2: return Z; - case 3: return W; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Quaternion index!"); - } - } - - set - { - switch (index) - { - case 0: X = value; break; - case 1: Y = value; break; - case 2: Z = value; break; - case 3: W = value; break; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Quaternion index!"); - } - } - } - - public override int GetHashCode() - { - return X.GetHashCode() ^ (Y.GetHashCode() << 2) ^ (Z.GetHashCode() >> 2) ^ (W.GetHashCode() >> 1); - } - - public override bool Equals(object other) - { - if (!(other is Quaternion)) - return false; - return Equals((Quaternion)other); - } - - public bool Equals(Quaternion other) - { - return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z) && W.Equals(other.W); - } - - public static float Dot(Quaternion a, Quaternion b) - { - return a.X * b.X + a.Y * b.Y + a.Z * b.Z + a.W * b.W; - } - - private static bool IsEqualUsingDot(float dot) - { - return dot > 1.0f - kEpsilon; - } - - public static bool operator ==(Quaternion lhs, Quaternion rhs) - { - return IsEqualUsingDot(Dot(lhs, rhs)); - } - - public static bool operator !=(Quaternion lhs, Quaternion rhs) - { - return !(lhs == rhs); - } - - private const float kEpsilon = 0.000001F; - } -} - -""" diff --git a/UnityPy/math/Rectangle.py b/UnityPy/math/Rectangle.py deleted file mode 100644 index 3bab0dc87..000000000 --- a/UnityPy/math/Rectangle.py +++ /dev/null @@ -1,42 +0,0 @@ -class Rectangle: - height: int - width: int - x: int - y: int - - def __init__(self, *args, **kwargs): - if args: - # Rectangle(Point, Size) - if len(args) == 4: - self.x, self.y, self.width, self.height = args - elif kwargs: - self.__dict__.update(kwargs) - - def round(rect): - return Rectangle( - round(rect.x), round(rect.y), round(rect.width), round(rect.height) - ) - - @property - def left(self): - return self.x - - @property - def top(self): - return self.y - - @property - def right(self): - return self.x + self.width - - @property - def bottom(self): - return self.y + self.height - - @property - def size(self): - return (self.width, self.height) - - @property - def location(self): - return (self.x, self.y) diff --git a/UnityPy/math/Vector2.py b/UnityPy/math/Vector2.py deleted file mode 100644 index e20601a68..000000000 --- a/UnityPy/math/Vector2.py +++ /dev/null @@ -1,160 +0,0 @@ -class Vector2: - X: float - Y: float - - def __init__(self, x: float, y: float): - self.X = x - self.Y = y - - -""" -using System; -using System.Runtime.InteropServices; - -namespace AssetStudio -{ - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct Vector2 : IEquatable - { - public float X; - public float Y; - - public Vector2(float x, float y) - { - X = x; - Y = y; - } - - public float this[int index] - { - get - { - switch (index) - { - case 0: return X; - case 1: return Y; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector2 index!"); - } - } - - set - { - switch (index) - { - case 0: X = value; break; - case 1: Y = value; break; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector2 index!"); - } - } - } - - public override int GetHashCode() - { - return X.GetHashCode() ^ (Y.GetHashCode() << 2); - } - - public override bool Equals(object other) - { - if (!(other is Vector2)) - return false; - return Equals((Vector2)other); - } - - public bool Equals(Vector2 other) - { - return X.Equals(other.X) && Y.Equals(other.Y); - } - - public void Normalize() - { - var length = Length(); - if (length > kEpsilon) - { - var invNorm = 1.0f / length; - X *= invNorm; - Y *= invNorm; - } - else - { - X = 0; - Y = 0; - } - } - - public float Length() - { - return (float)Math.Sqrt(LengthSquared()); - } - - public float LengthSquared() - { - return X * X + Y * Y; - } - - public static Vector2 Zero => new Vector2(); - - public static Vector2 operator +(Vector2 a, Vector2 b) - { - return new Vector2(a.X + b.X, a.Y + b.Y); - } - - public static Vector2 operator -(Vector2 a, Vector2 b) - { - return new Vector2(a.X - b.X, a.Y - b.Y); - } - - public static Vector2 operator *(Vector2 a, Vector2 b) - { - return new Vector2(a.X * b.X, a.Y * b.Y); - } - - public static Vector2 operator /(Vector2 a, Vector2 b) - { - return new Vector2(a.X / b.X, a.Y / b.Y); - } - - public static Vector2 operator -(Vector2 a) - { - return new Vector2(-a.X, -a.Y); - } - - public static Vector2 operator *(Vector2 a, float d) - { - return new Vector2(a.X * d, a.Y * d); - } - - public static Vector2 operator *(float d, Vector2 a) - { - return new Vector2(a.X * d, a.Y * d); - } - - public static Vector2 operator /(Vector2 a, float d) - { - return new Vector2(a.X / d, a.Y / d); - } - - public static bool operator ==(Vector2 lhs, Vector2 rhs) - { - return (lhs - rhs).LengthSquared() < kEpsilon * kEpsilon; - } - - public static bool operator !=(Vector2 lhs, Vector2 rhs) - { - return !(lhs == rhs); - } - - public static implicit operator Vector3(Vector2 v) - { - return new Vector3(v.X, v.Y, 0); - } - - public static implicit operator Vector4(Vector2 v) - { - return new Vector4(v.X, v.Y, 0.0F, 0.0F); - } - - private const float kEpsilon = 0.00001F; - } -} - -""" diff --git a/UnityPy/math/Vector3.py b/UnityPy/math/Vector3.py deleted file mode 100644 index b73a4dd67..000000000 --- a/UnityPy/math/Vector3.py +++ /dev/null @@ -1,174 +0,0 @@ -from math import sqrt - - -class Vector3: - X: float - Y: float - Z: float - - def __init__(self, *args): - if len(args) == 3 or len(args) == 1 and isinstance(args[0], (tuple, list)): - self.X, self.Y, self.Z = args - elif len(args) == 1: - # dirty patch for Vector4 - self.__dict__ = args[0].__dict__ - - def length(self) -> float: - return sqrt(self.X ** 2 + self.Y ** 2 + self.Z ** 2) - - def normalize(self): - length = self.length() - - invNorm = 1.0 / length - self.X *= invNorm - self.Y *= invNorm - self.Z *= invNorm - - -""" -using System; -using System.Runtime.InteropServices; - -namespace AssetStudio -{ - [StructLayout(LayoutKind.Sequential, Pack = 4)] - public struct Vector3 : IEquatable - { - public float X; - public float Y; - public float Z; - - public Vector3(float x, float y, float z) - { - X = x; - Y = y; - Z = z; - } - - public float this[int index] - { - get - { - switch (index) - { - case 0: return X; - case 1: return Y; - case 2: return Z; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector3 index!"); - } - } - - set - { - switch (index) - { - case 0: X = value; break; - case 1: Y = value; break; - case 2: Z = value; break; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector3 index!"); - } - } - } - - public override int GetHashCode() - { - return X.GetHashCode() ^ (Y.GetHashCode() << 2) ^ (Z.GetHashCode() >> 2); - } - - public override bool Equals(object other) - { - if (!(other is Vector3)) - return false; - return Equals((Vector3)other); - } - - public bool Equals(Vector3 other) - { - return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z); - } - - public void Normalize() - { - var length = Length(); - if (length > kEpsilon) - { - var invNorm = 1.0f / length; - X *= invNorm; - Y *= invNorm; - Z *= invNorm; - } - else - { - X = 0; - Y = 0; - Z = 0; - } - } - - public float Length() - { - return (float)Math.Sqrt(LengthSquared()); - } - - public float LengthSquared() - { - return X * X + Y * Y + Z * Z; - } - - public static Vector3 Zero => new Vector3(); - - public static Vector3 operator +(Vector3 a, Vector3 b) - { - return new Vector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z); - } - - public static Vector3 operator -(Vector3 a, Vector3 b) - { - return new Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z); - } - - public static Vector3 operator -(Vector3 a) - { - return new Vector3(-a.X, -a.Y, -a.Z); - } - - public static Vector3 operator *(Vector3 a, float d) - { - return new Vector3(a.X * d, a.Y * d, a.Z * d); - } - - public static Vector3 operator *(float d, Vector3 a) - { - return new Vector3(a.X * d, a.Y * d, a.Z * d); - } - - public static Vector3 operator /(Vector3 a, float d) - { - return new Vector3(a.X / d, a.Y / d, a.Z / d); - } - - public static bool operator ==(Vector3 lhs, Vector3 rhs) - { - return (lhs - rhs).LengthSquared() < kEpsilon * kEpsilon; - } - - public static bool operator !=(Vector3 lhs, Vector3 rhs) - { - return !(lhs == rhs); - } - - public static implicit operator Vector2(Vector3 v) - { - return new Vector2(v.X, v.Y); - } - - public static implicit operator Vector4(Vector3 v) - { - return new Vector4(v.X, v.Y, v.Z, 0.0F); - } - - private const float kEpsilon = 0.00001F; - } -} - -""" diff --git a/UnityPy/math/Vector4.py b/UnityPy/math/Vector4.py deleted file mode 100644 index 5b90096e7..000000000 --- a/UnityPy/math/Vector4.py +++ /dev/null @@ -1,155 +0,0 @@ -class Vector4: - X: float - Y: float - Z: float - W: float - - def __init__(self, *args): - if len(args) == 4: # float x, float y, float z, float w - self.X = args[0] - self.Y = args[1] - self.Z = args[2] - self.W = args[3] - elif len(args) == 2: # Vector3 value, float w - self.X = args[0].X - self.Y = args[0].Y - self.Z = args[0].Z - self.W = args[1] - - """ - public float this[int index] - { - get - { - switch (index) - { - case 0: return X; - case 1: return Y; - case 2: return Z; - case 3: return W; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector4 index!"); - } - } - - set - { - switch (index) - { - case 0: X = value; break; - case 1: Y = value; break; - case 2: Z = value; break; - case 3: W = value; break; - default: throw new ArgumentOutOfRangeException(nameof(index), "Invalid Vector4 index!"); - } - } - } - - - public override int GetHashCode() - { - return X.GetHashCode() ^ (Y.GetHashCode() << 2) ^ (Z.GetHashCode() >> 2) ^ (W.GetHashCode() >> 1); - } - - public override bool Equals(object other) - { - if (!(other is Vector4)) - return false; - return Equals((Vector4)other); - } - - public bool Equals(Vector4 other) - { - return X.Equals(other.X) && Y.Equals(other.Y) && Z.Equals(other.Z) && W.Equals(other.W); - } - - public void Normalize() - { - var length = Length(); - if (length > kEpsilon) - { - var invNorm = 1.0f / length; - X *= invNorm; - Y *= invNorm; - Z *= invNorm; - W *= invNorm; - } - else - { - X = 0; - Y = 0; - Z = 0; - W = 0; - } - } - - public float Length() - { - return (float)Math.Sqrt(LengthSquared()); - } - - public float LengthSquared() - { - return X * X + Y * Y + Z * Z + W * W; - } - - public static Vector4 Zero => new Vector4(); - - public static Vector4 operator +(Vector4 a, Vector4 b) - { - return new Vector4(a.X + b.X, a.Y + b.Y, a.Z + b.Z, a.W + b.W); - } - - public static Vector4 operator -(Vector4 a, Vector4 b) - { - return new Vector4(a.X - b.X, a.Y - b.Y, a.Z - b.Z, a.W - b.W); - } - - public static Vector4 operator -(Vector4 a) - { - return new Vector4(-a.X, -a.Y, -a.Z, -a.W); - } - - public static Vector4 operator *(Vector4 a, float d) - { - return new Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d); - } - - public static Vector4 operator *(float d, Vector4 a) - { - return new Vector4(a.X * d, a.Y * d, a.Z * d, a.W * d); - } - - public static Vector4 operator /(Vector4 a, float d) - { - return new Vector4(a.X / d, a.Y / d, a.Z / d, a.W / d); - } - - public static bool operator ==(Vector4 lhs, Vector4 rhs) - { - return (lhs - rhs).LengthSquared() < kEpsilon * kEpsilon; - } - - public static bool operator !=(Vector4 lhs, Vector4 rhs) - { - return !(lhs == rhs); - } - - public static implicit operator Vector2(Vector4 v) - { - return new Vector2(v.X, v.Y); - } - - public static implicit operator Vector3(Vector4 v) - { - return new Vector3(v.X, v.Y, v.Z); - } - - public static implicit operator Color(Vector4 v) - { - return new Color(v.X, v.Y, v.Z, v.W); - } - - private const float kEpsilon = 0.00001F; - } -} -""" diff --git a/UnityPy/math/__init__.py b/UnityPy/math/__init__.py deleted file mode 100644 index 6f6e76938..000000000 --- a/UnityPy/math/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -from .Color import Color -from .Vector2 import Vector2 -from .Vector3 import Vector3 -from .Vector4 import Vector4 -from .Matrix4x4 import Matrix4x4 -from .Quaternion import Quaternion -from .Rectangle import Rectangle diff --git a/UnityPy/py.typed b/UnityPy/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/UnityPy/resources/__init__.py b/UnityPy/resources/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/UnityPy/resources/lzma.tpk b/UnityPy/resources/lzma.tpk new file mode 100644 index 000000000..26e074496 --- /dev/null +++ b/UnityPy/resources/lzma.tpk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0b2277765f0f7a6253df04426abd83d2bf37f8d1ad30542cddd2d81ae484741b +size 207798 diff --git a/UnityPy/streams/EndianBinaryReader.py b/UnityPy/streams/EndianBinaryReader.py index 0048157ca..968b32d89 100644 --- a/UnityPy/streams/EndianBinaryReader.py +++ b/UnityPy/streams/EndianBinaryReader.py @@ -1,71 +1,139 @@ -import io -import struct - -from ..math import Color, Matrix4x4, Quaternion, Vector2, Vector3, Vector4, Rectangle +from __future__ import annotations + +import builtins +import re +import sys +from io import BufferedReader, IOBase +from struct import Struct, unpack +from types import MethodType +from typing import Any, Callable, ClassVar, Dict, List, Literal, Optional, Tuple, Union + +reNot0 = re.compile(b"(.*?)\x00", re.S) + +SYS_ENDIAN = "<" if sys.byteorder == "little" else ">" +Endianess = Literal["<", ">"] + +# generate unpack and unpack_from functions +TYPE_PARAM_SIZE_LIST = [ + ("short", "h"), + ("u_short", "H"), + ("int", "i"), + ("u_int", "I"), + ("long", "q"), + ("u_long", "Q"), + ("half", "e"), + ("float", "f"), + ("double", "d"), +] + +MEMORY_FUNCTIONS: Dict[Endianess, Dict[str, Callable[["EndianBinaryReader_Memoryview"], Any]]] = {"<": {}, ">": {}} +STREAM_FUNCTIONS: Dict[Endianess, Dict[str, Callable[["EndianBinaryReader_Streamable"], Any]]] = {"<": {}, ">": {}} class EndianBinaryReader: - endian: str Length: int Position: int - - def __new__(cls, item, endian=">"): + BaseOffset: int + _endian: Endianess + _function_map: ClassVar[Dict[Endianess, Dict[str, Callable]]] + + def __new__( + cls, + item: Union[bytes, bytearray, memoryview, IOBase, str], + endian: Endianess = ">", + offset: int = 0, + ): if isinstance(item, (bytes, bytearray, memoryview)): - obj = super(EndianBinaryReader, cls).__new__(EndianBinaryReader_Memoryview) + obj = super(EndianBinaryReader, cls).__new__(EndianBinaryReader_Memoryview) # type: ignore + elif isinstance(item, IOBase): + obj = super(EndianBinaryReader, cls).__new__(EndianBinaryReader_Streamable) # type: ignore + elif isinstance(item, str): + obj = super(EndianBinaryReader, cls).__new__(EndianBinaryReader_Streamable_LocalFile) # type: ignore + elif isinstance(item, EndianBinaryReader): + item = item.stream if isinstance(item, EndianBinaryReader_Streamable) else item.view + return EndianBinaryReader(item, endian, offset) + elif hasattr(item, "read"): + if hasattr(item, "seek") and hasattr(item, "tell"): + obj = super(EndianBinaryReader, cls).__new__(EndianBinaryReader_Streamable) + else: + item = item.read() + obj = super(EndianBinaryReader, cls).__new__(EndianBinaryReader_Memoryview) else: - obj = obj = super(EndianBinaryReader, cls).__new__( - EndianBinaryReader_Streamable - ) - obj.__init__(item, endian) + raise TypeError("Unsupported type for EndianBinaryReader: %s" % type(item)) return obj + def __init__(self, item, endian: Endianess = ">", offset: int = 0): + self._endian = "" # type: ignore + self.endian = endian + self.BaseOffset = offset + self.Position = 0 + + @property + def endian(self) -> Endianess: + return self._endian + + @endian.setter + def endian(self, value: Endianess): + if value not in ("<", ">"): + raise ValueError("Invalid endian") + if value != self._endian: + for func_name, func in self._function_map[value].items(): + setattr(self, func_name, MethodType(func, self)) + self._endian = value + + @property + def bytes(self) -> builtins.bytes: + # implemented by Streamable and Memoryview versions + return b"" + + def read(self, size: Optional[int] = -1, /) -> builtins.bytes: + # implemented by Streamable and Memoryview versions + return b"" + def read_byte(self) -> int: - return struct.unpack(self.endian + "b", self.read(1))[0] + return unpack(self.endian + "b", self.read(1))[0] def read_u_byte(self) -> int: - return struct.unpack(self.endian + "B", self.read(1))[0] + return unpack(self.endian + "B", self.read(1))[0] - def read_bytes(self, num) -> bytes: + def read_bytes(self, num: int) -> builtins.bytes: return self.read(num) def read_short(self) -> int: - return struct.unpack(self.endian + "h", self.read(2))[0] + return unpack(self.endian + "h", self.read(2))[0] def read_int(self) -> int: - return struct.unpack(self.endian + "i", self.read(4))[0] + return unpack(self.endian + "i", self.read(4))[0] def read_long(self) -> int: - return struct.unpack(self.endian + "q", self.read(8))[0] + return unpack(self.endian + "q", self.read(8))[0] def read_u_short(self) -> int: - return struct.unpack(self.endian + "H", self.read(2))[0] + return unpack(self.endian + "H", self.read(2))[0] def read_u_int(self) -> int: - return struct.unpack(self.endian + "I", self.read(4))[0] + return unpack(self.endian + "I", self.read(4))[0] def read_u_long(self) -> int: - return struct.unpack(self.endian + "Q", self.read(8))[0] + return unpack(self.endian + "Q", self.read(8))[0] def read_float(self) -> float: - return struct.unpack(self.endian + "f", self.read(4))[0] + return unpack(self.endian + "f", self.read(4))[0] def read_double(self) -> float: - return struct.unpack(self.endian + "d", self.read(8))[0] + return unpack(self.endian + "d", self.read(8))[0] def read_boolean(self) -> bool: - return bool(struct.unpack(self.endian + "?", self.read(1))[0]) + return bool(unpack(self.endian + "?", self.read(1))[0]) - def read_string(self, size=None, encoding="utf-8") -> str: + def read_string(self, size: Optional[int] = None) -> str: if size is None: - ret = self.read_string_to_null() + return self.read_string_to_null() else: - ret = struct.unpack(f"{self.endian}{size}is", self.read(size))[0] - try: - return ret.decode(encoding) - except UnicodeDecodeError: - return ret + raw = self.read_bytes(size) + return raw.decode("utf8", "surrogateescape") - def read_string_to_null(self, max_length=32767) -> str: + def read_string_to_null(self, max_length: int = 32767) -> str: ret = [] c = b"" while c != b"\0" and len(ret) < max_length and self.Position != self.Length: @@ -73,147 +141,209 @@ def read_string_to_null(self, max_length=32767) -> str: c = self.read(1) if not c: raise ValueError("Unterminated string: %r" % ret) - return b"".join(ret).decode("utf8", "replace") + return b"".join(ret).decode("utf8", "surrogateescape") - def read_aligned_string(self): + def read_aligned_string(self) -> str: length = self.read_int() if 0 < length <= self.Length - self.Position: - string_data = self.read_bytes(length) - result = string_data.decode("utf8", "backslashreplace") - self.align_stream(4) + string_data = bytes(self.read_bytes(length)) + result = string_data.decode("utf8", "surrogateescape") + self.align_stream() return result return "" def align_stream(self, alignment=4): - pos = self.Position - mod = pos % alignment - if mod != 0: - self.Position += alignment - mod + self.Position += (alignment - self.Position % alignment) % alignment - def read_quaternion(self): - return Quaternion( - self.read_float(), self.read_float(), self.read_float(), self.read_float() - ) + def read_byte_array(self) -> builtins.bytes: + return self.read(self.read_int()) - def read_vector2(self): - return Vector2(self.read_float(), self.read_float()) + def read_array(self, command: Callable, length: int) -> list: + return [command() for _ in range(length)] - def read_vector3(self): - return Vector3(self.read_float(), self.read_float(), self.read_float()) + def read_array_struct(self, param: str, length: Optional[int] = None) -> tuple: + if length is None: + length = self.read_int() + struct = Struct(f"{self.endian}{length}{param}") + return struct.unpack(self.read(struct.size)) - def read_vector4(self): - return Vector4( - self.read_float(), self.read_float(), self.read_float(), self.read_float() - ) + def read_boolean_array(self, length: Optional[int] = None) -> Tuple[bool, ...]: + return self.read_array_struct("?", length) - def read_rectangle_f(self): - return Rectangle( - self.read_float(), self.read_float(), self.read_float(), self.read_float() - ) + def read_u_byte_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("B", length) - def read_color4(self): - return Color( - self.read_float(), self.read_float(), self.read_float(), self.read_float() - ) + def read_u_short_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("h", length) - def read_matrix(self): - return Matrix4x4(self.read_float_array(16)) + def read_short_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("H", length) - def read_array(self, command, length: int): - return [command() for i in range(length)] + def read_int_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("i", length) - def read_boolean_array(self): - return self.read_array(self.read_boolean, self.read_int()) + def read_u_int_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("I", length) - def read_u_short_array(self): - return self.read_array(self.read_u_short, self.read_int()) + def read_long_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("q", length) - def read_int_array(self, length=0): - return self.read_array(self.read_int, length if length else self.read_int()) + def read_u_long_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("Q", length) - def read_u_int_array(self, length=0): - return self.read_array(self.read_u_int, length if length else self.read_int()) + def read_float_array(self, length: Optional[int] = None) -> Tuple[float, ...]: + return self.read_array_struct("f", length) - def read_u_int_array_array(self, length=0): - return self.read_array( - self.read_u_int_array, length if length else self.read_int() - ) + def read_double_array(self, length: Optional[int] = None) -> Tuple[float, ...]: + return self.read_array_struct("d", length) - def read_float_array(self, length=0): - return self.read_array(self.read_float, length if length else self.read_int()) - - def read_string_array(self): + def read_string_array(self) -> List[str]: return self.read_array(self.read_aligned_string, self.read_int()) - def read_vector2_array(self): - return self.read_array(self.read_vector2, self.read_int()) - - def read_vector4_array(self): - return self.read_array(self.read_vector4, self.read_int()) + def real_offset(self) -> int: + """Returns offset in the underlying file. + (Not working with unpacked streams.) + """ + return self.BaseOffset + self.Position + + def read_the_rest(self, obj_start: int, obj_size: int) -> builtins.bytes: + """Returns the rest of the current reader bytes.""" + return self.read_bytes(obj_size - (self.Position - obj_start)) + + def seek(self, offset: int, whence: int = 0) -> int: + if whence == 0: + new_pos = offset + elif whence == 1: + new_pos = self.Position + offset + elif whence == 2: + new_pos = self.Length + offset + else: + raise ValueError("Invalid whence value") + if new_pos < 0: + raise ValueError("New position is before the start of the stream") + self.Position = new_pos + return self.Position - def read_matrix_array(self): - return self.read_array(self.read_matrix, self.read_int()) + def tell(self) -> int: + return self.Position class EndianBinaryReader_Memoryview(EndianBinaryReader): + __slots__ = ("view", "_endian", "BaseOffset", "Position", "Length") + _endian: Endianess view: memoryview + _function_map = MEMORY_FUNCTIONS - def __init__(self, view, endian=">"): + def __init__(self, view, endian: Endianess = ">", offset: int = 0): + super().__init__(view, endian=endian, offset=offset) self.view = memoryview(view) - self.endian = endian self.Length = len(view) - self.Position = 0 @property def bytes(self): - return self.view + return self.view.tobytes() - def dispose(self): + def dispose(self) -> None: self.view.release() - def read(self, length: int): - ret = self.view[self.Position : self.Position + length] - self.Position += length - return ret - - def read_aligned_string(self): + def read(self, size: Optional[int] = -1, /): + if not size: + return b"" + if size == -1: + size = self.Length - self.Position + ret = self.view[self.Position : self.Position + size] + self.Position += size + return ret.tobytes() + + def read_array_struct(self, param: str, length: Optional[int] = None) -> tuple: + if length is None: + length = self.read_int() + struct = Struct(f"{self.endian}{length}{param}") + value = struct.unpack_from(self.view, self.Position) + self.Position += struct.size + return value + + def read_aligned_string(self) -> str: length = self.read_int() if 0 < length <= self.Length - self.Position: string_data = self.read_bytes(length) - result = string_data.tobytes().decode("utf8", "backslashreplace") - self.align_stream(4) + result = bytes(string_data).decode("utf8", "surrogateescape") + self.align_stream() return result return "" + def read_string_to_null(self, max_length: int = 32767) -> str: + match = reNot0.search(self.view, self.Position, self.Position + max_length) + if not match: + if self.Position + max_length >= self.Length: + raise Exception("String not terminated") + else: + return bytes(self.read_bytes(max_length)).decode("utf8", "surrogateescape") + ret = match[1].decode("utf8", "surrogateescape") + self.Position = match.end() + return ret + class EndianBinaryReader_Streamable(EndianBinaryReader): - stream: io.BufferedReader + __slots__ = ("stream", "_endian", "BaseOffset") + stream: BufferedReader + _function_map = STREAM_FUNCTIONS - def __init__(self, stream, endian=">"): + def __init__(self, stream: BufferedReader, endian: Endianess = ">", offset: int = 0): self.stream = stream - self.endian = endian - self.Length = self.stream.seek(0, 2) - self.Position = 0 + super().__init__(stream, endian=endian, offset=offset) + self.read = self.stream.read - def get_position(self): - return self.stream.tell() + @property + def Position(self) -> int: + return self.stream.tell() - self.BaseOffset - def set_position(self, value): - self.stream.seek(value) + @Position.setter + def Position(self, value: int): + if value < 0: + raise ValueError("Position cannot be negative") + self.stream.seek(value + self.BaseOffset) - Position = property(get_position, set_position) + @property + def Length(self): # type: ignore + pos = self.Position + length = self.stream.seek(0, 2) - self.BaseOffset + self.Position = pos + return length @property def bytes(self): last_pos = self.Position self.Position = 0 - ret = self.read() - self.Position = last_pos + ret = self.read(self.Length) + self.Position = last_pos # type: ignore return ret def dispose(self): self.stream.close() pass - def read(self, *args): - return self.stream.read(*args) + +class EndianBinaryReader_Streamable_LocalFile(EndianBinaryReader_Streamable): + def __init__(self, path: str, endian: Endianess = ">", offset: int = 0): + super().__init__(open(path, "rb"), endian=endian, offset=offset) + + def __del__(self): + self.stream.close() + + +for endian_s in ("<", ">"): + for reader_type_name, struct_type_char in TYPE_PARAM_SIZE_LIST: + func_name = f"read_{reader_type_name}" + struct = Struct(f"{endian_s}{struct_type_char}") + + def memory_read_func(self: EndianBinaryReader_Memoryview, /, struct=struct): + value = struct.unpack_from(self.view, self.Position)[0] + self.Position += struct.size + return value + + def stream_read_func(self: EndianBinaryReader_Streamable, /, struct=struct): + return struct.unpack(self.stream.read(struct.size))[0] + + MEMORY_FUNCTIONS[endian_s][func_name] = memory_read_func + STREAM_FUNCTIONS[endian_s][func_name] = stream_read_func diff --git a/UnityPy/streams/EndianBinaryWriter.py b/UnityPy/streams/EndianBinaryWriter.py index b8b6048ca..1473c871b 100644 --- a/UnityPy/streams/EndianBinaryWriter.py +++ b/UnityPy/streams/EndianBinaryWriter.py @@ -1,19 +1,22 @@ -import io -import struct +import builtins +from io import BytesIO, IOBase +from struct import pack +from typing import Callable, Sequence, TypeVar, Union -from ..math import Color, Matrix4x4, Quaternion, Vector2, Vector3, Vector4, Rectangle +T = TypeVar("T") class EndianBinaryWriter: endian: str - Length: int Position: int - stream: io.BufferedReader + stream: IOBase - def __init__(self, input_=b"", endian=">"): + def __init__(self, input_: Union[bytes, bytearray, IOBase] = b"", endian: str = ">"): if isinstance(input_, (bytes, bytearray)): - self.stream = io.BytesIO(input_) + self.stream = BytesIO(input_) self.stream.seek(0, 2) + elif isinstance(input_, IOBase): + self.stream = input_ else: raise ValueError("Invalid input type - %s." % type(input_)) self.endian = endian @@ -28,13 +31,12 @@ def bytes(self): def Length(self) -> int: pos = self.stream.tell() self.stream.seek(0, 2) - l = self.stream.tell() + length = self.stream.tell() self.stream.seek(pos) - return l + return length def dispose(self): self.stream.close() - pass def write(self, *args): if self.Position != self.stream.tell(): @@ -44,134 +46,85 @@ def write(self, *args): return ret def write_byte(self, value: int): - self.write(struct.pack(self.endian + "b", value)) + self.write(pack(self.endian + "b", value)) def write_u_byte(self, value: int): - self.write(struct.pack(self.endian + "B", value)) + self.write(pack(self.endian + "B", value)) - def write_bytes(self, value: bytes): + def write_bytes(self, value: builtins.bytes): return self.write(value) def write_short(self, value: int): - self.write(struct.pack(self.endian + "h", value)) + self.write(pack(self.endian + "h", value)) def write_int(self, value: int): - self.write(struct.pack(self.endian + "i", value)) + self.write(pack(self.endian + "i", value)) def write_long(self, value: int): - self.write(struct.pack(self.endian + "q", value)) + self.write(pack(self.endian + "q", value)) def write_u_short(self, value: int): - self.write(struct.pack(self.endian + "H", value)) + self.write(pack(self.endian + "H", value)) def write_u_int(self, value: int): - self.write(struct.pack(self.endian + "I", value)) + self.write(pack(self.endian + "I", value)) def write_u_long(self, value: int): - self.write(struct.pack(self.endian + "Q", value)) + self.write(pack(self.endian + "Q", value)) def write_float(self, value: float): - self.write(struct.pack(self.endian + "f", value)) + self.write(pack(self.endian + "f", value)) def write_double(self, value: float): - self.write(struct.pack(self.endian + "d", value)) + self.write(pack(self.endian + "d", value)) def write_boolean(self, value: bool): - self.write(struct.pack(self.endian + "?", value)) - - """ - def write_string(self, size=None, encoding="utf-8") -> str: - if size is None: - ret = self.write_string_to_null() - else: - ret = struct.unpack(f"{self.endian}{size}is", self.read(size))[0] - try: - return ret.decode(encoding) - except UnicodeDecodeError: - return ret - """ + self.write(pack(self.endian + "?", value)) def write_string_to_null(self, value: str): - self.write(value.encode("utf8")) + self.write(value.encode("utf8", "surrogateescape")) self.write(b"\0") def write_aligned_string(self, value: str): - self.write_int(len(value)) - self.write(value.encode("utf8", "backslashreplace")) + bstring = value.encode("utf8", "surrogateescape") + self.write_int(len(bstring)) + self.write(bstring) self.align_stream(4) - def align_stream(self, alignment=4): + def align_stream(self, alignment: int = 4): pos = self.stream.tell() - mod = pos % alignment - if mod != 0: - self.write(b"\0" * (alignment - mod)) - - def write_quaternion(self, value: Quaternion): - self.write_float(value.X) - self.write_float(value.Y) - self.write_float(value.Z) - self.write_float(value.W) - - def write_vector2(self, value: Vector2): - self.write_float(value.X) - self.write_float(value.Y) - - def write_vector3(self, value: Vector3): - self.write_float(value.X) - self.write_float(value.Y) - self.write_float(value.Z) - - def write_vector4(self, value: Vector4): - self.write_float(value.X) - self.write_float(value.Y) - self.write_float(value.Z) - self.write_float(value.W) - - def write_rectangle_f(self, value: Rectangle): - self.write_float(value.x) - self.write_float(value.y) - self.write_float(value.width) - self.write_float(value.height) - - def write_color4(self, value: Color): - self.write_float(value.R) - self.write_float(value.G) - self.write_float(value.B) - self.write_float(value.A) - - def write_matrix(self, value: Matrix4x4): - for val in value.M: - self.write_float(val) - - def write_array(self, command, value: list, write_length: bool = True): + align = (alignment - pos % alignment) % alignment + self.write(b"\0" * align) + + def write_array( + self, + command: Callable[[T], None], + value: Sequence[T], + write_length: bool = True, + ): if write_length: self.write_int(len(value)) for val in value: command(val) - def write_boolean_array(self, value: list): + def write_byte_array(self, value: builtins.bytes): + self.write_int(len(value)) + self.write(value) + + def write_boolean_array(self, value: Sequence[bool]): self.write_array(self.write_boolean, value) - def write_u_short_array(self, value: list): + def write_u_short_array(self, value: Sequence[int]): self.write_array(self.write_u_short, value) - def write_int_array(self, value: list, write_length: bool = False): + def write_int_array(self, value: Sequence[int], write_length: bool = False): return self.write_array(self.write_int, value, write_length) - def write_u_int_array(self, value: list, write_length: bool = False): + def write_u_int_array(self, value: Sequence[int], write_length: bool = False): return self.write_array(self.write_u_int, value, write_length) - def write_float_array(self, value: list, write_length: bool = False): + def write_float_array(self, value: Sequence[float], write_length: bool = False): return self.write_array(self.write_float, value, write_length) - def write_string_array(self, value: list): + def write_string_array(self, value: Sequence[str]): self.write_array(self.write_aligned_string, value) - - def write_vector2_array(self, value: list): - self.write_array(self.write_vector2, value) - - def write_vector4_array(self, value: list): - self.write_array(self.write_vector4, value) - - def write_matrix_array(self, value: list): - self.write_array(self.write_matrix, value) diff --git a/UnityPy/streams/__init__.py b/UnityPy/streams/__init__.py index 0b713c1ef..987543d53 100644 --- a/UnityPy/streams/__init__.py +++ b/UnityPy/streams/__init__.py @@ -1,2 +1,7 @@ from .EndianBinaryReader import EndianBinaryReader from .EndianBinaryWriter import EndianBinaryWriter + +__all__ = [ + "EndianBinaryReader", + "EndianBinaryWriter", +] diff --git a/UnityPy/tools/TpkClassGenerator.py b/UnityPy/tools/TpkClassGenerator.py new file mode 100644 index 000000000..2536644e8 --- /dev/null +++ b/UnityPy/tools/TpkClassGenerator.py @@ -0,0 +1,376 @@ +"""Generates the classes for the UnityPy objects from the TypeTree of the TPK files.""" + +from __future__ import annotations + +import os +import sys +from dataclasses import dataclass, field +from typing import Dict, List, Optional, Set, Tuple + +# import UnityPy from the parent directory instead of the installed package +ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.append(ROOT) +from UnityPy.helpers.Tpk import TpkUnityNode, get_typetree # noqa: E402 +from UnityPy.helpers.TypeTreeNode import clean_name # noqa: E402 + +NODES = get_typetree().NodeBuffer +STRINGS = get_typetree().StringBuffer + +BASE_TYPE_MAP = { + "char": "int", # used for byte data + "short": "int", + "int": "int", + "long long": "int", + "unsigned short": "int", + "unsigned int": "int", + "unsigned long long": "int", + "UInt8": "int", + "UInt16": "int", + "UInt32": "int", + "UInt64": "int", + "SInt8": "int", + "SInt16": "int", + "SInt32": "int", + "SInt64": "int", + "Type*": "int", + "FileSize": "int", + "float": "float", + "double": "float", + "bool": "bool", + "string": "str", + "TypelessData": "bytes", +} + +GENERATED_HEADER = """ +# type: ignore +from __future__ import annotations + +from abc import ABC +from typing import List, Optional, Tuple, TypeVar, Union + +from attrs import define as attrs_define + +from .math import ( + ColorRGBA, + Matrix3x4f, + Matrix4x4f, + Quaternionf, + Vector2f, + Vector3f, + Vector4f, + float3, + float4, +) +from .Object import Object +from .PPtr import PPtr + +T = TypeVar("T") + + +def unitypy_define(cls: T) -> T: + \"\"\" + A hacky solution to bypass multiple problems related to attrs and inheritance. + + The class inheritance is very lax and based on the typetrees. + Some of the child classes might not have the same attributes as the parent class, + which would make type-hinting more tricky, and breaks attrs.define. + + Therefore this function bypasses the issue + by redefining the bases for problematic classes for the attrs.define call. + \"\"\" + bases = cls.__bases__ + if bases[0] in (object, Object, ABC): + cls = attrs_define(cls, slots=True, unsafe_hash=True) + else: + cls.__bases__ = (Object,) + cls = attrs_define(cls, slots=False, unsafe_hash=True) + cls.__bases__ = bases + return cls +"""[1:] + +# LIST_BASE_TYPE_MAP = { +# "short": "np.int16", +# "int": "np.int32", +# "long long": "np.int64", +# "unsigned short": "np.uint16", +# "unsigned int": "np.uint32", +# "unsigned long long": "np.int64", +# "UInt8": "np.uint8", +# "UInt16": "np.uint16", +# "UInt32": "np.uint32", +# "UInt64": "np.uint64", +# "SInt8": "np.int8", +# "SInt16": "npt.int16", +# "SInt32": "np.int32", +# "SInt64": "np.int64", +# "Type*": "np.uint32", +# "FileSize": "np.uint64", +# "float": "np.float32", +# "double": "np.float64", +# "bool": "np.bool", +# } + +MATH_CLASSES = { + "ColorRGBA", + "Matrix3x4f", + "Matrix4x4f", + "Quaternionf", + "Vector2f", + "Vector3f", + "Vector4f", + "float3", + "float4", +} + +FORBIDDEN_CLASSES = {"bool", "float", "int", "void"} | MATH_CLASSES + +CLASS_CACHE_ID: Dict[Tuple[int, str], NodeClass] = {} +CLASS_CACHE_NAME: Dict[str, NodeClass] = {} +TYPE_CACHE: Dict[int, str] = {} + + +@dataclass +class NodeClassField: + ids: Set[int] + name: str + types: Set[str] = field(default_factory=set) + optional: bool = True + + def generate_str(self) -> str: + if len(self.types) == 1: + typ = next(iter(self.types)) + else: + typ = f"Union[{', '.join(sorted(self.types))}]" + + if self.optional: + return f" {self.clean_name}: Optional[{typ}] = None" + else: + return f" {self.clean_name}: {typ}" + + @property + def clean_name(self) -> str: + return clean_name(self.name) + + +@dataclass +class NodeClass: + ids: Set[int] + name: str + aliases: Set[str] = field(default_factory=set) + fields: Dict[str, NodeClassField] = field(default_factory=dict) + field_ids: Set[int] = field(default_factory=set) + key_fields: Set[str] = field(default_factory=set) + abstract: bool = False + base: Optional[str] = None + + @staticmethod + def sort_fields(field: NodeClassField) -> Tuple[bool, str]: + return (field.optional, field.clean_name) + + def generate_str(self) -> str: + # order fields by 1. non-optional>optional, 2. name + parents: List[str] = [] + + if self.base: + parents.append(self.base) + + if self.abstract: + parents.append("ABC") + + parentsString = f"({', '.join(parents)})" if parents else "" + + if len(self.fields) == 0: + field_strings = [" pass"] + else: + field_strings = map( + NodeClassField.generate_str, + sorted(self.fields.values(), key=NodeClass.sort_fields), + ) + return "\n".join( + [ + "@unitypy_define", + f"class {self.name}{parentsString}:", + *field_strings, + ] + ) + + +def implement_node_class( + node_id: int, + node: Optional[TpkUnityNode] = None, + override_name: Optional[str] = None, +) -> NodeClass: + if node is None: + node = NODES[node_id] + cls_name = override_name or STRINGS[node.TypeName] + + cls = CLASS_CACHE_ID.get((node_id, cls_name)) + if cls is not None: + return cls + + cls = CLASS_CACHE_NAME.get(cls_name) + first_impl = False + if cls is None: + first_impl = True + cls = NodeClass(ids={node_id}, name=cls_name) + CLASS_CACHE_NAME[cls_name] = cls + else: + cls.ids.add(node_id) + + CLASS_CACHE_ID[(node_id, cls_name)] = cls + if override_name and override_name != STRINGS[node.TypeName]: + cls.aliases.add(STRINGS[node.TypeName]) + + field_names: Set[str] = set() + for subnode_id in node.SubNodes: + subnode = NODES[subnode_id] + # TEST1 + # subname = clean_name(STRINGS[subnode.Name]) + subname = STRINGS[subnode.Name] + + field_names.add(subname) + + if subnode_id in cls.field_ids: + continue + + field = cls.fields.get(subname) + if field is None: + field = NodeClassField({subnode_id}, subname) + cls.fields[subname] = field + else: + field.ids.add(subnode_id) + + field_type = generate_field_type(subnode_id, subnode) + field.types.add(field_type) + + cls.field_ids |= set(node.SubNodes) + + if first_impl: + cls.key_fields = set(cls.fields.keys()) + for field in cls.fields.values(): + field.optional = False + else: + deprecated_field_names = cls.key_fields - field_names + cls.key_fields -= deprecated_field_names + for deprecated_name in deprecated_field_names: + cls.fields[deprecated_name].optional = True + + return cls + + +def generate_field_type(node_id: int, node: Optional[TpkUnityNode] = None) -> str: + res = TYPE_CACHE.get(node_id) + if res is not None: + return res + + if node is None: + node = NODES[node_id] + + typename = STRINGS[node.TypeName] + + py_typ = BASE_TYPE_MAP.get(typename) + if py_typ: + res = py_typ + + elif typename == "pair": + # Children: + # Typ1 first + # Typ2 second + typ1 = generate_field_type(node.SubNodes[0]) + typ2 = generate_field_type(node.SubNodes[1]) + res = f"Tuple[{typ1}, {typ2}]" + + elif typename.startswith("PPtr<"): + res = typename.replace("<", "[").replace(">", "]") + + else: + # map & vector + subnode0 = NODES[node.SubNodes[0]] if len(node.SubNodes) > 0 else None + if subnode0 and STRINGS[subnode0.TypeName] == "Array": + # Children: + # Array Array + # SInt32 size + # Typ data + subtype_node = NODES[subnode0.SubNodes[1]] + subtype_name = STRINGS[subtype_node.TypeName] + if subtype_name in BASE_TYPE_MAP: + res = f"List[{BASE_TYPE_MAP[subtype_name]}]" + else: + res = f"List[{generate_field_type(subnode0.SubNodes[1], subtype_node)}]" + else: + # custom class + implement_node_class(node_id, node) + res = typename + + TYPE_CACHE[node_id] = res + return res + + +def generate_classes(): + main_classes: Set[str] = set() + deps: Dict[str, List[str]] = {} + + for _class_id, class_info in get_typetree().ClassInformation.items(): + abstract = True + base = None + cls_name: Optional[str] = None + + for _version, unity_class in class_info: + if unity_class is None: + continue + cls_name = STRINGS[unity_class.Name] + base = STRINGS[unity_class.Base] + + if unity_class.ReleaseRootNode is not None: + abstract = False + cls = implement_node_class(unity_class.ReleaseRootNode, override_name=cls_name) + cls.base = base + + if isinstance(cls_name, str): + if abstract: + CLASS_CACHE_NAME[cls_name] = NodeClass({0}, name=cls_name, base=base, abstract=True) + + main_classes.add(cls_name) + if base: + if base in deps: + deps[base].append(cls_name) + else: + deps[base] = [cls_name] + + CLASS_CACHE_NAME.pop("Object") + sorted_classes: List[str] = [] + + stack = [*sorted(deps.pop("Object"))] + while stack: + cls_name = stack.pop(0) + sorted_classes.append(cls_name) + if cls_name in deps: + stack = sorted(deps.pop(cls_name)) + stack + + sorted_classes += sorted(set(CLASS_CACHE_NAME.keys()) - set(sorted_classes) - FORBIDDEN_CLASSES) + i = 0 + names = set() + while i < len(sorted_classes): + name = sorted_classes[i] + if name in names: + sorted_classes.pop(i) + else: + names.add(name) + i += 1 + + fp = os.path.join(ROOT, "classes", "generated.py") + with open(fp, "w", encoding="utf8") as f: + f.write(GENERATED_HEADER) + f.write("\n\n") + + f.write("\n\n\n".join(cls.generate_str() for cls in map(CLASS_CACHE_NAME.__getitem__, sorted_classes))) + f.write("\n") + + +if __name__ == "__main__": + import time + + t1 = time.time_ns() + generate_classes() + t2 = time.time_ns() + print(t2 - t1 / 10**9) diff --git a/UnityPy/tools/__init__.py b/UnityPy/tools/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/UnityPy/tools/extractor.py b/UnityPy/tools/extractor.py new file mode 100644 index 000000000..b1f0c8e60 --- /dev/null +++ b/UnityPy/tools/extractor.py @@ -0,0 +1,368 @@ +import json +import os +from io import BytesIO +from pathlib import Path +from typing import Callable, Dict, Iterable, List, Optional, Sequence, Tuple, Union + +import UnityPy +from UnityPy.classes import ( + AudioClip, + Font, + GameObject, + Mesh, + MonoBehaviour, + Object, + PPtr, + Shader, + Sprite, + TextAsset, + Texture2D, +) +from UnityPy.enums.ClassIDType import ClassIDType +from UnityPy.files import ObjectReader, SerializedFile + + +def export_obj( + obj: Union[ObjectReader, PPtr], + fp: str, + append_name: bool = False, + append_path_id: bool = False, + export_unknown_as_typetree: bool = False, + asset_filter: Optional[Callable[[Object], bool]] = None, +) -> List[Tuple[SerializedFile, int]]: + """Exports the given object to the given filepath. + + Args: + obj (Object, PPtr): A valid Unity object or a reference to one. + fp (Path): A valid filepath where the object should be exported to. + append_name (bool, optional): Decides if the obj name will be appended to the filepath. Defaults to False. + append_path_id (bool, optional): Decides if the obj path id will be appended to the filepath. Defaults to False. + export_unknown_as_typetree (bool, optional): If set, then unimplemented objects will be exported + via their typetree or dumped as bin. Defaults to False. + asset_filter (func(Object)->bool, optional): Determines whether to export an object. Defaults to all objects. + + Returns: + list: a list of exported object path_ids + """ + # figure out export function + export_func = EXPORT_TYPES.get(obj.type) + if not export_func: + if export_unknown_as_typetree: + export_func = exportMonoBehaviour + else: + return [] + + # set filepath + if isinstance(obj, PPtr): + obj = obj.deref() + + instance = obj.parse_as_object() + + # a filter that returned True during an earlier extract_assets check can return False now with more info from read() + if asset_filter and not asset_filter(instance): + return [] + + if append_name: + name = getattr(instance, "m_Name", obj.type.name) + fp = os.path.join( + fp, + name, + ) + + fp, extension = os.path.splitext(fp) + + if append_path_id: + fp = f"{fp}_{obj.m_PathID}" + + # export + return export_func(instance, fp, extension) + + +def extract_assets( + src: Union[Path, BytesIO, bytes, bytearray], + dst: Path, + use_container: bool = True, + ignore_first_container_dirs: int = 0, + append_path_id: bool = False, + export_unknown_as_typetree: bool = False, + asset_filter: Optional[Callable[[Object], bool]] = None, +) -> List[Tuple[SerializedFile, int]]: + """Extracts some or all assets from the given source. + + Args: + src (Union[Path, BytesIO, bytes, bytearray]): [description] + dst (Path): [description] + use_container (bool, optional): [description]. Defaults to True. + ignore_first_container_dirs (int, optional): [description]. Defaults to 0. + append_path_id (bool, optional): [description]. Defaults to False. + export_unknown_as_typetree (bool, optional): [description]. Defaults to False. + asset_filter (func(object)->bool, optional): Determines whether to export an object. + Defaults to all objects. + + Returns: + List[Tuple[SerializedFile, int]]: [description] + """ + # load source + env = UnityPy.load(src) + exported = [] + + export_types_keys = list(EXPORT_TYPES.keys()) + + def defaulted_export_index(type: ClassIDType): + try: + return export_types_keys.index(type) + except (IndexError, ValueError): + return 999 + + if use_container: + container = sorted(env.container.items(), key=lambda x: defaulted_export_index(x[1].type)) + for obj_path, obj in container: + # The filter here can only access metadata. + # The same filter may produce a different result later in extract_obj after obj.read() + if asset_filter is not None and not asset_filter(obj): + continue + # the check of the various sub directories is required to avoid // in the path + obj_dest = os.path.join( + dst, + *(x for x in obj_path.split("/")[ignore_first_container_dirs:] if x), + ) + os.makedirs(os.path.dirname(obj_dest), exist_ok=True) + exported.extend( + export_obj( + obj, + obj_dest, + append_path_id=append_path_id, + export_unknown_as_typetree=export_unknown_as_typetree, + asset_filter=asset_filter, + ) + ) + + else: + objects = sorted(env.objects, key=lambda x: defaulted_export_index(x.type)) + for obj in objects: + if asset_filter is not None and not asset_filter(obj): + continue + if (obj.assets_file, obj.path_id) not in exported: + exported.extend( + export_obj( + obj, + dst, + append_name=True, + append_path_id=append_path_id, + export_unknown_as_typetree=export_unknown_as_typetree, + asset_filter=asset_filter, + ) + ) + + return exported + + +############################################################################### +# EXPORT FUNCTIONS # +############################################################################### + + +def exportTextAsset( + obj: Union[TextAsset, ObjectReader], fp: str, extension: str = ".txt" +) -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() + if not extension: + extension = ".txt" + with open(f"{fp}{extension}", "wb") as f: + f.write(obj.m_Script.encode("utf-8", "surrogateescape")) + return [(obj.assets_file, obj.object_reader.path_id)] + + +def exportFont(obj: Union[Font, ObjectReader], fp: str, extension: str = "") -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() + # TODO - export glyphs + if obj.m_FontData: + extension = ".ttf" + if obj.m_FontData[0:4] == b"OTTO": + extension = ".otf" + with open(f"{fp}{extension}", "wb") as f: + f.write(bytes(obj.m_FontData)) + return [(obj.assets_file, obj.object_reader.path_id)] + + +def exportMesh(obj: Union[Mesh, ObjectReader], fp: str, extension=".obj") -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() + if not extension: + extension = ".obj" + with open(f"{fp}{extension}", "wt", encoding="utf8", newline="") as f: + f.write(obj.export()) + return [(obj.assets_file, obj.object_reader.path_id)] + + +def exportShader(obj: Union[Shader, ObjectReader], fp: str, extension=".txt") -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() + if not extension: + extension = ".txt" + with open(f"{fp}{extension}", "wt", encoding="utf8", newline="") as f: + f.write(obj.export()) + return [(obj.assets_file, obj.object_reader.path_id)] + + +def exportMonoBehaviour( + obj: Union[MonoBehaviour, ObjectReader], fp: str, extension: str = "" +) -> List[Tuple[SerializedFile, int]]: + reader = obj.object_reader if isinstance(obj, MonoBehaviour) else obj + + try: + export = reader.parse_as_dict() + extension = ".json" + export = json.dumps(export, indent=4, ensure_ascii=False).encode("utf8", errors="surrogateescape") + except Exception: + extension = ".bin" + export = reader.get_raw_data() + with open(f"{fp}{extension}", "wb") as f: + f.write(export) + return [(obj.assets_file, obj.path_id)] + + +def exportAudioClip( + obj: Union[AudioClip, ObjectReader], fp: str, extension: str = "" +) -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + clip = obj.parse_as_object() + else: + clip = obj + obj = clip.object_reader + + samples = clip.samples + if len(samples) == 0: + pass + elif len(samples) == 1: + with open(f"{fp}.wav", "wb") as f: + f.write(list(samples.values())[0]) + else: + os.makedirs(fp, exist_ok=True) + for name, clip_data in samples.items(): + with open(os.path.join(fp, f"{name}.wav"), "wb") as f: + f.write(clip_data) + return [(obj.assets_file, obj.path_id)] + + +def exportSprite( + obj: Union[Sprite, ObjectReader], fp: str, extension: str = ".png" +) -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + sprite = obj.parse_as_object() + else: + sprite = obj + obj = sprite.object_reader + + sprite.image.save(f"{fp}{extension}") + exported = [ + (obj.assets_file, obj.path_id), + (sprite.m_RD.texture.assetsfile, sprite.m_RD.texture.path_id), + ] + alpha_assets_file = getattr(sprite.m_RD.alphaTexture, "assets_file", None) + alpha_path_id = getattr(sprite.m_RD.alphaTexture, "path_id", None) + if alpha_path_id and alpha_assets_file: + exported.append((alpha_assets_file, alpha_path_id)) + return exported + + +def exportTexture2D( + obj: Union[Texture2D, ObjectReader], fp: str, extension: str = ".png" +) -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() + if not extension: + extension = ".png" + if obj.m_Width: + # textures can be empty + obj.image.save(f"{fp}{extension}") + return [(obj.assets_file, obj.object_reader.path_id)] + + +def exportGameObject( + obj: Union[GameObject, ObjectReader], fp: str, extension: str = "" +) -> List[Tuple[SerializedFile, int]]: + if isinstance(obj, ObjectReader): + obj = obj.parse_as_object() + exported = [(obj.assets_file, obj.object_reader.path_id)] + refs = crawl_obj(obj) + if refs: + os.makedirs(fp, exist_ok=True) + for ref_id, ref in refs.items(): + # Don't export already exported objects a second time + # and prevent circular calls by excluding other GameObjects. + # The other GameObjects were already exported in the this call. + if (ref.assets_file, ref_id) in exported or ref.type == ClassIDType.GameObject: + continue + try: + exported.extend(export_obj(ref, fp, True, True)) + except Exception as e: + print(f"Failed to export {ref_id}") + print(e) + return exported + + +EXPORT_TYPES = { + # following types can include other objects + ClassIDType.GameObject: exportGameObject, + ClassIDType.Sprite: exportSprite, + # following types don't include other objects + ClassIDType.AudioClip: exportAudioClip, + ClassIDType.Font: exportFont, + ClassIDType.Mesh: exportMesh, + ClassIDType.MonoBehaviour: exportMonoBehaviour, + ClassIDType.Shader: exportShader, + ClassIDType.TextAsset: exportTextAsset, + ClassIDType.Texture2D: exportTexture2D, +} + +ASSEMBLY_NAME_DLL = str +CLASS_NAME = str + +MONOBEHAVIOUR_TYPETREES: Dict[ASSEMBLY_NAME_DLL, Dict[CLASS_NAME, List[Dict]]] = {} + + +def crawl_obj(obj: Union[Object, ObjectReader, PPtr], ret: Optional[dict] = None) -> Dict[int, Union[Object, PPtr]]: + """Crawls through the data struture of the object + and returns a list of all the components. + """ + if not ret: + ret = {} + + values: Sequence + if isinstance(obj, PPtr): + if obj.m_PathID == 0 and obj.m_FileID == 0 and obj.m_Index == -2: + return ret + try: + instance = obj.deref_parse_as_dict() + values = instance.values() + except AttributeError: + return ret + elif isinstance(obj, Object): + values = obj.__dict__.values() + elif isinstance(obj, ObjectReader): + values = obj.parse_as_dict().values() + else: + return ret + + ret[obj.m_PathID] = obj + + for value in flatten(values): + if isinstance(value, (Object, PPtr)): + if value.m_PathID in ret: + continue + crawl_obj(value, ret) + + return ret + + +def flatten(seq: Sequence) -> Iterable: + for elem in list(seq): + if isinstance(elem, (list, tuple)): + yield from flatten(elem) + elif isinstance(elem, dict): + yield from flatten(elem.values()) # type: ignore + else: + yield elem diff --git a/UnityPyBoost/ArchiveStorageDecryptor.cpp b/UnityPyBoost/ArchiveStorageDecryptor.cpp new file mode 100644 index 000000000..b26534ff9 --- /dev/null +++ b/UnityPyBoost/ArchiveStorageDecryptor.cpp @@ -0,0 +1,90 @@ +// based on https://github.com/RazTools/Studio/blob/main/AssetStudio/Crypto/UnityCN.cs + +#include "ArchiveStorageDecryptor.hpp" +#include + +inline unsigned char decrypt_byte(unsigned char *bytes, uint64_t& offset, uint64_t& index, const unsigned char *index_data, const unsigned char *substitute_data) +{ + unsigned char count_byte = substitute_data[((index >> 2) & 3) + 4] + + substitute_data[index & 3] + + substitute_data[((index >> 4) & 3) + 8] + + substitute_data[((unsigned char)index >> 6) + 12]; + bytes[offset] = (unsigned char)(((index_data[bytes[offset] & 0xF] - count_byte) & 0xF) | 0x10 * (index_data[bytes[offset] >> 4] - count_byte)); + count_byte = bytes[offset++]; + index++; + return count_byte; +} + +inline uint64_t decrypt(unsigned char *bytes, uint64_t index, uint64_t remaining, const unsigned char *index_data, const unsigned char *substitute_data) +{ + uint64_t offset = 0; + + unsigned char current_byte = decrypt_byte(bytes, offset, index, index_data, substitute_data); + uint64_t current_byte_high = current_byte >> 4; + uint64_t current_byte_low = current_byte & 0xF; + + if (current_byte_high == 0xF) + { + unsigned char count_byte; + do + { + count_byte = decrypt_byte(bytes, offset, index, index_data, substitute_data); + current_byte_high += count_byte; + } while (count_byte == 0xFF); + } + + offset += current_byte_high; + + if (offset < remaining) + { + decrypt_byte(bytes, offset, index, index_data, substitute_data); + decrypt_byte(bytes, offset, index, index_data, substitute_data); + if (current_byte_low == 0xF) + { + unsigned char count_byte; + do + { + count_byte = decrypt_byte(bytes, offset, index, index_data, substitute_data); + } while (count_byte == 0xFF); + } + } + + return offset; +} + +PyObject *decrypt_block(PyObject *self, PyObject *args) { + Py_buffer index_data; + Py_buffer substitute_data; + Py_buffer data; + uint64_t index; + + if (!PyArg_ParseTuple(args, "y*y*y*K", &index_data, &substitute_data, &data, &index)) { + if (index_data.buf) PyBuffer_Release(&index_data); + if (substitute_data.buf) PyBuffer_Release(&substitute_data); + if (data.buf) PyBuffer_Release(&data); + return nullptr; + } + + PyObject *result = PyBytes_FromStringAndSize(nullptr, data.len); + if (result == nullptr) { + PyBuffer_Release(&index_data); + PyBuffer_Release(&substitute_data); + PyBuffer_Release(&data); + return nullptr; + } + + unsigned char *result_raw = (unsigned char *)PyBytes_AS_STRING(result); + memcpy(result_raw, data.buf, data.len); + + Py_ssize_t offset = 0; + while (offset < data.len) { + offset += decrypt(result_raw + offset, index++, data.len - offset, (unsigned char *)index_data.buf, (unsigned char *)substitute_data.buf); + } + + PyBuffer_Release(&index_data); + PyBuffer_Release(&substitute_data); + PyBuffer_Release(&data); + + return result; +} + diff --git a/UnityPyBoost/ArchiveStorageDecryptor.hpp b/UnityPyBoost/ArchiveStorageDecryptor.hpp new file mode 100644 index 000000000..fb155db16 --- /dev/null +++ b/UnityPyBoost/ArchiveStorageDecryptor.hpp @@ -0,0 +1,5 @@ +#define PY_SSIZE_T_CLEAN +#pragma once +#include + +PyObject *decrypt_block(PyObject *self, PyObject *args); diff --git a/UnityPyBoost/Mesh.cpp b/UnityPyBoost/Mesh.cpp new file mode 100644 index 000000000..e1baf0366 --- /dev/null +++ b/UnityPyBoost/Mesh.cpp @@ -0,0 +1,159 @@ +#include "Mesh.hpp" +#include +#include +#include + +#define MAX(x, y) (((x) > (y)) ? (x) : (y)) + +enum VertexFormat +{ + kVertexFormatFloat, + kVertexFormatFloat16, + kVertexFormatUNorm8, + kVertexFormatSNorm8, + kVertexFormatUNorm16, + kVertexFormatSNorm16, + kVertexFormatUInt8, + kVertexFormatSInt8, + kVertexFormatUInt16, + kVertexFormatSInt16, + kVertexFormatUInt32, + kVertexFormatSInt32 +}; + +template +void unpack_vertexdata_template(uint8_t *componentBytes, uint8_t *vertexData, uint32_t m_VertexCount, uint32_t m_StreamOffset, uint32_t m_StreamStride, uint32_t m_ChannelOffset, uint32_t m_ChannelDimension) +{ + const auto channelSize = componentByteSize * m_ChannelDimension; + + uint8_t *componentCur = componentBytes; + uint8_t *vertexCur = vertexData; + + // move vertexCur to the first vertex + vertexCur += m_StreamOffset + m_ChannelOffset; + + for (uint32_t v = 0; v < m_VertexCount; v++) + { + memcpy(componentCur, vertexCur, channelSize); + componentCur += channelSize; + vertexCur += m_StreamStride; + } +} + +template +void swap_vertexdata(uint8_t *componentBytes, uint32_t m_VertexCount, uint32_t m_ChannelDimension) +{ + if constexpr (componentByteSize == 1) + { + // do nothing + } + else if constexpr (componentByteSize == 2) + { + uint16_t *componentUints = (uint16_t *)componentBytes; + for (uint32_t i = 0; i < m_VertexCount * m_ChannelDimension; i++) + { + swap_any_inplace(componentUints++); + } + } + else if constexpr (componentByteSize == 4) + { + uint32_t *componentUints = (uint32_t *)componentBytes; + for (uint32_t i = 0; i < m_VertexCount * m_ChannelDimension; i++) + { + swap_any_inplace(componentUints++); + } + } + else if constexpr (componentByteSize == 8) + { + uint64_t *componentUints = (uint64_t *)componentBytes; + for (uint32_t i = 0; i < m_VertexCount * m_ChannelDimension; i++) + { + swap_any_inplace(componentUints++); + } + } + else + { + const auto compoentByteSizeStr = std::to_string(componentByteSize); + const auto error_message = "Swap not implemented for this size: " + compoentByteSizeStr; + PyErr_SetString(PyExc_ValueError, error_message.c_str()); + } +} + +PyObject *unpack_vertexdata(PyObject *self, PyObject *args) +{ + // define vars + int componentByteSize; + uint32_t m_VertexCount; + uint8_t swap; + // char format; + Py_buffer vertexDataView; + uint32_t m_StreamOffset; + uint32_t m_StreamStride; + uint32_t m_ChannelOffset; + uint32_t m_ChannelDimension; + + if (!PyArg_ParseTuple(args, "y*iIIIIIb", &vertexDataView, &componentByteSize, &m_VertexCount, &m_StreamOffset, &m_StreamStride, &m_ChannelOffset, &m_ChannelDimension, &swap)) + { + if (vertexDataView.buf) + { + PyBuffer_Release(&vertexDataView); + } + return nullptr; + } + + uint8_t *vertexData = (uint8_t *)vertexDataView.buf; + + Py_ssize_t componentBytesLength = m_VertexCount * m_ChannelDimension * componentByteSize; + + // check if max values are ok + uint32_t maxVertexDataAccess = (m_VertexCount - 1) * m_StreamStride + m_ChannelOffset + m_StreamOffset + componentByteSize * (m_ChannelDimension - 1) + componentByteSize; + if (maxVertexDataAccess > vertexDataView.len) + { + PyBuffer_Release(&vertexDataView); + PyErr_SetString(PyExc_ValueError, "Vertex data access out of bounds"); + return nullptr; + } + + PyObject *res = PyBytes_FromStringAndSize(nullptr, componentBytesLength); + if (!res) + { + PyBuffer_Release(&vertexDataView); + return nullptr; + } + uint8_t *componentBytes = (uint8_t *)PyBytes_AS_STRING(res); + + switch (componentByteSize) + { + case 1: + unpack_vertexdata_template<1>(componentBytes, vertexData, m_VertexCount, m_StreamOffset, m_StreamStride, m_ChannelOffset, m_ChannelDimension); + break; + case 2: + unpack_vertexdata_template<2>(componentBytes, vertexData, m_VertexCount, m_StreamOffset, m_StreamStride, m_ChannelOffset, m_ChannelDimension); + if (swap) + { + swap_vertexdata<2>(componentBytes, m_VertexCount, m_ChannelDimension); + } + break; + case 4: + unpack_vertexdata_template<4>(componentBytes, vertexData, m_VertexCount, m_StreamOffset, m_StreamStride, m_ChannelOffset, m_ChannelDimension); + if (swap) + { + swap_vertexdata<4>(componentBytes, m_VertexCount, m_ChannelDimension); + } + break; + case 8: + unpack_vertexdata_template<8>(componentBytes, vertexData, m_VertexCount, m_StreamOffset, m_StreamStride, m_ChannelOffset, m_ChannelDimension); + if (swap) + { + swap_vertexdata<8>(componentBytes, m_VertexCount, m_ChannelDimension); + } + break; + default: + PyBuffer_Release(&vertexDataView); + PyErr_SetString(PyExc_ValueError, "Unsupported component byte size"); + return nullptr; + } + + PyBuffer_Release(&vertexDataView); + return res; +} diff --git a/UnityPyBoost/Mesh.hpp b/UnityPyBoost/Mesh.hpp new file mode 100644 index 000000000..db18f1072 --- /dev/null +++ b/UnityPyBoost/Mesh.hpp @@ -0,0 +1,5 @@ +#define PY_SSIZE_T_CLEAN +#pragma once +#include + +PyObject *unpack_vertexdata(PyObject *self, PyObject *args); \ No newline at end of file diff --git a/UnityPyBoost/ReadMe.md b/UnityPyBoost/ReadMe.md new file mode 100644 index 000000000..18fb32e7d --- /dev/null +++ b/UnityPyBoost/ReadMe.md @@ -0,0 +1,7 @@ +# UnityPyBoost + +A C-extension for UnityPy that accelerates various parts of UnityPy (by a lot). + +## Structure + +The filename corresponds to where the original python functionality is situated. diff --git a/UnityPyBoost/TypeTreeHelper.cpp b/UnityPyBoost/TypeTreeHelper.cpp new file mode 100644 index 000000000..ec9fa234e --- /dev/null +++ b/UnityPyBoost/TypeTreeHelper.cpp @@ -0,0 +1,1283 @@ +#include +#include +#include +#include +#include +#include + +#include "swap.hpp" +#include "TypeTreeHelper.hpp" + +// TypeTreeReader - impl + +typedef struct Reader +{ + uint8_t *ptr; + uint8_t *end; + uint8_t *start; +} ReaderT; + +typedef struct TypeTreeReaderConfig +{ + bool as_dict; + PyObject *classes; + PyObject *assetfile; + bool has_registry; +} TypeTreeReaderConfigT; + +std::string clean_name(const std::string &name) +{ + // # keep in sync with TypeTreeNode.py + std::string cleaned_name = name; + if (cleaned_name.empty()) + { + return cleaned_name; + } + + // Remove "(int&)" prefix + if (cleaned_name.substr(0, 6) == "(int&)") + { + cleaned_name = cleaned_name.substr(6); + } + + // Remove certain characters + cleaned_name = std::regex_replace(cleaned_name, std::regex("[\\?\\*]"), ""); + + // Replace certain characters with "_" + cleaned_name = std::regex_replace(cleaned_name, std::regex("[ \\.:\\-\\[\\]]"), "_"); + + // Append "_" if the name is "pass" or "from" + if (cleaned_name == "pass" || cleaned_name == "from") + { + cleaned_name += "_"; + } + + // Prefix with "x" if the name starts with a digit + if (!cleaned_name.empty() && isdigit(cleaned_name[0])) + { + cleaned_name = "x" + cleaned_name; + } + + return cleaned_name; +} + +inline void align4(ReaderT *reader) +{ + size_t offset = (size_t)reader->ptr - (size_t)reader->start; + size_t aligned_offset = (offset + 4 - 1) & ~(4 - 1); + reader->ptr = reader->start + aligned_offset; +} + +inline PyObject *read_bool(ReaderT *reader) +{ + if (reader->ptr + 1 > reader->end) + { + PyErr_SetString(PyExc_ValueError, "read_bool out of bounds"); + return nullptr; + } + PyObject *value = *reader->ptr++ ? Py_True : Py_False; + Py_INCREF(value); + return value; +} + +inline PyObject *read_bool_array(ReaderT *reader, int32_t count) +{ + if (reader->ptr + count > reader->end) + { + PyErr_SetString(PyExc_ValueError, "read_bool_array out of bounds"); + return nullptr; + } + PyObject *list = PyList_New(count); + for (auto i = 0; i < count; i++) + { + PyObject *value = *reader->ptr++ ? Py_True : Py_False; + Py_INCREF(value); + PyList_SET_ITEM(list, i, value); + } + return list; +} + +inline PyObject *read_u8(ReaderT *reader) +{ + if (reader->ptr + 1 > reader->end) + { + PyErr_SetString(PyExc_ValueError, "read_u8 out of bounds"); + return nullptr; + } + return PyLong_FromUnsignedLong(*reader->ptr++); +} + +inline PyObject *read_u8_array(ReaderT *reader, int32_t count) +{ + if (reader->ptr + count > reader->end) + { + PyErr_SetString(PyExc_ValueError, "read_u8_array out of bounds"); + return nullptr; + } + PyObject *list = PyList_New(count); + for (auto i = 0; i < count; i++) + { + PyList_SET_ITEM(list, i, PyLong_FromUnsignedLong(*reader->ptr++)); + } + return list; +} + +inline PyObject *read_s8(ReaderT *reader) +{ + if (reader->ptr + 1 > reader->end) + { + PyErr_SetString(PyExc_ValueError, "read_s8 out of bounds"); + return nullptr; + } + return PyLong_FromLong((int8_t)*reader->ptr++); +} + +inline PyObject *read_s8_array(ReaderT *reader, int32_t count) +{ + if (reader->ptr + count > reader->end) + { + PyErr_SetString(PyExc_ValueError, "read_s8_array out of bounds"); + return nullptr; + } + PyObject *list = PyList_New(count); + int8_t *ptr = (int8_t *)reader->ptr; + for (auto i = 0; i < count; i++) + { + PyList_SET_ITEM(list, i, PyLong_FromLong(*ptr++)); + } + reader->ptr = (uint8_t *)ptr; + return list; +} + +template +inline PyObject *read_num(ReaderT *reader) +{ + static_assert(std::is_integral::value || std::is_floating_point::value, "Unsupported type for read_num"); + + if (reader->ptr + sizeof(T) > reader->end) + { + std::string error_msg = "read_" + std::string(typeid(T).name()) + " out of bounds"; + PyErr_SetString(PyExc_EOFError, error_msg.c_str()); + return nullptr; + } + T value = *(T *)reader->ptr; + if constexpr (swap) + { + swap_any_inplace(&value); + } + reader->ptr += sizeof(T); + if constexpr (std::is_floating_point::value) + { + return PyFloat_FromDouble(value); + } + else if constexpr (std::is_signed::value) + { + if constexpr (std::is_same::value) + { + return PyLong_FromLongLong(value); + } + else + { + return PyLong_FromLong((int32_t)value); + } + } + else if constexpr (std::is_unsigned::value) + { + if constexpr (std::is_same::value) + { + return PyLong_FromUnsignedLongLong(value); + } + else + { + return PyLong_FromUnsignedLong((uint32_t)value); + } + } + else + { + std::string error_msg = "Unsupported type for read_num: " + std::string(typeid(T).name()); + PyErr_SetString(PyExc_TypeError, error_msg.c_str()); + return nullptr; + } +} + +template +inline PyObject *read_num_array(ReaderT *reader, int32_t count) +{ + static_assert(std::is_integral::value || std::is_floating_point::value, "Unsupported type for read_num_array"); + + if (reader->ptr + sizeof(T) * count > reader->end) + { + std::string error_msg = "read_" + std::string(typeid(T).name()) + "_array out of bounds"; + PyErr_SetString(PyExc_EOFError, error_msg.c_str()); + return nullptr; + } + PyObject *list = PyList_New(count); + T *ptr = (T *)reader->ptr; + for (auto i = 0; i < count; i++) + { + T value = *ptr++; + if constexpr (swap) + { + swap_any_inplace(&value); + } + PyObject *item; + if constexpr (std::is_floating_point::value) + { + item = PyFloat_FromDouble(value); + } + else if constexpr (std::is_signed::value) + { + if constexpr (std::is_same::value) + { + item = PyLong_FromLongLong(value); + } + else + { + item = PyLong_FromLong((int32_t)value); + } + } + else if constexpr (std::is_unsigned::value) + { + if constexpr (std::is_same::value) + { + item = PyLong_FromUnsignedLongLong(value); + } + else + { + item = PyLong_FromUnsignedLong((uint32_t)value); + } + } + else + { + Py_DECREF(list); + std::string error_msg = "Unsupported type for read_num_array: " + std::string(typeid(T).name()); + PyErr_SetString(PyExc_TypeError, error_msg.c_str()); + return nullptr; + } + PyList_SET_ITEM(list, i, item); + } + reader->ptr = (uint8_t *)ptr; + return list; +} + +template +inline bool _read_length(ReaderT *reader, int32_t *length) +{ + if (reader->ptr + sizeof(int32_t) > reader->end) + { + PyErr_SetString(PyExc_EOFError, "read_length out of bounds"); + return false; + } + *length = *(int32_t *)reader->ptr; + if constexpr (swap) + { + swap_any_inplace(length); + } + if (*length < 0) + { + PyErr_SetString(PyExc_ValueError, "Negative length read from TypeTree"); + return false; + } + reader->ptr += sizeof(int32_t); + return true; +} + +template +inline PyObject *read_str(ReaderT *reader) +{ + int32_t length; + if (!_read_length(reader, &length)) + { + return nullptr; + } + if (reader->ptr + length > reader->end) + { + PyErr_SetString(PyExc_EOFError, "read_str out of bounds"); + return nullptr; + } + PyObject *py_str = PyUnicode_DecodeUTF8((char *)reader->ptr, length, "surrogateescape"); + reader->ptr += length; + align4(reader); + return py_str; +} + +template +inline PyObject *read_bytes(ReaderT *reader) +{ + int32_t length; + if (!_read_length(reader, &length)) + { + return nullptr; + } + if (reader->ptr + length > reader->end) + { + PyErr_SetString(PyExc_ValueError, "read_bytes out of bounds"); + return nullptr; + } + PyObject *bytes = PyBytes_FromStringAndSize((char *)reader->ptr, length); + reader->ptr += length; + return bytes; +} + +template +inline PyObject *read_pair(ReaderT *reader, TypeTreeNodeObject *node, TypeTreeReaderConfigT *config) +{ + if (PyList_GET_SIZE(node->m_Children) != 2) + { + PyErr_SetString(PyExc_ValueError, "Pair node must have 2 children"); + return nullptr; + } + + PyObject *first = read_typetree_value(reader, (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, 0), config); + if (first == nullptr) + { + return nullptr; + } + PyObject *second = read_typetree_value(reader, (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, 1), config); + if (second == nullptr) + { + Py_DECREF(first); + return nullptr; + } + // PyTuple_Pack creates two strong references + PyObject *pair = PyTuple_Pack(2, first, second); + // so we need to decref both values here to bring their ref count back to 1 + Py_DECREF(first); + Py_DECREF(second); + return pair; +} + +template +inline PyObject *read_pair_array(ReaderT *reader, TypeTreeNodeObject *node, TypeTreeReaderConfigT *config, int32_t count) +{ + if (PyList_GET_SIZE(node->m_Children) != 2) + { + PyErr_SetString(PyExc_ValueError, "Pair node must have 2 children"); + return nullptr; + } + + TypeTreeNodeObject *first_child = (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, 0); + TypeTreeNodeObject *second_child = (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, 1); + + PyObject *list = PyList_New(count); + if (list == nullptr) + { + return nullptr; + } + for (auto i = 0; i < count; i++) + { + PyObject *first = read_typetree_value(reader, first_child, config); + if (first == nullptr) + { + Py_DECREF(list); + return nullptr; + } + PyObject *second = read_typetree_value(reader, second_child, config); + if (second == nullptr) + { + Py_DECREF(first); + Py_DECREF(list); + return nullptr; + } + PyList_SET_ITEM(list, i, PyTuple_Pack(2, first, second)); // pack creates two strong references + // so we need to decref both values here to bring their ref count back to 1 + Py_DECREF(first); + Py_DECREF(second); + } + + return list; +} + +template +inline PyObject *read_class(ReaderT *reader, TypeTreeNodeObject *node, TypeTreeReaderConfigT *config) +{ + bool changed_registry = false; + PyObject *value = PyDict_New(); // value: 1 refcount + for (int i = 0; i < PyList_GET_SIZE(node->m_Children); i++) + { + TypeTreeNodeObject *child = (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, i); // no refcount change + if (child->_data_type == NodeDataType::ManagedReferencesRegistry) + { + if (config->has_registry) + { + continue; + } + else + { + changed_registry = true; + config->has_registry = true; + } + } + PyObject *child_value = read_typetree_value(reader, child, config); // child_value: 1 refcount + if (child_value == nullptr) + { + Py_DECREF(value); // value: 0 refcount + return nullptr; + } + int set_item_result; + if constexpr (as_dict == true) + { + set_item_result = PyDict_SetItem(value, child->m_Name, child_value); // child_value: 2 refcount + } + else + { + set_item_result = PyDict_SetItem(value, child->_clean_name, child_value); // child_value: 2 refcount + } + if (set_item_result != 0) + { + Py_DECREF(value); // value: 0 refcount + Py_DECREF(child_value); // child_value: 0 refcount + return nullptr; + } + // PyDict_SetItem increases ref count, so we need to decref here + Py_DECREF(child_value); // child_value: 1 refcount + } + + if (changed_registry) + { + config->has_registry = false; + } + + return value; +} + +static PyObject *_get_annotations = nullptr; + +inline PyObject *get_annotations(PyObject *clz) +{ +#if PY_VERSION_HEX >= 0x030e0000 + return PyObject_CallFunctionObjArgs(_get_annotations, clz, nullptr); +#else + return PyObject_GetAttrString(clz, "__annotations__"); +#endif +} + +inline PyObject *parse_class(PyObject *kwargs, TypeTreeNodeObject *node, TypeTreeReaderConfigT *config) +{ + PyObject *instance = nullptr; + PyObject *clz = nullptr; + PyObject *args = PyTuple_New(0); + PyObject *annotations = nullptr; + PyObject *extras = nullptr; + // dict iterator values + PyObject *key, *value = nullptr; + Py_ssize_t pos; + // slots check + PyObject *slots = nullptr; + + if (kwargs == nullptr) + { + return nullptr; + } + + if (node->_data_type == NodeDataType::PPtr) + { + clz = PyObject_GetAttrString(config->classes, "PPtr"); + if (clz == nullptr) + { + PyErr_SetString(PyExc_ValueError, "Failed to get PPtr class"); + goto PARSE_CLASS_CLEANUP; + } + PyDict_SetItemString(kwargs, "assetsfile", config->assetfile); + } + else + { + clz = PyObject_GetAttr(config->classes, node->m_Type); + if (clz == nullptr) + { + clz = PyObject_GetAttrString(config->classes, "UnknownObject"); + if (clz == nullptr) + { + PyErr_SetString(PyExc_ValueError, "Failed to get UnknownObject class"); + goto PARSE_CLASS_CLEANUP; + } + PyDict_SetItemString(kwargs, "__node__", (PyObject *)node); + } + } + + instance = PyObject_Call(clz, args, kwargs); + if (instance != nullptr) + { + goto PARSE_CLASS_CLEANUP; + } + PyErr_Clear(); + + // if __slots__ is defined, setattr for non-slots attributes will fail + // so we can skip the extra field check + slots = PyObject_GetAttrString(clz, "__slots__"); + if (PyTuple_Check(slots) && PyTuple_GET_SIZE(slots) > 0) + { + Py_XDECREF(slots); + goto PARSE_CLASS_UNKNOWN; + } + Py_XDECREF(slots); + + // possibly extra fields + annotations = get_annotations(clz); + if (annotations == nullptr) + { + PyErr_SetString(PyExc_ValueError, "Failed to get annotations"); + goto PARSE_CLASS_CLEANUP; + } + extras = PyDict_New(); + for (int i = 0; i < PyList_GET_SIZE(node->m_Children); i++) + { + TypeTreeNodeObject *child = (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, i); // - borrowed ref +/- 0 + if (PyDict_Contains(annotations, child->_clean_name) == 1) + { + continue; + } + PyObject *extra_value = PyDict_GetItem(kwargs, child->_clean_name); // - borrowed ref +/- 0 + PyDict_SetItem(extras, child->_clean_name, extra_value); // +1 + PyDict_DelItem(kwargs, child->_clean_name); // -1 + } + + if (PyDict_Size(extras) == 0) + { + goto PARSE_CLASS_UNKNOWN; + } + + instance = PyObject_Call(clz, args, kwargs); + if (instance != nullptr) + { + pos = 0; + while (PyDict_Next(extras, &pos, &key, &value)) + { + if (PyObject_GenericSetAttr(instance, key, value) != 0) + { + Py_DECREF(instance); + goto PARSE_CLASS_UNKNOWN; + } + } + } + else + { + PARSE_CLASS_UNKNOWN: + PyErr_Clear(); + // if we still failed to create an instance, fallback to UnknownObject + Py_DECREF(clz); + clz = PyObject_GetAttrString(config->classes, "UnknownObject"); + PyDict_SetItemString(kwargs, "__node__", (PyObject *)node); + // merge extras back into kwargs + if (extras != nullptr) + { + pos = 0; + while (PyDict_Next(extras, &pos, &key, &value)) + { + PyDict_SetItem(kwargs, key, value); + } + } + instance = PyObject_Call(clz, args, kwargs); + } + +PARSE_CLASS_CLEANUP: + Py_DECREF(args); + Py_DECREF(kwargs); + Py_XDECREF(clz); + Py_XDECREF(annotations); + Py_XDECREF(extras); + return instance; +} + +TypeTreeNodeObject *get_ref_type_node(PyObject *ref_object, PyObject *assetsfile) +{ + if (assetsfile == Py_None) + { + PyErr_SetString(PyExc_ValueError, "Reference Type found but no SerializedFile passed as assetsfile to read_typetree!"); + return nullptr; + } + PyObject *ref_types = PyObject_GetAttrString(assetsfile, "ref_types"); + if (!ref_types || !PyList_Check(ref_types)) + { + Py_XDECREF(ref_types); + PyErr_SetString(PyExc_ValueError, "No SerializedFile.ref_types"); + return nullptr; + } + + PyObject *type = PyDict_GetItemString(ref_object, "type"); + if (!type) + { + Py_DECREF(ref_types); + PyErr_SetString(PyExc_ValueError, "Failed to get 'type'"); + return nullptr; + } + + PyObject *cls = nullptr; + PyObject *ns = nullptr; + PyObject *asm_ = nullptr; + if (PyDict_Check(type)) + { + cls = PyDict_GetItemString(type, "class"); + ns = PyDict_GetItemString(type, "ns"); + asm_ = PyDict_GetItemString(type, "asm"); + Py_XINCREF(cls); + Py_XINCREF(ns); + Py_XINCREF(asm_); + } + else + { + cls = PyObject_GetAttrString(type, "class"); + ns = PyObject_GetAttrString(type, "ns"); + asm_ = PyObject_GetAttrString(type, "asm"); + } + + if (!cls || !ns || !asm_) + { + Py_DECREF(ref_types); + Py_XDECREF(cls); + Py_XDECREF(ns); + Py_XDECREF(asm_); + PyErr_SetString(PyExc_ValueError, "Failed to get 'class', 'ns' or 'asm'"); + return nullptr; + } + + if (PyUnicode_GET_LENGTH(cls) == 0) + { + Py_DECREF(ref_types); + Py_DECREF(cls); + Py_DECREF(ns); + Py_DECREF(asm_); + return (TypeTreeNodeObject *)Py_None; + } + + Py_ssize_t ref_types_len = PyList_Size(ref_types); + TypeTreeNodeObject *ref_type_node = nullptr; + for (Py_ssize_t i = 0; i < ref_types_len; i++) + { + PyObject *ref_type = PyList_GetItem(ref_types, i); + PyObject *m_ClassName = PyObject_GetAttrString(ref_type, "m_ClassName"); + PyObject *m_NameSpace = PyObject_GetAttrString(ref_type, "m_NameSpace"); + PyObject *m_AssemblyName = PyObject_GetAttrString(ref_type, "m_AssemblyName"); + if (!m_ClassName || !m_NameSpace || !m_AssemblyName) + { + Py_XDECREF(m_ClassName); + Py_XDECREF(m_NameSpace); + Py_XDECREF(m_AssemblyName); + PyErr_SetString(PyExc_ValueError, "Failed to get 'm_ClassName', 'm_NameSpace' or 'm_AssemblyName'"); + break; + } + + bool compare_cls = (PyUnicode_Compare(cls, m_ClassName) == 0) && (PyUnicode_Compare(ns, m_NameSpace) == 0) && (PyUnicode_Compare(asm_, m_AssemblyName) == 0); + Py_DECREF(m_ClassName); + Py_DECREF(m_NameSpace); + Py_DECREF(m_AssemblyName); + + if (compare_cls) + { + ref_type_node = (TypeTreeNodeObject *)PyObject_GetAttrString(ref_type, "node"); + break; + } + } + + Py_DECREF(ref_types); + Py_XDECREF(cls); + Py_XDECREF(ns); + Py_XDECREF(asm_); + + return ref_type_node; +} + +template +PyObject *read_typetree_value_array(ReaderT *reader, TypeTreeNodeObject *node, TypeTreeReaderConfigT *config, uint32_t size); + +const NodeDataType SUPPORTED_VALUE_ARRAY_READ_TYPES[] = { + NodeDataType::u8, + NodeDataType::u16, + NodeDataType::u32, + NodeDataType::u64, + NodeDataType::s8, + NodeDataType::s16, + NodeDataType::s32, + NodeDataType::s64, + NodeDataType::f32, + NodeDataType::f64, + NodeDataType::boolean, + NodeDataType::pair, +}; + +template +PyObject *read_typetree_value(ReaderT *reader, TypeTreeNodeObject *node, TypeTreeReaderConfigT *config) +{ + bool align = node->_align; + PyObject *value = nullptr; + + switch (node->_data_type) + { + case NodeDataType::u8: + value = read_u8(reader); + break; + case NodeDataType::u16: + value = read_num(reader); + break; + case NodeDataType::u32: + value = read_num(reader); + break; + case NodeDataType::u64: + value = read_num(reader); + break; + case NodeDataType::s8: + value = read_s8(reader); + break; + case NodeDataType::s16: + value = read_num(reader); + break; + case NodeDataType::s32: + value = read_num(reader); + break; + case NodeDataType::s64: + value = read_num(reader); + break; + case NodeDataType::f32: + value = read_num(reader); + break; + case NodeDataType::f64: + value = read_num(reader); + break; + case NodeDataType::boolean: + value = read_bool(reader); + break; + case NodeDataType::str: + value = read_str(reader); + break; + case NodeDataType::bytes: + value = read_bytes(reader); + break; + case NodeDataType::pair: + value = read_pair(reader, node, config); + break; + case NodeDataType::ReferencedObject: + { + value = PyDict_New(); + PyObject *child_value; + for (int i = 0; i < PyList_GET_SIZE(node->m_Children); i++) + { + TypeTreeNodeObject *child = (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, i); + if (child->_data_type == NodeDataType::ReferencedObjectData) + { + TypeTreeNodeObject *ref_node = get_ref_type_node(value, config->assetfile); + if (!ref_node) + { + PyErr_SetString(PyExc_ValueError, "Failed to get ref type node"); + Py_DECREF(value); + return nullptr; + } + else if (ref_node == (TypeTreeNodeObject *)Py_None) + { + Py_DECREF(ref_node); + continue; + } + child_value = read_typetree_value(reader, ref_node, config); + Py_DECREF(ref_node); + } + else + { + child_value = read_typetree_value(reader, child, config); + } + + if (child_value == nullptr) + { + Py_DECREF(value); + return nullptr; + } + if (PyDict_SetItem(value, child->m_Name, child_value)) + { + Py_DECREF(value); + Py_DECREF(child_value); + return nullptr; + } + // dict increases ref count, so we need to decref here + Py_DECREF(child_value); + } + if (!config->as_dict) + { + PyObject *clz = PyObject_GetAttrString(config->classes, "UnknownObject"); + if (clz == nullptr) + { + PyErr_SetString(PyExc_ValueError, "Failed to get class"); + Py_DECREF(value); + return nullptr; + } + PyObject *args = PyTuple_Pack(1, (PyObject *)node); + PyObject *instance = PyObject_Call(clz, args, value); + Py_DECREF(clz); + Py_DECREF(args); + Py_DECREF(value); + value = instance; + } + break; + } + default: + TypeTreeNodeObject *child = nullptr; + if (PyList_GET_SIZE(node->m_Children) > 0) + { + child = (TypeTreeNodeObject *)PyList_GET_ITEM(node->m_Children, 0); + } + + if (child && child->_data_type == NodeDataType::Array) + { + // array + if (PyList_GET_SIZE(child->m_Children) != 2) + { + PyErr_SetString(PyExc_ValueError, "Array node must have 2 children"); + return nullptr; + } + + if (child->_align) + { + align = true; + } + int32_t length; + if (!_read_length(reader, &length)) + { + return nullptr; + } + + child = (TypeTreeNodeObject *)PyList_GET_ITEM(child->m_Children, 1); + if (std::find(std::begin(SUPPORTED_VALUE_ARRAY_READ_TYPES), std::end(SUPPORTED_VALUE_ARRAY_READ_TYPES), child->_data_type) == std::end(SUPPORTED_VALUE_ARRAY_READ_TYPES)) + { + value = PyList_New(length); + if (value == nullptr) + { + return nullptr; + } + for (int i = 0; i < length; i++) + { + PyObject *item = read_typetree_value(reader, child, config); + if (item == nullptr) + { + Py_DECREF(value); + return nullptr; + } + PyList_SET_ITEM(value, i, item); + } + } + else + { + value = read_typetree_value_array(reader, child, config, length); + } + } + else + { + // class + if (config->as_dict) + { + value = read_class(reader, node, config); + } + else + { + value = read_class(reader, node, config); + value = parse_class(value, node, config); + } + } + } + + if (align && value != nullptr) + { + align4(reader); + } + + return value; +} + +template +PyObject *read_typetree_value_array(ReaderT *reader, TypeTreeNodeObject *node, TypeTreeReaderConfigT *config, int32_t count) +{ + bool align = node->_align; + PyObject *value = nullptr; + + switch (node->_data_type) + { + case NodeDataType::u8: + value = read_u8_array(reader, count); + break; + case NodeDataType::u16: + value = read_num_array(reader, count); + break; + case NodeDataType::u32: + value = read_num_array(reader, count); + break; + case NodeDataType::u64: + value = read_num_array(reader, count); + break; + case NodeDataType::s8: + value = read_s8_array(reader, count); + break; + case NodeDataType::s16: + value = read_num_array(reader, count); + break; + case NodeDataType::s32: + value = read_num_array(reader, count); + break; + case NodeDataType::s64: + value = read_num_array(reader, count); + break; + case NodeDataType::f32: + value = read_num_array(reader, count); + break; + case NodeDataType::f64: + value = read_num_array(reader, count); + break; + case NodeDataType::boolean: + value = read_bool_array(reader, count); + break; + case NodeDataType::pair: + value = read_pair_array(reader, node, config, count); + break; + default: + std::string error_msg = "Unsupported type for read_value_array: " + std::to_string(node->_data_type); + PyErr_SetString(PyExc_TypeError, error_msg.c_str()); + value = nullptr; + } + if (align && value != nullptr) + { + align4(reader); + } + + return value; +} + +static inline void set_none_if_null_n_incref(PyObject **field) +{ + if (*field == nullptr) + { + *field = Py_None; + } + Py_INCREF(*field); +} + +static bool is_null_none_or_type(PyObject *obj, PyTypeObject *type, const char *type_name, const char *field_name) +{ + if (obj == nullptr || obj == Py_None || PyObject_TypeCheck(obj, type)) + { + return true; + } + std::string error_msg = "Expected " + std::string(type_name) + " or None for " + std::string(field_name); + PyErr_SetString(PyExc_TypeError, error_msg.c_str()); + return false; +} + +PyObject *read_typetree(PyObject *self, PyObject *args, PyObject *kwargs) +{ + const char *kwlist[] = {"data", "node", "endian", "as_dict", "assetsfile", "classes", nullptr}; + Py_buffer view; + PyObject *node = nullptr; + int as_dict = 1; + PyObject *value = nullptr; + Py_ssize_t bytes_read = 0; + ReaderT reader; + + volatile uint16_t bint = 0x0100; + volatile bool is_big_endian = ((uint8_t *)&bint)[0] == 1; + + TypeTreeReaderConfigT config = { + false, + nullptr, + nullptr, + false, + }; + + char endian; + bool swap; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y*OC|pOO", (char **)kwlist, &view, &node, &endian, &as_dict, &config.assetfile, &config.classes)) + { + goto READ_TYPETREE_CLEANUP; + } + + set_none_if_null_n_incref(&config.assetfile); + set_none_if_null_n_incref(&config.classes); + + config.as_dict = as_dict == 1; + if (!config.as_dict) + { + if (config.classes == Py_None) + { + PyErr_SetString(PyExc_ValueError, "classes must be set if not as dict"); + goto READ_TYPETREE_CLEANUP; + } + } + + switch (endian) + { + case '<': + if (is_big_endian) + { + swap = true; + } + else + { + swap = false; + } + break; + case '>': + if (is_big_endian) + { + swap = false; + } + else + { + swap = true; + } + break; + default: + { + Py_DECREF(config.assetfile); + Py_DECREF(config.classes); + PyErr_SetString(PyExc_ValueError, "Invalid endian"); + return nullptr; + } + } + + reader = {static_cast(view.buf), static_cast(view.buf) + view.len, static_cast(view.buf)}; + + if (swap) + { + value = read_typetree_value(&reader, (TypeTreeNodeObject *)node, &config); + } + else + { + value = read_typetree_value(&reader, (TypeTreeNodeObject *)node, &config); + } + + bytes_read = reader.ptr - reader.start; + +READ_TYPETREE_CLEANUP: + if (view.buf) + { + PyBuffer_Release(&view); + } + Py_XDECREF(config.assetfile); + Py_XDECREF(config.classes); + + return (value != nullptr) ? Py_BuildValue("(Nn)", value, bytes_read) : nullptr; +} + +// TypeTreeNode impl +static void TypeTreeNode_finalize(TypeTreeNodeObject *self) +{ + Py_XDECREF(self->m_Level); + Py_XDECREF(self->m_Type); + Py_XDECREF(self->m_Name); + Py_XDECREF(self->m_ByteSize); + Py_XDECREF(self->m_TypeFlags); + Py_XDECREF(self->m_Version); + Py_XDECREF(self->m_VariableCount); + Py_XDECREF(self->m_Index); + Py_XDECREF(self->m_MetaFlag); + Py_XDECREF(self->m_RefTypeHash); + Py_XDECREF(self->m_Children); + Py_XDECREF(self->_clean_name); +} + +static const std::map typeToNodeDataType = { + {"SInt8", NodeDataType::s8}, + {"UInt8", NodeDataType::u8}, + {"char", NodeDataType::u8}, + {"short", NodeDataType::s16}, + {"SInt16", NodeDataType::s16}, + {"unsigned short", NodeDataType::u16}, + {"UInt16", NodeDataType::u16}, + {"int", NodeDataType::s32}, + {"SInt32", NodeDataType::s32}, + {"unsigned int", NodeDataType::u32}, + {"UInt32", NodeDataType::u32}, + {"Type*", NodeDataType::u32}, + {"long long", NodeDataType::s64}, + {"SInt64", NodeDataType::s64}, + {"unsigned long long", NodeDataType::u64}, + {"UInt64", NodeDataType::u64}, + {"FileSize", NodeDataType::u64}, + {"float", NodeDataType::f32}, + {"double", NodeDataType::f64}, + {"bool", NodeDataType::boolean}, + {"string", NodeDataType::str}, + {"TypelessData", NodeDataType::bytes}, + {"pair", NodeDataType::pair}, + {"Array", NodeDataType::Array}, + {"ReferencedObject", NodeDataType::ReferencedObject}, + {"ReferencedObjectData", NodeDataType::ReferencedObjectData}, + {"ManagedReferencesRegistry", NodeDataType::ManagedReferencesRegistry}, +}; + +static inline NodeDataType get_node_data_type(PyObject *py_type) +{ + if (py_type == Py_None) + { + return NodeDataType::unk; + } + + const char *type = PyUnicode_AsUTF8(py_type); + if (type[0] == 'P' && type[1] == 'P' && type[2] == 't' && type[3] == 'r' && type[4] == '<') + { + return NodeDataType::PPtr; + } + else + { + for (auto it = typeToNodeDataType.begin(); it != typeToNodeDataType.end(); ++it) + { + if (strcmp(it->first, type) == 0) + { + return it->second; + } + } + } + return NodeDataType::unk; +} + +static int TypeTreeNode_init(TypeTreeNodeObject *self, PyObject *args, PyObject *kwargs) +{ + const char *kwlist[] = { + "m_Level", + "m_Type", + "m_Name", + "m_ByteSize", + "m_Version", + "m_Children", + "m_TypeFlags", + "m_VariableCount", + "m_Index", + "m_MetaFlag", + "m_RefTypeHash", + nullptr}; + + // ensure all fields are set to 0 + // in case init fails, so that dealloc doesn't segfault + self->m_Level = nullptr; + self->m_Type = nullptr; + self->m_Name = nullptr; + self->m_ByteSize = nullptr; + self->m_TypeFlags = nullptr; + self->m_Version = nullptr; + self->m_Children = nullptr; + self->m_VariableCount = nullptr; + self->m_Index = nullptr; + self->m_MetaFlag = nullptr; + self->m_RefTypeHash = nullptr; + self->_clean_name = nullptr; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O!O!O!O!|OOOOOO", (char **)kwlist, + // required fields + &PyLong_Type, &self->m_Level, + &PyUnicode_Type, &self->m_Type, + &PyUnicode_Type, &self->m_Name, + &PyLong_Type, &self->m_ByteSize, + &PyLong_Type, &self->m_Version, + // optional fields + &self->m_Children, + &self->m_TypeFlags, + &self->m_VariableCount, + &self->m_Index, + &self->m_MetaFlag, + &self->m_RefTypeHash)) + { + return -1; + } + + // incref to keep values alive + Py_INCREF(self->m_Level); + Py_INCREF(self->m_Type); + Py_INCREF(self->m_Name); + Py_INCREF(self->m_ByteSize); + Py_INCREF(self->m_Version); + + // optional values - can still be nullptr + if (self->m_Children == nullptr || self->m_Children == Py_None) + { + if (self->m_Children == Py_None) + { + // in older Python's Py_None is not immortal + Py_DECREF(self->m_Children); + } + self->m_Children = PyList_New(0); + } + else + { + Py_INCREF(self->m_Children); + } + + if (!is_null_none_or_type(self->m_TypeFlags, &PyLong_Type, "int", "m_TypeFlags") || + !is_null_none_or_type(self->m_VariableCount, &PyLong_Type, "int", "m_VariableCount") || + !is_null_none_or_type(self->m_Index, &PyLong_Type, "int", "m_Index") || + !is_null_none_or_type(self->m_MetaFlag, &PyLong_Type, "int", "m_MetaFlag") || + !is_null_none_or_type(self->m_RefTypeHash, &PyLong_Type, "int", "m_RefTypeHash")) + return -1; + + set_none_if_null_n_incref(&self->m_TypeFlags); + set_none_if_null_n_incref(&self->m_VariableCount); + set_none_if_null_n_incref(&self->m_Index); + set_none_if_null_n_incref(&self->m_MetaFlag); + set_none_if_null_n_incref(&self->m_RefTypeHash); + + // set private fields required for fast access + self->_data_type = get_node_data_type(self->m_Type); + self->_align = (self->m_MetaFlag != Py_None && PyLong_AsLong(self->m_MetaFlag) & 0x4000); + + std::string sname = PyUnicode_AsUTF8(self->m_Name); + std::string sclean_name = clean_name(sname); + self->_clean_name = PyUnicode_FromString(sclean_name.c_str()); // comes with strong ref + return 0; +} + +static PyObject *TypeTreeNode_repr(TypeTreeNodeObject *self) +{ + return PyUnicode_FromFormat( + "TypeTreeNode(m_Level=%R, m_Type=%R, m_Name=%R, m_MetaFlag=%R)", + self->m_Level, + self->m_Type, + self->m_Name, + self->m_MetaFlag); +} + +static PyMemberDef TypeTreeNode_members[] = { + {"m_Level", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_Level), 0, ""}, + {"m_Type", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_Type), 0, ""}, + {"m_Name", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_Name), 0, ""}, + {"m_ByteSize", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_ByteSize), 0, ""}, + {"m_TypeFlags", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_TypeFlags), 0, ""}, + {"m_Version", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_Version), 0, ""}, + {"m_Children", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_Children), 0, ""}, + {"m_VariableCount", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_VariableCount), 0, ""}, + {"m_Index", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_Index), 0, ""}, + {"m_MetaFlag", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_MetaFlag), 0, ""}, + {"m_RefTypeHash", T_OBJECT_EX, offsetof(TypeTreeNodeObject, m_RefTypeHash), 0, ""}, + {"_clean_name", T_OBJECT_EX, offsetof(TypeTreeNodeObject, _clean_name), 0, ""}, + {nullptr} /* Sentinel */ +}; + +static PyTypeObject TypeTreeNodeType = []() -> PyTypeObject +{ + PyTypeObject type = { +#if PY_VERSION_HEX >= 0x03080000 + PyVarObject_HEAD_INIT(nullptr, 0) +#else + PyObject_HEAD_INIT(nullptr) 0 +#endif + }; + type.tp_name = "TypeTreeHelper.TypeTreeNode"; + type.tp_doc = "TypeTreeNode objects"; + type.tp_basicsize = sizeof(TypeTreeNodeObject); + type.tp_itemsize = 0; + type.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE; + type.tp_new = PyType_GenericNew; + type.tp_init = (initproc)TypeTreeNode_init; + type.tp_members = TypeTreeNode_members; + type.tp_finalize = (destructor)TypeTreeNode_finalize; + type.tp_repr = (reprfunc)TypeTreeNode_repr; + return type; +}(); + +int add_typetreenode_to_module(PyObject *m) +{ + if (PyType_Ready(&TypeTreeNodeType) < 0) + return -1; + Py_INCREF(&TypeTreeNodeType); + PyModule_AddObject(m, "TypeTreeNode", (PyObject *)&TypeTreeNodeType); +#if PY_VERSION_HEX >= 0x030e0000 + PyObject *annotationlib = PyImport_ImportModule("annotationlib"); + _get_annotations = PyObject_GetAttrString(annotationlib, "get_annotations"); + PyModule_AddObject(m, "_get_annotations", _get_annotations); // steals ref + Py_INCREF(_get_annotations); + Py_DECREF(annotationlib); +#endif + return 0; +} diff --git a/UnityPyBoost/TypeTreeHelper.hpp b/UnityPyBoost/TypeTreeHelper.hpp new file mode 100644 index 000000000..f46d1ec87 --- /dev/null +++ b/UnityPyBoost/TypeTreeHelper.hpp @@ -0,0 +1,55 @@ +#define PY_SSIZE_T_CLEAN +#pragma once +#include +#include "structmember.h" +#include + +enum NodeDataType +{ + u8 = 0, + u16 = 1, + u32 = 2, + u64 = 3, + s8 = 4, + s16 = 5, + s32 = 6, + s64 = 7, + f32 = 8, + f64 = 9, + boolean = 10, + str = 11, + bytes = 12, + pair = 13, + Array = 14, + PPtr = 15, + ReferencedObject = 16, + ReferencedObjectData = 17, + ManagedReferencesRegistry = 18, + unk = 255 +}; + +typedef struct TypeTreeNodeObject +{ + PyObject_HEAD + // helper field - simple hash of type for faster comparison + NodeDataType _data_type; + bool _align; + PyObject *_clean_name; // str + // used filds for fast access + PyObject *m_Children; // list of TypeTreeNodes + PyObject *m_Name; // str + PyObject *m_Type; // str + // fields not used in C + PyObject *m_Level; // legacy: /, blob: u8 + PyObject *m_ByteSize; // legacy: i32, blob: i32 + PyObject *m_Version; // legacy: i32, blob: i16 + PyObject *m_TypeFlags; // legacy: i32, blob: u8 + PyObject *m_VariableCount; // legacy: i32, blob: / + PyObject *m_Index; // legacy: i32, blob: i32 + PyObject *m_MetaFlag; // legacy: i32, blob: i32 + PyObject *m_RefTypeHash; // legacy: /, blob: u64 +} TypeTreeNodeObject; + +int add_typetreenode_to_module(PyObject *m); + +PyObject *read_typetree(PyObject *self, PyObject *args, PyObject *kwargs); diff --git a/UnityPyBoost/UnityPyBoost.cpp b/UnityPyBoost/UnityPyBoost.cpp new file mode 100644 index 000000000..4b3ed8fa0 --- /dev/null +++ b/UnityPyBoost/UnityPyBoost.cpp @@ -0,0 +1,47 @@ +#define PY_SSIZE_T_CLEAN +#include +#include "Mesh.hpp" +#include "TypeTreeHelper.hpp" +#include "ArchiveStorageDecryptor.hpp" + +/* Mesh.py */ + +static struct PyMethodDef method_table[] = { + {"unpack_vertexdata", + (PyCFunction)unpack_vertexdata, + METH_VARARGS, + "replacement for VertexData to ComponentData in Mesh.ReadVertexData"}, + {"read_typetree", + (PyCFunction)read_typetree, + METH_VARARGS | METH_KEYWORDS, + "replacement for TypeTreeHelper.read_typetree"}, + {"decrypt_block", + (PyCFunction)decrypt_block, + METH_VARARGS, + "replacement for ArchiveStorageDecryptor.decrypt_block"}, + {nullptr, + nullptr, + 0, + nullptr} // Sentinel value ending the table +}; + +// A struct contains the definition of a module +static PyModuleDef UnityPyBoost_module = { + PyModuleDef_HEAD_INIT, + "UnityPyBoost", // Module name + "TODO", + -1, // Optional size of the module state memory + method_table, + nullptr, // Optional slot definitions + nullptr, // Optional traversal function + nullptr, // Optional clear function + nullptr // Optional module deallocation function +}; + +// The module init function +PyMODINIT_FUNC PyInit_UnityPyBoost(void) +{ + PyObject *module = PyModule_Create(&UnityPyBoost_module); + add_typetreenode_to_module(module); + return module; +} diff --git a/UnityPyBoost/swap.hpp b/UnityPyBoost/swap.hpp new file mode 100644 index 000000000..5e2cbc463 --- /dev/null +++ b/UnityPyBoost/swap.hpp @@ -0,0 +1,78 @@ +#if __cplusplus >= 202101L // "C++23"; +#include +#define bswap16(x) std::byteswap(x) +#define bswap32(x) std::byteswap(x) +#define bswap64(x) std::byteswap(x) +#else +// set swap funcions (source: old version of nodejs/src/node_buffer.cc) +#if defined(__GNUC__) || defined(__clang__) +#define bswap16(x) __builtin_bswap16(x) +#define bswap32(x) __builtin_bswap32(x) +#define bswap64(x) __builtin_bswap64(x) +#elif defined(__linux__) +#include +#define bswap16(x) bswap_16(x) +#define bswap32(x) bswap_32(x) +#define bswap64(x) bswap_64(x) +#elif defined(_MSC_VER) +#include +#define bswap16(x) _byteswap_ushort(x) +#define bswap32(x) _byteswap_ulong(x) +#define bswap64(x) _byteswap_uint64(x) +#else +#ifdef __builtin_bswap16 +#define bswap16(x) __builtin_bswap16(x) +#else +#define bswap16 ((x) << 8) | ((x) >> 8) +#endif +#ifdef __builtin_bswap32 +#define bswap32(x) __builtin_bswap32(x) +#else +#define bswap32 \ + (((x) & 0xFF) << 24) | \ + (((x) & 0xFF00) << 8) | \ + (((x) >> 8) & 0xFF00) | \ + (((x) >> 24) & 0xFF) +#endif +#ifdef __builtin_bswap64 +#define bswap64 __builtin_bswap64(x) +#else +#define bswap64 \ + (((x) & 0xFF00000000000000ull) >> 56) | \ + (((x) & 0x00FF000000000000ull) >> 40) | \ + (((x) & 0x0000FF0000000000ull) >> 24) | \ + (((x) & 0x000000FF00000000ull) >> 8) | \ + (((x) & 0x00000000FF000000ull) << 8) | \ + (((x) & 0x0000000000FF0000ull) << 24) | \ + (((x) & 0x000000000000FF00ull) << 40) | \ + (((x) & 0x00000000000000FFull) << 56) +#endif +#endif +#endif + +template +inline void swap_any_inplace(T *x) +{ + if constexpr (sizeof(T) == 1) + { + // do nothing + } + else if constexpr (sizeof(T) == 2) + { + + *(uint16_t *)x = bswap16(*(uint16_t *)x); + } + else if constexpr (sizeof(T) == 4) + { + *(uint32_t *)x = bswap32(*(uint32_t *)x); + } + else if constexpr (sizeof(T) == 8) + { + *(uint64_t *)x = bswap64(*(uint64_t *)x); + } + else + { + // gcc is tripping and somehow reaching this at compile time + // static_assert(false, "Swap not implemented for this size"); + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..cad2658f8 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,91 @@ +[build-system] +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "UnityPy" +authors = [{ name = "Rudolf Kolbe", email = "rkolbe96@gmail.com" }] +description = "A Unity extraction and patching package" +readme = "README.md" +license = { file = "LICENSE" } +requires-python = ">=3.8" +keywords = [ + "python", + "unity", + "unity-asset", + "python3", + "data-mining", + "unitypack", + "assetstudio", + "unity-asset-extractor", +] +classifiers = [ + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", + "Intended Audience :: Developers", + "Development Status :: 5 - Production/Stable", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Games/Entertainment", + "Topic :: Multimedia :: Graphics", +] +dependencies = [ + # block compression/decompression + "lz4", # BundleFile block compression + "brotli", # WebFile compression + # Texture & Sprite handling + "Pillow", + "texture2ddecoder >= 1.0.5", # texture decompression + "etcpak", # ETC & DXT compression + "astc-encoder-py >= 0.1.12", # ASTC compression + # audio extraction + "fmod_toolkit", + # filesystem handling + "fsspec", + # better classes + "attrs", + # tpk handling + "tpk_ar" +] +dynamic = ["version"] + +[project.scripts] +UnityPy = "UnityPy.cli:main" + +[project.optional-dependencies] +# optional dependencies must be lowercase/normalized +ttgen = ["typetreegeneratorapi>=0.0.10"] +full = ["unitypy[ttgen]"] +tests = ["pytest", "pillow", "psutil", "unitypy[full]"] +dev = ["ruff", "unitypy[tests]"] + +[project.urls] +"Homepage" = "https://github.com/K0lb3/UnityPy" +"Bug Tracker" = "https://github.com/K0lb3/UnityPy/issues" + +[tool.setuptools.dynamic] +version = { attr = "UnityPy.__version__" } + +[tool.pytest.ini_options] +testpaths = ["tests"] + +[tool.cibuildwheel.linux] +archs = ["x86_64", "i686"] +# auditwheel issues with fmod on: "aarch64", "armv7l" + +[tool.cibuildwheel.macos] +archs = ["x86_64", "arm64"] + +[tool.cibuildwheel.windows] +archs = ["AMD64", "x86", "ARM64"] + +[tool.pyright] +pythonVersion = "3.8" diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 000000000..db2f2b3f3 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,33 @@ +target-version = "py38" +include = [ + "UnityPy/*.py", + "UnityPy/*.pyi", + "tests/*py", + "setup.py", + "generators/*.py", + "examples/*.py", +] +exclude = ["UnityPy/classes/generated.py"] +line-length = 120 + +[lint] +select = [ + # pycodestyle + "E", + # Pyflakes + "F", + # flake8-bugbear + "B", + # isort + "I", +] + +[lint.pydocstyle] +# Enforce numpy-style docstrings +convention = "numpy" + +[lint.per-file-ignores] +# Ignore docstring requirements for test files +"tests/**/*.py" = ["D"] +# Ignore undefined names +"UnityPy/streams/EndianBinaryReader.py" = ["F821"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 224a77957..000000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[metadata] -description-file = README.md \ No newline at end of file diff --git a/setup.py b/setup.py index e0b423bcc..5ca47e178 100644 --- a/setup.py +++ b/setup.py @@ -1,36 +1,124 @@ -import setuptools -from UnityPy import __version__ as version - -with open("README.md", "r") as fh: - long_description = fh.read() - -setuptools.setup( - name="UnityPy", - packages=setuptools.find_packages(), - version=version, - author="K0lb3", - description="A pythonic port of AssetStudio by Perfare", - long_description=long_description, - long_description_content_type="text/markdown", - url="https://github.com/K0lb3/UnityPy", - download_url="https://github.com/K0lb3/UnityPy/tarball/master", - keywords=['Unity', 'unitypack', 'UnityPy', "unpack"], - classifiers=[ - "License :: OSI Approved :: MIT License", - "Operating System :: OS Independent", - "Intended Audience :: Developers", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", - "Topic :: Multimedia :: Graphics", - ], - install_requires=[ - "lz4", - "brotli", - "Pillow", - "fsb5", - "texture2ddecoder" - ] +import os +import re +import subprocess +from typing import Literal, cast, get_args + +from setuptools import Extension, find_packages, setup +from setuptools.command.build_ext import build_ext +from setuptools.command.sdist import sdist + +try: + from setuptools.command.bdist_wheel import bdist_wheel +except ImportError: + from wheel.bdist_wheel import bdist_wheel # type: ignore + +System = Literal["Windows", "Linux", "Darwin"] +Arch = Literal["x64", "x86", "arm", "arm64", "x86_64"] + +INSTALL_DIR = os.path.dirname(os.path.realpath(__file__)) +UNITYPYBOOST_DIR = os.path.join(INSTALL_DIR, "UnityPyBoost") + + +class BuildExt(build_ext): + def build_extensions(self): + cpp_version_flag: str + compiler = self.compiler + # msvc - only ever used c++20, never c++2a + if compiler.compiler_type == "msvc": + cpp_version_flag = "/std:c++20" + # gnu & clang + elif compiler.compiler_type == "unix": + res = subprocess.run( + [compiler.compiler[0], "-v"], + capture_output=True, + ) + # for some reason g++ and clang++ return this as error + text = (res.stdout or res.stderr).decode("utf-8") + version = re.search(r"version\s+(\d+)\.", text) + if version is None: + raise Exception("Failed to determine compiler version") + version = int(version.group(1)) + cpp_version_flag = "-std=c++2a" if version < 10 else "-std=c++20" + else: + cpp_version_flag = "-std=c++20" + + for ext in self.extensions: + ext.extra_compile_args = [cpp_version_flag] + + build_ext.build_extensions(self) + + +class SDist(sdist): + def make_distribution(self) -> None: + # add all fmod libraries to the distribution + for root, _dirs, files in os.walk("UnityPy/lib/FMOD"): + for file in files: + fp = f"{root}/{file}" + if fp not in self.filelist.files: + self.filelist.files.append(fp) + return super().make_distribution() + + +BDIST_TAG_FMOD_MAP = { + # Windows + "win32": "x86", + "win_amd64": "x64", + "win_arm64": "arm", + # Linux and Mac endings + "arm64": "arm64", # Mac + "x86_64": "x64", + "aarch64": "arm64", # Linux + "i686": "x86", + "armv7l": "arm", # armhf +} + + +def get_fmod_path(system: System, arch: Arch) -> str: + if system == "Darwin": + # universal dylib + return "lib/FMOD/Darwin/libfmod.dylib" + + if system == "Windows": + return f"lib/FMOD/Windows/{arch}/fmod.dll" + + if system == "Linux": + if arch == "x64": + arch = "x86_64" + return f"lib/FMOD/Linux/{arch}/libfmod.so" + + raise NotImplementedError(f"Unsupported system: {system}") + + +class BDistWheel(bdist_wheel): # type: ignore + def run(self): + platform_tag = self.get_tag()[2] + if platform_tag.startswith("win"): + system = "Windows" + arch = BDIST_TAG_FMOD_MAP[platform_tag] + else: + arch = next( + (v for k, v in BDIST_TAG_FMOD_MAP.items() if platform_tag.endswith(k)), + None, + ) + system = "Darwin" if platform_tag.startswith("macosx") else "Linux" + + if arch and arch in get_args(Arch): + self.distribution.package_data["UnityPy"].append(get_fmod_path(system, cast(Arch, arch))) + super().run() + + +setup( + name="UnityPy", + packages=find_packages(), + package_data={"UnityPy": ["resources/lzma.tpk"]}, + ext_modules=[ + Extension( + "UnityPy.UnityPyBoost", + [f"UnityPyBoost/{f}" for f in os.listdir(UNITYPYBOOST_DIR) if f.endswith(".cpp")], + depends=[f"UnityPyBoost/{f}" for f in os.listdir(UNITYPYBOOST_DIR) if f.endswith(".hpp")], + language="c++", + include_dirs=[UNITYPYBOOST_DIR], + ) + ], + cmdclass={"build_ext": BuildExt, "sdist": SDist, "bdist_wheel": BDistWheel}, ) diff --git a/tests/samples/atlas_test b/tests/samples/atlas_test index faa7e996c..979701586 100644 Binary files a/tests/samples/atlas_test and b/tests/samples/atlas_test differ diff --git a/tests/samples/banner_1 b/tests/samples/banner_1 index 132d0e3bb..de0f8dff4 100644 Binary files a/tests/samples/banner_1 and b/tests/samples/banner_1 differ diff --git a/tests/samples/char_118_yuki.ab b/tests/samples/char_118_yuki.ab new file mode 100644 index 000000000..9c8d21674 --- /dev/null +++ b/tests/samples/char_118_yuki.ab @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6198cb71c7a820256208332bbd375d21b18ed1a36a12ad04be1eef2111b014a1 +size 704951 diff --git a/tests/samples/xinzexi_2_n_tex b/tests/samples/xinzexi_2_n_tex new file mode 100644 index 000000000..4c21cba5a --- /dev/null +++ b/tests/samples/xinzexi_2_n_tex @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a9f6e6bba7110cce647ef752d66655d13ce5850b013a60d80d4e8b08953e02ea +size 634283 diff --git a/tests/samples/xinzexi_2_n_tex_mesh b/tests/samples/xinzexi_2_n_tex_mesh new file mode 100644 index 000000000..785dbd862 --- /dev/null +++ b/tests/samples/xinzexi_2_n_tex_mesh @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8bb2082b9586b3f8f3bad2d22d33346ec0492053f2a76323aa8b4b4047368ca8 +size 44643 diff --git a/tests/test_UnityVersion.py b/tests/test_UnityVersion.py new file mode 100644 index 000000000..a01255633 --- /dev/null +++ b/tests/test_UnityVersion.py @@ -0,0 +1,74 @@ +import pytest + +from UnityPy.helpers.UnityVersion import UnityVersion, UnityVersionType + + +@pytest.mark.parametrize( + "version_str, expected_tuple", + [ + ("2018.1.1f2", (2018, 1, 1, UnityVersionType.f.value, 2)), + ("5.0.0", (5, 0, 0, UnityVersionType.f.value, 0)), + ("2020.3.12b1", (2020, 3, 12, UnityVersionType.b.value, 1)), + ("2019.4.28a3", (2019, 4, 28, UnityVersionType.a.value, 3)), + ("2017.2.0p1", (2017, 2, 0, UnityVersionType.p.value, 1)), + ("2021.1.0c1", (2021, 1, 0, UnityVersionType.c.value, 1)), + ("2022.2.0x1", (2022, 2, 0, UnityVersionType.x.value, 1)), + ("2018.1.1z2", (2018, 1, 1, UnityVersionType.u.value, 2)), # unknown type + ("2022.3.62f2\n2", (2022, 3, 62, UnityVersionType.f.value, 2)), + ], +) +def test_parse_unity_version(version_str, expected_tuple): + v = UnityVersion.from_str(version_str) + assert v.as_tuple() == expected_tuple + assert v.major == expected_tuple[0] + assert v.minor == expected_tuple[1] + assert v.build == expected_tuple[2] + assert v.type.value == expected_tuple[3] + assert v.type_number == expected_tuple[4] + assert UnityVersion.from_list(*expected_tuple) == v + + +@pytest.mark.parametrize( + "version_str, compare_tuple", + [ + ("2018.1.1f2", (2018, 1, 1, UnityVersionType.f.value, 2)), + ("2018.1.1f2", (2018, 1, 1, UnityVersionType.f.value, 1)), + ("2018.1.1f2", (2018, 1, 2, UnityVersionType.f.value, 2)), + ("2018.1.1f2", (2018, 2, 1, UnityVersionType.f.value, 2)), + ], +) +def test_comparison_with_tuple(version_str, compare_tuple): + v = UnityVersion.from_str(version_str) + # eq + assert (v == compare_tuple) == (v.as_tuple() == compare_tuple) + # ne + assert (v != compare_tuple) == (v.as_tuple() != compare_tuple) + # lt + assert (v < compare_tuple) == (v.as_tuple() < compare_tuple) + # le + assert (v <= compare_tuple) == (v.as_tuple() <= compare_tuple) + # gt + assert (v > compare_tuple) == (v.as_tuple() > compare_tuple) + # ge + assert (v >= compare_tuple) == (v.as_tuple() >= compare_tuple) + + +@pytest.mark.parametrize( + "version_str, other_str", + [ + ("2018.1.1f2", "2018.1.1f2"), + ("2018.1.1f2", "2018.1.1f1"), + ("2018.1.1f2", "2018.1.2f2"), + ("2018.1.1f2", "2018.2.1f2"), + ("2022.3.62f2\n2", "2022.3.62f2"), + ], +) +def test_comparison_with_unityversion(version_str, other_str): + v1 = UnityVersion.from_str(version_str) + v2 = UnityVersion.from_str(other_str) + assert (v1 == v2) == (v1.as_tuple() == v2.as_tuple()) + assert (v1 != v2) == (v1.as_tuple() != v2.as_tuple()) + assert (v1 < v2) == (v1.as_tuple() < v2.as_tuple()) + assert (v1 <= v2) == (v1.as_tuple() <= v2.as_tuple()) + assert (v1 > v2) == (v1.as_tuple() > v2.as_tuple()) + assert (v1 >= v2) == (v1.as_tuple() >= v2.as_tuple()) diff --git a/tests/test_extractor.py b/tests/test_extractor.py new file mode 100644 index 000000000..11e92c7ce --- /dev/null +++ b/tests/test_extractor.py @@ -0,0 +1,26 @@ +import os +from tempfile import TemporaryDirectory + +from UnityPy.tools.extractor import extract_assets + +SAMPLES = os.path.join(os.path.dirname(os.path.abspath(__file__)), "samples") + + +def test_extractor(): + temp_dir = TemporaryDirectory(prefix="unitypy_test") + extract_assets( + SAMPLES, + temp_dir.name, + True, + ) + files = [ + os.path.relpath(os.path.join(root, f), temp_dir.name) + for root, dirs, files in os.walk(temp_dir.name) + for f in files + ] + temp_dir.cleanup() + assert len(files) == 45 + + +if __name__ == "__main__": + test_extractor() diff --git a/tests/test_main.py b/tests/test_main.py index a134b6997..792e3626d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,38 +1,126 @@ +import io import os +import platform + +from PIL import Image + +import UnityPy +from UnityPy.streams import EndianBinaryReader SAMPLES = os.path.join(os.path.dirname(os.path.abspath(__file__)), "samples") -def test_import(): - import UnityPy -def test_read(): - import UnityPy +def test_read_single(): for f in os.listdir(SAMPLES): - am = UnityPy.AssetsManager(os.path.join(SAMPLES, f)) - for asset in am.assets.values(): - for obj in asset.objects.values(): - obj.read() - + env = UnityPy.load(os.path.join(SAMPLES, f)) + for obj in env.objects: + obj.parse_as_object() + obj.parse_as_dict() + + +def test_read_batch(): + env = UnityPy.load(SAMPLES) + for obj in env.objects: + obj.parse_as_object() + obj.parse_as_dict() + + +def test_save_dict(): + env = UnityPy.load(SAMPLES) + for obj in env.objects: + data = obj.get_raw_data() + item = obj.parse_as_dict() + assert isinstance(item, dict) + re_data = obj.patch(item) + assert data == re_data + + +def test_save_wrap(): + env = UnityPy.load(SAMPLES) + for obj in env.objects: + data = obj.get_raw_data() + item = obj.parse_as_object() + assert not isinstance(item, dict) + re_data = obj.patch(item) + assert data == re_data + + def test_texture2d(): - import UnityPy for f in os.listdir(SAMPLES): - am = UnityPy.AssetsManager(os.path.join(SAMPLES, f)) - for asset in am.assets.values(): - for obj in asset.objects.values(): - if obj.type == "Texture2D": - obj.read().image.save("test.png") + env = UnityPy.load(os.path.join(SAMPLES, f)) + for obj in env.objects: + if obj.type.name == "Texture2D": + data = obj.parse_as_object() + data.image.save(io.BytesIO(), format="PNG") + data.image = data.image.transpose(Image.ROTATE_90) + data.save() + def test_sprite(): - import UnityPy for f in os.listdir(SAMPLES): - am = UnityPy.AssetsManager(os.path.join(SAMPLES, f)) - for asset in am.assets.values(): - for obj in asset.objects.values(): - if obj.type == "Sprite": - obj.read().image.save("test.png") + env = UnityPy.load(os.path.join(SAMPLES, f)) + for obj in env.objects: + if obj.type.name == "Sprite": + sprite = obj.parse_as_object() + sprite.image.save(io.BytesIO(), format="PNG") + + +if platform.system() == "Darwin": + # crunch issue on macos leading to segfault + del test_texture2d + del test_sprite + + +def test_audioclip(): + from fmod_toolkit.importer import import_pyfmodex + + try: + import_pyfmodex() + except ValueError: + print("FMOD toolkit not available, skipping AudioClip tests") + return + + env = UnityPy.load(os.path.join(SAMPLES, "char_118_yuki.ab")) + for obj in env.objects: + if obj.type.name == "AudioClip": + clip = obj.parse_as_object() + assert len(clip.samples) == 1 + + +def test_mesh(): + env = UnityPy.load(os.path.join(SAMPLES, "xinzexi_2_n_tex")) + with open(os.path.join(SAMPLES, "xinzexi_2_n_tex_mesh"), "rb") as f: + wanted = f.read().replace(b"\r", b"") + for obj in env.objects: + if obj.type.name == "Mesh": + mesh = obj.parse_as_object() + data = mesh.export() + if isinstance(data, str): + data = data.encode("utf8").replace(b"\r", b"") + assert data == wanted + + +def test_read_typetree(): + env = UnityPy.load(SAMPLES) + for obj in env.objects: + obj.read_typetree() + + +def test_save(): + env = UnityPy.load(SAMPLES) + # TODO - check against original + # this only makes sure + # that the save function still produces a readable file + for name, file in env.files.items(): + if isinstance(file, EndianBinaryReader): + continue + save1 = file.save() + save2 = UnityPy.load(save1).file.save() + assert save1 == save2, f"Failed to save {name} correctly" + if __name__ == "__main__": for x in list(locals()): if str(x)[:4] == "test": locals()[x]() - input("All Tests Passed") \ No newline at end of file + input("All Tests Passed") diff --git a/tests/test_typetree.py b/tests/test_typetree.py new file mode 100644 index 000000000..f4a403e7b --- /dev/null +++ b/tests/test_typetree.py @@ -0,0 +1,208 @@ +import gc +import math +import os +import random +from typing import List, Tuple, Type, TypeVar, Union + +import psutil + +from UnityPy.classes.generated import GameObject +from UnityPy.helpers.Tpk import get_typetree_node +from UnityPy.helpers.TypeTreeHelper import read_typetree, write_typetree +from UnityPy.helpers.TypeTreeNode import TypeTreeNode +from UnityPy.helpers.UnityVersion import UnityVersion +from UnityPy.streams import EndianBinaryReader, EndianBinaryWriter + +PROCESS = psutil.Process(os.getpid()) + + +def get_memory(): + gc.collect() + return PROCESS.memory_info().rss + + +def check_leak(func): + def wrapper(*args, **kwargs): + mem_0 = get_memory() + func(*args, **kwargs) + mem_1 = get_memory() + diff = mem_1 - mem_0 + if diff != 0: + diff %= 4096 + assert diff == 0, f"Memory leak in {func.__name__}" + + return wrapper + + +TEST_NODE_STR = "TestNode" + + +@check_leak +def test_typetreenode(): + TypeTreeNode(m_Level=0, m_Type=TEST_NODE_STR, m_Name=TEST_NODE_STR, m_ByteSize=0, m_Version=0) + + +def generate_dummy_node(typ: str, name: str = ""): + return TypeTreeNode(m_Level=0, m_Type=typ, m_Name=name, m_ByteSize=0, m_Version=0) + + +SIMPLE_NODE_SAMPLES = [ + (["SInt8"], int, (-(2**7), 2**7)), + (["SInt16", "short"], int, (-(2**15), 2**15)), + (["SInt32", "int"], int, (-(2**31), 2**31)), + (["SInt64", "long long"], int, (-(2**63), 2**63)), + (["UInt8", "char"], int, (0, 2**8)), + (["UInt16", "unsigned short"], int, (0, 2**16)), + (["UInt32", "unsigned int", "Type*"], int, (0, 2**32)), + (["UInt64", "unsigned long long", "FileSize"], int, (0, 2**64)), + (["float"], float, (-1, 1)), + (["double"], float, (-1, 1)), + (["bool"], bool, (False, True)), +] + +T = TypeVar("T") + +INT_BYTESIZE_MAP = { + 1: "b", + 2: "h", + 4: "i", + 8: "q", +} + + +def generate_sample_data( + u_type: List[str], + py_typ: Type[Union[int, float, str]], + bounds: Tuple[T, T], + count: int = 10, +) -> List[T]: + if py_typ is int: + if bounds[0] < 0: + # signed + byte_size = math.log2(bounds[1]) + 1 + signed = True + elif bounds[0] == 0: + # unsigned + byte_size = math.log2(bounds[1]) + signed = False + + byte_size = round(byte_size / 8) + char = INT_BYTESIZE_MAP[byte_size] + if not signed: + char = char.upper() + + sample_values = [ + bounds[0], + *[random.randint(bounds[0], bounds[1] - 1) for _ in range(count)], + bounds[1] - 1, + ] + # sample_data = pack(f"<{count+2}{char}", *sample_values) + + elif py_typ is float: + sample_values = [ + bounds[0], + *[random.uniform(bounds[0], bounds[1]) for _ in range(count)], + bounds[1], + ] + char = "f" if u_type == "float" else "d" + # sample_data = pack(f"<{count+2}f", *sample_values) + + elif py_typ is bool: + sample_values = [ + bounds[0], + *[random.choice([True, False]) for _ in range(count)], + bounds[1], + ] + # sample_data = pack(f"<{count+2}?", *sample_values) + + elif py_typ is str: + raise NotImplementedError("String generation not implemented") + + elif py_typ is bytes: + raise NotImplementedError("Bytes generation not implemented") + + return sample_values + + +def _test_read_typetree(node: TypeTreeNode, data: bytes, as_dict: bool): + reader = EndianBinaryReader(data, "<") + py_values = read_typetree(node, reader, as_dict=as_dict, check_read=False) + reader.Position = 0 + cpp_values = read_typetree(node, reader, as_dict=as_dict, byte_size=len(data)) + assert py_values == cpp_values + return py_values + + +@check_leak +def test_simple_nodes(): + for typs, py_typ, bounds in SIMPLE_NODE_SAMPLES: + values = generate_sample_data(typs, py_typ, bounds) + for typ in typs: + node = generate_dummy_node(typ) + for value in values: + writer = EndianBinaryWriter(b"", "<") + write_typetree(value, node, writer) + raw = writer.bytes + re_value = _test_read_typetree(node, raw, as_dict=True) + assert abs(value - re_value) < 1e-5, f"Failed on {typ}: {value} != {re_value}" + + +@check_leak +def test_simple_nodes_array(): + def generate_list_node(item_node: TypeTreeNode): + root = generate_dummy_node("root", "root") + array = generate_dummy_node("Array", "Array") + array.m_Children = [None, item_node] + root.m_Children = [array] + return root + + for typs, py_typ, bounds in SIMPLE_NODE_SAMPLES: + values = generate_sample_data(typs, py_typ, bounds) + for typ in typs: + node = generate_dummy_node(typ) + array_node = generate_list_node(node) + writer = EndianBinaryWriter(b"", "<") + write_typetree(values, array_node, writer) + raw = writer.bytes + re_values = _test_read_typetree(array_node, raw, as_dict=True) + assert all((abs(value - re_value) < 1e-5) for value, re_value in zip(values, re_values)), ( + f"Failed on {typ}: {values} != {re_values}" + ) + + +TEST_CLASS_NODE = get_typetree_node(1, UnityVersion.from_list(5, 0, 0, 0)) +TEST_CLASS_NODE_OBJ = GameObject(m_Component=[], m_IsActive=True, m_Layer=0, m_Name="TestObject", m_Tag=0) +TEST_CLASS_NODE_DICT = TEST_CLASS_NODE_OBJ.__dict__ + + +def test_class_node_dict(): + writer = EndianBinaryWriter(b"", "<") + write_typetree(TEST_CLASS_NODE_DICT, TEST_CLASS_NODE, writer) + raw = writer.bytes + re_value = _test_read_typetree(TEST_CLASS_NODE, raw, as_dict=True) + assert re_value == TEST_CLASS_NODE_DICT + + +def test_class_node_clz(): + writer = EndianBinaryWriter(b"", "<") + write_typetree(TEST_CLASS_NODE_OBJ, TEST_CLASS_NODE, writer) + raw = writer.bytes + re_value = _test_read_typetree(TEST_CLASS_NODE, raw, as_dict=False) + assert re_value == TEST_CLASS_NODE_OBJ + + +def test_node_from_list_clz(): + node = TypeTreeNode.from_list(list(TEST_CLASS_NODE.traverse())) + assert node == TEST_CLASS_NODE + + +def test_node_from_list_dict(): + node = TypeTreeNode.from_list(TEST_CLASS_NODE.to_dict_list()) + assert node == TEST_CLASS_NODE + + +if __name__ == "__main__": + for x in list(locals()): + if str(x)[:4] == "test": + locals()[x]() + input("All Tests Passed")