diff --git a/.editorconfig b/.editorconfig index 83ffe4cde..b8fb7609b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -10,6 +10,7 @@ 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/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 88ad43f57..700306b52 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -9,59 +9,89 @@ # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # -name: "CodeQL" +name: "CodeQL Advanced" on: push: - branches: [ master ] + branches: [ "master" ] pull_request: - # The branches below must be a subset of the branches above - branches: [ master ] + branches: [ "master" ] schedule: - - cron: '37 5 * * 5' + - cron: '19 5 * * 0' jobs: analyze: - name: Analyze - runs-on: ubuntu-latest + 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: - language: [ 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] - # Learn more: - # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed - + 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@v2 + uses: actions/checkout@v4 + with: + submodules: recursive + lfs: true # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + 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. - # queries: ./path/to/local/query, your-org/your-repo/queries@main - # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v1 + # 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. - # 📚 https://git.io/JvXDl - - # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines - # and modify them (or add more) to build your code if your project - # uses a compiled language - - #- run: | - # make bootstrap - # make release + # 📚 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@v1 + 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 ed25cc8a1..000000000 --- a/.github/workflows/python-package.yml +++ /dev/null @@ -1,71 +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.9", - "3.8", - "3.7", - "3.6", - ] - architecture: ["x86", "x64"] - - timeout-minutes: 30 - - runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} ${{ matrix.architecture }} - ${{ matrix.python-version }} - env: - DEPENDENCIES: lz4 brotli Pillow texture2ddecoder tabulate - - 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 wheel setuptools twine - pip install pytest ${{ env.DEPENDENCIES }} - - - name: Install module - run: | - python setup.py install - - - name: Test with pytest - run: | - pytest -v -s - - - name: Build Python wheel - if: success() && runner.os != 'Linux' && github.event_name == 'push' - run: | - python setup.py sdist bdist_wheel - - - name: Build manylinux Python wheels - if: success() && runner.os == 'Linux' && github.event_name == 'push' - uses: RalfG/python-wheels-manylinux-build@v0.3.3-manylinux2010_x86_64 - with: - python-versions: 'cp36-cp36m cp37-cp37m cp38-cp38 cp39-cp39' - build-requirements: ${{ env.DEPENDENCIES }} - - - name: Publish wheels to PyPI - if: success() && github.event_name == 'push' - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: | - 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 6fb3e5542..85f418724 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,16 @@ +# test files and others +test.py +AssetStudio/ +.vscode/ +uv.lock + # Byte-compiled / optimized / DLL files __pycache__/ -.dump -.idea/ -*.py[cod] +*.py[codz] *$py.class -test.py -AssetStudio/ + +# C extensions +*.so # Distribution / packaging .Python @@ -15,18 +20,21 @@ dist/ downloads/ eggs/ .eggs/ +lib/ +lib64/ 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 @@ -37,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 @@ -54,6 +66,7 @@ coverage.xml *.log local_settings.py db.sqlite3 +db.sqlite3-journal # Flask stuff: instance/ @@ -66,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/ @@ -101,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/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 6bc7be1f7..bf224f947 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2019-2021 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 c6bf48637..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,3 +0,0 @@ -include UnityPy/* - -recursive-include UnityPy/lib *.dll *.lib *.so *.dylib \ No newline at end of file diff --git a/README.md b/README.md index 2e59f92ba..baf92789d 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,67 @@ # 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) -![Test and Publish](https://github.com/K0lb3/UnityPy/workflows/Test%20and%20Publish/badge.svg) +[![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). -Next to extraction it also supports editing Unity assets. -So far following obj types can be edited: - - Texture2D - - Sprite(indirectly via linked Texture2D) - - TextAsset - - MonoBehaviour +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 @@ -41,18 +69,24 @@ python setup.py install #### 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 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: @@ -64,12 +98,12 @@ def unpack_all_assets(source_folder : str, destination_folder : str): # iterate over internal objects for obj in env.objects: # process specific object types - if obj.type in ["Texture2D", "Sprite"]: + if obj.type.name in ["Texture2D", "Sprite"]: # parse the object data - data = obj.read() + data = obj.parse_as_object() # create destination path - dest = os.path.join(destination_folder, data.name) + 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 @@ -81,8 +115,8 @@ def unpack_all_assets(source_folder : str, destination_folder : str): # alternative way which keeps the original path for path,obj in env.container.items(): - if obj.type in ["Texture2D", "Sprite"]: - data = obj.read() + 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 @@ -96,27 +130,26 @@ def unpack_all_assets(source_folder : str, destination_folder : str): You probably have to read [Important Classes](#important-classes) and [Important Object Types](#important-object-types) to understand how it works. -People who have slightly advanced python skills should take a look at [examples/AssetBatchConverter.py](examples/AssetBatchConverter.py) for a more advanced example. -It can also be used as general template. - +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 -### [Environment](UnityPy/environment.py) +### Environment -Environment 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 ``.files``, which is a dict consisting of ``asset-name : asset``. +The unpacked assets will be loaded into `.files`, a dict consisting of `asset-name : asset`. -The all objects of the loaded assets can be easily accessed via ``.objects``, +All objects of the loaded assets can be easily accessed via `.objects`, which itself is a simple recursive iterator. ```python @@ -141,168 +174,363 @@ with open(dst, "wb") as f: f.write(env.file.save()) ``` -### [Asset](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 which have a file path can be found in the ``.container`` dict - ``{path : object}``. +The objects with a file path can be found in the `.container` dict - `{path : object}`. -### [Object](UnityPy/ObjectReader.py) +### Object -Objects contain the *actual* files which, e.g. textures, text files, meshes, settings, ... +Objects \([ObjectReader class](UnityPy/files/ObjectReader.py)\) contain the _actual_ files, e.g., textures, text files, meshes, settings, ... + +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. + +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. + +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. Serialized objects can be set with raw data using ``.set_raw_data(data)`` or modified with ``.save()`` function if supported. ## Important Object Types -All object types can be found in [UnityPy/classes](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 -### [Texture2D](UnityPy/classes/Texture2D.py) +- `.m_Name` +- `.image` converts the texture into a `PIL.Image` +- `.m_Width` - texture width (int) +- `.m_Height` - texture height (int) -* ``.name`` -* ``.image`` converts the texture into a ``PIL.Image`` -* ``.m_Width`` - texture width (int) -* ``.m_Height`` - texture height (int) +**Export** -__Export__ ```python from PIL import Image for obj in env.objects: - if obj.type == "Texture2D": + if obj.type.name == "Texture2D": # export texture - data = image.read() - data.image.save(path) + 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, data.name) + fp = os.path.join(replace_dir, f"{tex.m_Name}.png") pil_img = Image.open(fp) - data.image = pil_img - data.save() + tex.image = pil_img + tex.save() ``` -### [Sprite](UnityPy/classes/Sprite.py) +### 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. -* ``.name`` -* ``.image`` - converts the merged texture part into a ``PIL.Image`` -* ``.m_Width`` - sprite width (int) -* ``.m_Height`` - sprite height (int) +- `.m_Name` +- `.image` - converts the merged texture part into a `PIL.Image` +- `.m_Width` - sprite width (int) +- `.m_Height` - sprite height (int) + +**Export** -__Export__ ```python for obj in env.objects: - if obj.type == "Sprite": - data = image.read() - data.image.save(path) + 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](UnityPy/classes/TextAsset.py) +### 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. -Some games save binary data as TextFile, so it's usually better to use ``.script``. +**Export** -__Export__ ```python for obj in env.objects: - if obj.type == "TextAsset": + if obj.type.name == "TextAsset": # export asset - data = image.read() + txt = obj.parse_as_object() + path = os.path.join(export_dir, f"{txt.m_Name}.txt") with open(path, "wb") as f: - f.write(bytes(data.script)) + f.write(txt.m_Script.encode("utf-8", "surrogateescape")) # edit asset - fp = os.path.join(replace_dir, data.name) + fp = os.path.join(replace_dir, f"{txt.m_Name}.txt") with open(fp, "rb") as f: - data.script = f.read() - data.save() + txt.m_Script = f.read().decode("utf-8", "surrogateescape") + txt.save() ``` -### [MonoBehaviour](UnityPy/classes/MonoBehaviour.py) +### MonoBehaviour MonoBehaviour assets are usually used to save the class instances with their values. -If a type tree exists it can be used to read the whole data, -but if it doesn't, then it is usually necessary to investigate the class that loads the specific MonoBehaviour to extract the data. -([example](examples/CustomMonoBehaviour/get_scriptable_texture.py)) +The structure/typetree for these classes might not be contained in the asset files. +In such cases see the 2nd example (TypeTreeGenerator) below. + +- `.m_Name` +- `.m_Script` +- custom data -* ``.name`` -* ``.script`` -* ``.raw_data`` - data after the basic initialisation +**Export** -__Export__ ```python import json for obj in env.objects: - if obj.type == "MonoBehaviour": + if obj.type.name == "MonoBehaviour": # export - if obj.serialized_type.nodes: - # save decoded data - tree = obj.read_typetree() - fp = os.path.join(extract_dir, f"{data.name}.json"): - with open(fp, "wt", encoding = "utf8") as f: - json.dump(tree, f, ensure_ascii = False, indent = 4) - else: - # save raw relevant data (without Unity MonoBehaviour header) - data = obj.read() - fp = os.path.join(extract_dir, f"{data.name}.bin"): - with open(fp, "wb") as f: - f.write(data.raw_data) + # 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 - if obj.serialized_type.nodes: - tree = obj.read_typetree() - # apply modifications to the data within the tree - obj.save_typetree(tree) - else: - with open(replace_dir, data.name) as f: - data.save(raw_data = f.read()) + tree = obj.parse_as_dict() + # apply modifications to the data within the tree + obj.patch(tree) +``` + +**TypeTreeGenerator** + +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](UnityPy/classes/AudioClip.py) -* ``.samples`` - ``{sample-name : sample-data}`` +### AudioClip + +- `.samples` - `{sample-name : sample-data}` The samples are converted into the .wav format. -The sample-data is a .wav file in bytes. +The sample data is a .wav file in bytes. ```python -clip : AudioClip +clip: AudioClip for name, data in clip.samples.items(): with open(name, "wb") as f: f.write(data) ``` -### [Mesh](UnityPy/classes/Mesh.py) +### 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) +- `.export()` - mesh exported as .obj (str) -The mesh is converted into an Wavefront .obj file. +The mesh will be converted to the Wavefront .obj file format. ```python -mesh : Mesh -with open(f"{mesh.name}.obj", "wt", newline = "") as f: +mesh: Mesh +with open(f"{mesh.m_Name}.obj", "wt", newline = "") as f: # newline = "" is important f.write(mesh.export()) ``` -### [Font](UnityPy/classes/Font.py) +### 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 -if obj.type == "Font": - font : Font = obj.read() - if font.m_FontData: - extension = ".ttf" - if font.m_FontData[0:4] == b"OTTO": - extension = ".otf" +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) +``` - with open(os.path.join(path, font.name+extension), "wb") as f: - f.write(font.m_FontData) +### 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 ``` + +- `custom_compress(data: bytes) -> bytes` (where bytes can also be bytearray or memoryview) +- `custom_decompress(data: bytes, uncompressed_size: int) -> bytes` + +### Custom Filesystem + +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. + +Following methods of the filesystem have to be implemented for using it in UnityPy. + +- `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` + +## Credits + +First of all, +thanks a lot to all contributors of UnityPy and all of its users. + +Also, many thanks to: + +- [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/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 a273abbe8..44828896e 100644 --- a/UnityPy/__init__.py +++ b/UnityPy/__init__.py @@ -1,11 +1,11 @@ -__version__ = "1.7.2" +__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 3006fcee4..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.m_Animation = PPtr(reader) - num_animations = reader.read_int() - self.m_Animations = [PPtr(reader) for _ in range(num_animations)] diff --git a/UnityPy/classes/AnimationClip.py b/UnityPy/classes/AnimationClip.py deleted file mode 100644 index c68bbd600..000000000 --- a/UnityPy/classes/AnimationClip.py +++ /dev/null @@ -1,599 +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 save(self, writer): - writer.write_u_int(self.m_NumItems) - writer.write_float(self.m_Range) - writer.write_float(self.m_Start) - - writer.write_int(len(self.m_Data)) - writer.write_bytes(self.m_Data) - writer.align_stream() - - writer.write_byte(self.m_BitSize) - writer.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 self.m_Range else float('inf') - if numChunks == -1: - numChunks = self.m_NumItems // itemCountInChunk - end = int(chunkStride * numChunks / 4) - data = [] - for index in range(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 - denomi = scale * ((1 << self.m_BitSize) - 1) - data.append( (x / denomi if denomi else float('inf')) + 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 save(self, writer): - writer.write_u_int(self.m_NumItems) - - writer.write_int(len(self.m_Data)) - writer.write_bytes(self.m_Data) - writer.align_stream() - - writer.write_byte(self.m_BitSize) - writer.align_stream() - - def UnpackInts(self): - data = [0] * self.m_NumItems - 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 = [None]*self.m_NumItems - 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() - - def save(self, writer): - writer.write_vector3(self.m_Center) - writer.write_vector3(self.m_Extent) - - -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('inf') - - 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 a134cb865..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.m_Avatar = PPtr(reader) # Avatar - self.m_Controller = PPtr(reader) # RuntimeAnimatorController - self.m_CullingMode = reader.read_int() - version = self.version - - if version >= (4, 5): # 4.5 and up - self.m_UpdateMode = reader.read_int() - - self.m_ApplyRootMotion = reader.read_boolean() - if (4, 5) < version[2:] <= (5, 0): # 4.5 and up - 5.0 down - reader.align_stream() - - if version >= (5,): # 5.0 and up - self.m_LinearVelocityBlending = reader.read_boolean() - reader.align_stream() - - if version[2:] < (4, 5): # 4.5 down - self.m_AnimatePhysics = reader.read_boolean() - - if version >= (4, 3): # 4.3 and up - self.m_HasTransformHierarchy = reader.read_boolean() - - if version >= (4, 5): # 4.5 and up - self.m_AllowConstantClipSamplingOptimization = 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 0b8999e34..000000000 --- a/UnityPy/classes/AnimatorController.py +++ /dev/null @@ -1,324 +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 0544d3149..000000000 --- a/UnityPy/classes/AnimatorOverrideController.py +++ /dev/null @@ -1,17 +0,0 @@ -from .PPtr import PPtr -from .RuntimeAnimatorController import RuntimeAnimatorController - - -class AnimationClipOverride: - def __init__(self, reader): - self.m_OriginalClip = PPtr(reader) - self.m_OverrideClip = PPtr(reader) - - -class AnimatorOverrideController(RuntimeAnimatorController): - def __init__(self, reader): - super().__init__(reader=reader) - self.m_Controller = PPtr(reader) - num_overrides = reader.read_int() - self.m_Clips = [AnimationClipOverride( - reader) for _ in range(num_overrides)] diff --git a/UnityPy/classes/AssetBundle.py b/UnityPy/classes/AssetBundle.py deleted file mode 100644 index d4fd87c8e..000000000 --- a/UnityPy/classes/AssetBundle.py +++ /dev/null @@ -1,22 +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.m_PreloadTable = [PPtr(reader) for _ in range(preload_table_size)] - container_size = reader.read_int() - self.m_Container = {} - # TODO - m_Container is a multi-dict, multiple values can have the same key - for i in range(container_size): - key = reader.read_aligned_string() - self.m_Container[key] = AssetInfo(reader) diff --git a/UnityPy/classes/AudioClip.py b/UnityPy/classes/AudioClip.py deleted file mode 100644 index 3f8bb0128..000000000 --- a/UnityPy/classes/AudioClip.py +++ /dev/null @@ -1,60 +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() - self.m_Size = reader.read_int() - tsize = self.m_Size + 4 - self.m_Size % 4 if (self.m_Size % 4 != 0) else self.m_Size - if reader.byte_size + reader.byte_start - reader.Position != tsize: - self.m_Offset = reader.read_u_int() - self.m_Source = self.assets_file.full_name + ".resS" - else: - self.m_Size = reader.read_int() - - 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() - self.m_Offset = reader.read_u_long() - self.m_Size = reader.read_long() - self.m_CompressionFormat = AudioCompressionFormat(reader.read_int()) - - if self.m_Source: - self.m_AudioData = get_resource_data( - self.m_Source, self.assets_file, self.m_Offset, self.m_Size - ) - else: - self.m_AudioData = reader.read_bytes(self.m_Size) - - @property - def extension(self): - return AUDIO_TYPE_EXTEMSION.get(self.m_CompressionFormat, ".audioclip") - - @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 973770b64..000000000 --- a/UnityPy/classes/Avatar.py +++ /dev/null @@ -1,156 +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 0e0bb4bac..000000000 --- a/UnityPy/classes/Behaviour.py +++ /dev/null @@ -1,16 +0,0 @@ -from .Component import Component -from ..streams import EndianBinaryReader, EndianBinaryWriter - -class Behaviour(Component): - def __init__(self, reader : EndianBinaryReader): - super().__init__(reader=reader) - self.m_Enabled = reader.read_byte() - reader.align_stream() - - def save(self, writer: EndianBinaryWriter = None): - if writer is None: - writer = EndianBinaryWriter(endian=self.reader.endian) - version = self.version - super().save(writer) - writer.write_byte(self.m_Enabled) - writer.align_stream() \ No newline at end of file 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 f36abfe83..000000000 --- a/UnityPy/classes/Component.py +++ /dev/null @@ -1,16 +0,0 @@ -from .EditorExtension import EditorExtension -from .PPtr import PPtr, save_ptr -from ..streams import EndianBinaryReader, EndianBinaryWriter - - -class Component(EditorExtension): - def __init__(self, reader: EndianBinaryReader): - super().__init__(reader=reader) - self.m_GameObject = PPtr(reader) # GameObject - - def save(self, writer: EndianBinaryWriter = None): - if writer is None: - writer = EndianBinaryWriter(endian=self.reader.endian) - version = self.version - super().save(writer) - save_ptr(self.m_GameObject, writer) diff --git a/UnityPy/classes/EditorExtension.py b/UnityPy/classes/EditorExtension.py deleted file mode 100644 index 4d01b55b8..000000000 --- a/UnityPy/classes/EditorExtension.py +++ /dev/null @@ -1,20 +0,0 @@ -from .Object import Object -from .PPtr import PPtr, save_ptr -from ..enums import BuildTarget -from ..streams import EndianBinaryWriter - - -class EditorExtension(Object): - def __init__(self, reader): - super().__init__(reader=reader) - if self.platform == BuildTarget.NoTarget: - self.m_PrefabParentObject = PPtr(reader) - self.m_PrefabInternal = PPtr(reader) - - def save(self, writer: EndianBinaryWriter = None): - if not writer: - writer = EndianBinaryWriter(endian = self.reader.endian) - super().save(writer, intern_call=True) - if self.platform == BuildTarget.NoTarget: - save_ptr(self.m_PrefabParentObject, writer) - save_ptr(self.m_PrefabInternal, writer) diff --git a/UnityPy/classes/Font.py b/UnityPy/classes/Font.py deleted file mode 100644 index 0241b4782..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.m_LineSpacing = reader.read_float() - self.m_DefaultMaterial = PPtr(reader) - self.m_FontSize = reader.read_float() - self.m_Texture = PPtr(reader) - self.m_AsciiStartOffset = reader.read_int() - self.m_Tracking = reader.read_float() - self.m_CharacterSpacing = reader.read_int() - self.m_CharacterPadding = reader.read_int() - self.m_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.m_PixelScale = reader.read_float() - FontData_size = reader.read_int() - if FontData_size > 0: - self.m_FontData = reader.read_bytes(FontData_size) - else: - self.m_AsciiStartOffset = reader.read_int() - - if version[:1] <= (3,): - self.m_FontCountX = reader.read_int() - self.m_FontCountY = reader.read_int() - - self.m_Kerning = reader.read_float() - self.m_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.m_CharacterSpacing = reader.read_int() - self.m_CharacterPadding = reader.read_int() - - self.m_ConvertCase = reader.read_int() - self.m_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.m_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.m_GridFont = reader.read_boolean() - reader.align_stream() - else: - self.m_PixelScale = reader.read_float() - FontData_size = reader.read_int() - if FontData_size > 0: - self.m_FontData = reader.read_bytes(FontData_size) diff --git a/UnityPy/classes/GameObject.py b/UnityPy/classes/GameObject.py deleted file mode 100644 index 838f649d5..000000000 --- a/UnityPy/classes/GameObject.py +++ /dev/null @@ -1,15 +0,0 @@ -from .EditorExtension import EditorExtension -from .PPtr import PPtr - - -class GameObject(EditorExtension): - def __init__(self, reader): - super().__init__(reader=reader) - component_size = reader.read_int() - self.m_Components = [None]*component_size - for i in range(component_size): - if self.version < (5, 5): - first = reader.read_int() - self.m_Components[i] = PPtr(reader) - self.m_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 214c84780..000000000 --- a/UnityPy/classes/Mesh.py +++ /dev/null @@ -1,1020 +0,0 @@ -import math -import traceback - -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 -from ..streams import EndianBinaryWriter -from ..enums import GfxPrimitiveType -import struct -from enum import IntEnum -from ..export.MeshExporter import export_mesh - - -class MinMaxAABB: - def __init__(self, reader): - self.m_Min = reader.read_vector3() - self.m_Max = reader.read_vector3() - - def save(self, writer): - writer.write_vector3(self.m_Min) - writer.write_vector3(self.m_Max) - - -class CompressedMesh: - def __init__(self, reader): - version = reader.version - self.m_Vertices = PackedFloatVector(reader) - self.m_UV = PackedFloatVector(reader) - if version[0] < 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[0] < 5: # 5 down - self.m_Colors = PackedIntVector(reader) - else: - self.m_UVInfo = reader.read_u_int() - - def save(self, writer, version): - self.m_Vertices.save(writer) - self.m_UV.save(writer) - if version < (5,): # 5 down - self.m_BindPoses.save(writer) - self.m_Normals.save(writer) - self.m_Tangents.save(writer) - self.m_Weights.save(writer) - self.m_NormalSigns.save(writer) - self.m_TangentSigns.save(writer) - if version >= (5,): # 5 and up - self.m_FloatColors.save(writer) - self.m_BoneIndices.save(writer) - self.m_Triangles.save(writer) - if version >= (3, 5): # 3.5 and up - if version < (5,): # 5 down - self.m_Colors.save(writer) - else: - writer.write_u_int(self.m_UVInfo) - - -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 - - def save(self, writer: EndianBinaryWriter, version: tuple): - writer.write_u_int(self.channelMask) - writer.write_u_int(self.offset) - - if version < (4,): # 4.0 down - writer.write_u_int(self.stride) - writer.write_u_int(self.align) - else: - writer.write_byte(self.stride) - writer.write_byte(self.dividerOp) - writer.write_u_short(self.frequency) - - -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() - - def save(self, writer): - writer.write_byte(self.stream) - writer.write_byte(self.offset) - writer.write_byte(self.format) - writer.write_byte(self.dimension) - - -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 save(self, writer: EndianBinaryWriter, version): - if version < (2018,): # 2018 down - writer.write_u_int(self.m_CurrentChannels) - - writer.write_u_int(self.m_VertexCount) - - if version >= (4,): # 4.0 and up - writer.write_int(len(self.m_Channels)) - for ch in self.m_Channels: - ch.save(writer) - - if (4,) <= version[:2] < (5,): # 4.0 and up to 5.0 - writer.write_int(len(self.m_Streams)) - - for stream in self.m_Streams: - stream.save(writer=writer, version=version) - - if version < (4,): # 4.0 down - raise Exception("Unsupported version") - else: # 5.0 and up - # for stream in self.m_Streams: - # stream.save(writer) - pass - - writer.write_int(len(self.m_DataSize)) - writer.write_bytes(self.m_DataSize) - writer.align_stream() - - def GetStreams(self): - streamCount = 1 - if self.m_Channels: - streamCount += 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.GetFormatSize( - self.reader.version, - 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.GetFormatSize( - self.reader.version, m_Channel.format - ) - - -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 - - def save(self, writer): - writer.write_float_array(self.weight) - writer.write_int_array(self.boneIndex) - - -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 = GfxPrimitiveType(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) - - def save(self, writer, version): - writer.write_u_int(self.firstByte) - writer.write_u_int(self.indexCount) - writer.write_int(self.topology.value) - - if version < (4,): # 4.0 down - writer.write_u_int(self.triangleCount) - - if version >= (2017, 3): # 2017.3 and up - writer.write_u_int(self.baseVertex) - - if version >= (3,): # 3.0 and up - writer.write_u_int(self.firstVertex) - writer.write_u_int(self.vertexCount) - self.localAABB.save(writer) - - -class Mesh(NamedObject): - - def export(self): - return export_mesh(self) - - def __init__(self, reader): - super().__init__(reader=reader) - version = reader.version - - self.m_Use16BitIndices = True - self.m_Indices = [] - self.m_BindPose = [] - self.m_BoneNameHashes = [] - self.m_Vertices = [] - self.m_Skin = [] - self.m_Normals = [] - self.m_Colors = [] - self.m_UV0 = [] - self.m_UV1 = [] - self.m_UV2 = [] - self.m_UV3 = [] - self.m_UV4 = [] - self.m_UV5 = [] - self.m_UV6 = [] - self.m_UV7 = [] - self.m_Tangents = [] - - 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( - math.ceil(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 - # fixed after 2017.3.1px - or version[:3] == (2017, 3, 1) and self.build_type.IsPatch - # 2017.3.xfx with no compression - or version[:2] == (2017, 3) and self.m_MeshCompression == 0 - ): - self.m_IndexFormat = reader.read_int() - self.m_Use16BitIndices = self.m_IndexFormat == 0 - - 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( - math.ceil(m_IndexBuffer_size / 4)) - - if version < (3, 5): # 3.4.2 and earlier - self.m_VertexCount = reader.read_int() - self.m_Vertices = reader.read_float_array( - self.m_VertexCount * 3) # Vector3 - - self.m_SkinSize = reader.read_int() - self.m_Skin = [BoneWeights4(reader) - for _ in range(self.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() - # handedness - self.m_Tangents[v * 3 + 3] = reader.read_float() - 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[:2] < (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() / 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, version) - - try: - self.ProcessData() - except Exception as e: - print(traceback.format_exc()) - 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 >= (3, 5): # 3.5 and up - self.ReadVertexData() - - if version >= (2, 6): # 2.6.0 and later - self.DecompressCompressedMesh() - - self.GetTriangles() - - def ReadVertexData(self): - version = self.version - m_VertexData = self.m_VertexData - m_VertexCount = self.m_VertexCount = m_VertexData.m_VertexCount - - for chn, m_Channel in enumerate(m_VertexData.m_Channels): - if m_Channel.dimension > 0: - m_Stream = m_VertexData.m_Streams[m_Channel.stream] - channelMask = bin(m_Stream.channelMask)[::-1] - if channelMask[chn] == "1": - if version[0] < 2018 and chn == 2 and m_Channel.format == 2: - m_Channel.dimension = 4 - - componentByteSize = MeshHelper.GetFormatSize( - version, m_Channel.format) - componentBytes = bytearray( - m_VertexCount * m_Channel.dimension * componentByteSize) - for v in range(m_VertexCount): - vertexOffset = int( - m_Stream.offset) + m_Channel.offset + int(m_Stream.stride) * v - for d in range(m_Channel.dimension): - componentOffsetSrc = vertexOffset + componentByteSize * d # src offset - componentOffsetDst = componentByteSize * \ - (v * m_Channel.dimension + d) # dst offst - - buff = m_VertexData.m_DataSize[componentOffsetSrc: - componentOffsetSrc + componentByteSize] - - if self.reader.endian == "<" and componentByteSize > 1: # swap bytes - buff = buff[::-1] - - componentBytes[componentOffsetDst: componentOffsetDst + - componentByteSize] = buff - # Buffer.BlockCopy(m_VertexData.m_DataSize, componentOffset, componentBytes, componentByteSize * (v * m_Channel.dimension + d), componentByteSize); - # (Array src, int srcOffset, Array dst, int dstOffset, int count); - - if MeshHelper.IsIntFormat(version, m_Channel.format): - componentsIntArray = MeshHelper.BytesToIntArray( - componentBytes, componentByteSize) - else: - componentsFloatArray = MeshHelper.BytesToFloatArray( - componentBytes, componentByteSize) - - if version[0] >= 2018: - 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 - elif chn == 8: # kShaderChannelTexCoord4 - self.m_UV4 = componentsFloatArray - elif chn == 9: # kShaderChannelTexCoord5 - self.m_UV5 = componentsFloatArray - elif chn == 10: # kShaderChannelTexCoord6 - self.m_UV6 = componentsFloatArray - elif chn == 11: # kShaderChannelTexCoord7 - self.m_UV7 = componentsFloatArray - # 2018.2 and up - elif chn == 12: # kShaderChannelBlendWeight - if not self.m_Skin: - 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 not self.m_Skin: - 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[0] >= 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: - self.m_VertexCount = int( - m_CompressedMesh.m_Vertices.m_NumItems / 3) - self.m_Vertices = m_CompressedMesh.m_Vertices.UnpackFloats( - 3, 3 * 4) - m_VertexCount = self.m_VertexCount - # UV - if m_CompressedMesh.m_UV.m_NumItems > 0: # - - m_UVInfo = m_CompressedMesh.m_UVInfo - if m_UVInfo != 0: - kInfoBitsPerUV = 4 - kUVDimensionMask = 3 - kUVChannelExists = 4 - kMaxTexCoordShaderChannels = 8 - - uvSrcOffset = 0 - - for uv in range(kMaxTexCoordShaderChannels): - texCoordBits = m_UVInfo >> (uv * kInfoBitsPerUV) - texCoordBits &= (1 << kInfoBitsPerUV) - 1 - if (texCoordBits & kUVChannelExists) != 0: - uvDim = 1 + int(texCoordBits & kUVDimensionMask) - m_UV = m_CompressedMesh.m_UV.UnpackFloats( - uvDim, uvDim * 4, uvSrcOffset, self.m_VertexCount) - self.SetUV(uv, m_UV) - else: - self.m_UV0 = m_CompressedMesh.m_UV.UnpackFloats( - 2, 2*4, 0, m_VertexCount) - if m_CompressedMesh.m_UV.m_NumItems >= m_VertexCount * 4: # - self.m_UV1 = m_CompressedMesh.m_UV.UnpackFloats( - 2, 2 * 4, m_VertexCount * 2, 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 * 2 + 0] - y = normalData[i * 2 + 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 - j += 1 - - bonePos += 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.UnpackInts() - # 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 GetTriangles(self): - m_IndexBuffer = self.m_IndexBuffer - m_Indices = self.m_Indices - - for m_SubMesh in self.m_SubMeshes: - firstIndex = m_SubMesh.firstByte // 2 - if not self.m_Use16BitIndices: - firstIndex //= 2 - - indexCount = m_SubMesh.indexCount - topology = m_SubMesh.topology - if topology == GfxPrimitiveType.kPrimitiveTriangles: - m_Indices.extend( - m_IndexBuffer[firstIndex: firstIndex + indexCount * 3]) - - elif self.version[0] < 4 or topology == GfxPrimitiveType.kPrimitiveTriangleStrip: - # de-stripify : - triIndex = 0 - for i in range(indexCount - 2): - a, b, c = m_IndexBuffer[firstIndex + i: firstIndex + i + 3] - - # skip degenerates - if a == b or a == c or b == c: - continue - - # do the winding flip-flop of strips : - m_Indices.extend( - [b, a, c] if ((i & 1) == 1) else [a, b, c] - ) - triIndex += 3 - # fix indexCount - m_SubMesh.indexCount = triIndex - - elif topology == GfxPrimitiveType.kPrimitiveQuads: - for q in range(0, indexCount, 4): - m_Indices.extend([ - m_IndexBuffer[firstIndex + q], - m_IndexBuffer[firstIndex + q + 1], - m_IndexBuffer[firstIndex + q + 2], - m_IndexBuffer[firstIndex + q], - m_IndexBuffer[firstIndex + q + 2], - m_IndexBuffer[firstIndex + q + 3] - ]) - # fix indexCount - m_SubMesh.indexCount = indexCount // 2 * 3 - - else: - raise NotImplementedError( - "Failed getting triangles. Submesh topology is lines or points.") - - def InitMSkin(self): - self.m_Skin = [BoneWeights4() for _ in range(self.m_VertexCount)] - - def SetUV(self, uv: int, m_UV): - if uv == 0: - self.m_UV0 = m_UV - elif uv == 1: - self.m_UV1 = m_UV - elif uv == 2: - self.m_UV2 = m_UV - elif uv == 3: - self.m_UV3 == m_UV - elif uv == 4: - self.m_UV4 == m_UV - elif uv == 5: - self.m_UV5 == m_UV - elif uv == 6: - self.m_UV6 == m_UV - elif uv == 7: - self.m_UV7 == m_UV - else: - raise IndexError("Out of Range") - - def GetUV(self, uv: int): - if uv == 0: - return self.m_UV0 - elif uv == 1: - return self.m_UV1 - elif uv == 2: - return self.m_UV2 - elif uv == 3: - return self.m_UV3 - elif uv == 4: - return self.m_UV4 - elif uv == 5: - return self.m_UV5 - elif uv == 6: - return self.m_UV6 - elif uv == 7: - return self.m_UV7 - else: - raise IndexError("Out of Range") - - -class MeshHelper: - @staticmethod - def GetFormatSize(version, format: int) -> int: - if version[0] < 2017: - if format == VertexChannelFormat.kChannelFormatFloat: - return 4 - elif format == VertexChannelFormat.kChannelFormatFloat16: - return 2 - elif format == VertexChannelFormat.kChannelFormatColor: # in 4.x is size 4 - return 1 - elif format == VertexChannelFormat.kChannelFormatByte: - return 1 - elif format == VertexChannelFormat.kChannelFormatUInt32: # in 5.x - return 4 - elif version[0] < 2019: - if format == VertexFormat.kVertexFormatFloat: - return 4 - elif format == VertexFormat.kVertexFormatFloat16: - return 2 - elif format == VertexFormat.kVertexFormatColor: - return 1 - elif format == VertexFormat.kVertexFormatUNorm8: - return 1 - elif format == VertexFormat.kVertexFormatSNorm8: - return 1 - elif format == VertexFormat.kVertexFormatUNorm16: - return 2 - elif format == VertexFormat.kVertexFormatSNorm16: - return 2 - elif format == VertexFormat.kVertexFormatUInt8: - return 1 - elif format == VertexFormat.kVertexFormatSInt8: - return 1 - elif format == VertexFormat.kVertexFormatUInt16: - return 2 - elif format == VertexFormat.kVertexFormatSInt16: - return 2 - elif format == VertexFormat.kVertexFormatUInt32: - return 4 - elif format == VertexFormat.kVertexFormatSInt32: - return 4 - else: - if format == VertexFormatV2019.kVertexFormatFloat: - return 4 - elif format == VertexFormatV2019.kVertexFormatFloat16: - return 2 - elif format == VertexFormatV2019.kVertexFormatUNorm8: - return 1 - elif format == VertexFormatV2019.kVertexFormatSNorm8: - return 1 - elif format == VertexFormatV2019.kVertexFormatUNorm16: - return 2 - elif format == VertexFormatV2019.kVertexFormatSNorm16: - return 2 - elif format == VertexFormatV2019.kVertexFormatUInt8: - return 1 - elif format == VertexFormatV2019.kVertexFormatSInt8: - return 1 - elif format == VertexFormatV2019.kVertexFormatUInt16: - return 2 - elif format == VertexFormatV2019.kVertexFormatSInt16: - return 2 - elif format == VertexFormatV2019.kVertexFormatUInt32: - return 4 - elif format == VertexFormatV2019.kVertexFormatSInt32: - return 4 - raise ValueError(format) - - @staticmethod - def IsIntFormat(version, format: int) -> bool: - if version[0] < 2017: - return format == 4 - elif version[0] < 2019: - return format >= 7 - else: - return format >= 6 - - @staticmethod - def BytesToFloatArray(inputBytes, size): - return [ - inputBytes[i] / 255.0 if size == 1 else - struct.unpack(">e", inputBytes[i * 2: i * 2 + 2])[0] if size == 2 else - struct.unpack(">f", inputBytes[i * 4: i * 4 + 4])[0] if size == 4 else - 0 - for i in range(len(inputBytes) // size) - ] - - @staticmethod - def BytesToIntArray(inputBytes, size): - return [ - int.from_bytes(inputBytes[i: i + size], - byteorder="big", signed=True) - for i in range(0, len(inputBytes), size) - ] - - -class VertexChannelFormat(IntEnum): - kChannelFormatFloat = 0 - kChannelFormatFloat16 = 1 - kChannelFormatColor = 2 - kChannelFormatByte = 3 - kChannelFormatUInt32 = 4 - - -class VertexFormat(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 VertexFormatV2019(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 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 d2b54a749..000000000 --- a/UnityPy/classes/MonoBehaviour.py +++ /dev/null @@ -1,37 +0,0 @@ -from .Behaviour import Behaviour -from .PPtr import PPtr, save_ptr -from ..streams import EndianBinaryReader, EndianBinaryWriter - - -class MonoBehaviour(Behaviour): - def __init__(self, reader: EndianBinaryReader): - super().__init__(reader=reader) - self.m_Script = PPtr(reader) - self.name = reader.read_aligned_string() - - self._raw_offset = reader.Position - if self.assets_file._enable_type_tree: - self.read_typetree() - - def save(self, writer: EndianBinaryWriter = None, raw_data: bytes = None): - if writer is None: - writer = EndianBinaryWriter(endian=self.reader.endian) - if not raw_data: - ValueError("No raw data given") - - super().save(writer) - save_ptr(self.m_Script, writer) - writer.write_aligned_string(self.name) - writer.write(raw_data) - - self.set_raw_data(writer.bytes) - - @property - def raw_data(self) -> bytes: - """ - Reads the undocumentated data following the default init. - This is usefull for classes that are stored via MonoBehaviours. - """ - reader = self.reader - reader.Position = self._raw_offset - return reader.read_bytes(reader.byte_size - (self._raw_offset - reader.byte_start)) diff --git a/UnityPy/classes/MonoScript.py b/UnityPy/classes/MonoScript.py deleted file mode 100644 index 6635100ae..000000000 --- a/UnityPy/classes/MonoScript.py +++ /dev/null @@ -1,23 +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.m_ExecutionOrder = reader.read_int() - if version < (5,): # 5.0 down - self.m_PropertiesHash = reader.read_u_int() - else: - self.m_PropertiesHash = reader.read_bytes(16) - if version < (3,): # 3.0 down - self.m_PathName = reader.read_aligned_string() - - self.m_ClassName = reader.read_aligned_string() - if version >= (3,): # 3.0 and up - self.m_Namespace = reader.read_aligned_string() - - self.m_AssemblyName = reader.read_aligned_string() - if version < (2018, 2): # 2018.2 down - self.m_IsEditorScript = reader.read_boolean() diff --git a/UnityPy/classes/MovieTexture.py b/UnityPy/classes/MovieTexture.py deleted file mode 100644 index e27af18d3..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.m_Loop = reader.read_boolean() - reader.align_stream() - self.m_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 fd36ca45d..000000000 --- a/UnityPy/classes/NamedObject.py +++ /dev/null @@ -1,15 +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 = None): - if not writer: - writer = EndianBinaryWriter(endian=self.reader.endian) - super().save(writer) - writer.write_aligned_string(self.name) diff --git a/UnityPy/classes/Object.py b/UnityPy/classes/Object.py index ed8521f84..c6ea23738 100644 --- a/UnityPy/classes/Object.py +++ b/UnityPy/classes/Object.py @@ -1,176 +1,59 @@ -from .PPtr import PPtr -from ..enums import BuildTarget -from ..helpers import TypeTreeHelper -from ..streams import EndianBinaryWriter -from ..files import ObjectReader -import types +from __future__ import annotations +from abc import ABC, ABCMeta +from typing import TYPE_CHECKING, Any, Dict, Optional -class Object(object): - type_tree: dict +if TYPE_CHECKING: + from ..files.ObjectReader import ObjectReader + from ..files.SerializedFile import SerializedFile - def __init__(self, reader: ObjectReader): - 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.assets_file = reader.assets_file - 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.read_typetree() + 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_typetree(self, nodes: list = None) -> str: - return self.reader.dump_typetree(nodes=nodes) + @property + def assets_file(self) -> Optional[SerializedFile]: + if self.object_reader: + return self.object_reader.assets_file + return None - def dump_typetree_structure(self) -> str: - return self.reader.dump_typetree_structure() + def save(self) -> None: + if self.object_reader is None: + raise ValueError("ObjectReader not set") - def read_typetree(self, nodes: list = None) -> dict: - tree = self.reader.read_typetree() - self.type_tree = NodeHelper(tree, self.assets_file) - return tree + self.object_reader.save_typetree(self) - def save_typetree(self, nodes: list = None, writer: EndianBinaryWriter = None): - if not writer: - writer = EndianBinaryWriter(endian=self.reader.endian) + 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 class_to_dict(value): - if isinstance(value, list): - return [class_to_dict(val) for val in value] - elif isinstance(value, dict): - return { - key: class_to_dict(val) - for key, val in value.items() - } - elif hasattr(value, "__dict__"): - if isinstance(value, PPtr): - return {"m_PathID": value.path_id, "m_FileID": value.file_id} - return { - key: class_to_dict(val) - for key, val in value.__dict__.items() - if not isinstance(value, (types.FunctionType, types.MethodType)) and not key in ["type_tree", "assets_file"] - } - else: - return value + 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 - obj = class_to_dict(self if not self.type_tree else self.type_tree) - return self.reader.save_typetree(obj, nodes, writer) - def get_raw_data(self) -> bytes: - return self.reader.get_raw_data() - - def set_raw_data(self, data): - self.reader.set_raw_data(data) - - def save(self, writer: EndianBinaryWriter = None, intern_call=False): - if not writer: - writer = EndianBinaryWriter(endian=self.reader.endian) - if intern_call: - if self.platform == BuildTarget.NoTarget: - writer.write_u_int(self._object_hide_flags) - elif self.serialized_type.nodes: - # save for objects WITHOUT specific save function - # so we have to use the typetree if it exists - self.save_typetree() - else: - raise NotImplementedError( - "There is no save function for this obj.type nor has it any typetree nodes that could be used.") - - def _save(self, writer): - # the reader is actually an ObjectReader, - # the data value is written back into the asset - self.reader.data = writer.bytes - - def __getattr__(self, item): - """ - If item not found in __dict__, read type_tree and check if it is in there. - """ - if item == "type_tree" or self.type_tree == None: - old_pos = self.reader.Position - return self.read_typetree() - self.Reader.Position = old_pos - - ret = getattr(self.type_tree, item, None) - if ret: - return ret - else: - raise KeyError() - - def get(self, key, default=None): - return getattr(self, key, default) - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.name) - - -class NodeHelper: - 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__ = { - 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 __getitem__(self, item): - return getattr(self, item) - - 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 items(self): - return self.__dict__.items() - - def values(self): - return self.__dict__.values() - - def keys(self): - return self.__dict__.keys() - - def __repr__(self): - return "" % self.__dict__.__repr__() +__all__ = [ + "Object", +] diff --git a/UnityPy/classes/PPtr.py b/UnityPy/classes/PPtr.py index f1b5e4dd6..313e62745 100644 --- a/UnityPy/classes/PPtr.py +++ b/UnityPy/classes/PPtr.py @@ -1,73 +1,112 @@ -from ..files import ObjectReader -from ..streams import EndianBinaryWriter -from ..helpers import ImportHelper -from .. import files -from ..enums import FileType - -def save_ptr(obj, writer: EndianBinaryWriter): - if isinstance(obj, PPtr): - writer.write_int(obj.file_id) - else: - writer.write_int(0) # it's usually 0...... - if obj._version < 14: - writer.write_int(obj.path_id) - else: - writer.write_long(obj.path_id) - -cached_managers = dict() - -class PPtr: - def __init__(self, reader: ObjectReader): - self._version = reader.version2 - self.index = -2 - self.file_id = reader.read_int() - self.path_id = reader.read_int() if self._version < 14 else reader.read_long() - self.assets_file = reader.assets_file - self._obj = None - - def save(self, writer: EndianBinaryWriter): - save_ptr(self, writer) - - def get_obj(self): - if self._obj != None: - return self._obj - 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 - parent = self.assets_file.parent - if parent is not None: - if external_name not in parent.files: - external_name = external_name.upper() - if external_name in parent.files: - manager = parent.files[external_name] - else: - if external_name not in cached_managers: - typ, reader = ImportHelper.check_file_type(external_name) - if typ == FileType.AssetsFile: - cached_managers[external_name] = files.SerializedFile(reader) - if external_name in cached_managers: - manager = cached_managers[external_name] +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 + + # 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 manager and self.path_id in manager.objects: - self._obj = manager.objects[self.path_id] + 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: - self._obj = None + # 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!") - return self._obj + if assetsfile_dst is None: + raise FileNotFoundError(f"Failed to resolve pointer - {self.m_FileID} not found!") - def __getattr__(self, key): - obj = self.get_obj() - if obj is None: - raise AttributeError(key) - return getattr(obj, key) + return cast("ObjectReader[T]", assetsfile_dst.objects[self.m_PathID]) - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self._obj.__class__.__repr__(self.get_obj()) if self.get_obj() else "Not Found") + 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 True if self.get_obj() else False + 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 5cdc9ad4e..000000000 --- a/UnityPy/classes/Renderer.py +++ /dev/null @@ -1,92 +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.read_byte() - 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/ResourceManager.py b/UnityPy/classes/ResourceManager.py deleted file mode 100644 index 2f490caf2..000000000 --- a/UnityPy/classes/ResourceManager.py +++ /dev/null @@ -1,23 +0,0 @@ -from .Object import Object -from .PPtr import PPtr, save_ptr -from ..streams import EndianBinaryReader, EndianBinaryWriter - - -class ResourceManager(Object): - def __init__(self, reader): - super().__init__(reader) - m_ContainerSize = reader.read_int() - self.m_Container = { - reader.read_aligned_string(): PPtr(reader) - for _ in range(m_ContainerSize) - } - - def save(self, writer: EndianBinaryWriter = None): - if not writer: - writer = EndianBinaryWriter(endian=self.reader.endian) - super().save(writer, intern_call=True) - writer.write_int(len(self.m_Container)) - for key, val in self.m_Container.items(): - writer.write_aligned_string(key) - save_ptr(val, writer) - return writer 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 735c161ba..000000000 --- a/UnityPy/classes/Shader.py +++ /dev/null @@ -1,393 +0,0 @@ -from enum import IntEnum - -from .NamedObject import NamedObject -from ..export.ShaderConverter import export_shader -from ..enums import ShaderCompilerPlatform, ShaderGpuProgramType, SerializedPropertyType -from ..enums import TextureDimension, PassType - - -class Shader(NamedObject): - def export(self): - return export_shader(self) - - 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 SerializedTextureProperty: - def __init__(self, reader): - self.m_DefaultName = reader.read_aligned_string() - self.m_TexDim = TextureDimension(reader.read_int()) - - -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) - if version >= (2020,): # 2020.1 and up - self.conservative = 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() - if reader.version >= (2020,): # 2020.1 and up - m_ArraySize = 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 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 SerializedPass: - def __init__(self, reader): - version = reader.version - - if version >= (2020, 2): # 2020.2 and up - numEditorDataHash = reader.read_int() - m_EditorDataHash = [ - reader.read_bytes(16) # Hash128(reader) - for _ in range(numEditorDataHash) - ] - reader.align_stream() - m_Platforms = reader.read_byte_array() - reader.align_stream() - m_LocalKeywordMask = reader.read_u_short_array() - reader.align_stream() - m_GlobalKeywordMask = reader.read_u_short_array() - reader.align_stream() - - 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() 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 f85be9f05..000000000 --- a/UnityPy/classes/Sprite.py +++ /dev/null @@ -1,238 +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 -from ..streams import EndianBinaryWriter - - -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) - ] - - def save(self, writer: EndianBinaryWriter = None): - if writer is None: - writer = EndianBinaryWriter(endian=self.reader.endian) - version = self.version - - super().save(writer) - writer.write_rectangle_f(self.m_Rect) - writer.write_vector2(self.m_Offset) - if version >= (4, 5): # 4.5 and up - writer.write_vector4(self.m_Border) - - writer.write_float(self.m_PixelsToUnits) - if version >= (5, 4, 2) or ( - version >= (5, 4, 1, 3) and self.build_type.IsPatch - ): # 5.4.1p3 and up - writer.write_vector2(self.m_Pivot) - - writer.write_u_int(self.m_Extrude) - if version >= (5, 3): # 5.3 and up - writer.write_boolean(self.m_IsPolygon) - writer.align_stream() - - if version >= (2017,): # 2017 and up - writer.write_bytes(self.m_RenderDataKey[0]) # GUID - writer.write_long(self.m_RenderDataKey[1]) - writer.write_string_array(self.m_AtlasTags) - self.m_SpriteAtlas.save(writer) # SpriteAtlas - - self.m_RD.save(writer, version) - - if version >= (2017,): # 2017 and up - writer.write_int(len(self.m_PhysicsShape)) - for phys in self.m_PhysicsShape: - writer.write_vector2_array(phys) - - if version >= (2018,): # 2018 and up - writer.write_int(len(self.m_Bones)) - for bone in self.m_Bones: - writer.write_vector2_array(bone) - - self.set_raw_data(writer.bytes) - - -class SecondarySpriteTexture: - def __init__(self, reader): - self.texture = PPtr(reader) # Texture2D - self.name = reader.read_string_to_null() - - def save(self, writer): - self.texture.save(writer) - writer.write_string_to_null(self.name) - - -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): - self.settingsRaw = reader.read_u_int() - self.packed = 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 save(self, writer): - writer.write_u_int(self.settingsRaw) - - -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() - - def save(self, writer, version): - writer.write_vector3(self.pos) - if version[:2] <= (4, 3): # 4.3 and down - writer.write__vector2(self.uv) - - -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 - SubMeshesSize = reader.read_int() - self.m_SubMeshes = [SubMesh(reader) for _ in range(SubMeshesSize)] - IndexBufferSize = reader.read_int() - self.m_IndexBuffer = reader.read_bytes(IndexBufferSize) - 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 - self.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() - - def save(self, writer, version): - self.texture.save(writer) # Texture2D - if version >= (5, 2): # 5.2 and up - self.alphaTexture.save(writer) # Texture2D - - if version >= (2019,): # 2019 and up - writer.write_int(len(self.secondaryTextures)) - for tex in self.secondaryTextures: - tex.save(writer) - - if version >= (5, 6): # 5.6 and up - writer.write_int(len(self.m_SubMeshes)) - for mesh in self.m_SubMeshes: - mesh.save(writer, version) - writer.write_int(len(self.m_IndexBuffer)) - writer.write_bytes(self.m_IndexBuffer) - writer.align_stream() - self.m_VertexData.save(writer, version) - else: - writer.write_int(len(self.vertices)) - for vertex in self.vertices: - vertex.save(writer, version) - writer.write_u_short_array(self.indices) - writer.align_stream() - - if version >= (2018,): # 2018 and up - writer.write_matrix_array(self.m_Bindpose) - if version < (2018, 2): # 2018.2 down - writer.write_int(self.m_SourceSkinSize) - self.m_SourceSkin[0].save(writer) - - writer.write_rectangle_f(self.textureRect) - writer.write_vector2(self.textureRectOffset) - if version >= (5, 6): # 5.6 and up - writer.write_vector2(self.atlasRectOffset) - - self.settingsRaw.save(writer) - if version >= (4, 5): # 4.5 and up - writer.write_vector4(self.uvTransform) - - if version >= (2017,): # 2017 and up - writer.write_float(self.downscaleMultiplier) diff --git a/UnityPy/classes/SpriteAtlas.py b/UnityPy/classes/SpriteAtlas.py deleted file mode 100644 index 01c623812..000000000 --- a/UnityPy/classes/SpriteAtlas.py +++ /dev/null @@ -1,34 +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.m_PackedSprites = [PPtr(reader) - for _ in range(packed_sprites_size)] - - self.m_PackedSpriteNamesToIndex = reader.read_string_array() - m_render_data_map_size = reader.read_int() - self.m_RenderDataMap = {} - for _ in range(m_render_data_map_size): - first = reader.read_bytes(16) # GUID - second = reader.read_long() - value = SpriteAtlasData(reader) - self.m_RenderDataMap[(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 8c1a0165c..000000000 --- a/UnityPy/classes/TextAsset.py +++ /dev/null @@ -1,35 +0,0 @@ -from .NamedObject import NamedObject -from ..streams import EndianBinaryWriter - - -class TextAsset(NamedObject): - def __init__(self, reader): - super().__init__(reader=reader) - self.m_Script = reader.read_bytes(reader.read_int()) - - @property - def script(self): - # required for backward compatibility - return self.m_Script - - @script.setter - def script(self, value): - self.m_Script = value - - @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.m_Script)) - writer.write_bytes(self.m_Script) - writer.align_stream() - - self.set_raw_data(writer.bytes) diff --git a/UnityPy/classes/Texture.py b/UnityPy/classes/Texture.py deleted file mode 100644 index 652ea1b6b..000000000 --- a/UnityPy/classes/Texture.py +++ /dev/null @@ -1,22 +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.m_ForcedFallbackFormat = reader.read_int() - self.m_DownscaleFallback = reader.read_boolean() - if self.version >= (2020,2): # 2020.2 and up - self.m_IsAlphaChannelOptional = 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.m_ForcedFallbackFormat) - writer.write_boolean(self.m_DownscaleFallback) - if self.version >= (2020,2): # 2020.2 and up - writer.write_boolean(self.m_IsAlphaChannelOptional) - writer.align_stream() diff --git a/UnityPy/classes/Texture2D.py b/UnityPy/classes/Texture2D.py deleted file mode 100644 index a88fac864..000000000 --- a/UnityPy/classes/Texture2D.py +++ /dev/null @@ -1,205 +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 / image path / opened file - # overwrite original image data with the RGB(A) image data and sets the correct new format - img_data,tex_format = Texture2DConverter.image_to_texture(img) - if img is None: - raise Exception("No image provided") - - self.image_data = img_data - # width * height * channel count - self.m_CompleteImageSize = img.width * img.height * len(img.getbands()) - self.m_TextureFormat = tex_format - - @property - def image_data(self): - return self._image_data - - @image_data.setter - def image_data(self, data): - self._image_data = data - # prefer writing to cab if possible - if self.m_StreamData: - cab = self.assets_file.get_writeable_cab() - if cab: - self.m_StreamData.offset = cab.Position - cab.write(data) - self.m_StreamData.size = len(data) - self.m_StreamData.path = cab.path - else: - self.m_StreamData.offset = 0 - self.m_StreamData.size = 0 - self.m_StreamData.path = '' - - - 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.m_MipsStripped = 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 and up - self.m_IsReadable = reader.read_boolean() # 2.6 and up - if version >= (2020,): # 2020.1 and up - self.m_IsPreProcessed = reader.read_boolean() - if version >= (2019, 3): # 2019.3 and up - self.m_IgnoreMasterTextureLimit = reader.read_boolean() - if (3,) <= version[:2] <= (5, 4): # 3.0 - 5.4 - self.m_ReadAllowed = reader.read_boolean() - if version >= (2018, 2): # 2018.2 and up - self.m_StreamingMipmaps = 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, version) - if version >= (3,): # 3.0 and up - self.m_LightmapFormat = reader.read_int() - if version >= (3, 5): # 3.5 and up - self.m_ColorSpace = reader.read_int() - if version >= (2020,2): # 2020.2 and up - self.m_PlatformBlob = reader.read_byte_array() - reader.align_stream() - - image_data_size = reader.read_int() - self._image_data = b"" - - if image_data_size != 0: - self._image_data = reader.read_bytes(image_data_size) - - self.m_StreamData = None - if version >= (5, 3): # 5.3 and up - # always read the StreamingInfo for resaving - self.m_StreamData = StreamingInfo(reader, version) - if image_data_size == 0 and self.m_StreamData.path: - self._image_data = get_resource_data( - self.m_StreamData.path, - self.assets_file, - self.m_StreamData.offset, - self.m_StreamData.size, - ) - - def save(self, writer: EndianBinaryWriter = None): - if writer is None: - writer = EndianBinaryWriter(endian=self.reader.endian) - version = self.version - - super().save(writer) - writer.write_int(self.m_Width) - writer.write_int(self.m_Height) - writer.write_int(self.m_CompleteImageSize) - if version >= (2020,): # 2020.1 and up - writer.write_int(self.m_MipsStripped) - writer.write_int(self.m_TextureFormat.value) - if version[:2] < (5, 2): # 5.2 down - writer.write_boolean(self.m_MipMap) - else: - writer.write_int(self.m_MipCount) - - if version >= (2, 6): # 2.6 and up - writer.write_boolean(self.m_IsReadable) # 2.6 and up - if version >= (2020,): # 2020.1 and up - writer.write_boolean(self.m_IsPreProcessed) - if version >= (2019, 3): # 2019.3 and up - writer.write_boolean(self.m_IgnoreMasterTextureLimit) - if (3,) <= version[:2] <= (5, 4): # 3.0 - 5.4 - writer.write_boolean(self.m_ReadAllowed) # 3.0 - 5.4 - if version >= (2018, 2): # 2018.2 and up - writer.write_boolean(self.m_StreamingMipmaps) - - 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 and up - writer.write_int(self.m_ColorSpace) - if version >= (2020,2): # 2020.2 and up - writer.write_byte_array(self.m_PlatformBlob) - writer.align_stream() - - - if version[:2] < (5,3): - # version without m_StreamData - writer.write_int(len(self.image_data)) - writer.write_bytes(self.image_data) - else: - # decide if m_StreamData is used - if self.m_StreamData.path: - # used -> don't save the image_data - writer.write_int(0) - else: - writer.write_int(len(self.image_data)) - writer.write_bytes(self.image_data) - - self.m_StreamData.save(writer, version) - - self.set_raw_data(writer.bytes) - - -class StreamingInfo: - offset: int - size: int - path: str - - def __init__(self, reader, version): - if 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() - - def save(self, writer, version): - if version >= (2020,): # 2020.1 and up - writer.write_u_long(self.offset) - else: - writer.write_u_int(self.offset) - writer.write_int(self.size) - writer.write_aligned_string(self.path) - - -class GLTextureSettings: - def __init__(self, reader, version): - self.m_FilterMode = reader.read_int() - self.m_Aniso = reader.read_int() - self.m_MipBias = reader.read_float() - self.m_WrapMode = reader.read_int() # m_WrapU - if version >= (2017,): # 2017.x and up - self.m_WrapV = reader.read_int() - self.m_WrapW = reader.read_int() - - def save(self, writer, version): - writer.write_int(self.m_FilterMode) - writer.write_int(self.m_Aniso) - writer.write_float(self.m_MipBias) - writer.write_int(self.m_WrapMode) # m_WrapU - if version >= (2017,): # 2017.x and up - writer.write_int(self.m_WrapV) - writer.write_int(self.m_WrapW) diff --git a/UnityPy/classes/Transform.py b/UnityPy/classes/Transform.py deleted file mode 100644 index df8419d2b..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.m_LocalRotation = reader.read_quaternion() - self.m_LocalPosition = reader.read_vector3() - self.m_LocalScale = reader.read_vector3() - - children_count = reader.read_int() - self.m_Children = [PPtr(reader) for _ in range(children_count)] - self.m_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 66ceb03da..000000000 --- a/UnityPy/classes/VideoClip.py +++ /dev/null @@ -1,44 +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.m_OriginalPath = reader.read_aligned_string() - self.m_ProxyWidth = reader.read_u_int() - self.m_ProxyHeight = 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.m_PixelAspecRatioNum = reader.read_u_int() - self.m_PixelAspecRatioDen = reader.read_u_int() - self.m_FrameRate = reader.read_double() - self.m_FrameCount = reader.read_u_long() - self.m_Format = reader.read_int() - self.m_AudioChannelCount = reader.read_u_short_array() - reader.align_stream() - self.m_AudioSampleRate = reader.read_u_int_array() - self.m_AudioLanguage = reader.read_string_array() - if self.version >= (2020,): # 2020.1 and up - m_VideoShadersSize = reader.read_int() - self.m_VideoShaders = [ - PPtr(reader) for _ in range(m_VideoShadersSize) - ] - - # m_ExternalResources = new StreamedResource(reader) - self.source = reader.read_aligned_string() - self.offset = reader.read_u_long() - self.size = reader.read_u_long() - - self.m_HasSplitAlpha = reader.read_boolean() - if self.version >= (2020,): # 2020.1 and up - self.m_sRGB = reader.read_boolean() - - if self.source: - self.m_VideoData = get_resource_data( - self.source, self.assets_file, self.offset, self.size) - else: - self.reader.Position = self.data_offset - self.m_VideoData = self.reader.read_bytes(self.size) diff --git a/UnityPy/classes/__init__.py b/UnityPy/classes/__init__.py index 301aac1f4..f3ecdb219 100644 --- a/UnityPy/classes/__init__.py +++ b/UnityPy/classes/__init__.py @@ -1,38 +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 .ResourceManager import ResourceManager -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/BuildTarget.py b/UnityPy/enums/BuildTarget.py index 931beec28..d3e092cdd 100644 --- a/UnityPy/enums/BuildTarget.py +++ b/UnityPy/enums/BuildTarget.py @@ -1,7 +1,7 @@ -from enum import IntEnum +from .ExtendableEnum import ExtendableEnum -class BuildTarget(IntEnum): +class BuildTarget(ExtendableEnum): UnknownPlatform = 3716 DashboardWidget = 1 StandaloneOSX = 2 @@ -38,13 +38,3 @@ class BuildTarget(IntEnum): tvOS = 37 Switch = 38 NoTarget = -2 - - @classmethod - def _missing_(cls, value): - ret = BuildTarget.UnknownPlatform - ret._value = value - return ret - - @property - def value(self): - return getattr(self, "_value", self._value_) 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 bf66353ba..9168fb8da 100644 --- a/UnityPy/enums/ClassIDType.py +++ b/UnityPy/enums/ClassIDType.py @@ -1,8 +1,8 @@ -# https://docs.unity3d.com/Manual/ClassIDReference.html -from enum import IntEnum +# https://docs.unity3d.com/Manual/ClassIDReference.html +from .ExtendableEnum import ExtendableEnum -class ClassIDType(IntEnum): +class ClassIDType(ExtendableEnum): UnknownType = -1 Object = 0 GameObject = 1 @@ -371,22 +371,3 @@ class ClassIDType(IntEnum): VisualEffect = 2083052967 LocalizationAsset = 2083778819 ScriptedImporter = 2089858483 - - 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): - ret = ClassIDType.UnknownType - ret._value = value - return ret - - @property - def value(self): - return getattr(self, "_value", self._value_) diff --git a/UnityPy/enums/CommonString.py b/UnityPy/enums/CommonString.py deleted file mode 100644 index c91d2ca2f..000000000 --- a/UnityPy/enums/CommonString.py +++ /dev/null @@ -1,110 +0,0 @@ -CommonString = { - 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: "m_ByteSize", - 299: "m_Curve", - 307: "m_EditorClassIdentifier", - 331: "m_EditorHideFlags", - 349: "m_Enabled", - 359: "m_ExtensionPtr", - 374: "m_GameObject", - 387: "m_Index", - 395: "m_IsArray", - 405: "m_IsStatic", - 416: "m_MetaFlag", - 427: "m_Name", - 434: "m_ObjectHideFlags", - 452: "m_PrefabInternal", - 469: "m_PrefabParentObject", - 490: "m_Script", - 499: "m_StaticEditorFlags", - 519: "m_Type", - 526: "m_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/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/GfxPrimitiveType.py b/UnityPy/enums/GfxPrimitiveType.py index f6d4cc965..591a7a34a 100644 --- a/UnityPy/enums/GfxPrimitiveType.py +++ b/UnityPy/enums/GfxPrimitiveType.py @@ -7,4 +7,4 @@ class GfxPrimitiveType(IntEnum): kPrimitiveQuads = 2 kPrimitiveLines = 3 kPrimitiveLineStrip = 4 - kPrimitivePoints = 5 \ No newline at end of file + 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/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/TextureFormat.py b/UnityPy/enums/TextureFormat.py index 1e3085ae0..68d5dc992 100644 --- a/UnityPy/enums/TextureFormat.py +++ b/UnityPy/enums/TextureFormat.py @@ -7,9 +7,12 @@ class TextureFormat(IntEnum): RGB24 = 3 RGBA32 = 4 ARGB32 = 5 + ARGBFloat = 6 RGB565 = 7 + BGR24 = 8 R16 = 9 DXT1 = 10 + DXT3 = 11 DXT5 = 12 RGBA4444 = 13 BGRA32 = 14 @@ -21,10 +24,11 @@ class TextureFormat(IntEnum): RGBAFloat = 20 YUY2 = 21 RGB9e5Float = 22 - BC4 = 26 - BC5 = 27 + RGBFloat = 23 BC6H = 24 BC7 = 25 + BC4 = 26 + BC5 = 27 DXT1Crunched = 28 DXT5Crunched = 29 PVRTC_RGB2 = 30 @@ -65,3 +69,14 @@ class TextureFormat(IntEnum): 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 2c2e054b1..c9a99d83e 100644 --- a/UnityPy/enums/__init__.py +++ b/UnityPy/enums/__init__.py @@ -1,13 +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 .SpriteMeshType import SpriteMeshType from .GfxPrimitiveType import GfxPrimitiveType -from .CommonString import CommonString +from .GraphicsFormat import GraphicsFormat +from .PassType import PassType +from .SerializedPropertyType import SerializedPropertyType from .ShaderCompilerPlatform import ShaderCompilerPlatform from .ShaderGpuProgramType import ShaderGpuProgramType -from .SerializedPropertyType import SerializedPropertyType +from .SpriteMeshType import SpriteMeshType +from .SpritePackingMode import SpritePackingMode +from .SpritePackingRotation import SpritePackingRotation from .TextureDimension import TextureDimension -from .PassType import PassType +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 a757b78d3..c4a60aea2 100644 --- a/UnityPy/environment.py +++ b/UnityPy/environment.py @@ -1,144 +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 .files import File +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 -from .files import SerializedFile + +if TYPE_CHECKING: + from UnityPy.helpers.TypeTreeGenerator import TypeTreeGenerator + +reSplit = re.compile(r"(.*?([^\/\\]+?))\.split\d+") + class Environment: - files: 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.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_files([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.files[str(len(self.files)) - ] = self.load_file(stream=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.""" - # ImportHelper.merge_split_assets(path) - # to_read_file = ImportHelper.processing_split_files(files) - self.load(files) + 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.files[f[len(self.path):].lstrip( - "/\\")] = self.load_file(open(f, "rb"), self) - - def load_file(self, stream, parent=None): + """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 - typ, reader = ImportHelper.check_file_type(stream) - try: - if typ == FileType.AssetsFile: - return files.SerializedFile(reader, parent) - elif typ == FileType.BundleFile: - return files.BundleFile(reader, parent) - elif typ == FileType.WebFile: - return files.WebFile(reader, parent) - elif typ == FileType.ZIP: - self.load_zip_file(stream) - elif typ == FileType.ResourceFile: - return EndianBinaryReader(stream) - except: - # just to be sure - # cuz the SerializedFile detection isn't perfect - return EndianBinaryReader(stream) + + 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) - for path in z.namelist(): - stream = z.open(path) - if stream._orig_file_size == 0: - continue - *path, name = path.split("/") - cur = self - for d in path: - if d not in cur.files: - cur.files[d] = files.File(self) - cur = cur.files[d] - cur.files[name] = self.load_file(stream, cur) + self.load_assets(z.namelist(), lambda x: z.open(x, "r")) # type: ignore z.close() + 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): - return { - path: obj - for f in self.files.values() - if isinstance(f, File) - for path, obj in f.container.items() - } + 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 = []): + + 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, default=None): + 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 506d4a503..c381d9943 100644 --- a/UnityPy/export/AudioClipConverter.py +++ b/UnityPy/export/AudioClipConverter.py @@ -1,327 +1,49 @@ -from ctypes import * -from enum import Enum -import os -import platform -from UnityPy.streams import EndianBinaryWriter +from __future__ import annotations -_dll = None +from typing import TYPE_CHECKING, Dict +import fmod_toolkit -def load_fmod_library(): - global _dll - if _dll is not None: - return +from ..helpers.ResourceReader import get_resource_data - ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) +if TYPE_CHECKING: + from ..classes import AudioClip - # determine system - Windows, Darwin, Linux, Android - system = platform.system() - if system == "Linux" and "ANDROID_BOOTLOGO" in os.environ: - system = "Android" - # determine architecture - machine = platform.machine() - arch = platform.architecture()[0] - if system in ["Windows", "Darwin"]: - if arch == "32bit": - arch = "x86" - elif arch == "64bit": - arch = "x64" - elif system == "Linux": - # Raspberry Pi and Linux on arm projects - if "arm" in machine: - if arch == "32bit": - arch = "armhf" if machine.endswith("l") else "arm" - elif arch == "64bit": - # Raise an exception for now; Once it gets supported by FMOD we can just modify the code here - _dll = False - raise NotImplementedError("ARM64 not supported by FMOD.\nUse a 32bit python version.") - elif arch == "32bit": - arch = "x86" - elif arch == "64bit": - arch = "x86_64" - else: - _dll = False - raise NotImplementedError("Couldn't find a correct FMOD library for your system ({system} - {arch}).") - try: - # build path and load library - LIB_PATH = os.path.join(ROOT, "lib", "FMOD", system, arch) - if system == 'Windows': - _dll = WinDLL(os.path.join(LIB_PATH, "fmod.dll")) - elif system in ["Linux", "Android"]: - _dll = CDLL(os.path.join(LIB_PATH, "libfmod.so")) - elif system == "Darwin": - _dll = CDLL(os.path.join(LIB_PATH, "libfmod.dylib")) - except Exception as e: - raise ImportError( - f"Failed to import the fmod library - Exception: {e}.\ - If you want to export AudioClips, you have to set UnityPy.export.AudioClipConverter._dll yourself." - ) - - -def extract_audioclip_samples(audio) -> dict: +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 + :param audio: AudioClip + :type audio: AudioClip + :return: {filename : sample(bytes)} + :rtype: dict """ - if not audio.m_AudioData: - # eg. StreamedResource not available - return {} - - magic = memoryview(audio.m_AudioData)[:4] - if magic == b'OggS': - return {'%s.ogg' % audio.name: audio.m_AudioData} - elif magic == b'RIFF': - return {'%s.wav' % audio.name: audio.m_AudioData} - return dump_samples(audio) - - -def dump_samples(clip): - if _dll is None: - load_fmod_library() - if not _dll: - return {} - # init system - # system = pyfmodex.System() - sys_ptr = c_void_p() - ckresult(_dll.FMOD_System_Create(byref(sys_ptr))) - # system.init(1, INIT_FLAGS.NORMAL, None) - ckresult(_dll.FMOD_System_Init(sys_ptr, clip.m_Channels, None, None)) - - # get sound - exinfo = byref(CREATESOUNDEXINFO(length=clip.m_Size)) - # sound = system.create_sound(bytes(clip.m_AudioData),mode=MODE.OPENMEMORY,exinfo=exinfo) - snd_ptr = c_void_p() - ckresult(_dll.FMOD_System_CreateSound(sys_ptr, bytes( - clip.m_AudioData), 0x00000800, exinfo, byref(snd_ptr))) - sound = Sound(snd_ptr) - # iterate over subsounds - samples = {} - for i in range(sound.num_subsounds): - if i > 0: - filename = "%s-%i.wav" % (clip.name, i) - else: - filename = "%s.wav" % clip.name - subsound = sound.get_subsound(i) - samples[filename] = subsound_to_wav(subsound) - subsound.release() - - sound.release() - # system.release() - ckresult(_dll.FMOD_System_Release(sys_ptr)) - return samples - - -def subsound_to_wav(subsound): - # get sound settings - length = subsound.get_length(0x00000004) # TIMEUNIT.PCMBYTES - channels = subsound.format.channels - bits = subsound.format.bits - sample_rate = int(subsound.default_frequency) - - # write to buffer - w = EndianBinaryWriter(endian="<") - # riff chucnk - w.write(b"RIFF") - w.write_int(length + 36) # sizeof(FmtChunk) + sizeof(RiffChunk) + length - w.write(b"WAVE") - # fmt chunck - w.write(b"fmt ") - w.write_int(16) # sizeof(FmtChunk) - sizeof(RiffChunk) - w.write_short(1) - w.write_short(channels) - w.write_int(sample_rate) - w.write_int(sample_rate * channels * bits // 8) - w.write_short(channels * bits // 8) - w.write_short(bits) - # data chunck - w.write(b"data") - w.write_int(length) - # data - lock = subsound.lock(0, length) - for ptr, length in lock: - ptr_data = string_at(ptr, length.value) - w.write(ptr_data) - subsound.unlock(*lock) - return w.bytes - - -# following code is copied from -# https://github.com/tyrylu/pyfmodex -# pyfmodex can't be used by itself -# because it would require adding the libs to the path first - -class CREATESOUNDEXINFO(Structure): - _fields_ = [("cbsize", c_int), ("length", c_uint)] - - def __init__(self, *args, **kwargs): - Structure.__init__(self, *args, **kwargs) - self.cbsize = sizeof(self) - - -class so: - def __init__(self, **kwargs): - self.__dict__.update(**kwargs) - - -class Sound(object): - def __init__(self, ptr): - """Constructor. - :param ptr: The pointer representing this object. - """ - self._ptr = ptr - - def _call_fmod(self, funcname, *args): - result = getattr(_dll, funcname)(self._ptr, *args) - ckresult(result) - - @property - def num_subsounds(self): - num = c_int() - self._call_fmod("FMOD_Sound_GetNumSubSounds", byref(num)) - return num.value - - def get_subsound(self, index): - sh_ptr = c_void_p() - self._call_fmod("FMOD_Sound_GetSubSound", index, byref(sh_ptr)) - return Sound(sh_ptr) - - def get_length(self, ltype): - len = c_uint() - self._call_fmod("FMOD_Sound_GetLength", byref(len), int(ltype)) - return len.value - - @property - def format(self): - type = c_int() - format = c_int() - channels = c_int() - bits = c_int() - self._call_fmod("FMOD_Sound_GetFormat", byref(type), - byref(format), byref(channels), byref(bits)) - return so(type=type.value, format=format.value, channels=channels.value, bits=bits.value) - - @property - def default_frequency(self): - freq = c_float() - pri = c_int() - self._call_fmod("FMOD_Sound_GetDefaults", byref(freq), byref(pri)) - return freq.value - - def lock(self, offset, length): - ptr1 = c_void_p() - len1 = c_uint() - ptr2 = c_void_p() - len2 = c_uint() - ckresult(_dll.FMOD_Sound_Lock(self._ptr, offset, length, - byref(ptr1), byref(ptr2), byref(len1), byref(len2))) - return (ptr1, len1), (ptr2, len2) - - def release(self): - self._call_fmod("FMOD_Sound_Release") - - def unlock(self, i1, i2): - """I1 and I2 are tuples of form (ptr, len).""" - ckresult(_dll.FMOD_Sound_Unlock(self._ptr, i1[0], i2[0], i1[1], i2[1])) - - -def ckresult(result): - result = RESULT(result) - if result is not RESULT.OK: - raise FmodError(result) - - -class FmodError(Exception): - def __init__(self, result): - self.result = result - self.message = result.name.replace("_", " ") - - def __str__(self): - return self.message - - -class RESULT(Enum): - OK = 0 - BADCOMMAND = 1 - CHANNEL_ALLOC = 2 - CHANNEL_STOLEN = 3 - DMA = 4 - DSP_CONNECTION = 5 - DSP_DONTPROCESS = 6 - DSP_FORMAT = 7 - DSP_INUSE = 8 - DSP_NOTFOUND = 9 - DSP_RESERVED = 10 - DSP_SILENCE = 11 - DSP_TYPE = 12 - FILE_BAD = 13 - FILE_COULDNOTSEEK = 14 - FILE_DISKEJECTED = 15 - FILE_EOF = 16 - FILE_ENDOFDATA = 17 - FILE_NOTFOUND = 18 - FORMAT = 19 - HEADER_MISMATCH = 20 - HTTP = 21 - HTTP_ACCESS = 22 - HTTP_PROXY_AUTH = 23 - HTTP_SERVER_ERROR = 24 - HTTP_TIMEOUT = 25 - INITIALIZATION = 26 - INITIALIZED = 27 - INTERNAL = 28 - INVALID_FLOAT = 29 - INVALID_HANDLE = 30 - INVALID_PARAM = 31 - INVALID_POSITION = 32 - INVALID_SPEAKER = 33 - INVALID_SYNCPOINT = 34 - INVALID_THREAD = 35 - INVALID_VECTOR = 36 - MAXAUDIBLE = 37 - MEMORY = 38 - MEMORY_CANTPOINT = 39 - NEEDS3D = 40 - NEEDSHARDWARE = 41 - NET_CONNECT = 42 - NET_SOCKET_ERROR = 43 - NET_URL = 44 - NET_WOULD_BLOCK = 45 - NOTREADY = 46 - OUTPUT_ALLOCATED = 47 - OUTPUT_CREATEBUFFER = 48 - OUTPUT_DRIVERCALL = 49 - OUTPUT_FORMAT = 50 - OUTPUT_INIT = 51 - OUTPUT_NODRIVERS = 52 - PLUGIN = 53 - PLUGIN_MISSING = 54 - PLUGIN_RESOURCE = 55 - PLUGIN_VERSION = 56 - RECORD = 57 - REVERB_CHANNELGROUP = 58 - REVERB_INSTANCE = 59 - SUBSOUNDS = 60 - SUBSOUND_ALLOCATED = 61 - SUBSOUND_CANTMOVE = 62 - TAGNOTFOUND = 63 - TOOMANYCHANNELS = 64 - TRUNCATED = 65 - UNIMPLEMENTED = 66 - UNINITIALIZED = 67 - UNSUPPORTED = 68 - VERSION = 69 - EVENT_ALREADY_LOADED = 70 - EVENT_LIVEUPDATE_BUSY = 71 - EVENT_LIVEUPDATE_MISMATCH = 72 - EVENT_LIVEUPDATE_TIMEOUT = 73 - EVENT_NOTFOUND = 74 - STUDIO_UNINITIALIZED = 75 - STUDIO_NOT_LOADED = 76 - INVALID_STRING = 77 - ALREADY_LOCKED = 78 - NOT_LOCKED = 79 - RECORD_DISCONNECTED = 80 - TOOMANYSAMPLES = 81 + 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 index 0e0d81e3c..f61e3e729 100644 --- a/UnityPy/export/MeshExporter.py +++ b/UnityPy/export/MeshExporter.py @@ -1,59 +1,56 @@ -def export_mesh(m_Mesh): +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 {m_Mesh.name}\r\n"] - + 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 - c = 3 - if len(m_Mesh.m_Vertices) == m_Mesh.m_VertexCount * 4: - c = 4 - - for v in range(int(m_Mesh.m_VertexCount)): - sb.append("v {0} {1} {2}\r\n".format( - -m_Mesh.m_Vertices[v * c], m_Mesh.m_Vertices[v * c + 1], m_Mesh.m_Vertices[v * c + 2])) + 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: - if len(m_Mesh.m_UV0) == m_Mesh.m_VertexCount * 2: - c = 2 - elif len(m_Mesh.m_UV0) == m_Mesh.m_VertexCount * 3: - c = 3 - - for v in range(int(m_Mesh.m_VertexCount)): - sb.append("vt {0} {1}\r\n".format( - m_Mesh.m_UV0[v * c], m_Mesh.m_UV0[v * c + 1])) + 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: - if len(m_Mesh.m_Normals) == m_Mesh.m_VertexCount * 3: - c = 3 - elif len(m_Mesh.m_Normals) == m_Mesh.m_VertexCount * 4: - c = 4 - - for v in range(int(m_Mesh.m_VertexCount)): - sb.append("vn {0} {1} {2}\r\n".format( - -m_Mesh.m_Normals[v * c], m_Mesh.m_Normals[v * c + 1], m_Mesh.m_Normals[v * c + 2])) + 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 - sum = 0 - for i in range(len(m_Mesh.m_SubMeshes)): - sb.append(f"g {m_Mesh.name}_{i}\r\n") - indexCount = m_Mesh.m_SubMeshes[i].indexCount - end = sum + indexCount // 3 - for f in range(sum, end): - sb.append("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\r\n".format( - m_Mesh.m_Indices[f * 3 + 2] + 1, - m_Mesh.m_Indices[f * 3 + 1] + 1, - m_Mesh.m_Indices[f * 3] + 1) - ) - sum = end + 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 - sb = "".join(sb).replace("NaN", "0").replace("e", "E") - return sb + 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 index a94a64d2a..dd3ac1c55 100644 --- a/UnityPy/export/ShaderConverter.py +++ b/UnityPy/export/ShaderConverter.py @@ -1,85 +1,136 @@ -import traceback -from ..streams import EndianBinaryReader -from ..helpers import CompressionHelper +from __future__ import annotations + import re from itertools import groupby -from ..enums import ShaderCompilerPlatform, ShaderGpuProgramType, SerializedPropertyType -from ..enums import TextureDimension, PassType +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 -HEADER = ''' +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:] +"""[1:] +T = TypeVar("T") -def export_shader(m_Shader): - if m_Shader.m_SubProgramBlob: # 5.3 - 5.4 + +def export_shader(m_Shader: Shader) -> str: + if m_Shader.m_SubProgramBlob: # 5.3 - 5.4 decompressedBytes = CompressionHelper.decompress_lz4( - m_Shader.m_SubProgramBlob, m_Shader.decompressedSize) + bytes(m_Shader.m_SubProgramBlob), m_Shader.decompressedSize + ) blobReader = EndianBinaryReader(decompressedBytes) - program = ShaderProgram(blobReader, m_Shader.version) + program = ShaderProgram(blobReader, m_Shader.object_reader.version) - return HEADER + program.Export(m_Shader.m_Script.decode("utf8")) + return HEADER + program.Export(bytes(m_Shader.m_Script).decode("utf8")) - if m_Shader.compressedBlob: # 5.5 and up + if m_Shader.compressedBlob: # 5.5 and up return HEADER + ConvertSerializedShader(m_Shader) - return HEADER + m_Shader.m_Script.decode("utf8") + return HEADER + bytes(m_Shader.m_Script).decode("utf8") + -def ConvertSerializedShader(m_Shader): +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): - compressedSize = m_Shader.compressedLengths[i] - decompressedSize = m_Shader.decompressedLengths[i] + 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 - compressedBytes = m_Shader.compressedBlob[int(m_Shader.offsets[i]):int(m_Shader.offsets[i]) + compressedSize] + 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.version)) + 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, platforms, shaderPrograms): - sb = [] - - sb.append("Shader \"{0}\" {{\n".format(m_ParsedForm.m_Name)) - sb.append(ConvertSerializedProperties(m_ParsedForm.m_PropInfo)) - - for m_SubShader in m_ParsedForm.m_SubShaders: - sb.append(ConvertSerializedSubShader(m_SubShader, 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)) + 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('CustomEditor "{0}"\n'.format(m_ParsedForm.m_CustomEditorName)) sb.append("}") return "".join(sb) -def ConvertSerializedSubShader(m_SubShader, platforms, shaderPrograms): - sb = [] - sb.append("SubShader {\n") +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)) - for m_Passe in m_SubShader.m_Passes: - sb.append(ConvertSerializedPass(m_Passe, platforms, shaderPrograms)) - + 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, platforms, shaderPrograms): + +def ConvertSerializedPass(m_Passe: SerializedPass, platforms: List[int], shaderPrograms: List[ShaderProgram]) -> str: sb = [] if m_Passe.m_Type == PassType.kPassTypeNormal: sb.append(" Pass ") @@ -89,39 +140,39 @@ def ConvertSerializedPass(m_Passe, platforms, shaderPrograms): sb.append(" GrabPass ") if m_Passe.m_Type == PassType.kPassTypeUse: - sb.append("\"{0}\"\n".format(m_Passe.m_UseName)) + 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)) + 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('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('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('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('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('Program "dp" {\n') sb.append(ConvertSerializedSubPrograms(m_Passe.progDomain.m_SubPrograms, platforms, shaderPrograms)) sb.append("}\n") @@ -129,11 +180,19 @@ def ConvertSerializedPass(m_Passe, platforms, shaderPrograms): return "".join(sb) -def ConvertSerializedSubPrograms(m_SubPrograms, platforms, shaderPrograms): + +def ConvertSerializedSubPrograms( + m_SubPrograms: List[SerializedSubProgram], + platforms: List[int], + shaderPrograms: List[ShaderProgram], +) -> str: sb = [] - indexFunc = lambda x: x.m_BlobIndex - typeFunc = lambda x: x.m_GpuProgramType + def indexFunc(x: SerializedSubProgram): + return x.m_BlobIndex + + def typeFunc(x: SerializedSubProgram): + return x.m_GpuProgramType groups = groupby(sorted(m_SubPrograms, key=indexFunc), indexFunc) @@ -145,16 +204,20 @@ def ConvertSerializedSubPrograms(m_SubPrograms, platforms, shaderPrograms): 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))) + sb.append('SubProgram "{0} '.format(GetPlatformString(platform))) if isTier: sb.append("hw_tier{0:02} ".format(subProgram.m_ShaderHardwareTier)) - sb.append("\" {\n") + sb.append('" {\n') sb.append(shaderPrograms[i].m_SubPrograms[subProgram.m_BlobIndex].Export()) sb.append("\n}\n") @@ -164,17 +227,17 @@ def ConvertSerializedSubPrograms(m_SubPrograms, platforms, shaderPrograms): return "".join(sb) -def ConvertSerializedShaderState(m_State): +def ConvertSerializedShaderState(m_State: SerializedShaderState) -> str: sb = [] if m_State.m_Name: - sb.append(" Name \"{0}\"\n".format(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)) + # sb.append(ConvertSerializedShaderRTBlendState(m_State.rtBlend)) if m_State.alphaToMask.val > 0: sb.append(" AlphaToMask On\n") @@ -184,33 +247,33 @@ def ConvertSerializedShaderState(m_State): if m_State.zTest.val != 4: sb.append(" ZTest ") - if m_State.zTest.val == 0: # kFuncDisabled + if m_State.zTest.val == 0: # kFuncDisabled sb.append("Off") - elif m_State.zTest.val == 1: # kFuncNever + elif m_State.zTest.val == 1: # kFuncNever sb.append("Never") - elif m_State.zTest.val == 2: # kFuncLess + elif m_State.zTest.val == 2: # kFuncLess sb.append("Less") - elif m_State.zTest.val == 3: # kFuncEqual + elif m_State.zTest.val == 3: # kFuncEqual sb.append("Equal") - elif m_State.zTest.val == 5: # kFuncGreater + elif m_State.zTest.val == 5: # kFuncGreater sb.append("Greater") - elif m_State.zTest.val == 6: # kFuncNotEqual + elif m_State.zTest.val == 6: # kFuncNotEqual sb.append("NotEqual") - elif m_State.zTest.val == 7: # kFuncGEqual + elif m_State.zTest.val == 7: # kFuncGEqual sb.append("GEqual") - elif m_State.zTest.val == 8: # kFuncAlways + elif m_State.zTest.val == 8: # kFuncAlways sb.append("Always") sb.append("\n") - if m_State.zWrite.val != 1: # ZWrite On + if m_State.zWrite.val != 1: # ZWrite On sb.append(" ZWrite Off\n") - if m_State.culling.val != 2: # Cull Back + if m_State.culling.val != 2: # Cull Back sb.append(" Cull ") - if m_State.culling.val == 0: # kCullOff + if m_State.culling.val == 0: # kCullOff sb.append("Off") - elif m_State.culling.val == 1: # kCullFront + elif m_State.culling.val == 1: # kCullFront sb.append("Front") sb.append("\n") @@ -234,33 +297,33 @@ def ConvertSerializedShaderRTBlendState(rbBlend): sb = [] return "".join(sb) -def ConvertSerializedTagMap(m_Tags, intent: int): - sb = [] - if len(m_Tags.tags) > 0: - sb.append(" "*intent) - sb.append("Tags { ") - for key, value in m_Tags.tags.items(): - sb.append("\"{0}\" = \"{1}\" ".format(key, value)) - sb.append("}\n") - - return "".join(sb) -def ConvertSerializedProperties(m_PropInfo): - 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 "" - sb.append("Properties {\n") - for m_Prop in m_PropInfo.m_Props: - sb.append(ConvertSerializedProperty(m_Prop)) - sb.append("}\n") - return "".join(sb) +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): - sb = [] - for m_Attribute in m_Prop.m_Attributes: - sb.append("[{0}] ".format(m_Attribute)) - sb.append("{0} (\"{1}\", ".format(m_Prop.m_Name, m_Prop.m_Description)) +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") @@ -269,7 +332,7 @@ def ConvertSerializedProperty(m_Prop): 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])) + 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") @@ -286,47 +349,58 @@ def ConvertSerializedProperty(m_Prop): 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])) + 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 + SerializedPropertyType.kRange, ]: - sb.append(m_Prop.m_DefValue[0]) + sb.append(m_Prop.m_DefValue_0_) elif m_Prop.m_Type == SerializedPropertyType.kTexture: - sb.append("\"{0}\" {{ }}".format(m_Prop.m_DefTexture.m_DefaultName)) + 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)) + return "".join(map(str, sb)) + -def CheckGpuProgramUsable(platform, programType): +def CheckGpuProgramUsable(platform: int, programType: int) -> bool: if platform == ShaderCompilerPlatform.kShaderCompPlatformGL: return programType == ShaderGpuProgramType.kShaderGpuProgramGLLegacy elif platform == ShaderCompilerPlatform.kShaderCompPlatformD3D9: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramDX9VertexSM20 + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramDX9VertexSM20 or programType == ShaderGpuProgramType.kShaderGpuProgramDX9VertexSM30 or programType == ShaderGpuProgramType.kShaderGpuProgramDX9PixelSM20 - or programType == ShaderGpuProgramType.kShaderGpuProgramDX9PixelSM30 ) + or programType == ShaderGpuProgramType.kShaderGpuProgramDX9PixelSM30 + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformXbox360: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS - or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS ) + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformD3D11: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramDX11VertexSM40 + 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 ) + or programType == ShaderGpuProgramType.kShaderGpuProgramDX11DomainSM50 + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformGLES20: return programType == ShaderGpuProgramType.kShaderGpuProgramGLES elif platform == ShaderCompilerPlatform.kShaderCompPlatformNaCl: @@ -334,70 +408,92 @@ def CheckGpuProgramUsable(platform, programType): elif platform == ShaderCompilerPlatform.kShaderCompPlatformFlash: raise NotImplementedError elif platform == ShaderCompilerPlatform.kShaderCompPlatformD3D11_9x: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramDX10Level9Vertex - or programType == ShaderGpuProgramType.kShaderGpuProgramDX10Level9Pixel ) + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramDX10Level9Vertex + or programType == ShaderGpuProgramType.kShaderGpuProgramDX10Level9Pixel + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformGLES3Plus: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramGLES31AEP + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramGLES31AEP or programType == ShaderGpuProgramType.kShaderGpuProgramGLES31 - or programType == ShaderGpuProgramType.kShaderGpuProgramGLES3 ) + or programType == ShaderGpuProgramType.kShaderGpuProgramGLES3 + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformPSP2: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS - or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS ) + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformPS4: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS - or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS ) + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformXboxOne: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS - or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS ) + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformPSM: raise NotImplementedError elif platform == ShaderCompilerPlatform.kShaderCompPlatformMetal: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramMetalVS - or programType == ShaderGpuProgramType.kShaderGpuProgramMetalVS ) + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramMetalVS + or programType == ShaderGpuProgramType.kShaderGpuProgramMetalVS + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformOpenGLCore: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramGLCore32 + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramGLCore32 or programType == ShaderGpuProgramType.kShaderGpuProgramGLCore41 - or programType == ShaderGpuProgramType.kShaderGpuProgramGLCore43 ) + or programType == ShaderGpuProgramType.kShaderGpuProgramGLCore43 + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformN3DS: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS - or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS ) + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformWiiU: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS - or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS ) + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformVulkan: return programType == ShaderGpuProgramType.kShaderGpuProgramSPIRV elif platform == ShaderCompilerPlatform.kShaderCompPlatformSwitch: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS - or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS ) + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) elif platform == ShaderCompilerPlatform.kShaderCompPlatformXboxOneD3D12: - return ( programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS + return ( + programType == ShaderGpuProgramType.kShaderGpuProgramConsoleVS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleFS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleHS or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleDS - or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS ) + or programType == ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ) else: raise NotImplementedError -def GetPlatformString(platform): +def GetPlatformString(platform: int): if platform == ShaderCompilerPlatform.kShaderCompPlatformGL: return "openGL" elif platform == ShaderCompilerPlatform.kShaderCompPlatformD3D9: @@ -445,11 +541,13 @@ def GetPlatformString(platform): class ShaderProgram: - def __init__(self, reader: EndianBinaryReader, version): + m_SubPrograms: List[ShaderSubProgram] + + def __init__(self, reader: EndianBinaryReader, version: Tuple[int, int, int, int]): subProgramCapacity = reader.read_int() - self.m_SubPrograms = [] + self.m_SubPrograms = [None] * subProgramCapacity - if version >=(2019, 3): # 2019.3 and up + if version >= (2019, 3): # 2019.3 and up entrySize = 12 else: entrySize = 8 @@ -458,26 +556,34 @@ def __init__(self, reader: EndianBinaryReader, version): reader.Position = 4 + i * entrySize offset = reader.read_int() reader.Position = offset - self.m_SubPrograms.append(ShaderSubProgram(reader)) + self.m_SubPrograms[i] = ShaderSubProgram(reader) - def Export(self, shader): - shader = re.sub(r"GpuProgramIndex (.+)", - lambda math: self.m_SubPrograms[int(math.group(1))].Export(), - shader) + 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 + # 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()) @@ -487,39 +593,38 @@ def __init__(self, reader: EndianBinaryReader): reader.Position += 4 m_KeywordSize = reader.read_int() - self.m_Keywords = [] + self.m_Keywords = [reader.read_aligned_string() for _ in range(m_KeywordSize)] - for i in range(m_KeywordSize): - self.m_Keywords.append(reader.read_aligned_string()) - - if self.m_Version >= 201806140: + if 201806140 <= self.m_Version < 202012090: m_LocalKeywordsSize = reader.read_int() - self.m_LocalKeywords = [] - - for i in range(m_LocalKeywordsSize): - self.m_LocalKeywords.append(reader.read_aligned_string()) + 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): + 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('"{0}" '.format(keyword)) sb.append("}\n") - if hasattr(self, 'm_LocalKeywords') and len(self.m_LocalKeywords) > 0: + 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: - sb.append("\"{0}\" ".format(keyword)) + for keyword in self.m_LocalKeywords: # type: ignore + sb.append('"{0}" '.format(keyword)) sb.append("}\n") - sb.append("\"") + sb.append('"') if len(self.m_ProgramCode) > 0: if self.m_ProgramType in [ @@ -530,14 +635,14 @@ def Export(self): ShaderGpuProgramType.kShaderGpuProgramGLES, ShaderGpuProgramType.kShaderGpuProgramGLCore32, ShaderGpuProgramType.kShaderGpuProgramGLCore41, - ShaderGpuProgramType.kShaderGpuProgramGLCore43 + ShaderGpuProgramType.kShaderGpuProgramGLCore43, ]: - sb.append(self.m_ProgramCode.tobytes().decode("utf8")) + sb.append(bytes(self.m_ProgramCode).decode("utf8")) elif self.m_ProgramType in [ ShaderGpuProgramType.kShaderGpuProgramDX9VertexSM20, ShaderGpuProgramType.kShaderGpuProgramDX9VertexSM30, ShaderGpuProgramType.kShaderGpuProgramDX9PixelSM20, - ShaderGpuProgramType.kShaderGpuProgramDX9PixelSM30 + ShaderGpuProgramType.kShaderGpuProgramDX9PixelSM30, ]: sb.append("// shader disassembly not supported on DXBC") elif self.m_ProgramType in [ @@ -550,22 +655,22 @@ def Export(self): ShaderGpuProgramType.kShaderGpuProgramDX11GeometrySM40, ShaderGpuProgramType.kShaderGpuProgramDX11GeometrySM50, ShaderGpuProgramType.kShaderGpuProgramDX11HullSM50, - ShaderGpuProgramType.kShaderGpuProgramDX11DomainSM50 + ShaderGpuProgramType.kShaderGpuProgramDX11DomainSM50, ]: sb.append("// shader disassembly not supported on DXBC") elif self.m_ProgramType in [ ShaderGpuProgramType.kShaderGpuProgramMetalVS, - ShaderGpuProgramType.kShaderGpuProgramMetalFS + ShaderGpuProgramType.kShaderGpuProgramMetalFS, ]: - reader = EndianBinaryReader(self.m_ProgramCode) + reader = EndianBinaryReader(self.m_ProgramCode, endian="<") fourCC = reader.read_u_int() - if fourCC == 0xf00dcafe: + if fourCC == 0xF00DCAFE: offset = reader.read_int() reader.Position = offset - entryName = reader.read_string_to_null() + _entryName = reader.read_string_to_null() buff = reader.read_bytes(int(reader.Length - reader.Position)) - sp.append(buff.decode("utf8")) + sb.append(bytes(buff).decode("utf8")) elif self.m_ProgramType == ShaderGpuProgramType.kShaderGpuProgramSPIRV: # TODO SpirVShaderConverter pass @@ -574,9 +679,9 @@ def Export(self): ShaderGpuProgramType.kShaderGpuProgramConsoleFS, ShaderGpuProgramType.kShaderGpuProgramConsoleHS, ShaderGpuProgramType.kShaderGpuProgramConsoleDS, - ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ShaderGpuProgramType.kShaderGpuProgramConsoleGS, ]: - sb.append(self.m_ProgramCode.decode("utf8")) + sb.append(bytes(self.m_ProgramCode).decode("utf8")) else: sb.append("//shader disassembly not supported on {0}".format(self.m_ProgramType)) diff --git a/UnityPy/export/SpriteHelper.py b/UnityPy/export/SpriteHelper.py index 51dbd6731..9ab5c8e45 100644 --- a/UnityPy/export/SpriteHelper.py +++ b/UnityPy/export/SpriteHelper.py @@ -1,58 +1,82 @@ -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.m_RenderDataMap[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 @@ -64,90 +88,262 @@ def get_image_from_sprite(m_Sprite) -> Image: 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 5220d1c06..cdb743eee 100644 --- a/UnityPy/export/Texture2DConverter.py +++ b/UnityPy/export/Texture2DConverter.py @@ -1,146 +1,423 @@ -import texture2ddecoder -from PIL import Image -from copy import copy -from io import BytesIO, BufferedIOBase, RawIOBase, IOBase +from __future__ import annotations + import struct -from ..enums import TextureFormat, BuildTarget +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 ..enums import BuildTarget, TextureFormat +from ..helpers import TextureSwizzler + +if TYPE_CHECKING: + from ..classes import Texture2D + TF = TextureFormat +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_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)) -def image_to_texture(img: Image, flip: bool = True): - # tex for eventually later usage of compressions - _image = None - if (isinstance(img, str) or isinstance(img, BufferedIOBase) or - isinstance(img, RawIOBase) or isinstance(img, IOBase)): - _image = Image.open(img) - elif isinstance(img, Image.Image): - _image = img - if _image: - if flip: - _image = _image.transpose(Image.FLIP_TOP_BOTTOM) - - if img.mode == "RGBA": - tex_format = TextureFormat.RGBA32 - elif img.mode == "RGB": - tex_format = TextureFormat.RGB24 - elif len(img.mode) == "A": - tex_format = TextureFormat.Alpha8 - return _image.tobytes(), tex_format - - -def get_image_from_texture2d(texture_2d, flip=True) -> Image: - """converts the given texture into PIL.Image - - :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) - selection = CONV_TABLE[texture_format] - - if len(selection) == 0: - raise NotImplementedError( - f"Not implemented texture format: {texture_format.name}" - ) - - if texture_format in XBOX_SWAP_FORMATS: - image_data = swap_bytes_for_xbox(image_data, texture_2d.platform) - + raise ValueError("Texture2D has no image data") + + if not isinstance(texture_format, TextureFormat): + texture_format = TextureFormat(texture_format) + + if platform == BuildTarget.XBOX360 and texture_format in XBOX_SWAP_FORMATS: + image_data = swap_bytes_for_xbox(image_data) + + 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 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: tuple = None -) -> 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: image_data = texture2ddecoder.decode_atc_rgb4(image_data, width, height) - + 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") -def pvrtc(image_data: bytes, width: int, height: int, fmt: bool): +@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) -> 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": @@ -149,91 +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: tuple = None -) -> 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.unpack("e", stream.read(2))[0] * 256) - for _ in range(width * height * len(codec)) - ) + 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, True ), -( TF.PVRTC_RGBA2, pvrtc, True ), -( TF.PVRTC_RGB4, pvrtc, False ), -( TF.PVRTC_RGBA4, pvrtc, False ), -( 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 0759e2fbf..0381efb02 100644 --- a/UnityPy/export/__init__.py +++ b/UnityPy/export/__init__.py @@ -1 +1,15 @@ -from . import SpriteHelper, Texture2DConverter, AudioClipConverter, MeshExporter +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 05cd7249f..3e17d7dea 100644 --- a/UnityPy/files/BundleFile.py +++ b/UnityPy/files/BundleFile.py @@ -1,11 +1,18 @@ -from . import File -from ..helpers import CompressionHelper -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 BlockInfo = namedtuple("BlockInfo", "uncompressedSize compressedSize flags") DirectoryInfoFS = namedtuple("DirectoryInfoFS", "offset size flags path") +reVersion = re.compile(r"(\d+)\.(\d+)\.(\d+)\w.+") class BundleFile(File.File): @@ -14,53 +21,65 @@ class BundleFile(File.File): signature: str version_engine: str version_player: str - - def __init__(self, reader: EndianBinaryReader, parent: File): - super().__init__(parent=parent) + 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) + 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) else: - raise NotImplemented(f"Unknown Bundle signature: {signature}") - + raise NotImplementedError(f"Unknown Bundle signature: {signature}") + self.read_files(blocksReader, m_DirectoryInfo) - + def read_web_raw(self, reader: EndianBinaryReader): # def read_header_and_blocks_info(self, reader:EndianBinaryReader): version = self.version if version >= 4: - _hash = reader.read_bytes(16) - crc = reader.read_u_int() - - minimumStreamedBytes = reader.read_u_int() + self._hash = reader.read_bytes(16) + self.crc = 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() - + 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 - - uncompressedBytes = CompressionHelper.decompress_lzma( - reader.read_bytes(compressedSize) - ) - + + uncompressedBytes = reader.read_bytes(compressedSize) + if self.signature == "UnityWeb": + uncompressedBytes = CompressionHelper.decompress_lzma(uncompressedBytes, True) + blocksReader = EndianBinaryReader(uncompressedBytes, offset=headerSize) nodesCount = blocksReader.read_int() m_DirectoryInfo = [ @@ -71,44 +90,60 @@ def read_web_raw(self, reader: EndianBinaryReader): ) for _ in range(nodesCount) ] - + return m_DirectoryInfo, blocksReader - + def read_fs(self, reader: EndianBinaryReader): - version = self.version - size = reader.read_long() - + size = reader.read_long() # noqa: F841 + # header compressedSize = reader.read_u_int() uncompressedSize = reader.read_u_int() - self._data_flags = reader.read_u_int() - - if self.version >= 7: + 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) - - _position = reader.Position - if self._data_flags & 0x80 != 0: # kArchiveBlocksInfoAtTheEnd + self._uses_block_alignment = True + + 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 blocksInfoBytes = reader.read_bytes(compressedSize) - - switch = self._data_flags & 0x3F - - 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, offset=_position) - - uncompressedDataHash = blocksInfoReader.read_bytes(16) + + blocksInfoBytes = self.decompress_data(blocksInfoBytes, uncompressedSize, self.dataflags) + blocksInfoReader = EndianBinaryReader(blocksInfoBytes, offset=start) + + uncompressedDataHash = blocksInfoReader.read_bytes(16) # noqa: F841 blocksInfoCount = blocksInfoReader.read_int() - + m_BlocksInfo = [ BlockInfo( blocksInfoReader.read_u_int(), # uncompressedSize @@ -117,7 +152,7 @@ def read_fs(self, reader: EndianBinaryReader): ) for _ in range(blocksInfoCount) ] - + nodesCount = blocksInfoReader.read_int() m_DirectoryInfo = [ DirectoryInfoFS( @@ -128,124 +163,126 @@ def read_fs(self, reader: EndianBinaryReader): ) 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( - reader.read_bytes(blockInfo.compressedSize), - blockInfo.uncompressedSize, - ) - # elif switch == 4: #LZHAM: - else: # no compression - return reader.read_bytes(blockInfo.uncompressedSize) - + 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(decompress_block(blockInfo) for blockInfo in m_BlocksInfo), - offset=(blocksInfoReader.real_offset()) + b"".join( + self.decompress_data( + reader.read_bytes(blockInfo.compressedSize), + blockInfo.uncompressedSize, + blockInfo.flags, + i, + ) + for i, blockInfo in enumerate(m_BlocksInfo) + ), + offset=(blocksInfoReader.real_offset()), ) return m_DirectoryInfo, blocksReader - 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 + 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) + # 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_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) + 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, block_info_flag=self._block_info_flags, data_flag=self._data_flags) + self.save_fs( + writer, + data_flag=self.dataflags, + block_info_flag=self._block_info_flags, + ) elif packer == "lz4": - self.save_fs(writer, block_info_flag=194, data_flag=2) + 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 NotImplemented("UnityFS - Packer:", packer) + raise NotImplementedError("UnityFS - Packer:", packer) return writer.bytes - - def save_fs(self, writer: EndianBinaryWriter, block_info_flag : int, data_flag : int): + + def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: int): # header # compressed blockinfo (block details & directionary) # compressed assets - + # 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, - + # block_info: # *flag & 0x80 ? at the end : right after header # *decompression via flag & 0x3F # *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) + # size (long) + # flag (int) + # name (string_to_null) # ) - + # file list & file data # prep nodes and build up block data data_writer = EndianBinaryWriter() @@ -259,36 +296,34 @@ def save_fs(self, writer: EndianBinaryWriter, block_info_flag : int, data_flag : ) for name, f in self.files.items() ] - + 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_u_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 @@ -302,53 +337,200 @@ def save_fs(self, writer: EndianBinaryWriter, block_info_flag : int, data_flag : block_writer.write_u_int(f_flag) # name block_writer.write_string_to_null(f_name) - + # compress the block data block_data = block_writer.bytes block_writer.dispose() - + 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 - ## file size - writer.write_long( - writer.Length - + 8 - + 4 - + 4 - + 4 - + (8 if self.version >= 7 else 0) - + compressed_block_data_size - + compressed_data_size - ) + ## 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 flag - writer.write_u_int(block_info_flag) - - if self.version >= 7: + # 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 (block_info_flag & 0x80) != 0: # at end of file + + 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 5bc78a913..e672f197c 100644 --- a/UnityPy/files/File.py +++ b/UnityPy/files/File.py @@ -1,30 +1,68 @@ -from ..enums import FileType +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 -from collections import namedtuple +if TYPE_CHECKING: + from ..environment import Environment DirectoryInfo = namedtuple("DirectoryInfo", "path offset size") -class File(object): +class File: name: str - files: dict + files: Dict[str, File] + environment: Environment + cab_file: str is_changed: bool signature: str packer: str - - # parent: File - - def __init__(self, parent=None): + 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): - if self.isinstance(SerializedFile.SerializedFile): - return self.objects.values() - for f in self.files.values(): if isinstance(f, (BundleFile.BundleFile, WebFile.WebFile)): for obj in f.objects: @@ -34,78 +72,81 @@ def get_objects(self): 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 - f = EndianBinaryReader(reader.read(node.size), offset=(reader.BaseOffset + node.offset)) - # f._flag = getattr(node, "flags", None) # required for save - typ, _ = ImportHelper.check_file_type(f) - if typ == FileType.BundleFile: - f = BundleFile.BundleFile(f, self) - elif typ == FileType.WebFile: - f = WebFile.WebFile(f, self) - elif typ == FileType.AssetsFile: - # pre-check if resource file - if not name.endswith((".resS", ".resource", ".config", ".xml", ".dat")): - # try to load the file as serialized file - try: - f = SerializedFile.SerializedFile(f, self) - except ValueError: - pass + 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 : str = "CAB-UnityPy_Mod.resS"): + + def get_writeable_cab(self, name: Optional[str] = None): """ Creates a new cab file in the bundle that contains the given data. - This is usefull for asset types that use resource files. + 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() - writer.flags = 4 + # 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 {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() - + def items(self): return self.files.items() - + def values(self): return self.files.values() - + def __getitem__(self, item): return self.files[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, SerializedFile, WebFile, ObjectReader +from . import BundleFile, ObjectReader, SerializedFile, WebFile # noqa: E402 diff --git a/UnityPy/files/ObjectReader.py b/UnityPy/files/ObjectReader.py index 3e40e26c2..b5fe1dbd0 100644 --- a/UnityPy/files/ObjectReader.py +++ b/UnityPy/files/ObjectReader.py @@ -1,84 +1,133 @@ +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 . import SerializedFile -from .. import classes -from ..streams import EndianBinaryReader, EndianBinaryWriter +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 -class ObjectReader: - byte_start: int - byte_size: int +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 - path_id: int - # serialized_type: SerializedType - _read_until: int + 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 - - def __init__(self, assets_file, reader: EndianBinaryReader): - 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 - + @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: - self.path_id = reader.read_long() + path_id = reader.read_long() elif header.version < 14: - self.path_id = reader.read_int() + path_id = reader.read_int() else: reader.align_stream() - self.path_id = reader.read_long() + path_id = reader.read_long() if header.version >= 22: - self.byte_start_offset = (self.reader.real_offset(), 8) - self.byte_start = reader.read_long() + byte_start = reader.read_long() else: - self.byte_start_offset = (self.reader.real_offset(), 4) - self.byte_start = reader.read_u_int() + byte_start = reader.read_u_int() - self.byte_start += header.data_offset - self.byte_header_offset = header.data_offset - self.byte_base_offset = self.reader.BaseOffset + byte_start += header.data_offset + byte_size = reader.read_u_int() - self.byte_size_offset = (self.reader.real_offset(), 4) - self.byte_size = reader.read_u_int() + type_id = reader.read_int() - self.type_id = reader.read_int() + serialized_type = None 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).__next__() - else: - self.serialized_type = SerializedFile.SerializedType( - reader, self.assets_file) + class_id = reader.read_u_short() + for typ in types: + if typ.class_id == type_id: + serialized_type = typ + break else: - typ = types[self.type_id] - self.serialized_type = typ - self.class_id = typ.class_id + typ = types[type_id] + serialized_type = typ + class_id = typ.class_id - self.type = ClassIDType(self.class_id) + clz_type = ClassIDType(class_id) + is_destroyed = None if header.version < 11: - self.is_destroyed = reader.read_u_short() + 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 serialized_type: + serialized_type.script_type_index = script_type_index + is_stripped = None if header.version == 15 or header.version == 16: - self.stripped = reader.read_byte() + 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: @@ -89,16 +138,17 @@ def write(self, header, writer: EndianBinaryWriter, data_writer: EndianBinaryWri writer.align_stream() writer.write_long(self.path_id) - if self.data: + 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 - if self.type != ClassIDType.MonoBehaviour: - end_pos = self.byte_start + self.byte_size - if self._read_until != end_pos: - self.reader.Position = self._read_until - data += self.reader.read_bytes(end_pos - self._read_until) + # -> 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) @@ -117,25 +167,41 @@ def write(self, header, writer: EndianBinaryWriter, data_writer: EndianBinaryWri 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: - writer.write_byte(self.stripped) + assert self.is_stripped is not None + writer.write_byte(self.is_stripped) - def set_raw_data(self, data): + def set_raw_data(self, data: bytes): self.data = data - self.assets_file.mark_changed() + 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): - return ( - self.assets_file._container[self.path_id] - if self.path_id in self.assets_file._container - else None - ) + 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): @@ -148,23 +214,8 @@ def Position(self, pos): def reset(self): self.reader.Position = self.byte_start - def read(self): - try: - obj = getattr(classes, self.type.name, classes.Object)(self) - except Exception as e: - raise e - # TODO: only specific exceptions here? - except: - # HACK: 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) - self._read_until = self.reader.Position - return obj + 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) @@ -182,43 +233,44 @@ def __repr__(self): # ################################################### - def dump_typetree(self, nodes: list = None) -> str: - self.reset() - sb = [] - if nodes: - TypeTreeHelper.read_typetree_str(sb, nodes, self) - elif getattr(self.serialized_type, "nodes", None): - TypeTreeHelper.read_typetree_str( - sb, self.serialized_type.nodes, self) - return "".join(sb) - return "" - - def dump_typetree_structure(self) -> str: - if getattr(self.serialized_type, "nodes", None): - return TypeTreeHelper.dump_typetree(self.serialized_type.nodes) - return "" - - def read_typetree(self, nodes: list = None) -> dict: + 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() - if nodes: - tree = TypeTreeHelper.read_typetree(nodes, self) - elif getattr(self.serialized_type, "nodes", None): - tree = TypeTreeHelper.read_typetree( - self.serialized_type.nodes, self) - else: - tree = {} - return tree - - def save_typetree(self, tree: dict, nodes: list = None, writer: EndianBinaryWriter = None): + 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) - if nodes: - TypeTreeHelper.write_typetree(tree, nodes, writer) - elif self.serialized_type.nodes: - TypeTreeHelper.write_typetree( - tree, self.serialized_type.nodes, writer) - else: - raise ValueError("There are no typetree nodes for this object.") + TypeTreeHelper.write_typetree(tree, node, writer, self.assets_file) data = writer.bytes self.set_raw_data(data) return data @@ -230,5 +282,74 @@ def get_raw_data(self) -> bytes: self.Position = pos return ret - def set_raw_data(self, data): - self.data = data + 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 c0373b2a0..d8853378b 100644 --- a/UnityPy/files/SerializedFile.py +++ b/UnityPy/files/SerializedFile.py @@ -1,29 +1,45 @@ -import os -import re -import sys +from __future__ import annotations -from . import File, ObjectReader -from ..enums import BuildTarget, ClassIDType, CommonString -from ..streams import EndianBinaryReader, EndianBinaryWriter +from ntpath import basename +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple -RECURSION_LIMIT = sys.getrecursionlimit() +from attrs import define +from .. import config +from ..enums import BuildTarget, ClassIDType +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 +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 @@ -45,15 +61,17 @@ 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})>" @@ -68,55 +86,39 @@ 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 - - def __repr__(self): - return f"" - - -class BuildType: - build_type: str - - def __init__(self, build_type): - self.build_type = build_type - - @property - def is_alpha(self): - return self.build_type == "a" - - @property - def is_path(self): - return self.build_type == "p" - - +@define(slots=True, init=False) class SerializedType: class_id: int - is_stripped_type: bool - script_type_index = -1 - nodes: list = [] # TypeTreeNode - script_id: bytes # Hash128 - old_type_hash: bytes # Hash128} - - def __init__(self, reader, serialized_file): + 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() @@ -125,87 +127,123 @@ def __init__(self, reader, serialized_file): self.script_type_index = reader.read_short() if version >= 13: - if (version < 16 and self.class_id < 0) or ( - version >= 16 and self.class_id == 114 + 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) # Hash128 - self.old_type_hash = reader.read_bytes(16) # Hash128 + self.script_id = reader.read_bytes(16) + self.old_type_hash = reader.read_bytes(16) if serialized_file._enable_type_tree: - type_tree = [] if version >= 12 or version == 10: - self.string_data = serialized_file.read_type_tree_blob( - type_tree) + self.node = TypeTreeNode.parse_blob(reader, version) else: - serialized_file.read_type_tree(type_tree) + self.node = TypeTreeNode.parse(reader, version) if version >= 21: - self.type_dependencies = reader.read_int_array() - - self.nodes = type_tree - - def write(self, serialized_file, writer): + 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 (version < 16 and self.class_id < 0) or ( - version >= 16 and self.class_id == 114 + 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: - serialized_file.save_type_tree5( - self.nodes, writer, self.string_data) + self.node.dump_blob(writer, version) else: - serialized_file.save_type_tree(self.nodes, writer) + 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) + + @property + def nodes(self) -> Optional[TypeTreeNode]: + # for compatibility with old versions + return self.node class SerializedFile(File.File): reader: EndianBinaryReader - is_changed: bool + version: UnityVersion unity_version: str - version: tuple - 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, parent=None): - self.is_changed = False - 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. @@ -244,7 +282,7 @@ def __init__(self, reader: EndianBinaryReader, parent=None): # ReadTypes type_count = reader.read_int() - self.types = [SerializedType(reader, self) 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: @@ -254,28 +292,24 @@ def __init__(self, reader: EndianBinaryReader, parent=None): object_count = reader.read_int() self.objects = {} for _ in range(object_count): - obj = ObjectReader.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 = [ - SerializedType(reader, self) 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() @@ -283,119 +317,77 @@ def __init__(self, reader: EndianBinaryReader, 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.m_Container.items(): - asset = asset_info.asset - self.container_[container] = asset - if hasattr(asset, "path_id"): - self._container[asset.path_id] = container - # if environment is not None: - # environment.container = {**environment.container, **self.container} + 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_ + 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 - def set_version(self, string_version): + 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) - - def mark_changed(self): - self.is_changed = True - if self.parent: - self.parent.mark_changed() - - def read_type_tree(self, type_tree): - level_stack = [[0,1]] - while level_stack: - level, count = level_stack[-1] - if count == 1: - level_stack.pop() - else: - level_stack[-1][1] -= 1 - - 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() - if children_count: - level_stack.append([level+1, children_count]) - return type_tree - - - def read_type_tree_blob(self, type_tree): - reader = self.reader - number_of_nodes = self.reader.read_int() - string_buffer_size = self.reader.read_int() - - for _ in range(number_of_nodes): - node = TypeTreeNode() - type_tree.append(node) - node.version = reader.read_u_short() - node.level = reader.read_byte() - node.is_array = reader.read_boolean() - node.type_str_offset = reader.read_u_int() - node.name_str_offset = reader.read_u_int() - node.byte_size = reader.read_int() - node.index = reader.read_int() - node.meta_flag = reader.read_int() - - if self.header.version > 19: - node.ref_type_hash = reader.read_u_long() - - string_buffer_reader = EndianBinaryReader( - reader.read(string_buffer_size), reader.endian) - for node in type_tree: - node.type = read_string(string_buffer_reader, node.type_str_offset) - node.name = read_string(string_buffer_reader, node.name_str_offset) - - return string_buffer_reader.bytes + 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. + This is usefull for asset types that use resource files. """ if not isinstance(self.parent, (File.BundleFile.BundleFile, File.WebFile.WebFile)): return None - cab = self.parent.get_writeable_cab(name) - cab.path = f"archive:/{name}" + 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 + file_identifier = FileIdentifierFake() file_identifier.__class__ = FileIdentifier file_identifier.temp_empty = "" import uuid + file_identifier.guid = uuid.uuid1().urn[-16:].encode("ascii") file_identifier.path = cab.path file_identifier.type = 0 - self.externals.append( - file_identifier - ) + self.externals.append(file_identifier) return cab - def save(self) -> bytes: + def save(self, packer: Optional[str] = None) -> bytes: # 1. header -> has to be delayed until the very end # 2. data -> types, objects, scripts, ... @@ -403,6 +395,7 @@ def save(self) -> bytes: header = self.header meta_writer = EndianBinaryWriter(endian=header.endian) data_writer = EndianBinaryWriter(endian=header.endian) + if header.version >= 7: meta_writer.write_string_to_null(self.unity_version) @@ -415,14 +408,14 @@ def save(self) -> bytes: # ReadTypes meta_writer.write_int(len(self.types)) for typ in self.types: - typ.write(self, meta_writer) + typ.write(self, meta_writer, False) if 7 <= header.version < 14: meta_writer.write_int(self.big_id_enabled) # ReadObjects meta_writer.write_int(len(self.objects)) - for obj in self.objects.values(): + 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) @@ -438,11 +431,13 @@ def save(self) -> bytes: external.write(header, meta_writer) 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) + 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 @@ -498,103 +493,6 @@ def save(self) -> bytes: return 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() - 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) - # type str offfset - writer.write_u_int(strings_values[i][0]) - # name str offset - writer.write_u_int(strings_values[i][1]) - # byte size - writer.write_int(node.byte_size) - # index - writer.write_int(node.index) - # meta flag - writer.write_int(node.meta_flag) - # ref hash - if self.header.version > 19: - writer.write_u_long(node.ref_type_hash) - - # string buffer - writer.write(string_buffer.bytes) - - if self.header.version >= 21: - writer.write_bytes(b"\x00" * 4) - def read_string(string_buffer_reader: EndianBinaryReader, value: int) -> str: is_offset = (value & 0x80000000) == 0 @@ -603,7 +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 CommonString: - return CommonString[offset] - - return str(offset) + return get_common_strings().get(offset, str(offset)) diff --git a/UnityPy/files/WebFile.py b/UnityPy/files/WebFile.py index eec3e72af..5521fbd7c 100644 --- a/UnityPy/files/WebFile.py +++ b/UnityPy/files/WebFile.py @@ -1,6 +1,8 @@ -from . import File +from typing import Optional + from ..helpers import CompressionHelper from ..streams import EndianBinaryReader, EndianBinaryWriter +from . import File class WebFile(File.File): @@ -9,16 +11,15 @@ class WebFile(File.File): files -- list of all files in the WebFile """ - - def __init__(self, reader: EndianBinaryReader, parent: File): - """Constructor Method - """ - super().__init__(parent=parent) - + + 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.packer = "gzip" data = CompressionHelper.decompress_gzip(reader.bytes) @@ -34,16 +35,16 @@ def __init__(self, reader: EndianBinaryReader, parent: File): else: 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 head_length = reader.read_int() - + files = [] while reader.Position < head_length: offset = reader.read_int() @@ -51,46 +52,41 @@ def __init__(self, reader: EndianBinaryReader, parent: File): path_length = reader.read_int() 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, - packer: str = "none", - signature: str = "UnityWebData1.0", + self, + files: Optional[dict] = None, + packer: str = "none", + signature: str = "UnityWebData1.0", ) -> bytes: # solve defaults if not files: files = self.files 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="<") # signature writer.write_string_to_null(signature) - + # data offset offset = sum( [ writer.Position, # signature - sum( - len(path.encode("utf-8")) 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 ] ) - + writer.write_int(offset) - + # 1. file headers for name, data in files.items(): # offset @@ -103,11 +99,11 @@ def save( enc_path = name.encode("utf-8") writer.write_int(len(enc_path)) writer.write(enc_path) - + # 2. file data for data in files.values(): writer.write(data) - + if packer == "gzip": return CompressionHelper.compress_gzip(writer.bytes) elif packer == "brotli": diff --git a/UnityPy/files/__init__.py b/UnityPy/files/__init__.py index bdaecc90a..076605d16 100644 --- a/UnityPy/files/__init__.py +++ b/UnityPy/files/__init__.py @@ -1,5 +1,14 @@ -from .File import File, DirectoryInfo -from .SerializedFile import SerializedFile from .BundleFile import BundleFile -from .WebFile import WebFile +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 f6233b5a2..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 + :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 + :type data: ByteString :return: compressed data :rtype: bytes """ - ec = lzma.LZMACompressor( + 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 + :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. @@ -74,25 +94,23 @@ def decompress_lz4(data: bytes, uncompressed_size: int) -> bytes: # LZ4M/LZ4HC 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 + :type data: ByteString :return: compressed data :rtype: bytes """ - return lz4.block.compress( - data, mode="high_compression", compression=9, store_size=False - ) + 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 + :type data: ByteString :raises brotli.error: BrotliDecompress failed :return: uncompressed data :rtype: bytes @@ -100,11 +118,11 @@ def decompress_brotli(data: bytes) -> 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 + :type data: ByteString :return: compressed data :rtype: bytes """ @@ -112,11 +130,11 @@ def compress_brotli(data: bytes) -> bytes: # GZIP -def decompress_gzip(data: bytes) -> bytes: +def decompress_gzip(data: ByteString) -> bytes: """decompresses gzip-compressed data :param data: compressed data - :type data: bytes + :type data: ByteString :raises OSError: Not a gzipped file :return: uncompressed data :rtype: bytes @@ -124,14 +142,134 @@ def decompress_gzip(data: bytes) -> 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 + :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 4fbbefab5..2717c6f68 100644 --- a/UnityPy/helpers/ImportHelper.py +++ b/UnityPy/helpers/ImportHelper.py @@ -1,117 +1,92 @@ -import os +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_path, dest_file) - - 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 = 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 = bytes(reader.read_bytes(6)) reader.Position = 0 if BROTLI_MAGIC == magic: return FileType.WebFile, reader - + # check if AssetsFile old_endian = reader.endian # read as if assetsfile and check version @@ -121,29 +96,78 @@ def check_file_type(input_) -> (FileType, EndianBinaryReader): file_size = reader.read_u_int() version = reader.read_u_int() data_offset = reader.read_u_int() - + if version >= 22: - endian = ">" if reader.read_boolean() else "<" - reserved = reader.read_bytes(3) + 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 - + _unknown = reader.read_long() # unknown + # reset reader.endian = old_endian reader.Position = 0 # check info - if any(( + if any( + ( version < 0, version > 100, - *[ - x < 0 or x > reader.Length - for x in [file_size, metadata_size, version, data_offset] - ], + *[x < 0 or x > reader.Length for x in [file_size, metadata_size, version, data_offset]], file_size < metadata_size, - file_size < data_offset - )): + file_size < data_offset, + ) + ): return FileType.ResourceFile, reader else: return FileType.AssetsFile, 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 669883b27..62e9800a8 100644 --- a/UnityPy/helpers/ResourceReader.py +++ b/UnityPy/helpers/ResourceReader.py @@ -1,76 +1,38 @@ -import os, glob -from ..streams import EndianBinaryReader -from ..files import File +import ntpath +from typing import TYPE_CHECKING +from ..streams import EndianBinaryReader -def get_resource_data(*args): - """ - Input: - Option 1: - 0 - path - file path - 1 - assets_file - SerializedFile - 2 - offset - - 3 - size - - Option 2: - 0 - reader - EndianBinaryReader - 1 - offset - - 2 - size - - - -> -2 = offset, -1 = size - """ - if len(args) == 4: - reader = search_resource(res_path=args[0], assets_file=args[1]) - elif len(args) == 3: - reader = args[0] - else: - raise TypeError(f"3 or 4 arguments required, but only {len(args)} given") - - reader.Position = args[-2] - return reader.read_bytes(args[-1]) +if TYPE_CHECKING: + from ..files.SerializedFile import SerializedFile -def search_resource(res_path, assets_file): - # try to find the resource in the Unity packages - base_name = os.path.basename(res_path) - if os.path.splitext(base_name)[1] == ".resource": - base_name2 = base_name.replace('.resource', '.assets.resS') - else: - base_name2 = base_name.replace('.assets.resS', '.resource') - - for p in [res_path, base_name, base_name2]: - reader = assets_file.parent.files.get(p) +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: - if isinstance(reader, File.File): - # in case the import helper accidentally detected a resource file as something else - reader = reader.reader - return reader - - # try to find it in the dir environment - c = assets_file - path = getattr(assets_file, "path", None) - while not path: - c = getattr(c,"parent",None) - if c == None: - raise FileNotFoundError( - f"Can't find the resource file {res_path}" - ) - path = getattr(c, "path", None) - current_directory = path - resource_file_path = os.path.join(current_directory, *res_path.split("/")) - if not os.path.isfile(resource_file_path): - resource_file_path = search_resource_file(current_directory, base_name) - if not os.path.isfile(resource_file_path): - resource_file_path = search_resource_file(current_directory, base_name.replace('.assets.resS', '.resource')) - - if os.path.isfile(resource_file_path): - return EndianBinaryReader(open(resource_file_path, "rb")) - else: - raise FileNotFoundError( - f"Can't find the resource file {res_path}" - ) + 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) -def search_resource_file(path, name): - print("real file", path, name) - files = glob.glob(os.path.join(path, "**", name), recursive=True) - return files[0] if len(files) else "" +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 55dfa9d0f..7eb916eb6 100644 --- a/UnityPy/helpers/TypeTreeHelper.py +++ b/UnityPy/helpers/TypeTreeHelper.py @@ -1,392 +1,444 @@ -from UnityPy.streams import EndianBinaryReader, EndianBinaryWriter -from ctypes import c_uint32 -import base64 -import tabulate - - -def get_nodes(nodes: list, index: int) -> list: - '''Copies all nodes above the level of the node at the set index. - - Parameters - ---------- - nodes : list - nodes/nodes of the typetree - index : int - index of the node - - Returns - ------- - list - A list of nodes - ''' - nodes2 = [nodes[index]] - level = nodes[index].level - for node in nodes[index+1:]: - if (node.level <= level): - return nodes2 - nodes2.append(node) - return nodes2 - - -def read_typetree(nodes: list, reader: EndianBinaryReader) -> dict: - '''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 - The parsed typtree - ''' - # reader.reset() - obj = {} - i = c_uint32(1) - while i.value < len(nodes): - node = nodes[i.value] - obj[node.name] = read_value(nodes, reader, i) - i.value += 1 - - readed = reader.Position - reader.byte_start - if readed != reader.byte_size: - print( - f"Error while read type, read {readed} bytes but expected {reader.byte_size} bytes") +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 read_value(nodes: list, reader: EndianBinaryReader, i: c_uint32): - node = nodes[i.value] - typ = node.type - align = (node.meta_flag & 0x4000) != 0 - - if typ == "SInt8": - value = reader.read_byte() - elif typ in ["UInt8", "char"]: - value = reader.read_u_byte() - elif typ in ["short", "SInt16"]: - value = reader.read_short() - elif typ in ["UInt16", "unsigned short"]: - value = reader.read_u_short() - elif typ in ["int", "SInt32"]: - value = reader.read_int() - elif typ in ["UInt32", "unsigned int", "Type*"]: - value = reader.read_u_int() - elif typ in ["long long", "SInt64"]: - value = reader.read_long() - elif typ in ["UInt64", "unsigned long long", "FileSize"]: - value = reader.read_u_long() - elif typ == "float": - value = reader.read_float() - elif typ == "double": - value = reader.read_double() - elif typ == "bool": - value = reader.read_boolean() - elif typ == "string": - value = reader.read_aligned_string() - i.value += 3 # Array, Size, Data(typ) - elif typ == "map": # map == MultiDict - if ((nodes[i.value + 1].meta_flag & 0x4000) != 0): +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 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[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 - map_ = get_nodes(nodes, i.value) - i.value += len(map_) - 1 - first = get_nodes(map_, 4) - second = get_nodes(map_, 4 + len(first)) - size = reader.read_int() - value = [None]*size - for j in range(size): - key = read_value(first, reader, c_uint32(0)) - value[j] = (key, read_value(second, reader, c_uint32(0))) - elif typ == "TypelessData": + + # size = read_value(node.m_Children[0].m_Children[0], reader, as_dict) size = reader.read_int() - value = reader.read_bytes(size) - i.value += 2 # Size == int, Data(typ) == char/uint8 - else: - # Vector - if i.value < len(nodes) - 1 and nodes[i.value + 1].type == "Array": - if (nodes[i.value + 1].meta_flag & 0x4000) != 0: - align = True - vector = get_nodes(nodes, i.value) - i.value += len(vector) - 1 - size = reader.read_int() - value = [ - read_value(vector, reader, c_uint32(3)) - for _ in range(size) - ] - else: # Class - clz = get_nodes(nodes, i.value) - i.value += len(clz) - 1 - value = {} - j = c_uint32(1) - while j.value < len(clz): - clz_node = clz[j.value] - value[clz_node.name] = read_value(clz, reader, j) - j.value += 1 + 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 = [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: + 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"], + ) + 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_typetree_str(sb: list, nodes: list, reader: EndianBinaryReader) -> list: - '''Reads the typetree of the object contained in the reader via the node list and dumps it as string. - - Parameters - ---------- - sb : list - StringBuilder - a list used to build the string dump, should be empty - nodes : list - List of nodes/nodes - reader : EndianBinaryReader - Reader of the object to be parsed - - Returns - ------- - list - The sb given as input - ''' - # reader.reset() - i = c_uint32(0) - while i.value < len(nodes): - read_value_str(sb, nodes, reader, i) - i.value += 1 - - readed = reader.Position - reader.byte_start - if readed != reader.byte_size: - print( - f"Error while read type, read {readed} bytes but expected {reader.byte_size} bytes") - - return sb - - -def read_value_str(sb: list, nodes: list, reader: EndianBinaryReader, i: c_uint32) -> list: - node = nodes[i.value] - typ = node.type - align = (node.meta_flag & 0x4000) != 0 - append = True - - if typ == "SInt8": - value = reader.read_byte() - elif typ in ["UInt8", "char"]: - value = reader.read_u_byte() - elif typ in ["short", "SInt16"]: - value = reader.read_short() - elif typ in ["UInt16", "unsigned short"]: - value = reader.read_u_short() - elif typ in ["int", "SInt32"]: - value = reader.read_int() - elif typ in ["UInt32", "unsigned int", "Type*"]: - value = reader.read_u_int() - elif typ in ["long long", "SInt64"]: - value = reader.read_long() - elif typ in ["UInt64", "unsigned long long", "FileSize"]: - value = reader.read_u_long() - elif typ == "float": - value = reader.read_float() - elif typ == "double": - value = reader.read_double() - elif typ == "bool": - value = reader.read_boolean() - elif typ == "string": - value = reader.read_aligned_string() - i.value += 3 # Array, Size, Data(typ) - append = False - sb.append('{0}{1} {2} = "{3}"\r\n'.format( - "\t" * node.level, node.type, node.name, value - )) - elif typ == "map": - if ((nodes[i.value + 1].meta_flag & 0x4000) != 0): +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 - map_ = get_nodes(nodes, i.value) - i.value += len(map_) - 1 - first = get_nodes(map_, 4) - second = get_nodes(map_, 4 + len(first)) - size = reader.read_int() - append = False - sb.append("{0}{1} {2}\r\n".format( - "\t" * node.level, node.type, node.name)) - sb.append("{0}{1} {2}\r\n".format( - "\t" * (node.level + 1), "Array", "Array")) - sb.append("{0}{1} {2} = {3}\r\n".format( - "\t" * (node.level + 1), "int", "size", size)) - for j in range(size): - sb.append("{0}[{1}]\r\n".format("\t" * (node.level + 2), j)) - sb.append("{0}{1} {2}\r\n".format( - "\t" * (node.level + 2), "pair", "data")) - read_value_str(sb, first, reader, c_uint32(0)) - read_value_str(sb, second, reader, c_uint32(0)) - elif typ == "TypelessData": - size = reader.read_int() - value = reader.read_bytes(size) - i.value += 2 # Size == int, Data(typ) == char/uint8 - append = False - sb.append("{0}{1} {2}\r\n".format( - "\t" * node.level, node.type, node.name)) - sb.append("{0}{1} {2} = {3}\r\n".format( - "\t" * node.level, "int", "size", size)) - # sb.append("{0}{1} {2} = {3}\r\n".format( - # "\t" * node.level, "UInt8", "data", base64.b64encode(value))) - else: - # Vector - if i.value < len(nodes) - 1 and nodes[i.value + 1].type == "Array": - if (nodes[i.value + 1].meta_flag & 0x4000) != 0: - align = True - vector = get_nodes(nodes, i.value) - i.value += len(vector) - 1 - size = reader.read_int() - append = False - sb.append("{0}{1} {2}\r\n".format( - "\t" * node.level, node.type, node.name)) - sb.append("{0}{1} {2}\r\n".format( - "\t" * (node.level + 1), "Array", "Array")) - sb.append("{0}{1} {2} = {3}\r\n".format( - "\t" * (node.level + 1), "int", "size", size)) - for j in range(size): - sb.append("{0}[{1}]\r\n".format("\t" * (node.level + 2), j)) - read_value_str(sb, vector, reader, c_uint32(3)) - - else: # Class - clz = get_nodes(nodes, i.value) - i.value += len(clz) - 1 - j = c_uint32(1) - append = False - sb.append("{0}{1} {2}\r\n".format( - "\t" * node.level, node.type, node.name)) - while j.value < len(clz): - read_value_str(sb, clz, reader, j) - j.value += 1 - - if append: - sb.append("{0}{1} {2} = {3}\r\n".format( - "\t" * node.level, node.type, node.name, value - )) + 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: + 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}, + ) + 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}, + ) + 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: + 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 sb - - -def dump_typetree(nodes) -> str: - '''Dumps the structure of the given nodes. - - Parameters - ---------- - nodes : list - List of nodes/nodes - - Returns - ------- - str - The dumped structure - ''' - field_names = ["level", "type", "name", "meta_flag"] - rows = [ - [ - getattr(x, key) for key in field_names - ] - for x in nodes - ] - return tabulate.tabulate(rows, headers=field_names) - - -def write_typetree(obj: dict, nodes: list, writer: EndianBinaryWriter = None) -> EndianBinaryWriter: - '''Writes the data of the object via the given typetree of the object into the writer. - - Parameters - ---------- - obj : dict - Object to be saved - nodes : list - List of nodes/nodes - writer : EndianBinaryWriter - Writer of the object to be saved - - Returns - ------- - EndianBinaryWriter - The writer that was used to save the data of the given object. - ''' - if not writer: - writer = EndianBinaryWriter() - - i = c_uint32(1) - while i.value < len(nodes): - value = obj[nodes[i.value].name] - write_value(value, nodes, writer, i) - i.value += 1 - return writer - - -def write_value(value, nodes: list, writer: EndianBinaryWriter, i: c_uint32): - node = nodes[i.value] - typ = node.type - align = (node.meta_flag & 0x4000) != 0 - - if typ == "SInt8": - writer.write_byte(value) - elif typ in ["UInt8", "char"]: - writer.write_u_byte(value) - elif typ in ["short", "SInt16"]: - writer.write_short(value) - elif typ in ["UInt16", "unsigned short"]: - writer.write_u_short(value) - elif typ in ["int", "SInt32"]: - writer.write_int(value) - elif typ in ["UInt32", "unsigned int", "Type*"]: - writer.write_u_int(value) - elif typ in ["long long", "SInt64"]: - writer.write_long(value) - elif typ in ["UInt64", "unsigned long long", "FileSize"]: - writer.write_u_long(value) - elif typ == "float": - writer.write_float(value) - elif typ == "double": - writer.write_double(value) - elif typ == "bool": - writer.write_boolean(value) - elif typ == "string": - writer.write_aligned_string(value) - i.value += 3 # Array, Size, Data(typ) - elif typ == "map": - if ((nodes[i.value + 1].meta_flag & 0x4000) != 0): + 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 - map_ = get_nodes(nodes, i.value) - i.value += len(map_) - 1 - first = get_nodes(map_, 4) - second = get_nodes(map_, 4 + len(first)) - # size - writer.write_int(len(value)) - # data - for key, val in value: - write_value(key, first, writer, c_uint32(0)) - write_value(val, second, writer, c_uint32(0)) - elif typ == "TypelessData": writer.write_int(len(value)) - writer.write_bytes(value) - i.value += 2 # Size == int, Data(typ) == char/uint8 - else: - # Vector - if i.value < len(nodes) - 1 and nodes[i.value + 1].type == "Array": - if (nodes[i.value + 1].meta_flag & 0x4000) != 0: - align = True - vector = get_nodes(nodes, i.value) - i.value += len(vector) - 1 - writer.write_int(len(value)) - for val in value: - write_value(val, vector, writer, c_uint32(3)) - else: # Class - clz = get_nodes(nodes, i.value) - i.value += len(clz) - 1 - j = c_uint32(1) - while j.value < len(clz): - val = value[clz[j.value].name] - write_value(val, clz, writer, j) - j.value += 1 + 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 c67ce73f2..6dba06cb0 100644 --- a/UnityPy/helpers/__init__.py +++ b/UnityPy/helpers/__init__.py @@ -1 +1,10 @@ -from . import CompressionHelper, ImportHelper, TypeTreeHelper +from . import ArchiveStorageManager, CompressionHelper, ContainerHelper, ImportHelper, TypeTreeHelper, UnityVersion + +__all__ = [ + "ArchiveStorageManager", + "CompressionHelper", + "ImportHelper", + "TypeTreeHelper", + "ContainerHelper", + "UnityVersion", +] diff --git a/UnityPy/lib/FMOD/Darwin/x64/4.x64 b/UnityPy/lib/FMOD/Darwin/x64/4.x64 deleted file mode 100644 index d87cae138..000000000 Binary files a/UnityPy/lib/FMOD/Darwin/x64/4.x64 and /dev/null differ diff --git a/UnityPy/lib/FMOD/Darwin/x64/4.x86 b/UnityPy/lib/FMOD/Darwin/x64/4.x86 deleted file mode 100644 index 1309010b9..000000000 Binary files a/UnityPy/lib/FMOD/Darwin/x64/4.x86 and /dev/null differ diff --git a/UnityPy/lib/FMOD/Darwin/x64/libfmod.dylib b/UnityPy/lib/FMOD/Darwin/x64/libfmod.dylib deleted file mode 100644 index 01c3e67dd..000000000 Binary files a/UnityPy/lib/FMOD/Darwin/x64/libfmod.dylib and /dev/null differ diff --git a/UnityPy/lib/FMOD/Linux/arm/libfmod.so b/UnityPy/lib/FMOD/Linux/arm/libfmod.so deleted file mode 100644 index 9e3644e3f..000000000 Binary files a/UnityPy/lib/FMOD/Linux/arm/libfmod.so and /dev/null differ diff --git a/UnityPy/lib/FMOD/Linux/armhf/libfmod.so b/UnityPy/lib/FMOD/Linux/armhf/libfmod.so deleted file mode 100644 index dee60d3d6..000000000 Binary files a/UnityPy/lib/FMOD/Linux/armhf/libfmod.so and /dev/null differ diff --git a/UnityPy/lib/FMOD/Linux/x86/libfmod.so b/UnityPy/lib/FMOD/Linux/x86/libfmod.so deleted file mode 100644 index 9c2618d29..000000000 Binary files a/UnityPy/lib/FMOD/Linux/x86/libfmod.so and /dev/null differ diff --git a/UnityPy/lib/FMOD/Linux/x86_64/libfmod.so b/UnityPy/lib/FMOD/Linux/x86_64/libfmod.so deleted file mode 100644 index c661a41a4..000000000 Binary files a/UnityPy/lib/FMOD/Linux/x86_64/libfmod.so and /dev/null differ diff --git a/UnityPy/lib/FMOD/Windows/x64/fmod.dll b/UnityPy/lib/FMOD/Windows/x64/fmod.dll deleted file mode 100644 index cce1e68a2..000000000 Binary files a/UnityPy/lib/FMOD/Windows/x64/fmod.dll and /dev/null differ diff --git a/UnityPy/lib/FMOD/Windows/x86/fmod.dll b/UnityPy/lib/FMOD/Windows/x86/fmod.dll deleted file mode 100644 index 111ce17f9..000000000 Binary files a/UnityPy/lib/FMOD/Windows/x86/fmod.dll and /dev/null differ diff --git a/UnityPy/lib/README.MD b/UnityPy/lib/README.MD deleted file mode 100644 index 647bfd629..000000000 --- a/UnityPy/lib/README.MD +++ /dev/null @@ -1,2 +0,0 @@ -## FMOD -Source: [FMOD Engine 2.00.10](https://fmod.com/download) diff --git a/UnityPy/math/Color.py b/UnityPy/math/Color.py deleted file mode 100644 index ff008fda6..000000000 --- a/UnityPy/math/Color.py +++ /dev/null @@ -1,55 +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 __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 6d56e1b98..000000000 --- a/UnityPy/math/Half.py +++ /dev/null @@ -1,102 +0,0 @@ -import struct -import math - -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] - else: - raise ValueError("Invalid amount of arguments") - - 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 e315785f6..000000000 --- a/UnityPy/math/Quaternion.py +++ /dev/null @@ -1,50 +0,0 @@ -class Quaternion: - X: float - Y: float - Z: float - W: float - - def __init__(self, x: float = 0.0, y: float = 0.0, z: float = 0.0, w: float = 0.0): - self._data = [0.0] * 4 - self.X = x - self.Y = y - self.Z = z - self.W = w - - @property - def X(self) -> float: - return self._data[0] - - @X.setter - def X(self, value: float): - self._data[0] = value - - @property - def Y(self) -> float: - return self._data[1] - - @Y.setter - def Y(self, value: float): - self._data[1] = value - - @property - def Z(self) -> float: - return self._data[2] - - @Z.setter - def Z(self, value: float): - self._data[2] = value - - @property - def W(self) -> float: - return self._data[3] - - @W.setter - def W(self, value: float): - self._data[3] = value - - def __getitem__(self, value): - return self._data[value] - - def __setitem__(self, index, value): - self._data[index] = value diff --git a/UnityPy/math/Rectangle.py b/UnityPy/math/Rectangle.py deleted file mode 100644 index d32ca639e..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(self): - return Rectangle( - round(self.x), round(self.y), round(self.width), round(self.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 89226face..968b32d89 100644 --- a/UnityPy/streams/EndianBinaryReader.py +++ b/UnityPy/streams/EndianBinaryReader.py @@ -1,87 +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 BaseOffset: int - - def __new__(cls, item, endian=">", offset=0): + _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 = 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=">", offset=0): + def __init__(self, item, endian: Endianess = ">", offset: int = 0): + self._endian = "" # type: ignore self.endian = endian self.BaseOffset = offset self.Position = 0 @property - def bytes(self): + 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, *args): + 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="utf8") -> 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: @@ -91,7 +143,7 @@ def read_string_to_null(self, max_length=32767) -> str: raise ValueError("Unterminated string: %r" % ret) 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 = bytes(self.read_bytes(length)) @@ -103,103 +155,115 @@ def read_aligned_string(self): def align_stream(self, alignment=4): 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_byte_array(self): - return self.read(self.read_int()) + def read_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_int_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("i", length) - def read_array(self, command, length: int): - return [command() for _ in range(length)] + def read_u_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_long_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("q", length) - def read_u_short_array(self): - return self.read_array(self.read_u_short, self.read_int()) + def read_u_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_float_array(self, length: Optional[int] = None) -> Tuple[float, ...]: + return self.read_array_struct("f", 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_double_array(self, length: Optional[int] = None) -> Tuple[float, ...]: + return self.read_array_struct("d", 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_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 read_matrix_array(self): - return self.read_array(self.read_matrix, self.read_int()) - - def real_offset(self): - """ Returns offset in the underlying file. - (Not working with unpacked streams.) + 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 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=">", offset=0): + def __init__(self, view, endian: Endianess = ">", offset: int = 0): super().__init__(view, endian=endian, offset=offset) self.view = memoryview(view) self.Length = len(view) @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): - if not length: + def read(self, size: Optional[int] = -1, /): + if not size: return b"" - ret = self.view[self.Position: self.Position + length] - self.Position += length - return ret - - def read_aligned_string(self): + 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) @@ -208,36 +272,78 @@ def read_aligned_string(self): 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=">", offset=0): + def __init__(self, stream: BufferedReader, endian: Endianess = ">", offset: int = 0): self.stream = stream - self.Length = self.stream.seek(0, 2) - offset 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+self.BaseOffset) + @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.Length) - self.Position = last_pos + self.Position = last_pos # type: ignore return ret def dispose(self): self.stream.close() pass - def read(self, length: int): - if not length: - return b"" - return self.stream.read(length) + +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 c8b921c56..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,40 +46,40 @@ 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)) + self.write(pack(self.endian + "?", value)) def write_string_to_null(self, value: str): self.write(value.encode("utf8", "surrogateescape")) @@ -89,81 +91,40 @@ def write_aligned_string(self, value: str): self.write(bstring) self.align_stream(4) - def align_stream(self, alignment=4): + def align_stream(self, alignment: int = 4): pos = self.stream.tell() align = (alignment - pos % alignment) % alignment self.write(b"\0" * align) - 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): + 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_byte_array(self, value: bytes): + def write_byte_array(self, value: builtins.bytes): self.write_int(len(value)) self.write(value) - def write_boolean_array(self, value: list): + 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/examples/AssetBatchConverter.py b/examples/AssetBatchConverter.py deleted file mode 100644 index 85c35efaf..000000000 --- a/examples/AssetBatchConverter.py +++ /dev/null @@ -1,177 +0,0 @@ -import os -import UnityPy -from collections import Counter -import zipfile -import json - -TYPES = [ - # Images - 'Sprite', - 'Texture2D', - # Text (filish) - 'TextAsset', - 'Shader', - 'MonoBehaviour', - 'Mesh' - # Font - 'Font', - # Audio - 'AudioClip', -] - -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 - env = UnityPy.load(src) - - # iterate over assets - for asset in env.assets: - # 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) - - # streamlineable types - export = None - if obj.type == 'TextAsset': - if not extension: - extension = '.txt' - export = data.script - - elif obj.type == "Font": - if data.m_FontData: - extension = ".ttf" - if data.m_FontData[0:4] == b"OTTO": - extension = ".otf" - export = data.m_FontData - else: - return [obj.path_id] - - elif obj.type == "Mesh": - extension = ".obf" - export = data.export().encode("utf8") - - elif obj.type == "Shader": - extension = ".txt" - export = data.export().encode("utf8") - - elif obj.type == "MonoBehaviour": - # The data structure of MonoBehaviours is custom - # and is stored as nodes - # If this structure doesn't exist, - # it might still help to at least save the binary data, - # which can then be inspected in detail. - if obj.serialized_type.nodes: - extension = ".json" - export = json.dumps( - obj.read_typetree(), - indent=4, - ensure_ascii=False - ).encode("utf8") - else: - extension = ".bin" - export = data.raw_data - - if export: - with open(f"{fp}{extension}", "wb") as f: - f.write(export) - - # non-streamlineable types - if obj.type == "Sprite" and data.Width: - data.image.save(f"{fp}.png") - - return [obj.path_id, data.m_RD.texture.path_id, getattr(data.m_RD.alphaTexture, 'path_id', None)] - - elif obj.type == "Texture2D": - if not os.path.exists(fp) and data.Width: - # textures can have size 0..... - data.image.save(f"{fp}.png") - - elif obj.type == "AudioClip": - samples = data.samples - if len(samples) == 0: - pass - elif len(samples) == 1: - with open(f"{fp}.wav", "wb") as f: - f.write(list(data.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.path_id] - - -if __name__ == '__main__': - main() diff --git a/examples/AssetPatch/522608825 b/examples/AssetPatch/522608825 deleted file mode 100644 index 4938f2f97..000000000 Binary files a/examples/AssetPatch/522608825 and /dev/null differ diff --git a/examples/AssetPatch/patch.py b/examples/AssetPatch/patch.py deleted file mode 100644 index 7a4d199b1..000000000 --- a/examples/AssetPatch/patch.py +++ /dev/null @@ -1,65 +0,0 @@ -import json -import os -import time -import UnityPy -from googletrans import Translator - -root = os.path.dirname(os.path.realpath(__file__)) - -def main(): - # load the original japanese localisation - src = os.path.join(root, "522608825") - e = UnityPy.load(src) - # iterate over all localisation assets - for cont, obj in e.container.items(): - # read the asset data - data = obj.read() - # get the localisation data - script = json.loads(bytes(data.script)) # bytes wrapper to handle memoryview - if hasattr(script, "infos"): - continue - print(data.name) - # translate the localisation - for entry in script["infos"]: - entry["value"] = translate(entry["value"]) - # overwrite the original - data.script = json.dumps( - script, - ensure_ascii=False, indent=4 - ).encode("utf8") - # apply the changes - data.save() - - # save the modified Bundle as file - with open(os.path.join(root, "522608825_patched"), "wb") as f: - f.write(e.file.save()) - - -# simple cache for already translated strings -# prevents requesting the same string multiple times -# therefore saving time and reducing the chance of being IP-blocked -TL_CACHE = {} -def translate(text): - # check if the text is valid - if not text.strip(" "): - return text - # check if the text was already translated once - if text in TL_CACHE: - return TL_CACHE[text] - - # actual google translation - translator = Translator(service_urls=['translate.googleapis.com']) - try: - ret = translator.translate(text, "en", "ja").text - TL_CACHE[text] = ret - return ret - except json.JSONDecodeError: - input("DecodeError") - return translate(text) - except (ConnectionError, AttributeError) as e: - print(e) - time.sleep(1) - return translate(text) - -if __name__ == "__main__": - main() diff --git a/examples/CustomMonoBehaviour/data.unity3d b/examples/CustomMonoBehaviour/data.unity3d deleted file mode 100644 index 85abee47d..000000000 Binary files a/examples/CustomMonoBehaviour/data.unity3d and /dev/null differ diff --git a/examples/CustomMonoBehaviour/get_scriptable_texture.py b/examples/CustomMonoBehaviour/get_scriptable_texture.py deleted file mode 100644 index 06ba8e738..000000000 --- a/examples/CustomMonoBehaviour/get_scriptable_texture.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -This example shows how to write a custom MonoBehaviour class. - - -The script is for a game that generates its encryption key based on a specific image. -This image is linked to by a MonoBheaviour called "ScriptableTexture2D". -It is linked to by a pointer after the default MonoBehaviour structure. -This intel was acquired from the assembly of the game. - -Unfortunately, the asset doesn't have a type-tree, so the default MonoBehaviour class can't find the pointer. -So a custom MonoBehaviour class is required to get the pointer comfortably. -Doing so is simple, as seen in the following code. -""" - -import os -import UnityPy -from UnityPy.classes import MonoBehaviour, PPtr - -class ScriptableTexture2D(MonoBehaviour): - def __init__(self, reader): - # calls the default MonoBehaviour init - super().__init__(reader=reader) - # here goes the implementation of the extra data - self.texture = PPtr(reader) - -# set the path for the target file -root = os.path.dirname(os.path.realpath(__file__)) -fp = os.path.join(root,"data.unity3d") - -env = UnityPy.load(fp) - -# find the correct asset -for obj in env.objects: - if obj.type == "MonoBehaviour": - data = obj.read() - if data.name == "ScriptableTexture2D": - # correct obj found - # lets read it with the custom class - st = ScriptableTexture2D(obj) - # read the linked image and save it - tex = st.texture.read() - print(st.texture.read().name) - #tex.image.save(os.path.join(root, f"{tex.name}.png")) \ No newline at end of file diff --git a/examples/rebundle.py b/examples/rebundle.py deleted file mode 100644 index 4e3b244b2..000000000 --- a/examples/rebundle.py +++ /dev/null @@ -1,138 +0,0 @@ -""" -This script shows how to create a bundle from dumped assets from the memory. - -The dumped assets consist of SerializedFiles and their resources(cabs). -A sample file of the original game is required for this script. -This example uses the globalgamemanager as this asset should exist in all Unity games. -""" - -import os -import uuid -import random -from copy import copy -import re - -import UnityPy -from UnityPy.enums import ClassIDType -from UnityPy.files import BundleFile -from UnityPy.files.SerializedFile import FileIdentifier, ObjectReader, SerializedType - - -SERIALIZED_PATH = "globalgamemanagers" -DATA_PATH = os.path.join(os.path.dirname(os.path.realpath(__file__)), "data") - - -def main(): - bf = Fake( - signature="UnityFS", - version=6, - format=6, - version_engine="2017.4.30f1", - version_player="5.x.x", - _class=BundleFile, - files={}, - ) - # load default serialized file and prepare some variables for easier access to key objects - env = UnityPy.load(SERIALIZED_PATH) - sf = env.file # serialized file - or_bp = list(sf.objects.values())[0].__dict__ # object data - - bf.files["serialized_file"] = sf - sf._flag = 4 - - # remove all unnesessary stuff - for key in list(sf.objects.keys()): - del sf.objects[key] - sf.externals = [] - - # add all files from DATA_PATH - for root, dirs, files in os.walk(DATA_PATH): - for f in files: - fp = os.path.join(root, f) - if f[:3] == "CAB": - add_cab(bf, sf, root, f) - else: - add_object(sf, fp, or_bp) - - # save edited bundle - open("bundle_edited.unity3d", "wb").write(bf.save()) - - -def add_cab(bf, sf, root, f): - fp = os.path.join(root, f) - bf.files[f] = Fake(data=open(fp, "rb").read(), _flag=4) - sf.externals.append( - Fake( - temp_empty="", - guid=generate_16_byte_uid(), - path=f"archive:/{os.path.basename(root)}/{f}", - type=0, - _class=FileIdentifier, - ) - ) - - -def add_object(sf, fp, or_bp): - # get correct type id - path_id, class_name = os.path.splitext(os.path.basename(fp)) - path_id = int(path_id) if re.match( - r"^\d+$", path_id) else generate_path_id(sf.objects) - class_id = getattr( - ClassIDType, class_name[1:], ClassIDType.UnknownType).value - type_id = -1 - for i, styp in enumerate(sf.types): - if styp.class_id == class_id: - type_id = i - if type_id == -1: # not found, add type - type_id = len(sf.types) - sf.types.append( - Fake( - class_id=class_id, - is_stripped_type=False, - node=[], - script_type_index=-1, - old_type_hash=generate_16_byte_uid(), - _class=SerializedType, - ) - ) - - # add new object - odata = copy(or_bp) - odata.update( - { - "data": open(fp, "rb").read(), - "path_id": generate_path_id(sf.objects), - "class_id": class_id, - "type_id": type_id, - } - ) - sf.objects[path_id] = Fake(**odata, _class=ObjectReader) - - -def generate_path_id(objects): - while True: - uid = random.randint(-(2 ** 16), 2 ** 16 - 1) - if uid not in objects: - return uid - - -def generate_16_byte_uid(): - return uuid.uuid1().urn[-16:].encode("ascii") - - -class Fake(object): - """ - fake class for easy class creation without init call - """ - - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - if "_class" in kwargs: - self.__class__ = kwargs["_class"] - - def save(self): - return self.data - - -if __name__ == "__main__": - main() 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 38aa7ce38..5ca47e178 100644 --- a/setup.py +++ b/setup.py @@ -1,44 +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(), - include_package_data = True, - 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=['python', 'unity', 'unity-asset', 'python3', 'data-minig', '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.6", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Games/Entertainment", - "Topic :: Multimedia :: Graphics", - ], - install_requires=[ - # block compression/decompression - "lz4", # BundleFile block compression - "brotli", # WebFile compression - # Texture & Sprite handling - "Pillow", - "texture2ddecoder", # texture decompression - # raw typetree dumping - "tabulate" - ] +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 index dda9c41a1..9c8d21674 100644 Binary files a/tests/samples/char_118_yuki.ab and b/tests/samples/char_118_yuki.ab differ 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 ef6d474b0..792e3626d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,6 +1,12 @@ +import io import os -import UnityPy +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") @@ -8,42 +14,109 @@ def test_read_single(): for f in os.listdir(SAMPLES): env = UnityPy.load(os.path.join(SAMPLES, f)) for obj in env.objects: - obj.read() + obj.parse_as_object() + obj.parse_as_dict() + def test_read_batch(): env = UnityPy.load(SAMPLES) for obj in env.objects: - obj.read() + 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): env = UnityPy.load(os.path.join(SAMPLES, f)) for obj in env.objects: - if obj.type == "Texture2D": - data = obj.read() - data.image.save("test.png") + 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): env = UnityPy.load(os.path.join(SAMPLES, f)) for obj in env.objects: - if obj.type == "Sprite": - obj.read().image.save("test.png") + 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(): - import UnityPy - import platform + 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 == "AudioClip": - clip = obj.read() - assert(len(clip.samples) == 1) + 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__": 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")