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 46f510889..000000000 --- a/.github/workflows/python-package.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Test and Publish - -on: [push, pull_request, workflow_dispatch] - -jobs: - deploy: - strategy: - fail-fast: false - matrix: - os: ["ubuntu-latest", "macOS-latest", "windows-latest"] - cp: ["cp37","cp38","cp39","cp310"] - - timeout-minutes: 30 - - runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} - ${{ matrix.cp }} - - steps: - - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v1 - with: - python-version: '3.10' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip wheel setuptools hatch - pip install --upgrade twine - - - name: Set up QEMU - if: runner.os == 'Linux' - uses: docker/setup-qemu-action@v1 - with: - platforms: all - - - name: Build wheels - uses: pypa/cibuildwheel@v2.3.1 - env: - CIBW_ARCHS_LINUX: auto aarch64 - CIBW_BUILD: | - ${{ matrix.cp }}-manylinux_x86_64 ${{ matrix.cp }}-manylinux_i686 ${{ matrix.cp }}-manylinux_aarch64 ${{ matrix.cp }}-win_amd64 ${{ matrix.cp }}-win32 ${{ matrix.cp }}-macosx_x86_64 - CIBW_REPAIR_WHEEL_COMMAND: '' # Disable auditwheel - CIBW_TEST_REQUIRES: pytest - CIBW_TEST_COMMAND: pytest -v -s {package}/tests - - - name: Publish to PyPI - if: success() && github.event_name == 'push' && env.TWINE_PASSWORD != '' && github.ref == 'refs/heads/master' - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} - run: | - twine upload ./wheelhouse/*.whl --skip-existing - hatch build -t sdist - twine upload ./dist/*.tar.gz --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/README.md b/README.md index 1701f1138..baf92789d 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,64 @@ # 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 (and all other types that you have the typetree of) +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 a commercial project, +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.** -Those changes are usually limited to minor versions (x.y) and not to patch versions (x.y.z). +**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. [Credits](#credits) +5. [Configurations](#configurations) +6. [Credits](#credits) ## Installation -**Python 3.6.0 or higher is required** +**Python 3.8 or higher is required.** -via pypi +Install via PyPI: -```cmd +```bash pip install UnityPy ``` -from source +Install from source code: -```cmd +```bash git clone https://github.com/K0lb3/UnityPy.git cd UnityPy python -m pip install . @@ -63,27 +69,24 @@ python -m pip 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 +#### Crash without warning/error -The C-implementation of the typetree reader can directly crash python. -In case this happens, the usage of the C-typetree reader can be disabled by adding these two lines to your main file. - -```python -from UnityPy.helpers import TypeTreeHelper -TypeTreeHelper.read_typetree_c = False -``` +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: @@ -97,10 +100,10 @@ def unpack_all_assets(source_folder : str, destination_folder : str): # process specific object types 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 @@ -113,7 +116,7 @@ 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.name in ["Texture2D", "Sprite"]: - data = obj.read() + 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 @@ -127,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 with slightly advanced python skills should look at [UnityPy/tools/extractor.py](UnityPy/tools/extractor.py) for a more advanced example. +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 given files. +[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, file stream,... -* 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 if the file is a WebFile, BundleFile, Asset, or APK. -The unpacked assets will be loaded into ``.files``, a dict consisting of ``asset-name : asset``. +The unpacked assets will be loaded into `.files`, a dict consisting of `asset-name : asset`. -All objects of the loaded assets can be easily accessed via ``.objects``, +All objects of the loaded assets can be easily accessed via `.objects`, which itself is a simple recursive iterator. ```python @@ -172,199 +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 with 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 -### [Object](UnityPy/files/ObjectReader.py) +Objects \([ObjectReader class](UnityPy/files/ObjectReader.py)\) contain the _actual_ files, e.g., textures, text files, meshes, settings, ... -Objects 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](UnityPy/classes/Texture2D.py) +### Texture2D -* ``.name`` -* ``.image`` converts the texture into a ``PIL.Image`` -* ``.m_Width`` - texture width (int) -* ``.m_Height`` - texture height (int) +- `.m_Name` +- `.image` converts the texture into a `PIL.Image` +- `.m_Width` - texture width (int) +- `.m_Height` - texture height (int) + +**Export** -__Export__ ```python from PIL import Image for obj in env.objects: 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. -* ``.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.name == "Sprite": - data = image.read() - data.image.save(path) + 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.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 exist, 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. -* ``.name`` -* ``.script`` -* ``.raw_data`` - data after the basic initialisation +- `.m_Name` +- `.m_Script` +- custom data + +**Export** -__Export__ ```python import json for obj in env.objects: if obj.type.name == "MonoBehaviour": # export - if obj.serialized_type.nodes: - # save decoded data - tree = obj.read_typetree() - fp = os.path.join(extract_dir, f"{tree['m_Name']}.json") - with open(fp, "wt", encoding = "utf8") as f: - json.dump(tree, f, ensure_ascii = False, indent = 4) - else: - # save raw relevant data (without Unity MonoBehaviour header) - data = obj.read() - fp = os.path.join(extract_dir, f"{data.name}.bin") - with open(fp, "wb") as f: - f.write(data.raw_data) + # 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: - data = obj.read() - with open(os.path.join(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. ```python -clip : AudioClip +clip: AudioClip for name, data in clip.samples.items(): with open(name, "wb") as f: f.write(data) ``` -### [Font](UnityPy/classes/Font.py) +### Font + +**Export** ```python if obj.type.name == "Font": - font : Font = obj.read() + 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.name+extension), "wb") as f: + with open(os.path.join(path, font.m_Name+extension), "wb") as f: f.write(font.m_FontData) ``` +### Mesh -### [Mesh](UnityPy/classes/Mesh.py) - -* ``.export()`` - mesh exported as .obj (str) +- `.export()` - mesh exported as .obj (str) 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()) ``` ### Renderer, MeshRenderer, SkinnedMeshRenderer + ALPHA-VERSION -* ``.export(export_dir)`` - exports the associated mesh, materials, and textures into the given directory +- `.export(export_dir)` - exports the associated mesh, materials, and textures into the given directory The mesh and materials will be in the Wavefront formats. ```python -mesh_renderer : Renderer +mesh_renderer: Renderer export_dir: str if mesh_renderer.m_GameObject: # get the name of the model - game_object = mesh_renderer.m_GameObject.read() - export_dir = os.path.join(export_dir, game_object.name) + game_obj_reader = mesh_renderer.m_GameObject.deref() + game_obj_name = game_obj_reader.peek_name() + export_dir = os.path.join(export_dir, game_obj_name) mesh_renderer.export(export_dir) ``` +### Texture2DArray + +WARNING - not well tested + +- `.m_Name` +- `.image` converts the texture2darray into a `PIL.Image` +- `.m_Width` - texture width (int) +- `.m_Height` - texture height (int) + +**Export** + +```python +import os +from PIL import Image +for obj in env.objects: + if obj.type.name == "Texture2DArray": + # export texture + tex_arr = obj.parse_as_object() + for i, image in enumerate(tex_arr.images): + image.save(os.path.join(path, f"{tex_arr.m_Name}_{i}.png")) + # editing isn't supported yet! +``` + +## Configurations + +There're several configurations and interfaces that provide the customizability to UnityPy. + +### Unity CN Decryption + +The Chinese version of Unity has its own builtin option to encrypt AssetBundles/BundleFiles. As it's a feature of Unity itself, and not a game specific protection, it is included in UnityPy as well. +To enable encryption simply use the code as follow, with `key` being the value that the game that loads the bundles passes to `AssetBundle.SetAssetBundleDecryptKey`. + +```python +import UnityPy +UnityPy.set_assetbundle_decrypt_key(key) +``` + +### Unity Fallback Version + +In case UnityPy failed to detect the Unity version of the game assets, you can set a fallback version. e.g. + +```python +import UnityPy.config +UnityPy.config.FALLBACK_UNITY_VERSION = "2.5.0f5" +``` + +### Disable Typetree C-Implementation + +The [C-implementation](UnityPyBoost/) of typetree reader can boost the parsing of typetree by a lot. If you want to disable it and use pure Python reader, you can put the following 2 lines in your main file. + +```python +from UnityPy.helpers import TypeTreeHelper +TypeTreeHelper.read_typetree_boost = False +``` + +### Custom Block (De)compression + +Some game assets have non-standard compression/decompression algorithm applied on the block data. If you wants to customize the compression/decompression function, you can modify the corresponding function mapping. e.g. + +```python +from UnityPy.enums.BundleFile import CompressionFlags +flag = CompressionFlags.LZHAM + +from UnityPy.helpers import CompressionHelper +CompressionHelper.COMPRESSION_MAP[flag] = custom_compress +CompressionHelper.DECOMPRESSION_MAP[flag] = custom_decompress +``` + +- `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: +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) +- [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 d4701e7d7..44828896e 100644 --- a/UnityPy/__init__.py +++ b/UnityPy/__init__.py @@ -1,11 +1,11 @@ -__version__ = "1.9.15" +__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 9176ab1df..000000000 --- a/UnityPy/classes/AnimationClip.py +++ /dev/null @@ -1,619 +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 - -try: - from UnityPy import UnityPyBoost -except ImportError: - UnityPyBoost = None - - -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, - ): - if UnityPyBoost: - return UnityPyBoost.unpack_floats( - self.m_NumItems, - self.m_Range, - self.m_Start, - bytes(self.m_Data), - self.m_BitSize, - itemCountInChunk, - chunkStride, - start, - numChunks, - ) - - 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): - if UnityPyBoost: - return UnityPyBoost.unpack_ints( - self.m_NumItems, bytes(self.m_Data), self.m_BitSize - ) - - data = [0] * self.m_NumItems - indexPos = 0 - bitPos = 0 - for i in range(self.m_NumItems): - bits = 0 - data[i] = 0 - while bits < self.m_BitSize: - data[i] |= (self.m_Data[indexPos] >> bitPos) << bits - num = min(self.m_BitSize - bits, 8 - bitPos) - bitPos += num - bits += num - if bitPos == 8: - indexPos += 1 - bitPos = 0 - data[i] &= (1 << self.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 = b"".join(val.to_bytes(4, "big") for val in self.data) - 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 7517fc402..000000000 --- a/UnityPy/classes/GameObject.py +++ /dev/null @@ -1,50 +0,0 @@ -from .EditorExtension import EditorExtension -from .PPtr import PPtr -from ..enums import ClassIDType - - -class GameObject(EditorExtension): - m_Components: list - m_Layer: int - name: str - m_Animator: PPtr - m_Animation: PPtr - m_Transform: PPtr - m_MeshRenderer: PPtr - m_SkinnedMeshRender: PPtr - m_MeshFilter: PPtr - - def __init__(self, reader): - super().__init__(reader=reader) - - self.m_Animator = None - self.m_Animation = None - self.m_Transform = None - self.m_MeshRenderer = None - self.m_SkinnedMeshRenderer = None - self.m_MeshFilter = None - - 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() - component = PPtr(reader) - self.m_Components[i] = component - - if component.type == ClassIDType.Animator: - self.m_Animator = component - elif component.type == ClassIDType.Animation: - self.m_Animation = component - elif component.type in [ClassIDType.Transform, ClassIDType.RectTransform]: - self.m_Transform = component - elif component.type == ClassIDType.MeshRenderer: - self.m_MeshRenderer = component - elif component.type == ClassIDType.SkinnedMeshRenderer: - self.m_SkinnedMeshRenderer = component - elif component.type == ClassIDType.MeshFilter: - self.m_MeshFilter = component - - 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 1985bdd63..000000000 --- a/UnityPy/classes/Mesh.py +++ /dev/null @@ -1,1057 +0,0 @@ -import math -from typing import List - -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 import MeshExporter - -try: - from UnityPy import UnityPyBoost -except: - UnityPyBoost = None - - -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() & 0xF - - 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( - MeshHelper.ToVertexFormat( - m_Channel.format, self.reader.version - ) - ) - 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( - MeshHelper.ToVertexFormat(m_Channel.format, self.reader.version) - ) - - -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 MeshExporter.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) - - if self.m_StreamData.path and 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( - MeshHelper.ToVertexFormat(m_Channel.format, self.reader.version) - ) - swap = self.reader.endian == "<" and componentByteSize > 1 - - if UnityPyBoost: - componentBytes = UnityPyBoost.unpack_vertexdata( - bytes(m_VertexData.m_DataSize), - componentByteSize, - m_VertexCount, - m_Stream.offset, - m_Stream.stride, - m_Channel.offset, - m_Channel.dimension, - swap, - ) - else: - componentBytes = bytearray( - m_VertexCount * m_Channel.dimension * componentByteSize - ) - - vertexBaseOffset = m_Stream.offset + m_Channel.offset - for v in range(m_VertexCount): - vertexOffset = vertexBaseOffset + m_Stream.stride * v - for d in range(m_Channel.dimension): - componentOffset = vertexOffset + componentByteSize * d - vertexDataSrc = componentOffset - componentDataSrc = componentByteSize * ( - v * m_Channel.dimension + d - ) - buff = m_VertexData.m_DataSize[ - vertexDataSrc : vertexDataSrc + componentByteSize - ] - if swap: # swap bytes - buff = buff[::-1] - componentBytes[ - componentDataSrc : componentDataSrc - + componentByteSize - ] = buff - - if MeshHelper.IsIntFormat(version, m_Channel.format): - componentsIntArray = MeshHelper.BytesToIntArray( - componentBytes, componentByteSize - ) - else: - componentsFloatArray = MeshHelper.BytesToFloatArray( - componentBytes, - componentByteSize, - MeshHelper.ToVertexFormat(m_Channel.format, version), - ) - - 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 - 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(format: int) -> int: - if format in [ - VertexFormat.kVertexFormatFloat, - VertexFormat.kVertexFormatUInt32, - VertexFormat.kVertexFormatSInt32, - ]: - return 4 - elif format in [ - VertexFormat.kVertexFormatFloat16, - VertexFormat.kVertexFormatUNorm16, - VertexFormat.kVertexFormatSNorm16, - VertexFormat.kVertexFormatUInt16, - VertexFormat.kVertexFormatSInt16, - ]: - return 2 - elif format in [ - VertexFormat.kVertexFormatUNorm8, - VertexFormat.kVertexFormatSNorm8, - VertexFormat.kVertexFormatUInt8, - VertexFormat.kVertexFormatSInt8, - ]: - return 1 - 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, vformat: "VertexFormat") -> List[float]: - if vformat == VertexFormat.kVertexFormatFloat: - return struct.unpack(f">{'f'*(len(inputBytes)//4)}", inputBytes) - elif vformat == VertexFormat.kVertexFormatFloat16: - return struct.unpack(f">{'e'*(len(inputBytes)//2)}", inputBytes) - elif vformat == VertexFormat.kVertexFormatUNorm8: - return [byte / 255.0 for byte in inputBytes] - elif vformat == VertexFormat.kVertexFormatSNorm8: - return [max(((byte - 128) / 127.0), -1.0) for byte in inputBytes] - elif vformat == VertexFormat.kVertexFormatUNorm16: - return [ - x / 65535.0 - for x in struct.unpack(f">{'H'*(len(inputBytes)//2)}", inputBytes) - ] - elif vformat == VertexFormat.kVertexFormatSNorm16: - return [ - max(((x - 32768) / 32767.0), -1.0) - for x in struct.unpack(f">{'h'*(len(inputBytes)//2)}", inputBytes) - ] - - @staticmethod - def BytesToIntArray(inputBytes, size): - if size == 1: - return [x for x in inputBytes] - elif size == 2: - return [ - x for x in struct.unpack(f">{'h'*(len(inputBytes)//2)}", inputBytes) - ] - elif size == 4: - return [ - x for x in struct.unpack(f">{'i'*(len(inputBytes)//4)}", inputBytes) - ] - - @staticmethod - def ToVertexFormat(format: int, version: List[int]) -> "VertexFormat": - if version[0] < 2017: - if format == VertexChannelFormat.kChannelFormatFloat: - return VertexFormat.kVertexFormatFloat - elif format == VertexChannelFormat.kChannelFormatFloat16: - return VertexFormat.kVertexFormatFloat16 - elif format == VertexChannelFormat.kChannelFormatColor: # in 4.x is size 4 - return VertexFormat.kVertexFormatUNorm8 - elif format == VertexChannelFormat.kChannelFormatByte: - return VertexFormat.kVertexFormatUInt8 - elif format == VertexChannelFormat.kChannelFormatUInt32: # in 5.x - return VertexFormat.kVertexFormatUInt32 - else: - raise ValueError(f"Failed to convert {format.name} to VertexFormat") - elif version[0] < 2019: - if format == VertexFormat2017.kVertexFormatFloat: - return VertexFormat.kVertexFormatFloat - elif format == VertexFormat2017.kVertexFormatFloat16: - return VertexFormat.kVertexFormatFloat16 - elif ( - format == VertexFormat2017.kVertexFormatColor - or format == VertexFormat2017.kVertexFormatUNorm8 - ): - return VertexFormat.kVertexFormatUNorm8 - elif format == VertexFormat2017.kVertexFormatSNorm8: - return VertexFormat.kVertexFormatSNorm8 - elif format == VertexFormat2017.kVertexFormatUNorm16: - return VertexFormat.kVertexFormatUNorm16 - elif format == VertexFormat2017.kVertexFormatSNorm16: - return VertexFormat.kVertexFormatSNorm16 - elif format == VertexFormat2017.kVertexFormatUInt8: - return VertexFormat.kVertexFormatUInt8 - elif format == VertexFormat2017.kVertexFormatSInt8: - return VertexFormat.kVertexFormatSInt8 - elif format == VertexFormat2017.kVertexFormatUInt16: - return VertexFormat.kVertexFormatUInt16 - elif format == VertexFormat2017.kVertexFormatSInt16: - return VertexFormat.kVertexFormatSInt16 - elif format == VertexFormat2017.kVertexFormatUInt32: - return VertexFormat.kVertexFormatUInt32 - elif format == VertexFormat2017.kVertexFormatSInt32: - return VertexFormat.kVertexFormatSInt32 - else: - raise ValueError(f"Failed to convert {format.name} to VertexFormat") - else: - return VertexFormat(format) - - -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 diff --git a/UnityPy/classes/MeshFilter.py b/UnityPy/classes/MeshFilter.py deleted file mode 100644 index 0a2da5e18..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.m_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 956cc8f21..000000000 --- a/UnityPy/classes/MonoBehaviour.py +++ /dev/null @@ -1,42 +0,0 @@ -from .Behaviour import Behaviour -from .PPtr import PPtr, save_ptr -from ..streams import EndianBinaryReader, EndianBinaryWriter -from ..exceptions import TypeTreeError as TypeTreeError - -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: - try: - self.read_typetree() - except TypeTreeError as e: - print("Failed to read TypeTree:\n", e) - self.assets_file._enable_type_tree = False - - 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 0565a4e19..c6ea23738 100644 --- a/UnityPy/classes/Object.py +++ b/UnityPy/classes/Object.py @@ -1,180 +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 ..exceptions import TypeTreeError as TypeTreeError +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: - nodes = self.reader.get_typetree_nodes() - return any( - node.m_Name == name for node in 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(nodes) - 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): - 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 + 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 - obj = class_to_dict(self if not self.type_tree else self.type_tree) - return self.reader.save_typetree(obj, nodes, writer) + keys = set(self.__dict__.keys()) + annotation_keys = get_annotation_keys(clz) + extra_keys = keys - annotation_keys + instance = clz(**{key: data[key] for key in annotation_keys}) + for key in extra_keys: + setattr(instance, key, data[key]) + return instance - def 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) - else: - # save for objects WITHOUT specific save function - # so we have to use the typetree if it exists - self.save_typetree() - - 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, name): - """ - If item not found in __dict__, read type_tree and check if it is in there. - """ - if name == "type_tree" or self.type_tree == None: - old_pos = self.reader.Position - self.read_typetree() - self.reader.Position = old_pos - if name == "type_tree": - return self.type_tree - elif name == "read": - return lambda: self - return getattr(self.type_tree, name) - - def get(self, key, default=None): - return getattr(self, key, default) - - def __repr__(self): - return "<%s %s>" % (self.__class__.__name__, self.name) - - def __hash__(self): - return hash(self.path_id) - - def __eq__(self, other): - if isinstance(other, Object): - return self.path_id == other.path_id - elif isinstance(other, int): - return self.path_id == other - return False - - -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._obj = None - 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 cae164a30..313e62745 100644 --- a/UnityPy/classes/PPtr.py +++ b/UnityPy/classes/PPtr.py @@ -1,106 +1,112 @@ -from ..files import ObjectReader -from ..streams import EndianBinaryWriter -from ..helpers import ImportHelper -from .. import files -from ..enums import FileType, ClassIDType -import os -from .. import environment - - -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) - - -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: - environment = self.assets_file.environment - external_name = self.external_name - # try to find it in the already registered cabs - manager = environment.get_cab(external_name) - - if not manager: - # guess we have to try to find it as file then - path = environment.path - if path is not None: - basename = os.path.basename(external_name) - possible_names = [basename, basename.lower(), basename.upper()] - for root, dirs, files in os.walk(path): - for name in files: - if name in possible_names: - manager = environment.load_file( - os.path.join(root, name) - ) - environment.register_cab(name, manager) - break - else: - # else is reached if the previous loop didn't break - continue - break - if manager and self.path_id in manager.objects: - self._obj = manager.objects[self.path_id] - else: - self._obj = None - if self.external_name: - print(f"Couldn't find dependency {self.external_name}") - print("You can try to load it manually to the environment in advance") - print("for Web-&BundleFiles: env.load_file(dependency)") - print( - "for SerializedFiles: env.register_cab(depdency_basename, env.load_file(dependency)" - ) - elif self.path_id: - print(f"Couldn't find referenced object with path_id {self.path_id}") - - return self._obj +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 type(self): - obj = self.get_obj() - if obj is None: - return ClassIDType.UnknownType - return obj.type + def file_id(self) -> int: + # backwards compatibility + return self.m_FileID @property - def external_name(self): - if self.file_id > 0 and self.file_id - 1 < len(self.assets_file.externals): - return self.assets_file.externals[self.file_id - 1].name - - def __getattr__(self, key): - obj = self.get_obj() - return getattr(obj, key) - - def __repr__(self): - return "<%s %s>" % ( - self.__class__.__name__, - self._obj.__class__.__repr__(self.get_obj()) - if self.get_obj() - else "Not Found", - ) + 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 assetsfile is None: + raise ValueError("PPtr can't deref without an assetsfile!") + + if self.m_PathID == 0: + raise ValueError("PPtr can't deref with m_PathID == 0!") + + assetsfile_dst = None + if self.m_FileID == 0: + assetsfile_dst = assetsfile or self.assetsfile + else: + # resolve file id to external name + external_id = self.m_FileID - 1 + if external_id >= len(assetsfile.externals): + raise FileNotFoundError("Failed to resolve pointer - invalid m_FileID!") + external = assetsfile.externals[external_id] + + # resolve external name to assetsfile + container = assetsfile.parent + if container is None: + # TODO - use default fs + raise FileNotFoundError(f"PPtr points to {external.path} but no container is set!") + + external_clean_path = external.path + if external_clean_path.startswith("archive:/"): + external_clean_path = external_clean_path[9:] + if external_clean_path.startswith("assets/"): + external_clean_path = external_clean_path[7:] + external_clean_path = external_clean_path.rsplit("/")[-1].lower() + + for key, file in container.files.items(): + if key.lower() == external_clean_path: + assetsfile_dst = file + break + else: + env = assetsfile.environment + cab = env.find_file(external_clean_path) + if cab: + assetsfile_dst = cab + else: + raise FileNotFoundError(f"Failed to resolve pointer - {external.path} not found!") + + if assetsfile_dst is None: + raise FileNotFoundError(f"Failed to resolve pointer - {self.m_FileID} not found!") + + return cast("ObjectReader[T]", assetsfile_dst.objects[self.m_PathID]) + + def deref_parse_as_object(self, assetsfile: Optional[SerializedFile] = None) -> T: + return self.deref(assetsfile).parse_as_object() + + def deref_parse_as_dict(self, assetsfile: Optional[SerializedFile] = None) -> dict[str, Any]: + return self.deref(assetsfile).parse_as_dict() def __bool__(self): - return 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 4e65f415d..000000000 --- a/UnityPy/classes/Renderer.py +++ /dev/null @@ -1,95 +0,0 @@ -from .Component import Component -from .PPtr import PPtr -from ..export import MeshRendererExporter - - -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() - - def export(self, export_dir: str) -> None: - MeshRendererExporter.export_mesh_renderer(self, export_dir) 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 c2be9a08a..000000000 --- a/UnityPy/classes/Shader.py +++ /dev/null @@ -1,464 +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 - self.offsets = reader.read_u_int_array_array()[0] - self.compressedLengths = reader.read_u_int_array_array()[0] - self.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() - - if version >= (2021, 1, 4) or (version[0] == 2020 and version >= (2020, 3, 2)): - self.m_IsPartialCB = reader.read_boolean() - reader.align_stream() - - -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 SerializedProgramParameters: - def __init__(self, reader): - 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)] - - numSamplers = reader.read_int() - self.m_Samplers = [SamplerParameter(reader) for _ in range(numSamplers)] - - -class SerializedSubProgram: - def __init__(self, reader): - version = reader.version - - self.m_BlobIndex = reader.read_u_int() - self.m_Channels = ParserBindChannels(reader) - - if (2019,) <= version < (2021, 2): # 2019 ~2021.1 - 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() - - if version >= (2021, 1, 4) or (version[0] == 2020 and version >= (2020, 3, 2)): - self.m_Parameters = SerializedProgramParameters(reader) - else: - 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_Samples = [SamplerParameter(reader) for _ in range(numSamplers)] - - if version >= (2017, 2): # 2017.2 and up - if version >= (2021,): - self.m_ShaderRequirements = reader.read_long() - else: - self.m_ShaderRequirements = reader.read_int() - - -class SerializedProgram: - def __init__(self, reader): - version = reader.version - - numSubPrograms = reader.read_int() - self.m_SubPrograms = [ - SerializedSubProgram(reader) for _ in range(numSubPrograms) - ] - - if version >= (2021, 1, 4) or (version[0] == 2020 and version >= (2020, 3, 2)): - self.m_CommonParameters = SerializedProgramParameters(reader) - - -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() - if version < (2021, 2): - 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) - if version >= (2021, 2): - m_SerializedKeywordStateMask = reader.read_u_short_array() - reader.align_stream() - - -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 SerializedCustomEditorForRenderPipeline: - def __init__(self, reader): - self.customEditorName = reader.read_aligned_string() - self.renderPipelineType = reader.read_aligned_string() - - -class SerializedShader: - def __init__(self, reader): - version = reader.version - - self.m_PropInfo = SerializedProperties(reader) - numSubShaders = reader.read_int() - self.m_SubShaders = [SerializedSubShader(reader) for _ in range(numSubShaders)] - - if version >= (2021, 2): - self.m_KeywordNames = reader.read_string_array() - self.m_KeywordFlags = reader.read_bytes(reader.read_int()) - reader.align_stream() - - 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) - ] - - if version >= (2021,): - m_CustomEditorForRenderPipelinesSize = reader.read_int() - self.m_CustomEditorForRenderPipelines = [ - SerializedCustomEditorForRenderPipeline(reader) - for _ in range(m_CustomEditorForRenderPipelinesSize) - ] - - 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 3f65e84e2..000000000 --- a/UnityPy/classes/Sprite.py +++ /dev/null @@ -1,254 +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, SpritePackingMode, SpritePackingRotation -from ..streams import EndianBinaryWriter, EndianBinaryReader - - -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() - # TODO: might occur in earlier 2020 versions - 2020.3.13 reported - if version >= ( - 2020, - 3, - ): - self.m_Bones = [SpriteBone() for _ in range(m_BonesSize)] - else: - 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)) - if version >= (2020, 3): - for bone in self.m_Bones: - bone.save(writer) - else: - 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 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) - - -class SpriteBone: - name: str - position: tuple - rotation: tuple - length: float - parentId: int - - def __init__(self, reader: EndianBinaryReader) -> None: - self.name = reader.read_aligned_string() - self.position = reader.read_vector3() - self.rotation = reader.read_vector3() - self.length = reader.read_float() - self.parentId = reader.read_int() - - def save(self, writer: EndianBinaryWriter): - writer.write_aligned_string(self.name) - writer.write_vector3(self.position) - writer.write_vector3(self.rotation) - writer.write_float(self.length) - writer.write_int(self.parentId) diff --git a/UnityPy/classes/SpriteAtlas.py b/UnityPy/classes/SpriteAtlas.py deleted file mode 100644 index 8bf65c45c..000000000 --- a/UnityPy/classes/SpriteAtlas.py +++ /dev/null @@ -1,40 +0,0 @@ -from .NamedObject import NamedObject -from .PPtr import PPtr -from .Sprite import SpriteSettings, SecondarySpriteTexture - - -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 = 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 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) - - if version >= (2020, 2): - secondaryTexturesSize = reader.read_int() - self.secondaryTextures = [ - SecondarySpriteTexture(reader) for _ in range(secondaryTexturesSize) - ] - reader.align_stream() 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 5c5539a39..000000000 --- a/UnityPy/classes/Texture2D.py +++ /dev/null @@ -1,276 +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 -from PIL import Image -from io import BufferedIOBase, RawIOBase, IOBase - - -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 - if img is None: - raise Exception("No image provided") - - if ( - isinstance(img, str) - or isinstance(img, BufferedIOBase) - or isinstance(img, RawIOBase) - or isinstance(img, IOBase) - ): - img = Image.open(img) - - img_data, tex_format = Texture2DConverter.image_to_texture2d( - img, self.m_TextureFormat - ) - - # disable mipmaps as we don't store them ourselves by default - if self.version[:2] < (5, 2): # 5.2 down - self.m_MipMap = False - else: - self.m_MipCount = 1 - - self.image_data = img_data - self.m_MipCount = 1 - # width * height * channel count - self.m_CompleteImageSize = len( - img_data - ) # img.width * img.height * len(img.getbands()) - self.m_TextureFormat = tex_format - - @property - def image_data(self): - return self._image_data - - def reset_streamdata(self): - if not self.m_StreamData: - return - self.m_StreamData.offset = 0 - self.m_StreamData.size = 0 - self.m_StreamData.path = "" - - @image_data.setter - def image_data(self, data: bytes): - self._image_data = data - # ignore writing to cab for now until it's more stable - # if self.version >= (5, 3) and self.m_StreamData.path: - # 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.reset_streamdata() - self.reset_streamdata() - - def set_image( - self, - img, - target_format: TextureFormat = None, - in_cab: bool = False, - mipmap_count: int = 1, - ): - if img is None: - raise Exception("No image provided") - if not target_format: - target_format = self.m_TextureFormat - - img_data, tex_format = Texture2DConverter.image_to_texture2d(img, target_format) - 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.BICUBIC) - img_data += Texture2DConverter.image_to_texture2d( - re_img, target_format - )[0] - - if self.version[:2] < (5, 2): # 5.2 down - self.m_MipMap = mipmap_count > 1 - else: - self.m_MipCount = mipmap_count - - if in_cab: - self.image_data = img_data - else: - self._image_data = img_data - self.reset_streamdata() - - # width * height * channel count - self.m_CompleteImageSize = len( - img_data - ) # img.width * img.height * len(img.getbands()) - self.m_TextureFormat = tex_format - - 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 index f1481b035..98c5660b5 100644 --- a/UnityPy/config.py +++ b/UnityPy/config.py @@ -1,6 +1,40 @@ -# used when no version is defined by the SerializedFile or its BundleFile -FALLBACK_UNITY_VERSION = "2.5.0f5" -# 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 +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/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 b7c5f9241..9168fb8da 100644 --- a/UnityPy/enums/ClassIDType.py +++ b/UnityPy/enums/ClassIDType.py @@ -1,4 +1,4 @@ -# https://docs.unity3d.com/Manual/ClassIDReference.html +# https://docs.unity3d.com/Manual/ClassIDReference.html from .ExtendableEnum import ExtendableEnum 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/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 index 007208e23..e640576e6 100644 --- a/UnityPy/enums/SpritePackingMode.py +++ b/UnityPy/enums/SpritePackingMode.py @@ -1,5 +1,6 @@ from enum import IntEnum + class SpritePackingMode(IntEnum): kSPMTight = 0 - kSPMRectangle = 1 \ No newline at end of file + kSPMRectangle = 1 diff --git a/UnityPy/enums/SpritePackingRotation.py b/UnityPy/enums/SpritePackingRotation.py index 14d108a81..1a361de14 100644 --- a/UnityPy/enums/SpritePackingRotation.py +++ b/UnityPy/enums/SpritePackingRotation.py @@ -1,5 +1,6 @@ from enum import IntEnum + class SpritePackingRotation(IntEnum): kSPRNone = 0 kSPRFlipHorizontal = 1 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 d919e69a2..c9a99d83e 100644 --- a/UnityPy/enums/__init__.py +++ b/UnityPy/enums/__init__.py @@ -1,15 +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 c87fcbd6f..c4a60aea2 100644 --- a/UnityPy/environment.py +++ b/UnityPy/environment.py @@ -1,162 +1,187 @@ -from typing import List, Callable, Dict, Union import io +import ntpath import os -from zipfile import ZipFile import re -from . import files -from .files import File, ObjectReader +from typing import TYPE_CHECKING, BinaryIO, Callable, Dict, List, Optional, Union, cast +from zipfile import ZipFile + +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 - cabs: 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.cabs = {} - self.path = None - self.out_path = os.path.join(os.getcwd(), "output") + 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.path = ntpath.dirname(arg) or ntpath.curdir if reSplit.match(arg): self.load_files([arg]) else: self.load_file(arg) - elif os.path.isdir(arg): + elif self.fs.isdir(arg): self.path = arg self.load_folder(arg) else: - self.path = None self.load_file(file=arg) if len(self.files) == 1: self.file = list(self.files.values())[0] - if self.path == "": - self.path = os.getcwd() - 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 Environment.""" - self.load_files( - [ - os.path.join(root, f) - for root, dirs, files in os.walk(path) - for f in files - ] - ) + 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): + def load(self, files: List[str]): """Loads all files into the Environment.""" self.files.update( - { - os.path.basename(f): self.load_file(open(f, "rb"), self, f) - for f in files - if os.path.exists(f) - } + {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: Union[io.IOBase, str], - parent: Union["Environment", File] = None, - name: str = None, + file: FileSourceType, + parent: Optional[Union["Environment", File]] = None, + name: Optional[str] = None, + is_dependency: bool = False, ): if not parent: parent = self + if isinstance(file, str): split_match = reSplit.match(file) if split_match: - basepath, basename = split_match.groups() - file = [] - for i in range(0, 999): - item = f"{basepath}.split{i}" - if item in files: - with open(item, "rb") as f: - file.append(f.read()) - elif i: - break + basepath, _basename = split_match.groups() + assert isinstance(basepath, str) name = basepath - file = b"".join(file) + file = self._load_split_file(basepath) else: name = file - file = open(file, "rb") - - typ, reader = ImportHelper.check_file_type(file) - - try: - stream_name = ( - name - if name - else getattr( - file, - "name", - str(file.__hash__()) if hasattr(file, "__hash__") else "", - ) + 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 typ == FileType.AssetsFile: - f = files.SerializedFile(reader, parent, name=stream_name) - self.register_cab(stream_name, f) - elif typ == FileType.BundleFile: - f = files.BundleFile(reader, parent, name=stream_name) - elif typ == FileType.WebFile: - f = files.WebFile(reader, parent, name=stream_name) - elif typ == FileType.ZIP: - f = self.load_zip_file(file) - elif typ == FileType.ResourceFile: - f = EndianBinaryReader(file) - self.register_cab(stream_name, f) - - self.files[stream_name] = f - return f - except Exception as e: - # just to be sure - # cuz the SerializedFile detection isn't perfect - print("Error loading, reverting to EndianBinaryReader:\n", str(e)) - return EndianBinaryReader(file) + 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)): + if isinstance(value, str) and self.fs.exists(value): + buffer = cast(io.BufferedReader, self.fs.open(value, "rb")) + elif isinstance(value, (bytes, bytearray, memoryview)): buffer = io.BytesIO(value) elif isinstance(value, (io.BufferedReader, io.BufferedIOBase)): buffer = value + else: + raise TypeError("Unsupported type for loading zip file") z = ZipFile(buffer) - self.load_assets(z.namelist(), lambda x: z.open(x, "r")) + self.load_assets(z.namelist(), lambda x: z.open(x, "r")) # type: ignore z.close() - def save(self, pack="none"): + 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 f in self.files: - if self.files[f].is_changed: - with open( - os.path.join(self.out_path, os.path.basename(f)), "wb" - ) as out: - out.write(self.files[f].save(packer=pack)) + 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) -> List[ObjectReader]: @@ -166,27 +191,39 @@ 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) -> Dict[str, ObjectReader]: + def container(self) -> ContainerHelper: """Returns a dictionary of all objects in the Environment.""" - return { - path: obj - for f in self.files.values() - if isinstance(f, File) - for path, obj in f.container.items() - } + 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: @@ -194,8 +231,13 @@ 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: @@ -207,7 +249,7 @@ def gen_all_asset_files(file, ret=[]): def get(self, key: str, default=None): return getattr(self, key, default) - def register_cab(self, name: str, item: File) -> None: + def register_cab(self, name: str, item: Union[SerializedFile, EndianBinaryReader]) -> None: """ Registers a cab file. @@ -218,9 +260,10 @@ def register_cab(self, name: str, item: File) -> None: item : File The file to register. """ - self.cabs[os.path.basename(name.lower())] = item + self.cabs[simplify_name(name)] = item + self._container_index_built = False - def get_cab(self, name: str) -> File: + def get_cab(self, name: str) -> Union[SerializedFile, EndianBinaryReader, None]: """ Returns the cab file with the given name. @@ -234,9 +277,9 @@ def get_cab(self, name: str) -> File: File The cab file. """ - return self.cabs.get(os.path.basename(name.lower()), None) + return self.cabs.get(simplify_name(name), None) - def load_assets(self, assets: List[str], open_f: Callable[[str], io.IOBase]): + 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. @@ -252,22 +295,69 @@ def load_assets(self, assets: List[str], open_f: Callable[[str], io.IOBase]): for path in assets: splitMatch = reSplit.match(path) if splitMatch: - basepath, basename = splitMatch.groups() + basepath, _basename = splitMatch.groups() if basepath in split_files: continue split_files.append(basepath) - data = [] - for i in range(0, 999): - item = f"{basepath}.split{i}" - if item in assets: - with open_f(item) as f: - data.append(f.read()) - elif i: - break - data = b"".join(data) + data = self._load_split_file(basepath) path = basepath else: - data = open_f(path).read() + 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 index 1eda4850e..86bd144d0 100644 --- a/UnityPy/exceptions.py +++ b/UnityPy/exceptions.py @@ -1,5 +1,14 @@ class TypeTreeError(Exception): - def __init__(self, message, nodes): - # Call the base class constructor with the parameters it needs + 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) - self.nodes = nodes \ No newline at end of file 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 db68c8a91..f61e3e729 100644 --- a/UnityPy/export/MeshExporter.py +++ b/UnityPy/export/MeshExporter.py @@ -1,85 +1,56 @@ -from UnityPy.classes import Mesh +from __future__ import annotations +from typing import TYPE_CHECKING, List, Optional -def export_mesh(m_Mesh: Mesh, format="obj") -> str: +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(m_Mesh, material_names: list = None): +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 {m_Mesh.name}.mtl\r\n") + 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:.7G} {1:.7G} {2:.7G}\r\n".format( - -m_Mesh.m_Vertices[v * c], - m_Mesh.m_Vertices[v * c + 1], - m_Mesh.m_Vertices[v * c + 2], - ).replace("nan", "0") - ) + 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:.7G} {1:.7G}\r\n".format( - m_Mesh.m_UV0[v * c], m_Mesh.m_UV0[v * c + 1] - ).replace("nan", "0") - ) + 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:.7G} {1:.7G} {2:.7G}\r\n".format( - -m_Mesh.m_Normals[v * c], - m_Mesh.m_Normals[v * c + 1], - m_Mesh.m_Normals[v * c + 2], - ).replace("nan", "0") - ) + 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") + 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]}\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 + sb.append(f"usemtl {material_names[i]}\n") + sb.extend("f {0}/{0}/{0} {1}/{1}/{1} {2}/{2}/{2}\n".format(c + 1, b + 1, a + 1) for a, b, c in triangles) # endregion return "".join(sb) diff --git a/UnityPy/export/MeshRendererExporter.py b/UnityPy/export/MeshRendererExporter.py index 83fe11db7..138d9cf2e 100644 --- a/UnityPy/export/MeshRendererExporter.py +++ b/UnityPy/export/MeshRendererExporter.py @@ -1,66 +1,104 @@ -import os -from ..classes import Renderer, SkinnedMeshRenderer, Material, Texture2D -from ..enums import ClassIDType +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): - if isinstance(meshR, SkinnedMeshRenderer.SkinnedMeshRenderer): +def get_mesh(meshR: Renderer) -> Optional[Mesh]: + if isinstance(meshR, SkinnedMeshRenderer): if meshR.m_Mesh: - return meshR.m_Mesh.read() + return meshR.m_Mesh.deref_parse_as_object() else: - m_GameObject = meshR.m_GameObject.read() - if m_GameObject.m_MeshFilter: - filter = m_GameObject.m_MeshFilter.read() - if filter.m_Mesh: - return filter.m_Mesh.read() + 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(obj: Renderer, export_dir: str) -> None: - os.makedirs(export_dir, exist_ok=True) - meshR = obj.read() - mesh = get_mesh(meshR) +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(meshR, "m_StaticBatchInfo") and meshR.m_StaticBatchInfo.subMeshCount > 0: - firstSubMesh = meshR.m_StaticBatchInfo.firstSubMesh - elif hasattr(meshR, "m_SubsetIndices"): - firstSubMesh = min(meshR.m_SubsetIndices) + 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, submesh in enumerate(mesh.m_SubMeshes): + for i in range(len(mesh.m_SubMeshes)): mat_index = i - firstSubMesh - if mat_index < 0 or mat_index >= len(meshR.m_Materials): + if mat_index < 0 or mat_index >= len(renderer.m_Materials): continue - matPtr = meshR.m_Materials[i - firstSubMesh] + matPtr: Optional[PPtr[Material]] = renderer.m_Materials[i - firstSubMesh] if matPtr: - mat = matPtr.read() + mat: Material = matPtr.deref_parse_as_object() else: material_names.append(None) continue materials.append(export_material(mat)) - material_names.append(mat.name) + material_names.append(mat.m_Name) # save material textures - for key, texEnv in mat.m_SavedProperties.m_TexEnvs.items(): + for key, texEnv in mat.m_SavedProperties.m_TexEnvs: if not texEnv.m_Texture: continue - tex = texEnv.m_Texture.read() - texName = f"{tex.name if tex.name else key}.png" - tex.read().image.save(os.path.join(export_dir, texName)) + 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 open( - os.path.join(export_dir, f"{mesh.name}.obj"), "wt", encoding="utf8", newline="" + 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 open( - os.path.join(export_dir, f"{mesh.name}.mtl"), "wt", encoding="utf8", newline="" + 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)) @@ -69,52 +107,73 @@ 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) - ) + return color if isinstance(color, tuple) else (color.R, color.G, color.B, color.A) - colors = mat.m_SavedProperties.m_Colors - floats = mat.m_SavedProperties.m_Floats - texEnvs = mat.m_SavedProperties.m_TexEnvs + 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))) + # 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))) + # reflection = clt(colors.get("_ReflectColor", (0, 0, 0, 1))) shininess = floats.get("_Shininess", 20.0) transparency = floats.get("_Transparency", 0.0) - sb = [] - sb.append(f"newmtl {mat.name}") + 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); + # 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. + + # 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. + # 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. + # 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.items(): + for key, texEnv in texEnvs: if not texEnv.m_Texture: continue - tex = texEnv.m_Texture.read() - texName = f"{tex.name if tex.name else key}.png" + 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": diff --git a/UnityPy/export/ShaderConverter.py b/UnityPy/export/ShaderConverter.py index fb59ba618..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 hasattr(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(bytes(m_Shader.m_Script).decode("utf8")) - if hasattr(m_Shader, "compressedBlob"): # 5.5 and up + if m_Shader.compressedBlob: # 5.5 and up return HEADER + ConvertSerializedShader(m_Shader) return HEADER + bytes(m_Shader.m_Script).decode("utf8") -def ConvertSerializedShader(m_Shader): + +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 + + compressedSize = get_entry(m_Shader.compressedLengths, i) + decompressedSize = get_entry(m_Shader.decompressedLengths, i) + offset = get_entry(m_Shader.offsets, i) - compressedBytes = m_Shader.compressedBlob[int(m_Shader.offsets[i]):int(m_Shader.offsets[i]) + compressedSize] + 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 ConvertSerializedTagMap(m_Tags: SerializedTagMap, intent: int) -> str: + if m_Tags.tags: + return "".join( + [ + " " * intent, + "Tags { ", + *[f'"{key}" = "{value}" ' for key, value in m_Tags.tags], + "}\n", + ] + ) + return "" -def ConvertSerializedProperties(m_PropInfo): - sb = [] - - 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,27 +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 - #202012090 - Unity 2021.2 + # 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()) @@ -488,39 +593,38 @@ def __init__(self, reader: EndianBinaryReader): reader.Position += 4 m_KeywordSize = reader.read_int() - self.m_Keywords = [] - - for i in range(m_KeywordSize): - self.m_Keywords.append(reader.read_aligned_string()) + self.m_Keywords = [reader.read_aligned_string() for _ in range(m_KeywordSize)] if 201806140 <= self.m_Version < 202012090: m_LocalKeywordsSize = reader.read_int() - self.m_LocalKeywords = [] - - 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 [ @@ -531,14 +635,14 @@ def Export(self): ShaderGpuProgramType.kShaderGpuProgramGLES, ShaderGpuProgramType.kShaderGpuProgramGLCore32, ShaderGpuProgramType.kShaderGpuProgramGLCore41, - ShaderGpuProgramType.kShaderGpuProgramGLCore43 + ShaderGpuProgramType.kShaderGpuProgramGLCore43, ]: 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 [ @@ -551,20 +655,20 @@ 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, endian = "<") + 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)) sb.append(bytes(buff).decode("utf8")) elif self.m_ProgramType == ShaderGpuProgramType.kShaderGpuProgramSPIRV: @@ -575,7 +679,7 @@ def Export(self): ShaderGpuProgramType.kShaderGpuProgramConsoleFS, ShaderGpuProgramType.kShaderGpuProgramConsoleHS, ShaderGpuProgramType.kShaderGpuProgramConsoleDS, - ShaderGpuProgramType.kShaderGpuProgramConsoleGS + ShaderGpuProgramType.kShaderGpuProgramConsoleGS, ]: sb.append(bytes(self.m_ProgramCode).decode("utf8")) else: diff --git a/UnityPy/export/SpriteHelper.py b/UnityPy/export/SpriteHelper.py index ffcbf98e0..9ab5c8e45 100644 --- a/UnityPy/export/SpriteHelper.py +++ b/UnityPy/export/SpriteHelper.py @@ -1,49 +1,82 @@ -from enum import IntEnum, IntFlag +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 ..enums import ClassIDType, SpritePackingMode, SpritePackingRotation -from ..streams import EndianBinaryReader + +if TYPE_CHECKING: + from typing import List, Optional, Tuple + + from ..classes import PPtr, Sprite, Texture2D + +try: + import numpy as np +except ImportError: + np = None -def get_image(sprite, texture, alpha_texture) -> Image.Image: - if ( - alpha_texture - and getattr(alpha_texture, "type", ClassIDType.UnknownType) - == ClassIDType.Texture2D - ): +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: 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.Image: +def get_image_from_sprite(m_Sprite: Sprite) -> Image.Image: atlas = None - if getattr(m_Sprite, "m_SpriteAtlas", None): - atlas = m_Sprite.m_SpriteAtlas.read() - elif getattr(m_Sprite, "m_AtlasTags", None): + if m_Sprite.m_SpriteAtlas: + 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 == ClassIDType.SpriteAtlas: - atlas = obj.read() - if atlas.name == m_Sprite.m_AtlasTags[0]: + 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 @@ -63,87 +96,254 @@ def get_image_from_sprite(m_Sprite) -> Image.Image: ) ) + 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 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 437e3e0fb..cdb743eee 100644 --- a/UnityPy/export/Texture2DConverter.py +++ b/UnityPy/export/Texture2DConverter.py @@ -1,51 +1,179 @@ -import texture2ddecoder -import etcpak -from PIL import Image -from copy import copy -from io import BytesIO +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) -def image_to_texture2d(img: Image.Image, target_texture_format: TF, flip: bool = True): if flip: - img = img.transpose(Image.FLIP_TOP_BOTTOM) + img = img.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + + # defaults + compress_func = None + tex_format = TF.RGBA32 + pil_mode = "RGBA" - # DXT + # DXT / BC if target_texture_format in [TF.DXT1, TF.DXT1Crunched]: - raw_img = img.convert("RGBA").tobytes() - enc_img = etcpak.compress_to_dxt1(raw_img, img.width, img.height) tex_format = TF.DXT1 + compress_func = compress_etcpak elif target_texture_format in [TF.DXT5, TF.DXT5Crunched]: - raw_img = img.convert("RGBA").tobytes() - enc_img = etcpak.compress_to_dxt5(raw_img, img.width, img.height) 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]: - r, g, b, a = img.split() - raw_img = Image.merge("RGBA", (b, g, r, a)).tobytes() - enc_img = etcpak.compress_to_etc1(raw_img, img.width, img.height) - tex_format = TF.ETC_RGB4 + 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: - r, g, b, a = img.split() - raw_img = Image.merge("RGBA", (b, g, r, a)).tobytes() - enc_img = etcpak.compress_to_etc2_rgb(raw_img, img.width, img.height) tex_format = TF.ETC2_RGB - elif ( - target_texture_format in [TF.ETC2_RGBA8, TF.ETC2_RGBA8Crunched, TF.ETC2_RGBA1] - or "_RGB_" in target_texture_format.name - ): - r, g, b, a = img.split() - raw_img = Image.merge("RGBA", (b, g, r, a)).tobytes() - enc_img = etcpak.compress_to_etc2_rgba(raw_img, img.width, img.height) + 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: - enc_img = img.tobytes("raw", "A") tex_format = TF.Alpha8 - # R - should probably be moerged into #A, as pure R is used as Alpha + 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, @@ -55,58 +183,118 @@ def image_to_texture2d(img: Image.Image, target_texture_format: TF, flip: bool = TF.EAC_R, TF.EAC_R_SIGNED, ]: - enc_img = img.tobytes("raw", "R") 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, ]: - enc_img = img.tobytes("raw", "RGB") 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: - enc_img = img.tobytes("raw", "RGBA") - tex_format = TF.RGBA32 + 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, flip=True) -> Image.Image: - """converts the given texture into PIL.Image +def get_image_from_texture2d( + texture_2d: Texture2D, + flip: bool = True, +) -> Image.Image: + """Converts the given Texture2D object to PIL Image.""" + return parse_image_data( + texture_2d.get_image_data(), + texture_2d.m_Width, + texture_2d.m_Height, + texture_2d.m_TextureFormat, + getattr(texture_2d.object_reader, "version", (0, 0, 0, 0)), + getattr(texture_2d.object_reader, "platform", BuildTarget.UnknownPlatform), + getattr(texture_2d, "m_PlatformBlob", None), + flip, + ) + + +def parse_image_data( + image_data: Union[bytes, bytearray, memoryview], + width: int, + height: int, + texture_format: Union[TextureFormat, int], + version: Tuple[int, int, int, int], + platform: Union[BuildTarget, int], + platform_blob: Optional[List[int]] = None, + flip: bool = True, +) -> Image.Image: + """Converts the given image data bytes to PIL Image.""" + if not width or not height: + return Image.new("RGBA", (0, 0)) - :param texture_2d: texture to be converterd - :type texture_2d: Texture2D - :param flip: flips the image back to the original (all Unity textures are flipped by default) - :type flip: bool - :return: PIL.Image object - :rtype: Image - """ - image_data = copy(bytes(texture_2d.image_data)) if not image_data: - return Image.new("RGB", (0, 0)) + raise ValueError("Texture2D has no image data") - texture_format = ( - texture_2d.m_TextureFormat - if isinstance(texture_2d.m_TextureFormat, TF) - else TF(texture_2d.m_TextureFormat) - ) - selection = CONV_TABLE[texture_format] + 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) - if len(selection) == 0: - raise NotImplementedError( - f"Not implemented texture format: {texture_format.name}" - ) + original_width, original_height = (width, height) - if texture_format in XBOX_SWAP_FORMATS: - image_data = swap_bytes_for_xbox(image_data, texture_2d.platform) + 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 @@ -117,31 +305,26 @@ def get_image_from_texture2d(texture_2d, flip=True) -> Image.Image: 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 @@ -152,13 +335,9 @@ def pillow( mode: str, codec: str, args, - swap: tuple = None, + 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)) - ) + 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]) @@ -175,31 +354,70 @@ def atc(image_data: bytes, width: int, height: int, alpha: bool) -> Image.Image: def astc(image_data: bytes, width: int, height: int, block_size: tuple) -> Image.Image: - image_data = texture2ddecoder.decode_astc(image_data, width, height, *block_size) - return Image.frombytes("RGBA", (width, height), image_data, "raw", "BGRA") + image = astc_encoder.ASTCImage(astc_encoder.ASTCType.U8, width, height, 1) + texture_size = calculate_astc_compressed_size(width, height, block_size) + if len(image_data) < texture_size: + raise ValueError(f"Invalid ASTC data size: {len(image_data)} < {texture_size}") + + context = get_astc_context(block_size) + context.decompress(image_data[:texture_size], image, astc_encoder.ASTCSwizzle.from_str("RGBA")) + assert image.data is not None, "Decompression failed, image data is None" + + return Image.frombytes("RGBA", (width, height), image.data, "raw", "RGBA") + + +@lru_cache(maxsize=128) +def _get_astc_context(ident: int, block_size: tuple): + config = astc_encoder.ASTCConfig( + astc_encoder.ASTCProfile.LDR, + *block_size, + block_z=1, + quality=100, + flags=astc_encoder.ASTCConfigFlags.USE_DECODE_UNORM8, + ) + context = astc_encoder.ASTCContext(config) + return context + + +def get_astc_context(block_size: tuple): + """Get the ASTC context for the current thread using the given `block_size`. + Created contexts belong to and only to the calling thread, and may be cached. + This function is thread safe. + """ + return _get_astc_context(get_ident(), block_size) + +def calculate_astc_compressed_size(width: int, height: int, block_size: tuple) -> int: + """Calculate the size of the compressed data for ASTC.""" + # calculate the number of blocks + block_count_x = (width + block_size[0] - 1) // block_size[0] + block_count_y = (height + block_size[1] - 1) // block_size[1] + # ignore depth for 2D textures + # calculate the size of the compressed data + return block_count_x * block_count_y * 16 -def pvrtc(image_data: bytes, width: int, height: int, fmt: bool): + +def pvrtc(image_data: bytes, width: int, height: int, fmt: bool) -> Image.Image: image_data = texture2ddecoder.decode_pvrtc(image_data, width, height, fmt) return Image.frombytes("RGBA", (width, height), image_data, "raw", "BGRA") -def etc(image_data: bytes, width: int, height: int, fmt: list): - if fmt[0] == 1: +def etc(image_data: bytes, width: int, height: int, fmt: str) -> Image.Image: + if fmt == "ETC1": image_data = texture2ddecoder.decode_etc1(image_data, width, height) - elif fmt[0] == 2: - if fmt[1] == "RGB": - image_data = texture2ddecoder.decode_etc2(image_data, width, height) - elif fmt[1] == "A1": - image_data = texture2ddecoder.decode_etc2a1(image_data, width, height) - elif fmt[1] == "A8": - image_data = texture2ddecoder.decode_etc2a8(image_data, width, height) + elif fmt == "ETC2_RGB": + image_data = texture2ddecoder.decode_etc2(image_data, width, height) + elif fmt == "ETC2_A1": + image_data = texture2ddecoder.decode_etc2a1(image_data, width, height) + elif fmt == "ETC2_A8": + image_data = texture2ddecoder.decode_etc2a8(image_data, width, height) else: - raise NotImplementedError("unknown etc mode") + raise NotImplementedError(f"Unknown ETC mode: {fmt}") + return Image.frombytes("RGBA", (width, height), image_data, "raw", "BGRA") -def eac(image_data: bytes, width: int, height: int, fmt: list): +def eac(image_data: bytes, width: int, height: int, fmt: str) -> Image.Image: if fmt == "EAC_R": image_data = texture2ddecoder.decode_eacr(image_data, width, height) elif fmt == "EAC_R_SIGNED": @@ -208,6 +426,9 @@ 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") @@ -218,87 +439,131 @@ def half( mode: str, codec: str, args, - swap: tuple = None, + 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, "RGB", "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 995eb7741..0381efb02 100644 --- a/UnityPy/export/__init__.py +++ b/UnityPy/export/__init__.py @@ -1 +1,15 @@ -from . import MeshRendererExporter, 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 c6ec79890..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,22 +21,34 @@ class BundleFile(File.File): signature: str version_engine: str version_player: str - - def __init__(self, reader: EndianBinaryReader, parent: File, name: str = None): - super().__init__(parent=parent, name=name) + 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) @@ -37,29 +56,29 @@ 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() + self._hash = reader.read_bytes(16) + self.crc = reader.read_u_int() - minimumStreamedBytes = reader.read_u_int() + minimumStreamedBytes = reader.read_u_int() # noqa: F841 headerSize = reader.read_u_int() - numberOfLevelsToDownloadBeforeStreaming = reader.read_u_int() + numberOfLevelsToDownloadBeforeStreaming = reader.read_u_int() # noqa: F841 levelCount = reader.read_int() reader.Position += 4 * 2 * (levelCount - 1) compressedSize = reader.read_u_int() - uncompressedSize = reader.read_u_int() + 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() @@ -75,30 +94,54 @@ def read_web_raw(self, reader: EndianBinaryReader): return m_DirectoryInfo, blocksReader def read_fs(self, reader: EndianBinaryReader): - 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() + 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.version >= 7: + if self.dataflags & self.dataflags.UsesAssetBundleEncryption: + self.decryptor = ArchiveStorageManager.ArchiveStorageDecryptor(reader) + + # if header version is 7 or later we need to align the reader + # for 2019.4.15 and later, version should be 7 and aligned + # but some games in these versions somehow has version 6 while aligned + if self.version >= 7 or (version[0] == 2019 and version >= (2019, 4, 15)): reader.align_stream(16) + self._uses_block_alignment = True start = reader.Position - if self._data_flags & 0x80 != 0: # kArchiveBlocksInfoAtTheEnd + if self.dataflags & ArchiveFlags.BlocksInfoAtTheEnd: # kArchiveBlocksInfoAtTheEnd reader.Position = reader.Length - compressedSize blocksInfoBytes = reader.read_bytes(compressedSize) reader.Position = start else: # 0x40 kArchiveBlocksAndDirectoryInfoCombined blocksInfoBytes = reader.read_bytes(compressedSize) - blocksInfoBytes = decompress_data( - blocksInfoBytes, uncompressedSize, self._data_flags - ) + blocksInfoBytes = self.decompress_data(blocksInfoBytes, uncompressedSize, self.dataflags) blocksInfoReader = EndianBinaryReader(blocksInfoBytes, offset=start) - uncompressedDataHash = blocksInfoReader.read_bytes(16) + uncompressedDataHash = blocksInfoReader.read_bytes(16) # noqa: F841 blocksInfoCount = blocksInfoReader.read_int() m_BlocksInfo = [ @@ -124,17 +167,18 @@ def read_fs(self, reader: EndianBinaryReader): if m_BlocksInfo: self._block_info_flags = m_BlocksInfo[0].flags - if self._data_flags & 0x200: + if isinstance(self.dataflags, ArchiveFlags) and self.dataflags & ArchiveFlags.BlockInfoNeedPaddingAtStart: reader.align_stream(16) blocksReader = EndianBinaryReader( b"".join( - decompress_data( + self.decompress_data( reader.read_bytes(blockInfo.compressedSize), blockInfo.uncompressedSize, blockInfo.flags, + i, ) - for blockInfo in m_BlocksInfo + for i, blockInfo in enumerate(m_BlocksInfo) ), offset=(blocksInfoReader.real_offset()), ) @@ -154,8 +198,8 @@ def save(self, packer=None): 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() @@ -166,25 +210,29 @@ def save(self, packer=None): 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, - data_flag=self._data_flags, + data_flag=self.dataflags, block_info_flag=self._block_info_flags, ) elif packer == "lz4": self.save_fs(writer, data_flag=194, block_info_flag=2) + elif packer == "lzma": + self.save_fs(writer, data_flag=65, block_info_flag=1) elif isinstance(packer, tuple): self.save_fs(writer, *packer) else: - raise NotImplemented("UnityFS - Packer:", packer) + raise NotImplementedError("UnityFS - Packer:", packer) return writer.bytes def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: int): @@ -204,11 +252,11 @@ def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: i # 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, @@ -218,21 +266,21 @@ def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: i # *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 @@ -243,9 +291,7 @@ def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: i name, f.flags, data_writer.write_bytes( - f.bytes - if isinstance(f, (EndianBinaryReader, EndianBinaryWriter)) - else f.save() + f.bytes if isinstance(f, (EndianBinaryReader, EndianBinaryWriter)) else f.save() ), ) for name, f in self.files.items() @@ -253,31 +299,27 @@ def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: i file_data = data_writer.bytes data_writer.dispose() - uncompressed_data_size = len(file_data) - - # compress the data - switch = block_info_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(block_info_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: @@ -303,12 +345,10 @@ def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: i uncompressed_block_data_size = len(block_data) switch = data_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 + 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) @@ -323,7 +363,11 @@ def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: i # compression and file layout flag writer.write_u_int(data_flag) - if self.version >= 7: + # 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 @@ -341,38 +385,152 @@ def save_fs(self, writer: EndianBinaryWriter, data_flag: int, block_info_flag: i 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 -def decompress_data( - compressed_data: bytes, uncompressed_size: int, flags: int -) -> 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.""" - switch = flags & 0x3F - - if switch == 1: # LZMA - return CompressionHelper.decompress_lzma(compressed_data) - elif switch in [2, 3]: # LZ4, LZ4HC - return CompressionHelper.decompress_lz4(compressed_data, uncompressed_size) - elif switch == 4: # LZHAM - raise NotImplementedError("LZHAM decompression not implemented") - else: - return compressed_data + 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 4d3f43d5d..e672f197c 100644 --- a/UnityPy/files/File.py +++ b/UnityPy/files/File.py @@ -1,31 +1,42 @@ -from ..enums import FileType -from ..helpers import ImportHelper -from ..streams import EndianBinaryReader, EndianBinaryWriter +from __future__ import annotations from collections import namedtuple from os.path import basename +from typing import TYPE_CHECKING, Dict, Optional + +from ..helpers import ImportHelper +from ..streams import EndianBinaryReader, EndianBinaryWriter + +if TYPE_CHECKING: + from ..environment import Environment DirectoryInfo = namedtuple("DirectoryInfo", "path offset size") -class File(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 - # environment: Environment - - def __init__(self, parent=None, name=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 = self.environment = getattr(parent, "environment", parent) if parent else None + 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): @@ -38,8 +49,8 @@ def get_assets(self): elif isinstance(f, SerializedFile.SerializedFile): yield f - def get_filtered_objects(self, obj_types=[]): - if len(obj_types) == 0: + 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)): @@ -67,23 +78,8 @@ def read_files(self, reader: EndianBinaryReader, files: list): 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, name=name) - elif typ == FileType.WebFile: - f = WebFile.WebFile(f, self, name=name) - 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, name=name) - 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: @@ -93,10 +89,10 @@ def read_files(self, reader: EndianBinaryReader, files: list): f.flags = getattr(node, "flags", 0) self.files[name] = f - def get_writeable_cab(self, name: str = None): + 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: @@ -109,9 +105,7 @@ def get_writeable_cab(self, name: str = None): if isinstance(self.files[name], EndianBinaryWriter): return self.files[name] else: - raise ValueError( - "This cab already exists and isn't an EndianBinaryWriter" - ) + raise ValueError("This cab already exists and isn't an EndianBinaryWriter") writer = EndianBinaryWriter() # try to find another resource file to copy the flags from @@ -128,12 +122,7 @@ def get_writeable_cab(self, name: str = None): @property def container(self): - return { - path: obj - for f in self.files.values() - if isinstance(f, File) - for path, obj in f.container.items() - } + 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) @@ -160,5 +149,4 @@ def mark_changed(self): # 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 622e310df..b5fe1dbd0 100644 --- a/UnityPy/files/ObjectReader.py +++ b/UnityPy/files/ObjectReader.py @@ -1,93 +1,135 @@ +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 ..classes.Object import NodeHelper -from ..streams import EndianBinaryReader, EndianBinaryWriter -from ..helpers import TypeTreeHelper -from ..helpers.Tpk import get_typetree_nodes 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() - - self.type_id = reader.read_int() + type_id = reader.read_int() + serialized_type = None if header.version < 16: - self.class_id = reader.read_u_short() - self.serialized_type = None + class_id = reader.read_u_short() for typ in types: - if typ.class_id == self.type_id: - self.serialized_type = typ + 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 - ): + def write(self, header, writer: EndianBinaryWriter, data_writer: EndianBinaryWriter): if self.assets_file.big_id_enabled: writer.write_long(self.path_id) elif header.version < 14: @@ -96,7 +138,7 @@ def write( 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 @@ -125,25 +167,41 @@ def write( 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): @@ -156,26 +214,8 @@ def Position(self, pos): def reset(self): self.reader.Position = self.byte_start - def read(self, return_typetree_on_error: bool=True): - cls = getattr(classes, self.type.name, None) - - obj = None - if cls: - try: - obj = cls(self) - except Exception as e: - if return_typetree_on_error: - print(f"Error during the parsing of object {self.path_id}") - print(e) - print("Returning the typetree") - else: - raise e - if not obj: - typetree = self.read_typetree() - if typetree: - obj = NodeHelper(typetree, self.assets_file) - 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) @@ -193,40 +233,44 @@ def __repr__(self): # ################################################### - def dump_typetree(self, nodes: list = None) -> str: + 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() - sb = [] - nodes = self.get_typetree(nodes) - TypeTreeHelper.read_typetree_str(sb, nodes, self) - return "".join(sb) - - def dump_typetree_structure(self) -> str: - return TypeTreeHelper.dump_typetree(self.get_typetree_nodes()) - - def get_typetree_nodes(self, nodes: list = None) -> list: - if nodes: - return nodes - - if self.serialized_type: - nodes = self.serialized_type.nodes - if not nodes: - nodes = get_typetree_nodes(self.class_id, self.version) - if not nodes: - raise TypeTreeError("There are no TypeTree nodes for this object.") - return nodes - - def read_typetree(self, nodes: list = None) -> dict: - self.reset() - nodes = self.get_typetree_nodes(nodes) - return TypeTreeHelper.read_typetree(nodes, self) + 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: dict, nodes: list = None, writer: EndianBinaryWriter = None + self, + tree: Union[dict, T], + nodes: Optional[NodeInput] = None, + writer: Optional[EndianBinaryWriter] = None, ): - nodes = self.get_typetree_nodes(nodes) + node = self._get_typetree_node(nodes) if not writer: writer = EndianBinaryWriter(endian=self.reader.endian) - writer = TypeTreeHelper.write_typetree(tree, nodes, writer) + TypeTreeHelper.write_typetree(tree, node, writer, self.assets_file) data = writer.bytes self.set_raw_data(data) return data @@ -238,7 +282,74 @@ def get_raw_data(self) -> bytes: self.Position = pos return ret - def set_raw_data(self, data): - self.data = data - if self.assets_file: - self.assets_file.mark_changed() + 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 d9a534825..d8853378b 100644 --- a/UnityPy/files/SerializedFile.py +++ b/UnityPy/files/SerializedFile.py @@ -1,25 +1,33 @@ -import os -import re +from __future__ import annotations -from . import File, ObjectReader -from ..enums import BuildTarget, ClassIDType, CommonString -from ..streams import EndianBinaryReader, EndianBinaryWriter -from ..helpers.TypeTreeHelper import TypeTreeNode +from ntpath import basename +from typing import TYPE_CHECKING, Dict, List, Optional, Tuple -from struct import Struct +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 -# only print the version warning once -VERSION_WARNED = False +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): @@ -31,6 +39,7 @@ def __init__(self, reader: EndianBinaryReader): ) = reader.read_u_int_array(4) +@define(slots=True) class LocalSerializedObjectIdentifier: # script type local_serialized_file_index: int local_identifier_in_file: int @@ -52,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})>" @@ -75,39 +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 BuildType: - build_type: str - - def __init__(self, build_type): - self.build_type = build_type - - @property - def IsAlpha(self): - return self.build_type == "a" - - @property - def IsPatch(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() @@ -116,60 +127,101 @@ 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: if version >= 12 or version == 10: - self.nodes, self.string_data = serialized_file.read_type_tree_blob() + self.node = TypeTreeNode.parse_blob(reader, version) else: - self.nodes = serialized_file.read_type_tree() + self.node = TypeTreeNode.parse(reader, version) if version >= 21: - self.type_dependencies = reader.read_int_array() - - 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): @@ -181,22 +233,17 @@ def files(self): def files(self, value): self.objects = value - def __init__(self, reader: EndianBinaryReader, parent=None, name=None): - super().__init__(parent=parent, name=name) + 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. @@ -235,9 +282,7 @@ def __init__(self, reader: EndianBinaryReader, parent=None, name=None): # ReadTypes type_count = reader.read_int() - self.types = [SerializedType(reader, self) for _ in range(type_count)] - if config.SERIALIZED_FILE_PARSE_TYPETREE is False: - self._enable_type_tree = False + self.types = [SerializedType(reader, self, False) for _ in range(type_count)] self.big_id_enabled = 0 if 7 <= header.version < 14: @@ -247,28 +292,24 @@ def __init__(self, reader: EndianBinaryReader, parent=None, name=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() @@ -276,120 +317,55 @@ def __init__(self, reader: EndianBinaryReader, parent=None, name=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. - def set_version(self, string_version): + Parameters + ---------- + possible_dependencies : list + List of possible dependencies for cases + where the target file is not listed as external. + """ + for file_id in self.externals: + self.environment.load_file(file_id.path, True) + + if possible_dependencies is None: + return + + for dependency in possible_dependencies: + try: + self.environment.load_file(dependency, True) + except FileNotFoundError: + pass + + def set_version(self, string_version: str): self.unity_version = string_version - if string_version == "0.0.0": + 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 - string_version = self.parent.version_engine - if string_version == "0.0.0": - global VERSION_WARNED - if not VERSION_WARNED: - print( - f"Warning: 0.0.0 version found, defaulting to UnityPy.config.FALLBACK_UNITY_VERSION\n{config.FALLBACK_UNITY_VERSION}" - ) - VERSION_WARNED = True - string_version = config.FALLBACK_UNITY_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 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( - m_Level = level, - m_Type = self.reader.read_string_to_null(), - m_Name = self.reader.read_string_to_null(), - m_ByteSize = self.reader.read_int() - ) - - type_tree.append(type_tree_node) - if self.header.version == 2: - type_tree_node.m_VariableCount = self.reader.read_int() - - if self.header.version != 3: - type_tree_node.m_Index = self.reader.read_int() - - type_tree_node.m_IsArray = bool(self.reader.read_int()) - type_tree_node.m_Version = self.reader.read_int() - if self.header.version != 3: - type_tree_node.m_MetaFlag = 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): - reader = self.reader - number_of_nodes = self.reader.read_int() - string_buffer_size = self.reader.read_int() - - type = f"{reader.endian}hb?IIiii" - keys = [ - "m_Version", - "m_Level", - "m_IsArray", - "m_TypeStrOffset", - "m_NameStrOffset", - "m_ByteSize", - "m_Index", - "m_MetaFlag", - ] - if self.header.version >= 19: - type += "Q" - keys.append("m_RefTypeHash") - - node_struct = Struct(type) - struct_data = reader.read(node_struct.size * number_of_nodes) - string_buffer_reader = EndianBinaryReader( - reader.read(string_buffer_size), reader.endian - ) - - if not config.SERIALIZED_FILE_PARSE_TYPETREE: - return [], string_buffer_reader.bytes - - type_tree = [ - TypeTreeNode( - **dict(zip(keys, raw_node)), - m_Type=read_string(string_buffer_reader, raw_node[3]), - m_Name=read_string(string_buffer_reader, raw_node[4]), - ) - for i, raw_node in enumerate(node_struct.iter_unpack(struct_data)) - ] - - return type_tree, string_buffer_reader.bytes + if isinstance(self.parent, BundleFile.BundleFile): + string_version = self.parent.version_engine + if not string_version or string_version == "0.0.0": + string_version = config.get_fallback_version() + self.version = UnityVersion.from_str(string_version) def get_writeable_cab(self, name: str = "CAB-UnityPy_Mod.resS"): """ Creates a new cab file in the bundle that contains the given data. This is usefull for asset types that use resource files. """ - if not isinstance( - self.parent, (File.BundleFile.BundleFile, File.WebFile.WebFile) - ): + if not isinstance(self.parent, (File.BundleFile.BundleFile, File.WebFile.WebFile)): return None cab = self.parent.get_writeable_cab(name) @@ -411,7 +387,7 @@ class FileIdentifierFake: return cab - def save(self, packer: str = None) -> bytes: + def save(self, packer: Optional[str] = None) -> bytes: # 1. header -> has to be delayed until the very end # 2. data -> types, objects, scripts, ... @@ -432,14 +408,14 @@ def save(self, packer: str = None) -> 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) @@ -455,11 +431,13 @@ def save(self, packer: str = None) -> 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 @@ -515,103 +493,6 @@ def save(self, packer: str = None) -> 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.m_Type) - writer.write_string_to_null(node.m_Name) - writer.write_int(node.byte_size) - if self.header.version == 2: - writer.write_int(node.m_VariableCount) - - if self.header.version != 3: - writer.write_int(node.m_Index) - - writer.write_int(node.m_IsArray) - writer.write_int(node.m_Version) - if self.header.version != 3: - writer.write_int(node.m_MetaFlag) - - # calc children count - children_count = 0 - for node2 in nodes[i + 1 :]: - if node2.m_Level == node.m_Level: - break - if node2.m_Level == node.m_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.m_TypeStrOffset, node.m_NameStrOffset) 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.m_Version) - # level - writer.write_byte(node.m_Level) - # is array - writer.write_boolean(node.m_IsArray) - # 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.m_ByteSize) - # index - writer.write_int(node.m_Index) - # meta flag - writer.write_int(node.m_MetaFlag) - # ref hash - if self.header.version > 19: - writer.write_u_long(node.m_RefTypeHash) - - # 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 @@ -620,4 +501,4 @@ def read_string(string_buffer_reader: EndianBinaryReader, value: int) -> str: return string_buffer_reader.read_string_to_null() offset = value & 0x7FFFFFFF - return CommonString.get(offset, str(offset)) + return get_common_strings().get(offset, str(offset)) diff --git a/UnityPy/files/WebFile.py b/UnityPy/files/WebFile.py index d0d48386b..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, name=None): - """Constructor Method - """ - super().__init__(parent=parent, name=name) - + + 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, name=None): 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, name=None): 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,15 +99,14 @@ 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": return CompressionHelper.compress_brotli(writer.bytes) else: return writer.bytes - 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 39f7365ee..2717c6f68 100644 --- a/UnityPy/helpers/ImportHelper.py +++ b/UnityPy/helpers/ImportHelper.py @@ -1,14 +1,20 @@ -import os -from typing import Union, List -from .CompressionHelper import BROTLI_MAGIC, GZIP_MAGIC +from __future__ import annotations + +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.join( - os.path.dirname(file_name), 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[str]: @@ -16,7 +22,7 @@ def list_all_files(directory: str) -> List[str]: 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 @@ -27,44 +33,38 @@ 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 check_file_type(input_) -> Union[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": return FileType.ZIP, reader @@ -75,7 +75,12 @@ def check_file_type(input_) -> Union[FileType, EndianBinaryReader]: 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 @@ -93,12 +98,13 @@ def check_file_type(input_) -> Union[FileType, EndianBinaryReader]: 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 @@ -108,10 +114,7 @@ def check_file_type(input_) -> Union[FileType, EndianBinaryReader]: ( 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, ) @@ -119,3 +122,52 @@ def check_file_type(input_) -> Union[FileType, EndianBinaryReader]: 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 be0cae4a8..62e9800a8 100644 --- a/UnityPy/helpers/ResourceReader.py +++ b/UnityPy/helpers/ResourceReader.py @@ -1,77 +1,38 @@ -import os, glob -from ..streams import EndianBinaryReader -from ..files import File - - -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]) - +import ntpath +from typing import TYPE_CHECKING -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') +from ..streams import EndianBinaryReader - for p in [res_path, base_name, base_name2]: - reader = assets_file.parent.files.get(p) +if TYPE_CHECKING: + from ..files.SerializedFile import SerializedFile + + +def get_resource_data(res_path: str, assets_file: "SerializedFile", offset: int, size: int): + basename = ntpath.basename(res_path) + name, ext = ntpath.splitext(basename) + possible_names = [ + basename, + f"{name}.resource", + f"{name}.assets.resS", + f"{name}.resS", + ] + environment = assets_file.environment + reader = None + for possible_name in possible_names: + reader = environment.get_cab(possible_name) if reader: - 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}" - ) - - -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 "" - + 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 _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 index 84b65d42a..3ccdfbef9 100644 --- a/UnityPy/helpers/Tpk.py +++ b/UnityPy/helpers/Tpk.py @@ -1,44 +1,62 @@ from __future__ import annotations -from enum import IntEnum, IntFlag -from struct import Struct + +import sys +from functools import cache from io import BytesIO -from typing import List, Tuple, Any, Dict -from .TypeTreeHelper import TypeTreeNode +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 -TPKTYPETREE: TpkTypeTreeBlob = None -NODES_CACHE: dict = {} +@cache +def get_typetree() -> TpkTypeTreeBlob: + package = "UnityPy.resources" + resource = "lzma.tpk" -def init(): - import os + tpk_data: bytes + if sys.version_info >= (3, 9): + from importlib.resources import files - with open( - os.path.join(os.path.dirname(os.path.dirname(__file__)), "resources", "uncompressed.tpk"), "rb" - ) as f: - global TPKTYPETREE - TPKTYPETREE = TpkFile(f).GetDataBlob() + tpk_data = files(package).joinpath(resource).read_bytes() + else: + from importlib.resources import open_binary -def get_typetree_nodes(class_id: int, version: tuple): - global NODES_CACHE - key = (class_id, version) - if key in NODES_CACHE: - return NODES_CACHE[key] + tpk_data = open_binary(package, resource).read() - class_info = TPKTYPETREE.ClassInformation[class_id].getVersionedClass( - UnityVersion.fromList(*version) - ) + 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)) - nodes = generate_flat_nodes(class_info) - NODES_CACHE[key] = nodes - return nodes + 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 -def generate_flat_nodes(class_info: TpkUnityClass) -> List[TypeTreeNode]: nodes = [] - NODES = TPKTYPETREE.NodeBuffer.Nodes + NODES = get_typetree().NodeBuffer + STRINGBUFFER = get_typetree().StringBuffer stack = [(class_info.ReleaseRootNode, 0)] index = 0 while stack: @@ -51,461 +69,16 @@ def generate_flat_nodes(class_info: TpkUnityClass) -> List[TypeTreeNode]: m_Version=node.Version, m_MetaFlag=node.MetaFlag, m_Level=level, - m_Type=TPKTYPETREE.StringBuffer.Strings[node.TypeName], - m_Name=TPKTYPETREE.StringBuffer.Strings[node.Name], + 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 nodes - - -###################################################################################### -# -# Enums -# -###################################################################################### - - -class TpkCompressionType(IntEnum): - NONE = 0 - Lz4 = 1 - Lzma = 2 - Brotli = 3 - - -class UnityVersionType(IntEnum): - Alpha = 0 - Beta = 1 - China = 2 - Final = 3 - Patch = 4 - Experimental = 5 - - -class TpkDataType(IntEnum): - TypeTreeInformation = 0 - Collection = 1 - FileSystem = 2 - Json = 3 - ReferenceAssemblies = 4 - EngineAssets = 5 - - def ToBlob(self, stream): - if self.value == TpkDataType.TypeTreeInformation: - return TpkTypeTreeBlob(stream) - elif self.value == TpkDataType.Collection: - return TpkCollectionBlob(stream) - elif self.value == TpkDataType.FileSystem: - return TpkFileSystemBlob(stream) - elif self.value == TpkDataType.Json: - return TpkJsonBlob(stream) - else: - raise Exception("Unimplemented TpkDataType -> Blob conversion") - - -class TpkUnityClassFlags(IntFlag): - NONE = 0 - IsAbstract = 1 - IsSealed = 2 - IsEditorOnly = 4 - IsReleaseOnly = 8 - IsStripped = 16 - Reserved = 32 - HasEditorRootNode = 64 - HasReleaseRootNode = 128 - - -###################################################################################### -# -# Main Class -# -###################################################################################### - - -class TpkFile: - Struct = Struct(" TpkDataBlob: - decompressed = None - if self.CompressionType == TpkCompressionType.NONE: - decompressed = self.CompressedBytes - - elif self.CompressionType == TpkCompressionType.Lz4: - import lz4.block - - decompressed = lz4.block.decompress( - self.CompressedBytes, self.UncompressedSize - ) - - elif self.CompressionType == TpkCompressionType.Lzma: - import lzma - - raise Exception("LZMA compression not implemented") - - elif self.CompressionType == TpkCompressionType.Brotli: - import brotli - - decompressed = brotli.decompress(self.CompressedBytes) - - else: - raise Exception("Invalid compression type") - - return self.DataType.ToBlob(BytesIO(decompressed)) - - -###################################################################################### -# -# Blobs -# -###################################################################################### - - -class TpkDataBlob: - __slots__ = "DataType" - DataType: TpkDataType - - def __init__(self, stream: BytesIO) -> None: - raise NotImplementedError("TpkDataBlob is an abstract class") - - -class TpkTypeTreeBlob(TpkDataBlob): - __slots__ = ( - "CreationTime", - "Versions", - "ClassInformation", - "CommonString", - "NodeBuffer", - "StringBuffer", - ) - CreationTime: int - Versions: List[UnityVersion] - ClassInformation: Dict[int, TpkClassInformation] # List[TpkClassInformation] - CommonString: TpkCommonString - NodeBuffer: TpkUnityNodeBuffer - StringBuffer: TpkStringBuffer - DataType: TpkDataType = TpkDataType.TypeTreeInformation - - def __init__(self, stream: BytesIO) -> None: - (self.CreationTime,) = INT64.unpack(stream.read(INT64.size)) - (versionCount,) = INT32.unpack(stream.read(INT32.size)) - self.Versions = [UnityVersion.fromStream(stream) for _ in range(versionCount)] - (classCount,) = INT32.unpack(stream.read(INT32.size)) - self.ClassInformation = { - x.ID: x for x in (TpkClassInformation(stream) for _ in range(classCount)) - } - self.CommonString = TpkCommonString(stream) - self.NodeBuffer = TpkUnityNodeBuffer(stream) - self.StringBuffer = TpkStringBuffer(stream) - - -class TpkCollectionBlob(TpkDataBlob): - __slots__ = "Blobs" - Blobs: List[Tuple[str, TpkDataBlob]] - - def __init__(self, stream: BytesIO) -> None: - (count,) = INT32.unpack(stream.read(INT32.size)) - self.Blobs = [ - # relativePath, data - ( - read_string(stream), - TpkDataType(BYTE.unpack(stream.read(1))[0]).ToBlob(stream), - ) - for _ in range(count) - ] - - -class TpkFileSystemBlob(TpkDataBlob): - __slots__ = "Files" - # TODO: check if dict might be better - Files: List[Tuple[str, bytes]] - - def __init__(self, stream: BytesIO) -> None: - (count,) = INT32.unpack(stream.read(INT32.size)) - self.Files = [ - # relativePath, data - (read_string(stream), read_data(stream)) - for _ in range(count) - ] - - -class TpkJsonBlob(TpkDataBlob): - __slots__ = "Text" - Text: str - DataType = TpkDataType.Json - - def __init__(self, stream: BytesIO) -> None: - self.Text = read_string(stream) - - -###################################################################################### -# -# Unity -# -###################################################################################### - - -class UnityVersion(int): - # https://github.com/AssetRipper/VersionUtilities/blob/master/VersionUtilities/UnityVersion.cs - """ - use following static methos instead of the constructor(__init__): - UnityVersion.fromStream(stream: BytesIO) - UnityVersion.fromString(version: str) - UnityVersion.fromList(major: int, minor: int, patch: int, build: int) - """ - - @staticmethod - def fromStream(stream: BytesIO) -> UnityVersion: - (m_data,) = UINT64.unpack(stream.read(UINT64.size)) - return UnityVersion(m_data) - - @staticmethod - def fromString(version: str) -> UnityVersion: - return UnityVersion(version.split(".")) - - @staticmethod - def fromList(major: int, minor: int, patch: int, build: int) -> UnityVersion: - return UnityVersion(major << 48 | minor << 32 | patch << 16 | build) - - @property - def major(self) -> int: - return (self >> 48) & 0xFFFF - - @property - def minor(self) -> int: - return (self >> 32) & 0xFFFF - - @property - def build(self) -> int: - return (self >> 16) & 0xFFFF - - @property - def type(self) -> int: - return UnityVersionType(self >> 8) & 0xFF - - @property - def type_number(self) -> int: - return self & 0xFF - - def __repr__(self) -> str: - return f"UnityVersion {self.major}.{self.minor}.{self.build}.{self.type_number}" - - -class TpkUnityClass: - __slots__ = ("Name", "Base", "Flags", "EditorRootNode", "ReleaseRootNode") - Struct = Struct(" None: - self.Name, self.Base, Flags = TpkUnityClass.Struct.unpack( - stream.read(TpkUnityClass.Struct.size) - ) - self.Flags = TpkUnityClassFlags(Flags) - self.EditorRootNode = self.ReleaseRootNode = None - if self.Flags & TpkUnityClassFlags.HasEditorRootNode: - (self.EditorRootNode,) = UINT16.unpack(stream.read(UINT16.size)) - if self.Flags & TpkUnityClassFlags.HasReleaseRootNode: - (self.ReleaseRootNode,) = UINT16.unpack(stream.read(UINT16.size)) - - def __eq__(self, other: TpkUnityClass) -> bool: - return self.__dict__ == other.__dict__ - - def __hash__(self) -> int: - # TODO - return hash(self.__dict__) - - -class TpkClassInformation: - __slots__ = ("ID", "Classes") - ID: int - # TODO - might want to use dict - Classes: List[Tuple[UnityVersion, TpkUnityClass]] - - def __init__(self, stream: BytesIO) -> None: - (self.ID,) = INT32.unpack(stream.read(INT32.size)) - (count,) = INT32.unpack(stream.read(INT32.size)) - self.Classes = [ - ( - UnityVersion.fromStream(stream), - TpkUnityClass(stream) if stream.read(1)[0] else None, - ) - for _ in range(count) - ] - - def getVersionedClass(self, version: UnityVersion) -> TpkUnityClass: - return get_item_for_version(version, self.Classes) - - -class TpkUnityNodeBuffer: - Nodes: List[TpkUnityNode] - - def __init__(self, stream: BytesIO) -> None: - (count,) = INT32.unpack(stream.read(INT32.size)) - self.Nodes = [TpkUnityNode(stream) for _ in range(count)] - - def __getitem__(self, index: int) -> TpkUnityNode: - return self.Nodes[index] - - -class TpkUnityNode: - __slots__ = ( - "TypeName", - "Name", - "ByteSize", - "Version", - "TypeFlags", - "MetaFlag", - "SubNodes", - ) - Struct = Struct(" None: - ( - self.TypeName, - self.Name, - self.ByteSize, - self.Version, - self.TypeFlags, - self.MetaFlag, - count, - ) = TpkUnityNode.Struct.unpack(stream.read(TpkUnityNode.Struct.size)) - - SubNodeStruct = Struct(f"<{count}H") - self.SubNodes = list(SubNodeStruct.unpack(stream.read(SubNodeStruct.size))) - - def __eq__(self, other: TpkUnityNode) -> bool: - return self.__dict__ == other.__dict__ - - def __hash__(self) -> int: - # TODO - return hash(self.__dict__) - - -###################################################################################### -# -# Strings -# -###################################################################################### - - -class TpkStringBuffer: - __slots__ = "Strings" - Strings: List[str] - - def __init__(self, stream: BytesIO) -> None: - self.Strings = [ - read_string(stream) for _ in range(INT32.unpack(stream.read(INT32.size))[0]) - ] - - @property - def Count(self) -> int: - return len(self.Strings) - - -class TpkCommonString: - __slots__ = ("VersionInformation", "StringBufferIndices") - VersionInformation: List[Tuple[UnityVersion, int]] - StringBufferIndices: List[int] - - def __init__(self, stream: BytesIO) -> None: - (versionCount,) = INT32.unpack(stream.read(INT32.size)) - self.VersionInformation = [ - (UnityVersion.fromStream(stream), stream.read(1)[0]) - for _ in range(versionCount) - ] - (indicesCount,) = INT32.unpack(stream.read(INT32.size)) - indicesStruct = Struct(f"<{indicesCount}H") - self.StringBufferIndices = indicesStruct.unpack(stream.read(indicesStruct.size)) - - def GetStrings(self, buffer: TpkStringBuffer) -> List[str]: - return [buffer.Strings[i] for i in self.StringBufferIndices] - - def GetCount(self, exactVersion: UnityVersion) -> int: - return get_item_for_version(exactVersion, self.VersionInformation) - - -###################################################################################### -# -# helper functions -# -###################################################################################### - -BYTE = Struct("b") -UINT16 = Struct(" str: - # varint - shift = 0 - length = 0 - while True: - (i,) = stream.read(1) - length |= (i & 0x7F) << shift - shift += 7 - if not (i & 0x80): - break - # string - return stream.read(length).decode("utf-8") - - -def read_data(stream: BytesIO) -> bytes: - return stream.read(INT32.unpack(stream.read(INT32.size))[0]) - - -def get_item_for_version( - exactVersion: UnityVersion, items: List[Tuple[UnityVersion, Any]] -) -> Any: - ret = None - for version, item in items: - if exactVersion >= version: - ret = item - else: - break - if ret: - return ret - raise ValueError("Could not find exact version") + return TypeTreeNode.from_list(nodes) -init() +@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 e36ec51d1..7eb916eb6 100644 --- a/UnityPy/helpers/TypeTreeHelper.py +++ b/UnityPy/helpers/TypeTreeHelper.py @@ -1,139 +1,132 @@ -from typing import Any, Dict, List, Union, Iterable, Tuple -from ..streams import EndianBinaryReader, EndianBinaryWriter -from ctypes import c_uint32 -import tabulate -from ..exceptions import TypeTreeError as TypeTreeError +from __future__ import annotations -kAlignBytes = 0x4000 +from sys import version_info as py_version_info +from typing import TYPE_CHECKING, Any, Optional, Union +from attrs import define -class TypeTreeNode(object): - __slots__ = ( - "m_Version", - "m_Level", - "m_IsArray", - "m_ByteSize", - "m_Index", - "m_MetaFlag", - "m_Type", - "m_Name", - "m_TypeStrOffset", - "m_NameStrOffset", - "m_RefTypeHash", - "m_VariableCount", - ) - m_Type: str - m_Name: str - m_ByteSize: int - m_Index: int - m_Version: int - m_MetaFlag: int - m_Level: int - m_TypeStrOffset: int - m_NameStrOffset: int - m_RefTypeHash: str - m_IsArray: int - m_VariableCount: int - - def __init__(self, data: Union[dict, Iterable[Tuple]] = None, **kwargs): - if isinstance(data, dict): - items = data.items() - elif kwargs: - items = kwargs.items() - else: - items = data +from .. import classes +from ..streams.EndianBinaryReader import EndianBinaryReader +from ..streams.EndianBinaryWriter import EndianBinaryWriter +from .TypeTreeNode import TypeTreeNode - for key, val in items: - setattr(self, key, val) +Object = classes.Object +UnknownObject = classes.UnknownObject +PPtr = classes.PPtr - def __repr__(self): - return f"" +if TYPE_CHECKING: + from ..files.SerializedFile import SerializedFile try: - from ..UnityPyBoost import TypeTreeNode, read_typetree as read_typetree_c -except: - read_typetree_c = None - - -def node_dict_to_node_cls(nodes: List[dict]) -> List[TypeTreeNode]: - """Converts all dict-type nodes into TypeTreeNodes - - Parameters - ---------- - nodes : List[dict] - nodes/nodes of the typetree as dict + from ..UnityPyBoost import read_typetree as read_typetree_boost +except ImportError: + read_typetree_boost = None - Returns - ------- - List[TypeTreeNode] - a list of TypeTreeNode-type nodes - """ - # legacy support - if not next(iter(nodes[0])).startswith("m_"): - return [ - TypeTreeNode( - m_Name=x["name"], - m_Type=x["type"], - m_Level=x["level"], - m_MetaFlag=x["meta_flag"], - ) - for x in nodes - ] +kAlignBytes = 0x4000 - return [TypeTreeNode(**node) for node in nodes] +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") -def check_nodes(nodes: List[Union[dict, TypeTreeNode]]) -> List[TypeTreeNode]: - """Checks the type of the nodes and converts them if necessary. + if cls == "": + return None - Parameters - ---------- - nodes : List[Union[dict, TypeTreeNode]] - nodes/nodes of the typetree as dict or TypeTreeNode + 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}") - Returns - ------- - List[TypeTreeNode] - a list of TypeTreeNode-type nodes - """ - if isinstance(nodes, list): - if len(nodes) == 0: - raise ValueError("not enough nodes") - if isinstance(nodes[0], TypeTreeNode): - return nodes - elif isinstance(nodes[0], dict): - return node_dict_to_node_cls(nodes) - raise ValueError( - f"nodes must be a list of dict or TypeTreeNode elements, but received {type(nodes)} - {type(nodes[0]) if isinstance(nodes, list) else ''}" - ) +if py_version_info >= (3, 14): + from annotationlib import get_annotations as annotationlib_get_annotations -def get_nodes(nodes: List[TypeTreeNode], index: int) -> list: - """Copies all nodes above the level of the node at the set index. + def get_annotation_keys(clz) -> set[str]: + return set(annotationlib_get_annotations(clz).keys()) +else: - Parameters - ---------- - nodes : list - nodes/nodes of the typetree - index : int - index of the node - - Returns - ------- - list - A list of nodes - """ - level = nodes[index].m_Level - for i, node in enumerate(nodes[index + 1 :], index + 1): - if node.m_Level <= level: - return nodes[index:i] - return nodes[index:] + def get_annotation_keys(clz) -> set[str]: + return set(clz.__annotations__) def read_typetree( - nodes: List[Union[dict, TypeTreeNode]], reader: EndianBinaryReader -) -> dict: + 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 @@ -145,380 +138,307 @@ def read_typetree( Returns ------- - dict + dict | objects.Object The parsed typtree """ - reader.reset() - - nodes = check_nodes(nodes) - - if read_typetree_c: - return read_typetree_c( - nodes, reader.read_bytes(reader.byte_size), reader.endian - ) + 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 - obj = read_value(nodes, reader, c_uint32(0)) - - read = reader.Position - reader.byte_start - if read != reader.byte_size: - raise TypeTreeError( - f"Error while read type, read {read} bytes but expected {reader.byte_size} bytes", - nodes, - ) + 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[TypeTreeNode], reader: EndianBinaryReader, i: c_uint32): - node = nodes[i.value] - typ = node.m_Type - align = (node.m_MetaFlag & kAlignBytes) != 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].m_MetaFlag & kAlignBytes) != 0: - 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 = 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].m_Type == "Array": - if (nodes[i.value + 1].m_MetaFlag & kAlignBytes) != 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.m_Name] = read_value(clz, reader, j) - j.value += 1 - - if align: - reader.align_stream() - return value - - -def read_typetree_str( - sb: List[str], nodes: List[Union[dict, TypeTreeNode]], reader: EndianBinaryReader -) -> list: - """Reads the typetree of the object contained in the reader via the node list and dumps it as string. +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 ---------- - sb : list - StringBuilder - a list used to build the string dump, should be empty + value: dict | objects.Object + The object to be written nodes : list List of nodes/nodes - reader : EndianBinaryReader - Reader of the object to be parsed - - Returns - ------- - list - The sb given as input + writer : EndianBinaryWriter + Writer of the object to be parsed """ - # reader.reset() - nodes = check_nodes(nodes) - - 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: - raise TypeTreeError( - f"Error while read type, read {readed} bytes but expected {reader.byte_size} bytes", - nodes, - ) - - return sb - - -def read_value_str( - sb: List[str], nodes: List[TypeTreeNode], reader: EndianBinaryReader, i: c_uint32 -) -> list: - node = nodes[i.value] - typ = node.m_Type - align = (node.m_MetaFlag & kAlignBytes) != 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.m_Level, node.m_Type, node.m_Name, value - ) - ) - elif typ == "map": - if (nodes[i.value + 1].m_MetaFlag & kAlignBytes) != 0: + 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() - append = False - sb.append( - "{0}{1} {2}\r\n".format("\t" * node.m_Level, node.m_Type, node.m_Name) - ) - sb.append("{0}{1} {2}\r\n".format("\t" * (node.m_Level + 1), "Array", "Array")) - sb.append( - "{0}{1} {2} = {3}\r\n".format( - "\t" * (node.m_Level + 1), "int", "size", size - ) - ) - for j in range(size): - sb.append("{0}[{1}]\r\n".format("\t" * (node.m_Level + 2), j)) - sb.append( - "{0}{1} {2}\r\n".format("\t" * (node.m_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 = 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 - append = False - sb.append( - "{0}{1} {2}\r\n".format("\t" * node.m_Level, node.m_Type, node.m_Name) - ) - sb.append( - "{0}{1} {2} = {3}\r\n".format("\t" * node.m_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].m_Type == "Array": - if (nodes[i.value + 1].m_MetaFlag & kAlignBytes) != 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.m_Level, node.m_Type, node.m_Name) - ) - sb.append( - "{0}{1} {2}\r\n".format("\t" * (node.m_Level + 1), "Array", "Array") - ) - sb.append( - "{0}{1} {2} = {3}\r\n".format( - "\t" * (node.m_Level + 1), "int", "size", size + 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"], ) - ) - for j in range(size): - sb.append("{0}[{1}]\r\n".format("\t" * (node.m_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.m_Level, node.m_Type, node.m_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.m_Level, node.m_Type, node.m_Name, value - ) - ) + 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 sb - - -def dump_typetree(nodes: List[TypeTreeNode]) -> str: - """Dumps the structure of the given nodes. - - Parameters - ---------- - nodes : list - List of nodes/nodes - - Returns - ------- - str - The dumped structure - """ - field_names = ["m_Level", "m_Type", "m_Name", "m_MetaFlag"] - rows = [[getattr(x, key) for key in field_names] for x in nodes] - return tabulate.tabulate(rows, headers=field_names) + return value -def write_typetree( - obj: dict, nodes: List[Union[dict, TypeTreeNode]], 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 +def read_value_array( + node: TypeTreeNode, + reader: EndianBinaryReader, + config: TypeTreeConfig, + size: int, +) -> Any: + align = metaflag_is_aligned(node.m_MetaFlag) + + func = FUNCTION_READ_MAP_ARRAY.get(node.m_Type) + if func: + value = func(reader, size) + elif node.m_Type == "string": + value = [reader.read_aligned_string() for _ in range(size)] + elif node.m_Type == "TypelessData": + value = [reader.read_byte_array() for _ in range(size)] + elif node.m_Type == "pair": + key_node = node.m_Children[0] + value_node = node.m_Children[1] + + key_func = FUNCTION_READ_MAP.get( + key_node.m_Type, + lambda reader: read_value(key_node, reader, config), + ) + value_func = FUNCTION_READ_MAP.get( + value_node.m_Type, + lambda reader: read_value(value_node, reader, config), + ) + value = [(key_func(reader), value_func(reader)) for _ in range(size)] + elif node.m_Type == "ReferencedObject": + value = [None] * size + for i in range(size): + item = {} + for child in node.m_Children: + if child.m_Type == "ReferencedObjectData": + ref_type_nodes = get_ref_type_node(item, config.assetsfile) + item[child.m_Name] = read_value(ref_type_nodes, reader, config) + else: + item[child.m_Name] = read_value(child, reader, config) + value[i] = item + # Vector + elif node.m_Children and node.m_Children[0].m_Type == "Array": + if metaflag_is_aligned(node.m_Children[0].m_MetaFlag): + align = True + subtype = node.m_Children[0].m_Children[1] + if metaflag_is_aligned(subtype.m_MetaFlag): + value = [read_value_array(subtype, reader, config, reader.read_int()) for _ in range(size)] + else: + 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) + ] - Returns - ------- - EndianBinaryWriter - The writer that was used to save the data of the given object. - """ - if not writer: - writer = EndianBinaryWriter() + if align: + reader.align_stream() + return value - nodes = check_nodes(nodes) - i = c_uint32(1) - while i.value < len(nodes): - value = obj[nodes[i.value].m_Name] - write_value(value, nodes, writer, i) - i.value += 1 - return writer +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: Any, nodes: List[TypeTreeNode], writer: EndianBinaryWriter, i: c_uint32 -): - node = nodes[i.value] - typ = node.m_Type - align = (node.m_MetaFlag & kAlignBytes) != 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].m_MetaFlag & kAlignBytes) != 0: + 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].m_Type == "Array": - if (nodes[i.value + 1].m_MetaFlag & kAlignBytes) != 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].m_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 fced5e1c5..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 = 0.0, g: float = 0.0, b: float = 0.0, a: float = 0.0): - 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 a38c59676..000000000 --- a/UnityPy/math/Matrix4x4.py +++ /dev/null @@ -1,266 +0,0 @@ -from .Vector3 import Vector3 - - -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 - - def __getitem__(self, index): - if isinstance(index, tuple): - index = index[0] + index[1] * 4 - return self.M[index] - - def __setitem__(self, index, value): - if isinstance(index, tuple): - # row, column - index = index[0] + index[1] * 4 - self.M[index] = value - - def __eq__(self, other): - if not isinstance(other, Matrix4x4): - return False - print() - - def __mul__(lhs, rhs): - res = Matrix4x4([0] * 16) - 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 - - @staticmethod - def Scale(vector: Vector3): - return Matrix4x4( - [vector.X, 0, 0, 0, 0, vector.Y, 0, 0, 0, 0, vector.Z, 0, 0, 0, 0, 1] - ) - - @property - def M00(self): - return self.M[0] - - @M00.setter - def M00(self, value): - self.M[0] = value - - @property - def M10(self): - return self.M[1] - - @M10.setter - def M10(self, value): - self.M[1] = value - - @property - def M20(self): - return self.M[2] - - @M20.setter - def M20(self, value): - self.M[2] = value - - @property - def M30(self): - return self.M[3] - - @M30.setter - def M30(self, value): - self.M[3] = value - - @property - def M01(self): - return self.M[4] - - @M01.setter - def M01(self, value): - self.M[4] = value - - @property - def M11(self): - return self.M[5] - - @M11.setter - def M11(self, value): - self.M[5] = value - - @property - def M21(self): - return self.M[6] - - @M21.setter - def M21(self, value): - self.M[6] = value - - @property - def M31(self): - return self.M[7] - - @M31.setter - def M31(self, value): - self.M[7] = value - - @property - def M02(self): - return self.M[8] - - @M02.setter - def M02(self, value): - self.M[8] = value - - @property - def M12(self): - return self.M[9] - - @M12.setter - def M12(self, value): - self.M[9] = value - - @property - def M22(self): - return self.M[10] - - @M22.setter - def M22(self, value): - self.M[10] = value - - @property - def M32(self): - return self.M[11] - - @M32.setter - def M32(self, value): - self.M[11] = value - - @property - def M03(self): - return self.M[12] - - @M03.setter - def M03(self, value): - self.M[12] = value - - @property - def M13(self): - return self.M[13] - - @M13.setter - def M13(self, value): - self.M[13] = value - - @property - def M23(self): - return self.M[14] - - @M23.setter - def M23(self, value): - self.M[14] = value - - @property - def M33(self): - return self.M[15] - - @M33.setter - def M33(self, value): - self.M[15] = value 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 5e19f9940..000000000 --- a/UnityPy/math/Vector3.py +++ /dev/null @@ -1,99 +0,0 @@ -from dataclasses import dataclass -from math import sqrt - - -kEpsilon = 0.00001 - - -@dataclass -class Vector3: - X: float = 0.0 - Y: float = 0.0 - Z: float = 0.0 - - 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 __getitem__(self, index): - return (self.X, self.Y, self.Z)[index] - - def __setitem__(self, index, value): - if index == 0: - self.X = value - elif index == 1: - self.Y = value - elif index == 2: - self.Z = value - else: - raise IndexError("Index out of range") - - def __hash__(self): - return self.X.__hash__() ^ (self.Y.__hash__() << 2) ^ (self.Z.__hash__() >> 2) - - def __eq__(self, other): - if isinstance(other, Vector3): - return self.X == other.X and self.Y == other.Y and self.Z == other.Z - else: - return False - - def normalize(self): - length = self.length() - if length > kEpsilon: - invNorm = 1.0 / length - self.X *= invNorm - self.Y *= invNorm - self.Z *= invNorm - else: - X = 0 - Y = 0 - Z = 0 - - def Normalize(self): - self.normalize() - - def length(self): - return sqrt(self.LengthSquared()) - - def Length(self): - return self.length() - - def LengthSquared(self): - return self.X ** 2 + self.Y ** 2 + self.Y ** 2 - - @staticmethod - def Zero(): - return Vector3(0, 0, 0) - - @staticmethod - def One(): - return Vector3(1, 1, 1) - - def __add__(a, b): - return Vector3(a.X + b.X, a.Y + b.Y, a.Z + b.Z) - - def __sub__(a, b): - return Vector3(a.X - b.X, a.Y - b.Y, a.Z - b.Z) - - def __mul__(a, d): - return Vector3(a.X * d, a.Y * d, a.Z * d) - - def __div__(a, d): - return Vector3(a.X / d, a.Y / d, a.Z / d) - - def __eq__(lhs, rhs): - return (lhs - rhs).LengthSquared() < kEpsilon - - def __ne__(lhs, rhs): - return not (lhs == rhs) - - def Vector2(self): - from .Vector2 import Vector2 - return Vector2(self.X, self.Y) - - def Vector4(self): - from .Vector4 import Vector4 - return Vector4(self.X, self.Y, self.Z, 0.0) 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/tools/libil2cpp_helper/il2cpp.py b/UnityPy/py.typed similarity index 100% rename from UnityPy/tools/libil2cpp_helper/il2cpp.py rename to UnityPy/py.typed 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/resources/uncompressed.tpk b/UnityPy/resources/uncompressed.tpk deleted file mode 100644 index 5128be38c..000000000 Binary files a/UnityPy/resources/uncompressed.tpk and /dev/null differ diff --git a/UnityPy/streams/EndianBinaryReader.py b/UnityPy/streams/EndianBinaryReader.py index c89fdfbc3..968b32d89 100644 --- a/UnityPy/streams/EndianBinaryReader.py +++ b/UnityPy/streams/EndianBinaryReader.py @@ -1,77 +1,92 @@ -import io +from __future__ import annotations + +import builtins +import re import sys +from io import BufferedReader, IOBase from struct import Struct, unpack -import re -from typing import List, Union -from io import BytesIO, BufferedIOBase +from types import MethodType +from typing import Any, Callable, ClassVar, Dict, List, Literal, Optional, Tuple, Union -reNot0 = re.compile(b"(.*?)\x00") +reNot0 = re.compile(b"(.*?)\x00", re.S) SYS_ENDIAN = "<" if sys.byteorder == "little" else ">" - -from ..math import Color, Matrix4x4, Quaternion, Vector2, Vector3, Vector4, Rectangle +Endianess = Literal["<", ">"] # generate unpack and unpack_from functions TYPE_PARAM_SIZE_LIST = [ - ("short", "h", 2), - ("u_short", "H", 2), - ("int", "i", 4), - ("u_int", "I", 4), - ("long", "q", 8), - ("u_long", "Q", 8), - ("half", "e", 2), - ("float", "f", 4), - ("double", "d", 8), - ("vector2", "2f", 8), - ("vector3", "3f", 12), - ("vector4", "4f", 16), + ("short", "h"), + ("u_short", "H"), + ("int", "i"), + ("u_int", "I"), + ("long", "q"), + ("u_long", "Q"), + ("half", "e"), + ("float", "f"), + ("double", "d"), ] -LOCALS = locals() -for endian_s, endian_l in (("<", "little"), (">", "big")): - for typ, param, _ in TYPE_PARAM_SIZE_LIST: - LOCALS[f"unpack_{endian_l}_{typ}"] = Struct(f"{endian_s}{param}").unpack - LOCALS[f"unpack_{endian_l}_{typ}_from"] = Struct( - f"{endian_s}{param}" - ).unpack_from +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 + _endian: Endianess + _function_map: ClassVar[Dict[Endianess, Dict[str, Callable]]] def __new__( cls, - item: Union[bytes, bytearray, memoryview, BytesIO, str], - endian: str = ">", + 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) - elif isinstance(item, BufferedIOBase): - obj = super(EndianBinaryReader, cls).__new__(EndianBinaryReader_Streamable) + 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): - item = open(item, "rb") - obj = super(EndianBinaryReader, cls).__new__(EndianBinaryReader_Streamable) + 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) - obj.__init__(item, endian) + 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: + 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"" @@ -81,7 +96,7 @@ def read_byte(self) -> int: def read_u_byte(self) -> int: 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: @@ -111,17 +126,14 @@ def read_double(self) -> float: def read_boolean(self) -> bool: 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 = 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: @@ -143,136 +155,115 @@ def read_aligned_string(self) -> str: def align_stream(self, alignment=4): self.Position += (alignment - self.Position % alignment) % alignment - def read_quaternion(self) -> Quaternion: - return Quaternion( - self.read_float(), self.read_float(), self.read_float(), self.read_float() - ) - - def read_vector2(self) -> Vector2: - return Vector2(self.read_float(), self.read_float()) - - def read_vector3(self) -> Vector3: - return Vector3(self.read_float(), self.read_float(), self.read_float()) - - def read_vector4(self) -> Vector4: - return Vector4( - self.read_float(), self.read_float(), self.read_float(), self.read_float() - ) - - def read_rectangle_f(self) -> Rectangle: - return Rectangle( - self.read_float(), self.read_float(), self.read_float(), self.read_float() - ) - - def read_color4(self) -> Color: - return Color( - self.read_float(), self.read_float(), self.read_float(), self.read_float() - ) - - def read_byte_array(self) -> bytes: + def read_byte_array(self) -> builtins.bytes: return self.read(self.read_int()) - def read_matrix(self) -> Matrix4x4: - return Matrix4x4(self.read_float_array(16)) - - def read_array(self, command, length: int) -> list: + def read_array(self, command: Callable, length: int) -> list: return [command() for _ in range(length)] - def read_array_struct(self, param: str, length: int = None) -> list: + 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_boolean_array(self, length: int = None) -> List[bool]: + def read_boolean_array(self, length: Optional[int] = None) -> Tuple[bool, ...]: return self.read_array_struct("?", length) - def read_u_short_array(self, length: int = None) -> List[int]: + def read_u_byte_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("B", length) + + def read_u_short_array(self, length: Optional[int] = None) -> Tuple[int, ...]: return self.read_array_struct("h", length) - def read_short_array(self, length: int = None) -> List[int]: + def read_short_array(self, length: Optional[int] = None) -> Tuple[int, ...]: return self.read_array_struct("H", length) - def read_int_array(self, length: int = None) -> List[int]: + def read_int_array(self, length: Optional[int] = None) -> Tuple[int, ...]: return self.read_array_struct("i", length) - def read_u_int_array(self, length: int = None) -> List[int]: + def read_u_int_array(self, length: Optional[int] = None) -> Tuple[int, ...]: return self.read_array_struct("I", length) - def read_u_int_array_array(self, length: int = None) -> List[List[int]]: - return self.read_array( - self.read_u_int_array, length if length is not None else self.read_int() - ) + def read_long_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("q", length) - def read_float_array(self, length: int = None) -> List[float]: + def read_u_long_array(self, length: Optional[int] = None) -> Tuple[int, ...]: + return self.read_array_struct("Q", length) + + def read_float_array(self, length: Optional[int] = None) -> Tuple[float, ...]: return self.read_array_struct("f", length) + def read_double_array(self, length: Optional[int] = None) -> Tuple[float, ...]: + return self.read_array_struct("d", length) + def read_string_array(self) -> List[str]: return self.read_array(self.read_aligned_string, self.read_int()) - def read_vector2_array(self) -> List[Vector2]: - return self.read_array(self.read_vector2, self.read_int()) - - def read_vector4_array(self) -> List[Vector4]: - return self.read_array(self.read_vector4, self.read_int()) - - def read_matrix_array(self) -> List[Matrix4x4]: - return self.read_array(self.read_matrix, self.read_int()) - def real_offset(self) -> int: """Returns offset in the underlying file. (Not working with unpacked streams.) """ return self.BaseOffset + self.Position - def read_the_rest(self, obj_start: int, obj_size: int) -> bytes: + 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): - self._endian = "" + 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 endian(self): - return self._endian - - @endian.setter - def endian(self, value: str): - if value not in ("<", ">"): - raise ValueError("Invalid endian") - if value != self._endian: - setattr( - self, - "__class__", - EndianBinaryReader_Memoryview_LittleEndian - if value == "<" - else EndianBinaryReader_Memoryview_BigEndian, - ) - self._endian = value - @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 + 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): + def read_aligned_string(self) -> str: length = self.read_int() if 0 < length <= self.Length - self.Position: string_data = self.read_bytes(length) @@ -281,7 +272,7 @@ def read_aligned_string(self): return result return "" - def read_string_to_null(self, max_length=32767) -> str: + 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: @@ -293,179 +284,39 @@ def read_string_to_null(self, max_length=32767) -> str: return ret -class EndianBinaryReader_Memoryview_LittleEndian(EndianBinaryReader_Memoryview): - def read_u_short(self): - (ret,) = unpack_little_u_short_from(self.view, self.Position) - self.Position += 2 - return ret - - def read_short(self): - (ret,) = unpack_little_short_from(self.view, self.Position) - self.Position += 2 - return ret - - def read_int(self): - (ret,) = unpack_little_int_from(self.view, self.Position) - self.Position += 4 - return ret - - def read_u_int(self): - (ret,) = unpack_little_u_int_from(self.view, self.Position) - self.Position += 4 - return ret - - def read_long(self): - (ret,) = unpack_little_long_from(self.view, self.Position) - self.Position += 8 - return ret - - def read_u_long(self): - (ret,) = unpack_little_u_long_from(self.view, self.Position) - self.Position += 8 - return ret - - def read_half(self): - (ret,) = unpack_little_half_from(self.view, self.Position) - self.Position += 2 - return ret - - def read_float(self): - (ret,) = unpack_little_float_from(self.view, self.Position) - self.Position += 4 - return ret - - def read_double(self): - (ret,) = unpack_little_double_from(self.view, self.Position) - self.Position += 8 - return ret - - def read_vector2(self): - (x, y) = unpack_little_vector2_from(self.view, self.Position) - self.Position += 8 - return Vector2(x, y) - - def read_vector3(self): - (x, y, z) = unpack_little_vector3_from(self.view, self.Position) - self.Position += 12 - return Vector3(x, y, z) - - def read_vector4(self): - (x, y, z, w) = unpack_little_vector4_from(self.view, self.Position) - self.Position += 16 - return Vector4(x, y, z, w) - - -class EndianBinaryReader_Memoryview_BigEndian(EndianBinaryReader_Memoryview): - def read_u_short(self): - (ret,) = unpack_big_u_short_from(self.view, self.Position) - self.Position += 2 - return ret - - def read_short(self): - (ret,) = unpack_big_short_from(self.view, self.Position) - self.Position += 2 - return ret - - def read_int(self): - (ret,) = unpack_big_int_from(self.view, self.Position) - self.Position += 4 - return ret - - def read_u_int(self): - (ret,) = unpack_big_u_int_from(self.view, self.Position) - self.Position += 4 - return ret - - def read_long(self): - (ret,) = unpack_big_long_from(self.view, self.Position) - self.Position += 8 - return ret - - def read_u_long(self): - (ret,) = unpack_big_u_long_from(self.view, self.Position) - self.Position += 8 - return ret - - def read_half(self): - (ret,) = unpack_big_half_from(self.view, self.Position) - self.Position += 2 - return ret - - def read_float(self): - (ret,) = unpack_big_float_from(self.view, self.Position) - self.Position += 4 - return ret - - def read_double(self): - (ret,) = unpack_big_double_from(self.view, self.Position) - self.Position += 8 - return ret - - def read_vector2(self): - (x, y) = unpack_big_vector2_from(self.view, self.Position) - self.Position += 8 - return Vector2(x, y) - - def read_vector3(self): - (x, y, z) = unpack_big_vector3_from(self.view, self.Position) - self.Position += 12 - return Vector3(x, y, z) - - def read_vector4(self): - (x, y, z, w) = unpack_big_vector4_from(self.view, self.Position) - self.Position += 16 - return Vector4(x, y, z, w) - - class EndianBinaryReader_Streamable(EndianBinaryReader): __slots__ = ("stream", "_endian", "BaseOffset") - stream: io.BufferedReader + stream: BufferedReader + _function_map = STREAM_FUNCTIONS - def __init__(self, stream, endian=">", offset=0): - self._endian = "" + def __init__(self, stream: BufferedReader, endian: Endianess = ">", offset: int = 0): self.stream = stream super().__init__(stream, endian=endian, offset=offset) self.read = self.stream.read - def get_position(self): - return self.stream.tell() - - def set_position(self, value): - self.stream.seek(value + self.BaseOffset) - @property - def endian(self): - return self._endian + def Position(self) -> int: + return self.stream.tell() - self.BaseOffset - @endian.setter - def endian(self, value): - if value not in ("<", ">"): - raise ValueError("Invalid endian") - if value != self._endian: - setattr( - self, - "__class__", - EndianBinaryReader_Streamable_LittleEndian - if value == "<" - else EndianBinaryReader_Streamable_BigEndian, - ) - self._endian = value + @Position.setter + def Position(self, value: int): + if value < 0: + raise ValueError("Position cannot be negative") + self.stream.seek(value + self.BaseOffset) @property - def Length(self): + def Length(self): # type: ignore pos = self.Position length = self.stream.seek(0, 2) - self.BaseOffset self.Position = pos return length - Position = property(get_position, set_position) - @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): @@ -473,77 +324,26 @@ def dispose(self): pass -class EndianBinaryReader_Streamable_LittleEndian(EndianBinaryReader_Streamable): - def read_u_short(self): - return unpack_little_u_short(self.read(2))[0] - - def read_short(self): - return unpack_little_short(self.read(2))[0] - - def read_int(self): - return unpack_little_int(self.read(4))[0] - - def read_u_int(self): - return unpack_little_u_int(self.read(4))[0] - - def read_long(self): - return unpack_little_long(self.read(8))[0] +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 read_u_long(self): - return unpack_little_u_long(self.read(8))[0] - - def read_half(self): - return unpack_little_half(self.read(2))[0] - - def read_float(self): - return unpack_little_float(self.read(4))[0] - - def read_double(self): - return unpack_little_double(self.read(8))[0] - - def read_vector2(self): - return Vector2(*unpack_little_vector2(self.read(8))) - - def read_vector3(self): - return Vector3(*unpack_little_vector3(self.read(12))) - - def read_vector4(self): - return Vector4(*unpack_little_vector4(self.read(16))) - - -class EndianBinaryReader_Streamable_BigEndian(EndianBinaryReader_Streamable): - def read_u_short(self): - return unpack_big_u_short(self.read(2))[0] - - def read_short(self): - return unpack_big_short(self.read(2))[0] - - def read_int(self): - return unpack_big_int(self.read(4))[0] - - def read_u_int(self): - return unpack_big_u_int(self.read(4))[0] - - def read_long(self): - return unpack_big_long(self.read(8))[0] - - def read_u_long(self): - return unpack_big_u_long(self.read(8))[0] - - def read_half(self): - return unpack_big_half(self.read(2))[0] + def __del__(self): + self.stream.close() - def read_float(self): - return unpack_big_float(self.read(4))[0] - def read_double(self): - return unpack_big_double(self.read(8))[0] +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 read_vector2(self): - return Vector2(*unpack_big_vector2(self.read(8))) + 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 read_vector3(self): - return Vector3(*unpack_big_vector3(self.read(12))) + def stream_read_func(self: EndianBinaryReader_Streamable, /, struct=struct): + return struct.unpack(self.stream.read(struct.size))[0] - def read_vector4(self): - return Vector4(*unpack_big_vector4(self.read(16))) + 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 d9861401f..1473c871b 100644 --- a/UnityPy/streams/EndianBinaryWriter.py +++ b/UnityPy/streams/EndianBinaryWriter.py @@ -1,20 +1,21 @@ -import io +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_, io.IOBase): + elif isinstance(input_, IOBase): self.stream = input_ else: raise ValueError("Invalid input type - %s." % type(input_)) @@ -30,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(): @@ -51,7 +51,7 @@ def write_byte(self, value: int): def write_u_byte(self, value: int): 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): @@ -91,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 index f3976862f..b1f0c8e60 100644 --- a/UnityPy/tools/extractor.py +++ b/UnityPy/tools/extractor.py @@ -1,34 +1,35 @@ -from io import BytesIO -import os 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, - MonoBehaviour, - TextAsset, - Font, Shader, - Mesh, Sprite, + TextAsset, Texture2D, - AudioClip, - GameObject, ) from UnityPy.enums.ClassIDType import ClassIDType -from typing import Union, List, Dict -from pathlib import Path -from collections.abc import Callable +from UnityPy.files import ObjectReader, SerializedFile def export_obj( - obj: Union[Object, PPtr], - fp: Path, + obj: Union[ObjectReader, PPtr], + fp: str, append_name: bool = False, append_path_id: bool = False, export_unknown_as_typetree: bool = False, - asset_filter: Callable[[Object], bool] = None, -) -> List[int]: + asset_filter: Optional[Callable[[Object], bool]] = None, +) -> List[Tuple[SerializedFile, int]]: """Exports the given object to the given filepath. Args: @@ -36,7 +37,8 @@ def export_obj( 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. + 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: @@ -51,22 +53,29 @@ def export_obj( return [] # set filepath - obj = obj.read() + 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(obj): + if asset_filter and not asset_filter(instance): return [] if append_name: - fp = os.path.join(fp, obj.name if obj.name else obj.type.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.path_id}" + fp = f"{fp}_{obj.m_PathID}" # export - return export_func(obj, fp, extension) + return export_func(instance, fp, extension) def extract_assets( @@ -76,8 +85,8 @@ def extract_assets( ignore_first_container_dirs: int = 0, append_path_id: bool = False, export_unknown_as_typetree: bool = False, - asset_filter: Callable[[Object], bool] = None, -) -> List[int]: + asset_filter: Optional[Callable[[Object], bool]] = None, +) -> List[Tuple[SerializedFile, int]]: """Extracts some or all assets from the given source. Args: @@ -87,10 +96,11 @@ def extract_assets( 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. + asset_filter (func(object)->bool, optional): Determines whether to export an object. + Defaults to all objects. Returns: - List[int]: [description] + List[Tuple[SerializedFile, int]]: [description] """ # load source env = UnityPy.load(src) @@ -105,15 +115,16 @@ def defaulted_export_index(type: ClassIDType): return 999 if use_container: - container = sorted(env.container, key=lambda x: defaulted_export_index(x[1].type)) + 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() + # 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), + *(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( @@ -122,6 +133,7 @@ def defaulted_export_index(type: ClassIDType): obj_dest, append_path_id=append_path_id, export_unknown_as_typetree=export_unknown_as_typetree, + asset_filter=asset_filter, ) ) @@ -130,7 +142,7 @@ def defaulted_export_index(type: ClassIDType): for obj in objects: if asset_filter is not None and not asset_filter(obj): continue - if obj.path_id not in exported: + if (obj.assets_file, obj.path_id) not in exported: exported.extend( export_obj( obj, @@ -138,6 +150,7 @@ def defaulted_export_index(type: ClassIDType): append_name=True, append_path_id=append_path_id, export_unknown_as_typetree=export_unknown_as_typetree, + asset_filter=asset_filter, ) ) @@ -149,83 +162,78 @@ def defaulted_export_index(type: ClassIDType): ############################################################################### -def exportTextAsset(obj: TextAsset, fp: str, extension: str = ".txt") -> List[int]: +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.script) - return [obj.path_id] + f.write(obj.m_Script.encode("utf-8", "surrogateescape")) + return [(obj.assets_file, obj.object_reader.path_id)] -def exportFont(obj: Font, fp: str, extension: str = "") -> List[int]: +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(obj.m_FontData) - return [obj.path_id] + f.write(bytes(obj.m_FontData)) + return [(obj.assets_file, obj.object_reader.path_id)] -def exportMesh(obj: Mesh, fp: str, extension=".obj") -> List[int]: +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.path_id] + return [(obj.assets_file, obj.object_reader.path_id)] -def exporShader(obj: Shader, fp: str, extension=".txt") -> List[int]: +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.path_id] + return [(obj.assets_file, obj.object_reader.path_id)] def exportMonoBehaviour( - obj: Union[MonoBehaviour, Object], fp: str, extension: str = "" -) -> List[int]: - # TODO - add generic way to add external typetrees - if obj.serialized_type and obj.serialized_type.nodes: + 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(obj.read_typetree(), indent=4, ensure_ascii=False).encode( - "utf8", errors="surrogateescape" - ) - elif isinstance(obj, MonoBehaviour): - # no set typetree - # check if we have a script - script = obj.m_Script - if script: - # looks like we have a script - script = script.read() - # check if there is a locally stored typetree for it - nodes = MONOBEHAVIOUR_TYPETREES.get(script.m_AssemblyName, {}).get( - script.m_ClassName, None - ) - if nodes: - # we have a typetree - # adjust the name - # name = ( - # f"{script.m_ClassName}-{obj.name}" - # if obj.name - # else script.m_ClassName - # ) - extension = ".json" - export = json.dumps( - obj.read_typetree(nodes), indent=4, ensure_ascii=False - ).encode("utf8", errors="surrogateescape") - if not export: + export = json.dumps(export, indent=4, ensure_ascii=False).encode("utf8", errors="surrogateescape") + except Exception: extension = ".bin" - export = obj.raw_data + export = reader.get_raw_data() with open(f"{fp}{extension}", "wb") as f: f.write(export) - return [obj.path_id] + return [(obj.assets_file, obj.path_id)] -def exportAudioClip(obj: AudioClip, fp: str, extension: str = "") -> List[int]: - samples = obj.samples +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: @@ -236,31 +244,49 @@ def exportAudioClip(obj: AudioClip, fp: str, extension: str = "") -> List[int]: 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] + return [(obj.assets_file, obj.path_id)] -def exportSprite(obj: Sprite, fp: str, extension: str = ".png") -> List[int]: - if not extension: - extension = ".png" - obj.image.save(f"{fp}{extension}") - return [ - obj.path_id, - obj.m_RD.texture.path_id, - getattr(obj.m_RD.alphaTexture, "path_id", None), +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: Texture2D, fp: str, extension: str = ".png") -> List[int]: +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.path_id] + return [(obj.assets_file, obj.object_reader.path_id)] -def exportGameObject(obj: GameObject, fp: str, extension: str = "") -> List[int]: - exported = [obj.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) @@ -268,7 +294,7 @@ def exportGameObject(obj: GameObject, fp: str, extension: str = "") -> List[int] # 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_id in exported or ref_id.type == ClassIDType.GameObject: + if (ref.assets_file, ref_id) in exported or ref.type == ClassIDType.GameObject: continue try: exported.extend(export_obj(ref, fp, True, True)) @@ -287,52 +313,56 @@ def exportGameObject(obj: GameObject, fp: str, extension: str = "") -> List[int] ClassIDType.Font: exportFont, ClassIDType.Mesh: exportMesh, ClassIDType.MonoBehaviour: exportMonoBehaviour, - ClassIDType.Shader: exporShader, + ClassIDType.Shader: exportShader, ClassIDType.TextAsset: exportTextAsset, ClassIDType.Texture2D: exportTexture2D, } -MONOBEHAVIOUR_TYPETREES: Dict["Assembly-Name.dll", Dict["Class-Name", List[Dict]]] = {} +ASSEMBLY_NAME_DLL = str +CLASS_NAME = str + +MONOBEHAVIOUR_TYPETREES: Dict[ASSEMBLY_NAME_DLL, Dict[CLASS_NAME, List[Dict]]] = {} -def crawl_obj(obj: Object, ret: dict = None) -> Dict[int, Union[Object, PPtr]]: - """Crawls through the data struture of the object and returns a list of all the components.""" +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.path_id == 0 and obj.file_id == 0 and obj.index == -2: + if obj.m_PathID == 0 and obj.m_FileID == 0 and obj.m_Index == -2: return ret try: - obj = obj.read() + 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.path_id] = obj - # MonoBehaviour really on their typetree - # while Object denotes that the class of the object isn't implemented yet - if isinstance(obj, (MonoBehaviour, Object)): - obj.read_typetree() - data = obj.type_tree.__dict__.values() - else: - data = obj.__dict__.values() + ret[obj.m_PathID] = obj - for value in flatten(data): + for value in flatten(values): if isinstance(value, (Object, PPtr)): - if value.path_id in ret: + if value.m_PathID in ret: continue crawl_obj(value, ret) return ret -def flatten(l): - for el in list(l): - if isinstance(el, (list, tuple)): - yield from flatten(el) - elif isinstance(el, dict): - yield from flatten(el.values()) +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 el + yield elem diff --git a/UnityPy/tools/libil2cpp_helper/__init__.py b/UnityPy/tools/libil2cpp_helper/__init__.py deleted file mode 100644 index 3d4cb9311..000000000 --- a/UnityPy/tools/libil2cpp_helper/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .metadata import Metadata \ No newline at end of file diff --git a/UnityPy/tools/libil2cpp_helper/helper.py b/UnityPy/tools/libil2cpp_helper/helper.py deleted file mode 100644 index be3aa96e7..000000000 --- a/UnityPy/tools/libil2cpp_helper/helper.py +++ /dev/null @@ -1,111 +0,0 @@ -from enum import Enum -from io import BytesIO -from struct import pack, unpack -from typing import List, Iterator, Tuple, Union, BinaryIO, get_origin, get_args - -# save original int class -_int = int - - -class CustomIntWrapper(int): - __size: int - __format: str - - @classmethod - def read_from(cls, f: BytesIO): - return cls( - unpack("<" + getattr(cls, "__format"), f.read(getattr(cls, "__size")))[0] - ) - - -def CustomIntWrapperFactory(name: str, __size: int, __format: str) -> CustomIntWrapper: - return type(name, (CustomIntWrapper,), {"__size": __size, "__format": __format}) - - -byte = CustomIntWrapperFactory("byte", 1, "B") -short = CustomIntWrapperFactory("short", 2, "h") -ushort = CustomIntWrapperFactory("ushort", 2, "H") -int = CustomIntWrapperFactory("int", 4, "i") -uint = CustomIntWrapperFactory("uint", 4, "I") -long = CustomIntWrapperFactory("long", 8, "q") -ulong = CustomIntWrapperFactory("ulong", 8, "Q") - - -class Version: - Min: float - Max: float - - def __new__(cls, Min: float = 0, Max: float = 99): - spec = [] - if Min: - spec.append(f"Min={Min}") - if Max != 99: - spec.append(f"Max={Max}") - newclass = type( - f"Version ({', '.join(spec)})", (Version,), {"Min": Min, "Max": Max} - ) - return newclass - - @classmethod - def check_compatiblity(cls, version): - return cls.Min <= version <= cls.Max - - -class MetaDataClass: - version: float - size: int - parseString: str - - def __init__(self, reader: BinaryIO = None) -> None: - if not (self.version): - raise NotImplementedError( - "Using an unversioned MetaDataClass isn't possible." - ) - if reader: - self.read_from(reader) - - def read_from(self, reader: BytesIO): - self.__dict__.update( - zip( - self.__annotations__.keys(), - unpack(self.parseString, reader.read(self.size)), - ) - ) - - def write_to(self, writer: BytesIO): - writer.write( - pack( - "<" + self.parseString, - (self.get(key) for key in self.__annotations__.keys()), - ) - ) - - @classmethod - def generate_versioned_subclass(cls, version: float): - # fetch fields & calculate size - compatible_fields = {} - size = 0 - parseString = [] - for key, clz in cls.__annotations__.items(): - if get_origin(clz) == Union: - clz, *version_checks = get_args(clz) - if not any( - version_check.check_compatiblity(version) - for version_check in version_checks - ): - continue - compatible_fields[key] = clz - size += getattr(clz, "__size") - parseString.append(getattr(clz, "__format")) - - newclass = type( - f"{cls.__name__} - V{version:.1f}", - (MetaDataClass,), - { - "__annotations__": compatible_fields, - "size": size, - "version": version, - "parseString": "".join(parseString), - }, - ) - return newclass diff --git a/UnityPy/tools/libil2cpp_helper/il2cpp_class.py b/UnityPy/tools/libil2cpp_helper/il2cpp_class.py deleted file mode 100644 index 64e30063f..000000000 --- a/UnityPy/tools/libil2cpp_helper/il2cpp_class.py +++ /dev/null @@ -1,230 +0,0 @@ -from .helper import * - - -class Il2CppCodeRegistration(MetaDataClass): - methodPointersCount: Union[long, Version(Max=24.1)] - methodPointers: Union[ulong, Version(Max=24.1)] - delegateWrappersFromNativeToManagedCount: Union[ulong, Version(Max=21)] - delegateWrappersFromNativeToManaged: Union[ - ulong, Version(Max=21) - ] # note the double indirection to handle different calling conventions - reversePInvokeWrapperCount: Union[long, Version(Min=22)] - reversePInvokeWrappers: Union[ulong, Version(Min=22)] - delegateWrappersFromManagedToNativeCount: Union[ulong, Version(Max=22)] - delegateWrappersFromManagedToNative: Union[ulong, Version(Max=22)] - marshalingFunctionsCount: Union[ulong, Version(Max=22)] - marshalingFunctions: Union[ulong, Version(Max=22)] - ccwMarshalingFunctionsCount: Union[ulong, Version(Min=21, Max=22)] - ccwMarshalingFunctions: Union[ulong, Version(Min=21, Max=22)] - genericMethodPointersCount: long - genericMethodPointers: ulong - genericAdjustorThunks: Union[ulong, Version(Min=24.4, Max=24.4), Version(Min=27.1)] - invokerPointersCount: long - invokerPointers: ulong - customAttributeCount: Union[long, Version(Max=24.4)] - customAttributeGenerators: Union[ulong, Version(Max=24.4)] - guidCount: Union[long, Version(Min=21, Max=22)] - guids: Union[ulong, Version(Min=21, Max=22)] # Il2CppGuid - unresolvedVirtualCallCount: Union[long, Version(Min=22)] - unresolvedVirtualCallPointers: Union[ulong, Version(Min=22)] - interopDataCount: Union[ulong, Version(Min=23)] - interopData: Union[ulong, Version(Min=23)] - windowsRuntimeFactoryCount: Union[ulong, Version(Min=24.3)] - windowsRuntimeFactoryTable: Union[ulong, Version(Min=24.3)] - codeGenModulesCount: Union[long, Version(Min=24.2)] - codeGenModules: Union[ulong, Version(Min=24.2)] - - -class Il2CppMetadataRegistration(MetaDataClass): - genericClassesCount: long - genericClasses: ulong - genericInstsCount: long - genericInsts: ulong - genericMethodTableCount: long - genericMethodTable: ulong - typesCount: long - types: ulong - methodSpecsCount: long - methodSpecs: ulong - methodReferencesCount: Union[long, Version(Max=16)] - methodReferences: Union[ulong, Version(Max=16)] - - fieldOffsetsCount: long - fieldOffsets: ulong - - typeDefinitionsSizesCount: long - typeDefinitionsSizes: ulong - metadataUsagesCount: Union[ulong, Version(Min=19)] - metadataUsages: Union[ulong, Version(Min=19)] - - -class Il2CppTypeEnum(uint, Enum): - IL2CPP_TYPE_END = 0x00 # End of List - IL2CPP_TYPE_VOID = 0x01 - IL2CPP_TYPE_BOOLEAN = 0x02 - IL2CPP_TYPE_CHAR = 0x03 - IL2CPP_TYPE_I1 = 0x04 - IL2CPP_TYPE_U1 = 0x05 - IL2CPP_TYPE_I2 = 0x06 - IL2CPP_TYPE_U2 = 0x07 - IL2CPP_TYPE_I4 = 0x08 - IL2CPP_TYPE_U4 = 0x09 - IL2CPP_TYPE_I8 = 0x0A - IL2CPP_TYPE_U8 = 0x0B - IL2CPP_TYPE_R4 = 0x0C - IL2CPP_TYPE_R8 = 0x0D - IL2CPP_TYPE_STRING = 0x0E - IL2CPP_TYPE_PTR = 0x0F # arg: token - IL2CPP_TYPE_BYREF = 0x10 # arg: token - IL2CPP_TYPE_VALUETYPE = 0x11 # arg: token - IL2CPP_TYPE_CLASS = 0x12 # arg: token - IL2CPP_TYPE_VAR = 0x13 # Generic parameter in a generic type definition, represented as number (compressed unsigned integer) number - IL2CPP_TYPE_ARRAY = 0x14 # type, rank, boundsCount, bound1, loCount, lo1 - IL2CPP_TYPE_GENERICINST = 0x15 # \x{2026} - IL2CPP_TYPE_TYPEDBYREF = 0x16 - IL2CPP_TYPE_I = 0x18 - IL2CPP_TYPE_U = 0x19 - IL2CPP_TYPE_FNPTR = 0x1B # arg: full method signature - IL2CPP_TYPE_OBJECT = 0x1C - IL2CPP_TYPE_SZARRAY = 0x1D # 0-based one-dim-array - IL2CPP_TYPE_MVAR = 0x1E # Generic parameter in a generic method definition, represented as number (compressed unsigned integer) - IL2CPP_TYPE_CMOD_REQD = 0x1F # arg: typedef or typeref token - IL2CPP_TYPE_CMOD_OPT = 0x20 # optional arg: typedef or typref token - IL2CPP_TYPE_INTERNAL = 0x21 # CLR internal type - - IL2CPP_TYPE_MODIFIER = 0x40 # Or with the following types - IL2CPP_TYPE_SENTINEL = 0x41 # Sentinel for varargs method signature - IL2CPP_TYPE_PINNED = 0x45 # Local var that points to pinned object - - IL2CPP_TYPE_ENUM = 0x55 # an enumeration - - -# class Il2CppType(MetaDataClass): -# datapoint: ulong -# bits: uint -# data { get: Union set; } -# attrs { get: uint set; } -# type { get: Il2CppTypeEnum set; } -# num_mods { get: uint set; } -# byref { get: uint set; } -# pinned { get: uint set; } - -# public void Init() -# { -# attrs = bits & 0xffff; -# type = (Il2CppTypeEnum)((bits >> 16) & 0xff); -# num_mods = (bits >> 24) & 0x3f; -# byref = (bits >> 30) & 1; -# pinned = bits >> 31; -# data = new Union { dummy = datapoint }; -# } - -# public class Union -# { -# dummy: ulong -# #/ -# #/ for VALUETYPE and CLASS -# #/ -# klassIndex => (long)dummy: long -# #/ -# #/ for VALUETYPE and CLASS at runtime -# #/ -# typeHandle => dummy: ulong -# #/ -# #/ for PTR and SZARRAY -# #/ -# type => dummy: ulong -# #/ -# #/ for ARRAY -# #/ -# array => dummy: ulong -# #/ -# #/ for VAR and MVAR -# #/ -# genericParameterIndex => (long)dummy: long -# #/ -# #/ for VAR and MVAR at runtime -# #/ -# genericParameterHandle => dummy: ulong -# #/ -# #/ for GENERICINST -# #/ -# generic_class => dummy: ulong -# } -# } - - -class Il2CppGenericContext(MetaDataClass): - # The instantiation corresponding to the class generic parameters - class_inst: ulong - # The instantiation corresponding to the method generic parameters - method_inst: ulong - - -class Il2CppGenericClass(MetaDataClass): - typeDefinitionIndex: Union[long, Version(Max=24.4)] # the generic type definition - type: Union[ulong, Version(Min=27)] # the generic type definition - context: Il2CppGenericContext # a context that contains the type instantiation doesn't contain any method instantiation - cached_class: ulong # if present, the Il2CppClass corresponding to the instantiation. - - -class Il2CppGenericInst(MetaDataClass): - type_argc: long - type_argv: ulong - - -class Il2CppArrayType(MetaDataClass): - etype: ulong - rank: byte - numsizes: byte - numlobounds: byte - sizes: ulong - lobounds: ulong - - -class Il2CppGenericMethodIndices(MetaDataClass): - methodIndex: int - invokerIndex: int - adjustorThunk: Union[int, Version(Min=24.4, Max=24.4), Version(Min=27.1)] - - -class Il2CppGenericMethodFunctionsDefinitions(MetaDataClass): - genericMethodIndex: int - indices: Il2CppGenericMethodIndices - - -class Il2CppMethodSpec(MetaDataClass): - methodDefinitionIndex: int - classIndexIndex: int - methodIndexIndex: int - - -class Il2CppCodeGenModule(MetaDataClass): - moduleName: ulong - methodPointerCount: long - methodPointers: ulong - adjustorThunkCount: Union[long, Version(Min=24.4, Max=24.4), Version(Min=27.1)] - adjustorThunks: Union[ulong, Version(Min=24.4, Max=24.4), Version(Min=27.1)] - invokerIndices: ulong - reversePInvokeWrapperCount: ulong - reversePInvokeWrapperIndices: ulong - rgctxRangesCount: long - rgctxRanges: ulong - rgctxsCount: long - rgctxs: ulong - debuggerMetadata: ulong - customAttributeCacheGenerator: Union[ulong, Version(Min=27)] - moduleInitializer: Union[ulong, Version(Min=27)] - staticConstructorTypeIndices: Union[ulong, Version(Min=27)] - metadataRegistration: Union[ulong, Version(Min=27)] # Per-assembly mode only - codeRegistaration: Union[ulong, Version(Min=27)] # Per-assembly mode only - - -class Il2CppRange(MetaDataClass): - start: int - length: int - - -class Il2CppTokenRangePair(MetaDataClass): - token: uint - range: Il2CppRange diff --git a/UnityPy/tools/libil2cpp_helper/metadata.py b/UnityPy/tools/libil2cpp_helper/metadata.py deleted file mode 100644 index e9d5e54e9..000000000 --- a/UnityPy/tools/libil2cpp_helper/metadata.py +++ /dev/null @@ -1,297 +0,0 @@ -from .metadata_class import * -from typing import List, Dict -from collections import OrderedDict - - -class Metadata: - header: Il2CppGlobalMetadataHeader - imageDefs: List[Il2CppImageDefinition] - typeDefs: List[Il2CppTypeDefinition] - methodDefs: List[Il2CppMethodDefinition] - parameterDefs: List[Il2CppParameterDefinition] - fieldDefs: List[Il2CppFieldDefinition] - fieldDefaultValuesDic: Dict[int, Il2CppFieldDefaultValue] - parameterDefaultValuesDic: Dict[int, Il2CppParameterDefaultValue] - propertyDefs: List[Il2CppPropertyDefinition] - attributeTypeRanges: List[Il2CppCustomAttributeTypeRange] - attributeTypeRangesDic: Dict[Il2CppImageDefinition, Dict[uint, int]] - stringLiterals: List[Il2CppStringLiteral] - metadataUsageLists: List[Il2CppMetadataUsageList] - metadataUsagePairs: List[Il2CppMetadataUsagePair] - attributeTypes: List[int] - interfaceIndices: List[int] - metadataUsageDic: Dict[uint, OrderedDict[uint, uint]] - maxMetadataUsages: int # long - nestedTypeIndices: List[int] - eventDefs: List[Il2CppEventDefinition] - genericContainers: List[Il2CppGenericContainer] - fieldRefs: List[Il2CppFieldRef] - genericParameters: List[Il2CppGenericParameter] - constraintIndices: List[int] - vtableMethods: List[uint] - rgctxEntries: List[Il2CppRGCTXDefinition] - - stringCache: Dict[uint, str] - Address: int # ulong - - version: int - Version: float - - def __init__(self, stream: BytesIO): - self.reader = stream - self.stringCache = {} - - sanity = uint.read_from(stream) - if sanity != 0xFAB11BAF: - raise ValueError( - "ERROR: Metadata file supplied is not valid metadata file." - ) - self.version = version = int.read_from(stream) - if version < 16 or version > 27: - raise NotImplementedError( - f"ERROR: Metadata file supplied is not a supported version[{version}]." - ) - - self.Version = Version = float(version) - self.header = header = self.ReadClass(Il2CppGlobalMetadataHeader, 0) - if version == 24: - if header.stringLiteralOffset == 264: - self.Version = Version = 24.2 - self.header = header = self.ReadClass(Il2CppGlobalMetadataHeader, 0) - else: - self.imageDefs = imageDefs = self.ReadMetadataClassArray( - Il2CppImageDefinition, header.imagesOffset, header.imagesCount - ) - if any(x.token != 1 for x in imageDefs): - self.Version = Version = 24.1 - - self.imageDefs = imageDefs = self.ReadMetadataClassArray( - Il2CppImageDefinition, header.imagesOffset, header.imagesCount - ) - self.typeDefs = self.ReadMetadataClassArray( - Il2CppTypeDefinition, - header.typeDefinitionsOffset, - header.typeDefinitionsCount, - ) - self.methodDefs = self.ReadMetadataClassArray( - Il2CppMethodDefinition, header.methodsOffset, header.methodsCount - ) - self.parameterDefs = self.ReadMetadataClassArray( - Il2CppParameterDefinition, header.parametersOffset, header.parametersCount - ) - self.fieldDefs = self.ReadMetadataClassArray( - Il2CppFieldDefinition, header.fieldsOffset, header.fieldsCount - ) - self.fieldDefaultValues = self.ReadMetadataClassArray( - Il2CppFieldDefaultValue, - header.fieldDefaultValuesOffset, - header.fieldDefaultValuesCount, - ) - self.parameterDefaultValues = self.ReadMetadataClassArray( - Il2CppParameterDefaultValue, - header.parameterDefaultValuesOffset, - header.parameterDefaultValuesCount, - ) - self.fieldDefaultValuesDic = {x.fieldIndex: x for x in self.fieldDefaultValues} - self.parameterDefaultValuesDic = { - x.parameterIndex: x for x in self.parameterDefaultValues - } - self.propertyDefs = self.ReadMetadataClassArray( - Il2CppPropertyDefinition, header.propertiesOffset, header.propertiesCount - ) - self.interfaceIndices = self.ReadClassArray( - int, header.interfacesOffset, header.interfacesCount // 4 - ) - self.nestedTypeIndices = self.ReadClassArray( - int, header.nestedTypesOffset, header.nestedTypesCount // 4 - ) - self.eventDefs = self.ReadMetadataClassArray( - Il2CppEventDefinition, header.eventsOffset, header.eventsCount - ) - self.genericContainers = self.ReadMetadataClassArray( - Il2CppGenericContainer, - header.genericContainersOffset, - header.genericContainersCount, - ) - self.genericParameters = self.ReadMetadataClassArray( - Il2CppGenericParameter, - header.genericParametersOffset, - header.genericParametersCount, - ) - self.constraintIndices = self.ReadClassArray( - int, - header.genericParameterConstraintsOffset, - header.genericParameterConstraintsCount // 4, - ) - self.vtableMethods = self.ReadClassArray( - uint, header.vtableMethodsOffset, header.vtableMethodsCount // 4 - ) - if 16 < Version < 27: # TODO - self.stringLiterals = self.ReadMetadataClassArray( - Il2CppStringLiteral, - header.stringLiteralOffset, - header.stringLiteralCount, - ) - self.metadataUsageLists = self.ReadMetadataClassArray( - Il2CppMetadataUsageList, - header.metadataUsageListsOffset, - header.metadataUsageListsCount, - ) - self.metadataUsagePairs = self.ReadMetadataClassArray( - Il2CppMetadataUsagePair, - header.metadataUsagePairsOffset, - header.metadataUsagePairsCount, - ) - - self.ProcessingMetadataUsage() - - self.fieldRefs = self.ReadMetadataClassArray( - Il2CppFieldRef, header.fieldRefsOffset, header.fieldRefsCount - ) - - if Version > 20: - self.attributeTypeRanges = self.ReadMetadataClassArray( - Il2CppCustomAttributeTypeRange, - header.attributesInfoOffset, - header.attributesInfoCount, - ) - self.attributeTypes = self.ReadClassArray( - int, header.attributeTypesOffset, header.attributeTypesCount // 4 - ) - - if Version >= 24.1: - self.attributeTypeRangesDic = attributeTypeRangesDic = {} - for imageDef in imageDefs: - dic = {} - attributeTypeRangesDic[imageDef] = dic - end = imageDef.customAttributeStart + imageDef.customAttributeCount - for i in range(imageDef.customAttributeStart, end): - dic[self.attributeTypeRanges[i].token] = i - - if Version <= 24.1: - self.rgctxEntries = self.ReadMetadataClassArray( - Il2CppRGCTXDefinition, - header.rgctxEntriesOffset, - header.rgctxEntriesCount, - ) - - def ReadClass(self, clz: MetaDataClass, addr: int) -> List[MetaDataClass]: - self.reader.seek(addr) - return clz.generate_versioned_subclass(self.Version)(self.reader) - - def ReadClassArray( - self, - clz: Union[MetaDataClass, CustomIntWrapper, Il2CppRGCTXDefinition], - addr: int, - count: int, - ) -> List[MetaDataClass]: - self.reader.seek(addr) - if issubclass(clz, (MetaDataClass, Il2CppRGCTXDefinition)): - return [clz(self.reader) for _ in range(count)] - elif issubclass(clz, CustomIntWrapper): - return [clz.read_from(self.reader) for _ in range(count)] - elif clz == Il2CppRGCTXDefinition: - return [clz.read_from(self.reader) for _ in range(count)] - else: - raise ValueError("Invalid clz type") - - def ReadMetadataClassArray( - self, clz: MetaDataClass, addr: int, count: int - ) -> List[MetaDataClass]: - clz = clz.generate_versioned_subclass(self.Version) - return self.ReadClassArray(clz, addr, count // clz.size) - - def GetFieldDefaultValueFromIndex(self, index: int) -> Il2CppFieldDefaultValue: - return self.fieldDefaultValuesDic.get(index, None) - - def GetParameterDefaultValueFromIndex( - self, index: int - ) -> Il2CppParameterDefaultValue: - return self.parameterDefaultValuesDic.get(index, None) - - def GetDefaultValueFromIndex(self, index: int) -> int: - return self.header.fieldAndParameterDefaultValueDataOffset + index - - def GetStringFromIndex(self, index: int) -> str: - result = self.stringCache.get(index, None) - if result == None: - result = self.ReadStringToNull(self.header.stringOffset + index) - self.stringCache[index] = result - return result - - # public int GetCustomAttributeIndex(Il2CppImageDefinition imageDef, int customAttributeIndex, uint token) - # { - # if (Version > 24) - # { - # if (attributeTypeRangesDic[imageDef].TryGetValue(token, out var index)) - # { - # return index - # } - # else - # { - # return -1 - # } - # } - # else - # { - # return customAttributeIndex - # } - # } - - def GetStringLiteralFromIndex(self, index: int) -> str: - stringLiteral = self.stringLiterals[index] - self.reader.seek(self.header.stringLiteralDataOffset + stringLiteral.dataIndex) - return self.reader.read(stringLiteral.Length).encode("utf8") - - def GetStringLiterals(self): - return [self.GetDecodedMethodIndex(i) for i in range(len(self.stringLiterals))] - - def ProcessingMetadataUsage(self): - self.metadataUsageDic = metadataUsageDic = { - i: OrderedDict() for i in range(1, 7) - } - for metadataUsageList in self.metadataUsageLists: - for i in range(metadataUsageList.count): - offset = metadataUsageList.start + i - metadataUsagePair = self.metadataUsagePairs[offset] - usage = self.GetEncodedIndexType(metadataUsagePair.encodedSourceIndex) - decodedIndex = self.GetDecodedMethodIndex( - metadataUsagePair.encodedSourceIndex - ) - metadataUsageDic[usage][ - metadataUsagePair.destinationIndex - ] = decodedIndex - - self.maxMetadataUsages = ( - max(y for x in metadataUsageDic.values() for y in x.keys()) + 1 - ) - - def GetEncodedIndexType(self, index: int) -> int: - return (index & 0xE0000000) >> 29 - - def GetDecodedMethodIndex(self, index: int): - if self.Version >= 27: - return (index & 0x1FFFFFFE) >> 1 - return index & 0x1FFFFFFF - - def SizeOf(typ) -> int: - return typ.size - - def ReadString(self, numChars: int) -> str: - start = self.reader.tell() - # UTF8 takes up to 4 bytes per character - string = self.reader.read(numChars * 4).encode("utf8")[:numChars] - # make our position what it would have been if we'd known the exact number of bytes needed. - self.reader.seek(start) - self.reader.read(len(string.encode("utf8"))) - return string - - def ReadStringToNull(self, offset: int) -> str: - read_one = lambda: self.reader.read(1) - start = self.reader.seek(offset) - c = b"\x00" - ret = [] - while c == b"\x00": - c = read_one() - ret.append(c) - return b"".join(ret).encode("utf8") diff --git a/UnityPy/tools/libil2cpp_helper/metadata_class.py b/UnityPy/tools/libil2cpp_helper/metadata_class.py deleted file mode 100644 index bba2c4b5a..000000000 --- a/UnityPy/tools/libil2cpp_helper/metadata_class.py +++ /dev/null @@ -1,325 +0,0 @@ -from .helper import * - - -class Il2CppGlobalMetadataHeader(MetaDataClass): - sanity: uint - version: int - stringLiteralOffset: uint # string data for managed code - stringLiteralCount: int - stringLiteralDataOffset: uint - stringLiteralDataCount: int - stringOffset: uint # string data for metadata - stringCount: int - eventsOffset: uint # Il2CppEventDefinition - eventsCount: int - propertiesOffset: uint # Il2CppPropertyDefinition - propertiesCount: int - methodsOffset: uint # Il2CppMethodDefinition - methodsCount: int - parameterDefaultValuesOffset: uint # Il2CppParameterDefaultValue - parameterDefaultValuesCount: int - fieldDefaultValuesOffset: uint # Il2CppFieldDefaultValue - fieldDefaultValuesCount: int - fieldAndParameterDefaultValueDataOffset: uint # uint8_t - fieldAndParameterDefaultValueDataCount: int - fieldMarshaledSizesOffset: int # Il2CppFieldMarshaledSize - fieldMarshaledSizesCount: int - parametersOffset: uint # Il2CppParameterDefinition - parametersCount: int - fieldsOffset: uint # Il2CppFieldDefinition - fieldsCount: int - genericParametersOffset: uint # Il2CppGenericParameter - genericParametersCount: int - genericParameterConstraintsOffset: uint # TypeIndex - genericParameterConstraintsCount: int - genericContainersOffset: uint # Il2CppGenericContainer - genericContainersCount: int - nestedTypesOffset: uint # TypeDefinitionIndex - nestedTypesCount: int - interfacesOffset: uint # TypeIndex - interfacesCount: int - vtableMethodsOffset: uint # EncodedMethodIndex - vtableMethodsCount: int - interfaceOffsetsOffset: int # Il2CppInterfaceOffsetPair - interfaceOffsetsCount: int - typeDefinitionsOffset: uint # Il2CppTypeDefinition - typeDefinitionsCount: int - rgctxEntriesOffset: Union[uint, Version(Max=24.1)] # Il2CppRGCTXDefinition - rgctxEntriesCount: Union[int, Version(Max=24.1)] - imagesOffset: uint # Il2CppImageDefinition - imagesCount: int - assembliesOffset: int # Il2CppAssemblyDefinition - assembliesCount: int - metadataUsageListsOffset: Union[ - uint, Version(Min=19, Max=24.4) - ] # Il2CppMetadataUsageList - metadataUsageListsCount: Union[int, Version(Min=19, Max=24.4)] - metadataUsagePairsOffset: Union[ - uint, Version(Min=19, Max=24.4) - ] # Il2CppMetadataUsagePair - metadataUsagePairsCount: Union[int, Version(Min=19, Max=24.4)] - fieldRefsOffset: Union[uint, Version(Min=19)] # Il2CppFieldRef - fieldRefsCount: Union[int, Version(Min=19)] - referencedAssembliesOffset: Union[int, Version(Min=20)] # int32_t - referencedAssembliesCount: Union[int, Version(Min=20)] - attributesInfoOffset: Union[uint, Version(Min=21)] # Il2CppCustomAttributeTypeRange - attributesInfoCount: Union[int, Version(Min=21)] - attributeTypesOffset: Union[uint, Version(Min=21)] # TypeIndex - attributeTypesCount: Union[int, Version(Min=21)] - unresolvedVirtualCallParameterTypesOffset: Union[int, Version(Min=22)] # TypeIndex - unresolvedVirtualCallParameterTypesCount: Union[int, Version(Min=22)] - unresolvedVirtualCallParameterRangesOffset: Union[ - int, Version(Min=22) - ] # Il2CppRange - unresolvedVirtualCallParameterRangesCount: Union[int, Version(Min=22)] - windowsRuntimeTypeNamesOffset: Union[ - int, Version(Min=23) - ] # Il2CppWindowsRuntimeTypeNamePair - windowsRuntimeTypeNamesSize: Union[int, Version(Min=23)] - windowsRuntimeStringsOffset: Union[int, Version(Min=27)] # const char* - windowsRuntimeStringsSize: Union[int, Version(Min=27)] - exportedTypeDefinitionsOffset: Union[int, Version(Min=24)] # TypeDefinitionIndex - exportedTypeDefinitionsCount: Union[int, Version(Min=24)] - - -class Il2CppImageDefinition(MetaDataClass): - nameIndex: uint - assemblyIndex: int - - typeStart: int - typeCount: uint - - exportedTypeStart: Union[int, Version(Min=24)] - exportedTypeCount: Union[uint, Version(Min=24)] - - entryPointIndex: int - token: Union[uint, Version(Min=19)] - - customAttributeStart: Union[int, Version(Min=24.1)] - customAttributeCount: Union[uint, Version(Min=24.1)] - - -class Il2CppTypeDefinition(MetaDataClass): - nameIndex: uint - namespaceIndex: uint - customAttributeIndex: Union[int, Version(Max=24)] - byvalTypeIndex: int - byrefTypeIndex: Union[int, Version(Max=24.4)] - - declaringTypeIndex: int - parentIndex: int - elementTypeIndex: int # we can probably remove this one. Only used for enums - - rgctxStartIndex: Union[int, Version(Max=24.1)] - rgctxCount: Union[int, Version(Max=24.1)] - - genericContainerIndex: int - - delegateWrapperFromManagedToNativeIndex: Union[int, Version(Max=22)] - marshalingFunctionsIndex: Union[int, Version(Max=22)] - ccwFunctionIndex: Union[int, Version(Min=21, Max=22)] - guidIndex: Union[int, Version(Min=21, Max=22)] - - flags: uint - - fieldStart: int - methodStart: int - eventStart: int - propertyStart: int - nestedTypesStart: int - interfacesStart: int - vtableStart: int - interfaceOffsetsStart: int - - method_count: ushort - property_count: ushort - field_count: ushort - event_count: ushort - nested_type_count: ushort - vtable_count: ushort - interfaces_count: ushort - interface_offsets_count: ushort - - # bitfield to portably encode boolean values as single bits - # 01 - valuetype; - # 02 - enumtype; - # 03 - has_finalize; - # 04 - has_cctor; - # 05 - is_blittable; - # 06 - is_import_or_windows_runtime; - # 07-10 - One of nine possible PackingSize values (0, 1, 2, 4, 8, 16, 32, 64, or 128) - # 11 - PackingSize is default - # 12 - ClassSize is default - # 13-16 - One of nine possible PackingSize values (0, 1, 2, 4, 8, 16, 32, 64, or 128) - the specified packing size (even for explicit layouts) - bitfield: uint - token: Union[uint, Version(Min=19)] - - @property - def IsValueType(self) -> bool: - return (self.bitfield & 0x1) == 1 - - @property - def IsEnum(self) -> bool: - return ((self.bitfield >> 1) & 0x1) == 1 - - -class Il2CppMethodDefinition(MetaDataClass): - nameIndex: uint - declaringType: int - returnType: int - parameterStart: int - customAttributeIndex: Union[int, Version(Max=24)] - genericContainerIndex: int - methodIndex: Union[int, Version(Max=24.1)] - invokerIndex: Union[int, Version(Max=24.1)] - delegateWrapperIndex: Union[int, Version(Max=24.1)] - rgctxStartIndex: Union[int, Version(Max=24.1)] - rgctxCount: Union[int, Version(Max=24.1)] - token: uint - flags: ushort - iflags: ushort - slot: ushort - parameterCount: ushort - - -class Il2CppParameterDefinition(MetaDataClass): - nameIndex: uint - token: uint - customAttributeIndex: Union[int, Version(Max=24)] - typeIndex: int - - -class Il2CppFieldDefinition(MetaDataClass): - nameIndex: uint - typeIndex: int - customAttributeIndex: Union[int, Version(Max=24)] - token: Union[uint, Version(Min=19)] - - -class Il2CppFieldDefaultValue(MetaDataClass): - fieldIndex: int - typeIndex: int - dataIndex: int - - -class Il2CppPropertyDefinition(MetaDataClass): - nameIndex: uint - get: int - set: int - attrs: uint - customAttributeIndex: Union[int, Version(Max=24)] - token: Union[uint, Version(Min=19)] - - -class Il2CppCustomAttributeTypeRange(MetaDataClass): - token: Union[uint, Version(Min=24.1)] - start: int - count: int - - -class Il2CppMetadataUsageList(MetaDataClass): - start: uint - count: uint - - -class Il2CppMetadataUsagePair(MetaDataClass): - destinationIndex: uint - encodedSourceIndex: uint - - -class Il2CppStringLiteral(MetaDataClass): - length: uint - dataIndex: int - - -class Il2CppParameterDefaultValue(MetaDataClass): - parameterIndex: int - typeIndex: int - dataIndex: int - - -class Il2CppEventDefinition(MetaDataClass): - nameIndex: uint - typeIndex: int - add: int - remove: int - rais: int - customAttributeIndex: Union[int, Version(Max=24)] - token: Union[uint, Version(Min=19)] - - -class Il2CppGenericContainer(MetaDataClass): - # index of the generic type definition or the generic method definition corresponding to this container - ownerIndex: int # either index into Il2CppClass metadata array or Il2CppMethodDefinition array - type_argc: int - # If true, we're a generic method, otherwise a generic type definition. - is_method: int - # Our type parameters. - genericParameterStart: int - - -class Il2CppFieldRef(MetaDataClass): - typeIndex: int - fieldIndex: int # local offset into type fields - - -class Il2CppGenericParameter(MetaDataClass): - ownerIndex: int # Type or method this parameter was defined in. - nameIndex: uint - constraintsStart: short - constraintsCount: short - num: ushort - flags: ushort - - -class Il2CppRGCTXDataType(uint, Enum): - IL2CPP_RGCTX_DATA_INVALID = 0 - IL2CPP_RGCTX_DATA_TYPE = 1 - IL2CPP_RGCTX_DATA_CLASS = 2 - IL2CPP_RGCTX_DATA_METHOD = 3 - IL2CPP_RGCTX_DATA_ARRAY = 4 - - -class Il2CppRGCTXDefinitionData(MetaDataClass): - rgctxDataDummy: int - - @property - def methodIndex(self) -> int: - return self.rgctxDataDummy - - @property - def typeIndex(self) -> int: - return self.rgctxDataDummy - - -class Il2CppRGCTXDefinition: - type: Il2CppRGCTXDataType - data: Il2CppRGCTXDefinitionData - - version: float - size: int - parseString: str - - def __init__(self, reader: BinaryIO = None) -> None: - if not (self.version): - raise NotImplementedError( - "Using an unversioned MetaDataClass isn't possible." - ) - if reader: - self.read_from(reader) - - def read_from(self, reader: BytesIO): - self.type = Il2CppRGCTXDataType.read_from(reader) - self.data = Il2CppRGCTXDefinitionData.generate_versioned_subclass(self.version)( - reader - ) - - def write_to(self, writer: BytesIO): - self.type = self.data.write_to(writer) - self.data.write_to(writer) - - @classmethod - def generate_versioned_subclass(cls, version: float): - cls.version = version - cls.size = 8 # TODO if data changes - return cls diff --git a/UnityPyBoost/AnimationClip.c b/UnityPyBoost/AnimationClip.c deleted file mode 100644 index 59ef7c984..000000000 --- a/UnityPyBoost/AnimationClip.c +++ /dev/null @@ -1,150 +0,0 @@ -#include "AnimationClip.h" -#include - -#define MIN(x, y) (((x) < (y)) ? (x) : (y)) - -/* AnimationClip.py */ -static float *UnpackFloats(uint32_t m_NumItems, float m_Range, float m_Start, uint8_t *m_Data, char m_BitSize, int itemCountInChunk, int chunkStride, int start, int numChunks) -{ - int bitPos = m_BitSize * start; - int indexPos = bitPos / 8; - bitPos %= 8; - - float scale = 1.0f / m_Range; - if (numChunks == -1) - numChunks = (int)m_NumItems / itemCountInChunk; - - // TODO: might be better to use ulong instead of uint - uint32_t end = chunkStride * numChunks / 4; - // TODO: check if this is correct - float *data = (float *)malloc(sizeof(float) * numChunks * itemCountInChunk); - float *dataStart = data; - for (uint32_t index = 0; index != end; index += chunkStride / 4) - { - for (int i = 0; i < itemCountInChunk; ++i) - { - uint32_t x = 0; - int bits = 0; - while (bits < m_BitSize) - { - x |= (uint32_t)((m_Data[indexPos] >> bitPos) << bits); - int num = MIN(m_BitSize - bits, 8 - bitPos); - bitPos += num; - bits += num; - if (bitPos == 8) - { - indexPos++; - bitPos = 0; - } - } - x &= (uint32_t)(1 << m_BitSize) - 1u; - *data++ = (x / (scale * ((1 << m_BitSize) - 1)) + m_Start); - } - } - return dataStart; -} - -// TODO: check if bitPos, bits, and num can be reduced to chars -static int *UnpackInts(uint32_t m_NumItems, uint8_t *m_Data, char m_BitSize) -{ - int *data = (int *)malloc(m_NumItems * sizeof(int)); - - int indexPos = 0; - int bitPos = 0; - for (uint32_t i = 0; i < m_NumItems; i++) - { - int bits = 0; - data[i] = 0; - while (bits < m_BitSize) - { - data[i] |= (m_Data[indexPos] >> bitPos) << bits; - int num = MIN(m_BitSize - bits, 8 - bitPos); - bitPos += num; - bits += num; - if (bitPos == 8) - { - indexPos++; - bitPos = 0; - } - } - data[i] &= (1 << m_BitSize) - 1; - } - return data; -} - -// TODO: implement unpack quaternions - -PyObject *unpack_floats(PyObject *self, PyObject *args) -{ - // define vars - uint32_t m_NumItems; - float m_Range; - float m_Start; - uint8_t *m_Data; - char m_BitSize; - int itemCountInChunk; - int chunkStride; - int start; - int numChunks; - Py_ssize_t data_size; - - start = 0; - numChunks = -1; - if (!PyArg_ParseTuple(args, "Iffy#bii|ii", &m_NumItems, &m_Range, &m_Start, &m_Data, &data_size, &m_BitSize, &itemCountInChunk, &chunkStride, &start, &numChunks)) - return NULL; - - // decode - float *array = UnpackFloats(m_NumItems, m_Range, m_Start, m_Data, m_BitSize, itemCountInChunk, chunkStride, start, numChunks); - - if (numChunks == -1) - numChunks = (int)m_NumItems / itemCountInChunk; - Py_ssize_t array_len = numChunks * itemCountInChunk; - // return - PyObject *lst = PyList_New(array_len); - if (!lst) - return NULL; - for (Py_ssize_t i = 0; i < array_len; i++) - { - PyObject *num = PyFloat_FromDouble(array[i]); - if (!num) - { - Py_DECREF(lst); - return NULL; - } - PyList_SET_ITEM(lst, i, num); // reference to num stolen - } - free(array); - return lst; -} - -PyObject *unpack_ints(PyObject *self, PyObject *args) -{ - // define vars - uint32_t m_NumItems; - uint8_t *m_Data; - char m_BitSize; - Py_ssize_t data_size; - - if (!PyArg_ParseTuple(args, "Iy#b", &m_NumItems, &m_Data, &data_size, &m_BitSize)) - return NULL; - - // decode - int *array = UnpackInts(m_NumItems, m_Data, m_BitSize); - - // return - PyObject *lst = PyList_New(m_NumItems); - if (!lst) - return NULL; - for (uint32_t i = 0; i < m_NumItems; i++) - { - PyObject *num = PyLong_FromLong(array[i]); - if (!num) - { - Py_DECREF(lst); - return NULL; - } - PyList_SET_ITEM(lst, i, num); // reference to num stolen - } - free(array); - return lst; -} \ No newline at end of file diff --git a/UnityPyBoost/AnimationClip.h b/UnityPyBoost/AnimationClip.h deleted file mode 100644 index f231d55fa..000000000 --- a/UnityPyBoost/AnimationClip.h +++ /dev/null @@ -1,5 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#pragma once -#include -PyObject *unpack_floats(PyObject *self, PyObject *args); -PyObject *unpack_ints(PyObject *self, PyObject *args); \ No newline at end of file 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.c b/UnityPyBoost/Mesh.c deleted file mode 100644 index 0eb618fe6..000000000 --- a/UnityPyBoost/Mesh.c +++ /dev/null @@ -1,197 +0,0 @@ -#include "Mesh.h" -#include -#include - -#define MAX(x, y) (((x) > (y)) ? (x) : (y)) - -enum -{ - kVertexFormatFloat, - kVertexFormatFloat16, - kVertexFormatUNorm8, - kVertexFormatSNorm8, - kVertexFormatUNorm16, - kVertexFormatSNorm16, - kVertexFormatUInt8, - kVertexFormatSInt8, - kVertexFormatUInt16, - kVertexFormatSInt16, - kVertexFormatUInt32, - kVertexFormatSInt32 -}; - -PyObject *unpack_vertexdata(PyObject *self, PyObject *args) -{ - // define vars - int componentByteSize; - uint32_t m_VertexCount; - uint8_t swap; - // char format; - uint8_t *vertexData; // m_VertexData.m_DataSize - uint32_t m_StreamOffset; - uint32_t m_StreamStride; - uint32_t m_ChannelOffset; - uint32_t m_ChannelDimension; - Py_ssize_t vertexDataSize; - - if (!PyArg_ParseTuple(args, "y#iIIIIIb", &vertexData, &vertexDataSize, &componentByteSize, &m_VertexCount, &m_StreamOffset, &m_StreamStride, &m_ChannelOffset, &m_ChannelDimension, &swap)) - return NULL; - - Py_ssize_t componentBytesLength = m_VertexCount * m_ChannelDimension * componentByteSize; - uint8_t *componentBytes = (uint8_t *)PyMem_Malloc(componentBytesLength + 1); - componentBytes[componentBytesLength] = 0; - - // 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 > vertexDataSize) - { - PyErr_SetString(PyExc_ValueError, "Vertex data access out of bounds"); - return NULL; - } - - for (uint32_t v = 0; v < m_VertexCount; v++) - { - uint32_t vertexOffset = m_StreamOffset + m_ChannelOffset + m_StreamStride * v; - for (uint32_t d = 0; d < m_ChannelDimension; d++) - { - uint32_t vertexDataOffset = vertexOffset + componentByteSize * d; - uint32_t componentOffset = componentByteSize * (v * m_ChannelDimension + d); - memcpy(componentBytes + componentOffset, vertexData + vertexDataOffset, componentByteSize); - } - } - - if (swap) // swap bytes - { - if (componentByteSize == 2) - { - uint16_t *componentUints = (uint16_t *)componentBytes; - for (uint32_t i = 0; i < componentBytesLength; i += 2) - { - *componentUints++ = bswap16(*componentUints); - } - } - else if (componentByteSize == 4) - { - - uint32_t *componentUints = (uint32_t *)componentBytes; - for (uint32_t i = 0; i < componentBytesLength; i += 4) - { - *componentUints++ = bswap32(*componentUints); - } - } - } - - PyObject *res = PyByteArray_FromStringAndSize(componentBytes, componentBytesLength); - PyMem_Free(componentBytes); - return res; - - // fast enough in Python - // uint32_t itemCount = componentBytesLength / componentByteSize; - // PyObject *lst = PyList_New(itemCount); - // if (!lst) - // return NULL; - - // switch (format) - // { - // case kVertexFormatFloat: - // { - // float *items = (float *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyFloat_FromDouble((double)*items++)); - // } - // // result[i] = BitConverter.ToSingle(inputBytes, i * 4); - // break; - // } - // case kVertexFormatFloat16: - // { - // uint16_t *items = (uint16_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // double x = _PyFloat_Unpack2(items++, 0); - // if (x == -1.0 && PyErr_Occurred()) - // { - // return NULL; - // } - // PyList_SetItem(lst, i, PyFloat_FromDouble(x)); - // } - // // result[i] = Half.ToHalf(inputBytes, i * 2); - // break; - // } - // case kVertexFormatUNorm8: - // { - // uint8_t *items = componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyFloat_FromDouble((double)(*items++ / 255.0f))); - // } - // // result[i] = inputBytes[i] / 255f; - // break; - // } - // case kVertexFormatSNorm8: - // { - // int8_t *items = (int8_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyFloat_FromDouble((double)MAX((*items++ / 127.0f), -1.0f))); - // } - // // result[i] = Math.Max((sbyte)inputBytes[i] / 127f, -1f); - // break; - // } - // case kVertexFormatUNorm16: - // { - // uint16_t *items = (uint16_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyFloat_FromDouble((double)(*items++ / 65535.0f))); - // } - // // result[i] = BitConverter.ToUInt16(inputBytes, i * 2) / 65535f; - // break; - // } - // case kVertexFormatSNorm16: - // { - // int16_t *items = (int16_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyFloat_FromDouble((double)MAX((*items++ / 32767.0f), -1.0f))); - // } - // // result[i] = Math.Max(BitConverter.ToInt16(inputBytes, i * 2) / 32767f, -1f); - // break; - // } - // case kVertexFormatUInt8: - // case kVertexFormatSInt8: - // { - // uint8_t *items = componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyLong_FromUnsignedLong((uint32_t)*items++)); - // } - // // result[i] = inputBytes[i]; - // break; - // } - // case kVertexFormatUInt16: - // case kVertexFormatSInt16: - // { - // uint16_t *items = (uint16_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyLong_FromUnsignedLong((uint32_t)*items++)); - // } - // // result[i] = BitConverter.ToInt16(inputBytes, i * 2); - // break; - // } - // case kVertexFormatUInt32: - // case kVertexFormatSInt32: - // { - // uint32_t *items = (uint32_t *)componentBytes; - // for (uint32_t i = 0; i < itemCount; i++) - // { - // PyList_SetItem(lst, i, PyLong_FromUnsignedLong(*items++)); - // } - // // result[i] = BitConverter.ToInt32(inputBytes, i * 4); - // break; - // } - // } - // free(componentBytes); - // return lst; -} 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.h b/UnityPyBoost/Mesh.hpp similarity index 81% rename from UnityPyBoost/Mesh.h rename to UnityPyBoost/Mesh.hpp index e74f9ba11..db18f1072 100644 --- a/UnityPyBoost/Mesh.h +++ b/UnityPyBoost/Mesh.hpp @@ -1,6 +1,5 @@ #define PY_SSIZE_T_CLEAN #pragma once -#include "AnimationClip.h" #include PyObject *unpack_vertexdata(PyObject *self, PyObject *args); \ No newline at end of file diff --git a/UnityPyBoost/TypeTreeHelper.c b/UnityPyBoost/TypeTreeHelper.c deleted file mode 100644 index b3068c6d3..000000000 --- a/UnityPyBoost/TypeTreeHelper.c +++ /dev/null @@ -1,830 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#pragma once -#include -#include "structmember.h" -#include "TypeTreeHelper.h" -#include -#include -#include "swap.h" - -typedef struct -{ - char *data; - char *dataStart; - char *dataEnd; - char swap; - PyObject *obj; -} Reader; -typedef PyObject *(*read_type)(Reader *); - -#define kAlignBytesFlag 1 << 14 -#define kAnyChildUsesAlignBytesFlag 1 << 15 - -#define HASH_SInt8 235330747 -#define HASH_UInt8 237702589 -#define HASH_char 2090147939 -#define HASH_SInt16 3470947178 -#define HASH_short 274395349 -#define HASH_UInt16 3549217964 -#define HASH_unsigned_short 878862258 -#define HASH_SInt32 3470947240 -#define HASH_int 193495088 -#define HASH_UInt32 3549218026 -#define HASH_unsigned_int 2990314445 -#define HASH_TypePtr 238243313 -#define HASH_SInt64 3470947341 -#define HASH_long_long 2373003301 -#define HASH_UInt64 3549218127 -#define HASH_unsigned_long_long 857652610 -#define HASH_FileSize 2878770112 -#define HASH_float 259121563 -#define HASH_double 4181547808 -#define HASH_bool 2090120081 -#define HASH_string 479440892 -#define HASH_TypelessData 1242572536 -#define HASH_map 193499011 - -#define CHECK_LENGTH(reader, length) \ - if (reader->data + length > reader->dataEnd) \ - { \ - PyErr_Format(PyExc_ValueError, "Can't read %d bytes at position %d of %d\nError occured at %s:%d:%s", length, (int)(reader->data - reader->dataStart), (int)(reader->dataEnd - reader->dataStart), __FILE__, __LINE__, __func__); \ - return NULL; \ - } - -static char SURROGATEESCAPE[] = "surrogateescape"; - -/* function signatures */ -static int read_length(Reader *reader); -static PyObject *read_SInt8(Reader *reader); -static PyObject *read_UInt8(Reader *reader); -static PyObject *read_SInt16(Reader *reader); -static PyObject *read_UInt16(Reader *reader); -static PyObject *read_SInt32(Reader *reader); -static PyObject *read_UInt32(Reader *reader); -static PyObject *read_SInt64(Reader *reader); -static PyObject *read_UInt64(Reader *reader); -static PyObject *read_float(Reader *reader); -static PyObject *read_double(Reader *reader); -static PyObject *read_bool(Reader *reader); -static PyObject *read_string(Reader *reader); -static PyObject *read_TypelessData(Reader *reader); - -static read_type getReadFunction(int hash_value, int *index); -static PyObject *getSubNodes(PyObject *nodes, int *index); - -static PyObject *TypeTreeHelper_ReadValue(PyObject *nodes, Reader *reader, int *index); -static PyObject *TypeTreeHelper_ReadValueVector(PyObject *nodes, Reader *reader, int *index); - -PyObject *read_typetree(PyObject *self, PyObject *args); - -/* implementation */ -static inline uint32_t hash_str(const char *str) -{ - unsigned int hash = 5381; - int c; - while ((c = *str++)) - hash = ((hash << 5) + hash) + c; - return hash; -} - -static inline void initReadFuncOrNodes(PyObject *nodes, int *index, PyObject **subnodes, read_type *func, char *subalign) -{ - TypeTreeNodeObject *node = (TypeTreeNodeObject *)PyList_GetItem(nodes, *index); - *func = getReadFunction(node->typehash, index); - if (*func == NULL) - { - *subnodes = getSubNodes(nodes, index); - } - else - { - *subalign = (node->m_MetaFlag & kAlignBytesFlag) ? 1 : 0; - } -} - -static inline void align4(Reader *reader) -{ - char mod = (reader->data - reader->dataStart) % 4; - if (mod != 0) - { - reader->data += 4 - mod; - } -} - -static inline PyObject *read_bool(Reader *reader) -{ - CHECK_LENGTH(reader, 1); - return PyBool_FromLong(*(char *)reader->data++); -} - -static inline PyObject *read_SInt8(Reader *reader) -{ - CHECK_LENGTH(reader, 1); - return PyLong_FromLong(*(signed char *)reader->data++); -} - -static inline PyObject *read_UInt8(Reader *reader) -{ - CHECK_LENGTH(reader, 1); - return PyLong_FromUnsignedLong(*(unsigned char *)reader->data++); -} - -static inline PyObject *read_SInt16(Reader *reader) -{ - CHECK_LENGTH(reader, 2); - PyObject *ret = NULL; - if (reader->swap) - { - ret = PyLong_FromLong((signed short)bswap16(*(unsigned short *)reader->data)); - } - else - { - ret = PyLong_FromLong(*(signed short *)(reader->data)); - } - reader->data += 2; - return ret; -} - -static inline PyObject *read_UInt16(Reader *reader) -{ - CHECK_LENGTH(reader, 2); - PyObject *ret = NULL; - if (reader->swap) - { - ret = PyLong_FromUnsignedLong((unsigned short)bswap16(*(unsigned short *)reader->data)); - } - else - { - ret = PyLong_FromUnsignedLong(*(unsigned short *)(reader->data)); - } - reader->data += 2; - return ret; -} - -static inline PyObject *read_SInt32(Reader *reader) -{ - CHECK_LENGTH(reader, 4); - PyObject *ret = NULL; - if (reader->swap) - { - ret = PyLong_FromLong((signed int)bswap32(*(unsigned int *)reader->data)); - } - else - { - ret = PyLong_FromLong(*(signed int *)(reader->data)); - } - reader->data += 4; - return ret; -} - -static inline PyObject *read_UInt32(Reader *reader) -{ - CHECK_LENGTH(reader, 4); - PyObject *ret = NULL; - if (reader->swap) - { - ret = PyLong_FromUnsignedLong((unsigned int)bswap32(*(unsigned int *)reader->data)); - } - else - { - ret = PyLong_FromUnsignedLong(*(unsigned int *)(reader->data)); - } - reader->data += 4; - return ret; -} - -static inline PyObject *read_SInt64(Reader *reader) -{ - CHECK_LENGTH(reader, 8); - PyObject *ret = NULL; - if (reader->swap) - { - ret = PyLong_FromLongLong((signed long long)bswap64(*(unsigned long long *)reader->data)); - } - else - { - ret = PyLong_FromLongLong(*(signed long long *)(reader->data)); - } - reader->data += 8; - return ret; -} - -static inline PyObject *read_UInt64(Reader *reader) -{ - CHECK_LENGTH(reader, 8); - PyObject *ret = NULL; - if (reader->swap) - { - ret = PyLong_FromUnsignedLongLong((unsigned long long)bswap64(*(unsigned long long *)reader->data)); - } - else - { - ret = PyLong_FromUnsignedLongLong(*(unsigned long long *)(reader->data)); - } - reader->data += 8; - return ret; -} - -static inline PyObject *read_float(Reader *reader) -{ - CHECK_LENGTH(reader, 4); - PyObject *ret = NULL; - if (reader->swap) - { - ret = PyFloat_FromDouble((float)bswap32(*(unsigned int *)reader->data)); - } - else - { - ret = PyFloat_FromDouble(*(float *)(reader->data)); - } - reader->data += 4; - return ret; -} - -static inline PyObject *read_double(Reader *reader) -{ - CHECK_LENGTH(reader, 8); - PyObject *ret = NULL; - if (reader->swap) - { - ret = PyFloat_FromDouble((double)bswap64(*(unsigned long long *)reader->data)); - } - else - { - ret = PyFloat_FromDouble(*(double *)(reader->data)); - } - reader->data += 8; - return ret; -} - -static inline int read_length(Reader *reader) -{ - int length = *(int *)reader->data; - reader->data += 4; - if (reader->swap) - { - length = bswap32(length); - } - return length; -} - -static inline PyObject *read_string(Reader *reader) -{ - CHECK_LENGTH(reader, 4); - int length = read_length(reader); - CHECK_LENGTH(reader, length); - PyObject *str = PyUnicode_DecodeUTF8(reader->data, length, SURROGATEESCAPE); - reader->data += length; - // align - align4(reader); - return str; -} - -static inline PyObject *read_TypelessData(Reader *reader) -{ - CHECK_LENGTH(reader, 4); - int length = read_length(reader); - CHECK_LENGTH(reader, length); - PyObject *value = PyMemoryView_FromMemory(reader->data, length, PyBUF_READ); - reader->data += length; - return value; -} - -static inline read_type getReadFunction(int hash_value, int *index) -{ - switch (hash_value) - { - case HASH_SInt8: - return read_SInt8; - case HASH_UInt8: - case HASH_char: - return read_UInt8; - case HASH_SInt16: - case HASH_short: - return read_SInt16; - case HASH_UInt16: - case HASH_unsigned_short: - return read_UInt16; - case HASH_SInt32: - case HASH_int: - return read_SInt32; - case HASH_UInt32: - case HASH_unsigned_int: - case HASH_TypePtr: // Type* - return read_UInt32; - case HASH_SInt64: - case HASH_long_long: - return read_SInt64; - case HASH_UInt64: - case HASH_unsigned_long_long: - case HASH_FileSize: - return read_UInt64; - case HASH_float: - return read_float; - case HASH_double: - return read_double; - case HASH_bool: - return read_bool; - case HASH_string: - *index += 3; - return read_string; - case HASH_TypelessData: - *index += 2; - return read_TypelessData; - default: - return NULL; - } -} - -static PyObject *getSubNodes(PyObject *nodes, int *index) -{ - PyObject *result = NULL; - TypeTreeNodeObject *node = (TypeTreeNodeObject *)PyList_GetItem(nodes, *index); - unsigned short level = node->m_Level; - for (int i = *index + 1; i < PyList_Size(nodes); i++) - { - if (((TypeTreeNodeObject *)PyList_GetItem(nodes, i))->m_Level <= level) - { - result = PyList_GetSlice(nodes, *index, i); - *index = i - 1; - return result; - } - } - result = PyList_GetSlice(nodes, *index, PyList_Size(nodes)); - *index = PyList_Size(nodes) - 1; - return result; -} - -static inline int PyDict_SetItemString_Safe(PyObject *dict, const char *key, PyObject *value) -{ - int ret = PyDict_SetItemString(dict, key, value); - // SetItemString increases the ref count - // so we have to decrease it here again - // so that the value will be destroyed with the dict - Py_XDECREF(value); - return ret; -} - -static inline int PyList_SetItem_Safe(PyObject *list, int i, PyObject *value) -{ - int ret = PyList_SetItem(list, i, value); - // SetItem increases the ref count - // so we have to decrease it here again - // so that the value will be destroyed with the list - // Py_XDECREF(value); - return ret; -} - -static PyObject *TypeTreeHelper_ReadValue(PyObject *nodes, Reader *reader, int *index) -{ - if (*index >= PyList_Size(nodes)) - { - PyErr_SetString(PyExc_RuntimeError, "index out of range"); - return NULL; - } - TypeTreeNodeObject *node = (TypeTreeNodeObject *)PyList_GetItem(nodes, *index); - PyObject *value = NULL; - int sub_index = 0; - - char align = (node->m_MetaFlag & kAlignBytesFlag) ? 1 : 0; - // printf("RVa: %d\t%lld\t%s\t%s\t%d\t%d\n", *index, (reader->data - reader->dataStart), node->m_Name, node->m_Type, align, node->m_MetaFlag); - - int hash_value = node->typehash; - read_type func = getReadFunction(hash_value, index); - if (func) - { - value = func(reader); - } - else - { - if (hash_value == HASH_map) - { - TypeTreeNodeObject *node2 = (TypeTreeNodeObject *)PyList_GetItem(nodes, *index + 1); - if (node2->m_MetaFlag & kAlignBytesFlag) - align = 1; - - CHECK_LENGTH(reader, 4); - int size = read_length(reader); - - *index += 4; // skip self, Array, size, pair - PyObject *first_nodes = NULL; - read_type first_func = NULL; - char firstalign = 0; - initReadFuncOrNodes(nodes, index, &first_nodes, &first_func, &firstalign); - *index += 1; // move to start of second - PyObject *second_nodes = NULL; - read_type second_func = NULL; - char secondalign = 0; - initReadFuncOrNodes(nodes, index, &second_nodes, &second_func, &secondalign); - - value = PyList_New(size); - PyObject *first; - PyObject *second; - for (int i = 0; i < size; i++) - { - sub_index = 0; - first = (first_func) ? first_func(reader) : TypeTreeHelper_ReadValue(first_nodes, reader, &sub_index); - if (first == NULL) - { - Py_XDECREF(value); - return NULL; - } - if (firstalign) - align4(reader); - sub_index = 0; - second = (second_func) ? second_func(reader) : TypeTreeHelper_ReadValue(second_nodes, reader, &sub_index); - if (second == NULL) - { - Py_XDECREF(first); - Py_XDECREF(second); - return NULL; - } - if (secondalign) - align4(reader); - PyList_SetItem(value, i, PyTuple_Pack(2, first, second)); - Py_XDECREF(first); - Py_XDECREF(second); - } - Py_XDECREF(first_nodes); - Py_XDECREF(second_nodes); - } - else - { - TypeTreeNodeObject *node2 = (TypeTreeNodeObject *)PyList_GetItem(nodes, *index + 1); - if (strcmp(node2->m_Type, "Array") == 0) - { - if (node2->m_MetaFlag & kAlignBytesFlag) - align = 1; - *index += 3; // skip self, Array, size - PyObject *vector_nodes = NULL; - read_type vector_func = NULL; - char subalign = 0; - initReadFuncOrNodes(nodes, index, &vector_nodes, &vector_func, &subalign); - - int size = read_length(reader); - value = PyList_New(size); - for (int i = 0; i < size; i++) - { - int sub_index = 0; - PyObject *vector_value = (vector_func) ? vector_func(reader) : TypeTreeHelper_ReadValue(vector_nodes, reader, &sub_index); - if (vector_value == NULL) - { - Py_XDECREF(value); - return NULL; - } - if (subalign) - align4(reader); - PyList_SetItem_Safe(value, i, vector_value); - } - Py_XDECREF(vector_nodes); - } - else // Class - { - PyObject *cls_nodes = getSubNodes(nodes, index); - int j = 1; - value = PyDict_New(); - char *j_name = NULL; - while (j < PyList_Size(cls_nodes)) - { - j_name = ((TypeTreeNodeObject *)PyList_GetItem(cls_nodes, j))->m_Name; - PyObject *j_value = TypeTreeHelper_ReadValue(cls_nodes, reader, &j); - if (j_value == NULL) - { - Py_XDECREF(value); - return NULL; - } - PyDict_SetItemString_Safe(value, j_name, j_value); - j++; - } - Py_XDECREF(cls_nodes); - } - } - } - - if (align) - align4(reader); - return value; -} - -static PyObject *TypeTreeHelper_ReadTypeTree(PyObject *nodes, PyObject *buf, char swap) -{ - Py_buffer view; - if (Py_TYPE(buf)->tp_as_buffer && Py_TYPE(buf)->tp_as_buffer->bf_releasebuffer) - { - buf = PyMemoryView_FromObject(buf); - if (buf == NULL) - { - return NULL; - } - } - else - { - Py_INCREF(buf); - } - - if (PyObject_GetBuffer(buf, &view, PyBUF_WRITABLE | PyBUF_SIMPLE) < 0) - { - PyErr_Clear(); - if (PyObject_GetBuffer(buf, &view, PyBUF_SIMPLE) < 0) - { - Py_DECREF(buf); - return NULL; - } - } - - Reader reader = { - .data = (char *)view.buf, - .dataStart = (char *)view.buf, - .dataEnd = (char *)view.buf + view.len, - .swap = swap, - .obj = buf}; - - PyBuffer_Release(&view); - - int index = 0; - PyObject *result = TypeTreeHelper_ReadValue(nodes, &reader, &index); - - Py_DECREF(buf); - - return result; -} - -PyObject *read_typetree(PyObject *self, PyObject *args) -{ - PyObject *nodes = PyTuple_GetItem(args, 0); - PyObject *buf = PyTuple_GetItem(args, 1); - PyObject *swap_obj = PyTuple_GetItem(args, 2); - char swap = 0; - - if (!PyUnicode_Check(swap_obj)) - { - PyErr_SetString(PyExc_TypeError, - "The endian attribute value must be a string"); - return NULL; - } - if (PyUnicode_GET_LENGTH(swap_obj) != 1) - { - PyErr_SetString(PyExc_TypeError, - "The endian attribute value must be a string of size 1"); - return NULL; - } - char endian = *(char *)PyUnicode_DATA(swap_obj); - switch (endian) - { - case '<': - if (IS_LITTLE_ENDIAN == 0) - swap = 1; - break; - case '>': - if (IS_LITTLE_ENDIAN == 1) - swap = 1; - break; - case '=': - case '|': - break; - default: - { - PyErr_SetString(PyExc_TypeError, - "The endian attribute value must be one of '>', '<', '=', '|'"); - return NULL; - } - } - return TypeTreeHelper_ReadTypeTree(nodes, buf, swap); -} - -static void -TypeTreeNode_dealloc(TypeTreeNodeObject *self) -{ - PyMem_Free(self->m_Name); - PyMem_Free(self->m_Type); - // for (unsigned short i = 0; i < self->children_count; i++) - // { - // Py_DECREF((PyObject*)self->children[i]); - // } - // PyMem_Free(self->children); - Py_TYPE(self)->tp_free((PyObject *)self); -} - -PyObject * -TypeTreeNode_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - TypeTreeNodeObject *self; - self = (TypeTreeNodeObject *)type->tp_alloc(type, 0); - if (self != NULL) - { - self->m_Version = 0; - self->m_Level = 0; - self->m_IsArray = 0; - self->m_ByteSize = 0; - self->m_Index = 0; - self->m_MetaFlag = 0; - self->m_Type = PyMem_Malloc(sizeof(char)); - self->m_Name = PyMem_Malloc(sizeof(char)); - self->m_Type[0] = '\0'; - self->m_Name[0] = '\0'; - // self->children_count = 0; - // self->children = NULL; - self->m_TypeStrOffset = 0; - self->m_NameStrOffset = 0; - self->m_RefTypeHash = 0; - self->m_VariableCount = 0; - } - return (PyObject *)self; -} - -static int -TypeTreeNode_init(TypeTreeNodeObject *self, PyObject *args, PyObject *kwds) -{ - static char *kwlist[] = { - "m_Name", // char* - "m_Type", // char* - "m_Level", // uint8 - "m_MetaFlag", // int32 - "m_Version", // int16 - "m_IsArray", // char - "m_ByteSize", // int - "m_Index", // int - "m_TypeStrOffset", // unsigned int - "m_NameStrOffset", // unsigned int - "m_RefTypeHash", // unsigned long long - "m_VariableCount", // int - NULL}; - const char *type = NULL; - const char *name = NULL; - if (!PyArg_ParseTupleAndKeywords( - args, - kwds, - "|zzbihbiiIIKi", - kwlist, - &name, - &type, - &self->m_Level, - &self->m_MetaFlag, - &self->m_Version, - &self->m_IsArray, - &self->m_ByteSize, - &self->m_Index, - &self->m_TypeStrOffset, - &self->m_NameStrOffset, - &self->m_RefTypeHash, - &self->m_VariableCount)) - return -1; - if (type != NULL) - { - PyMem_Free(self->m_Type); - self->m_Type = PyMem_Malloc(strlen(type) + 1); - strcpy(self->m_Type, type); - self->typehash = hash_str(type); - } - if (name != NULL) - { - PyMem_Free(self->m_Name); - self->m_Name = PyMem_Malloc(strlen(name) + 1); - strcpy(self->m_Name, name); - } - return 0; -}; - -static PyObject * -TypeTreeNode_getType(TypeTreeNodeObject *self, void *closure) -{ - return PyUnicode_DecodeUTF8(self->m_Type, strlen(self->m_Type), SURROGATEESCAPE); -} - -static int -TypeTreeNode_setType(TypeTreeNodeObject *self, PyObject *value, void *closure) -{ - if (!PyUnicode_Check(value)) - { - PyErr_SetString(PyExc_TypeError, "The type attribute value must be a string"); - return NULL; - } - PyMem_Free(self->m_Type); - char *type = PyUnicode_AsUTF8(value); - self->m_Type = PyMem_Malloc(strlen(type) + 1); - strcpy(self->m_Type, type); - self->typehash = hash_str(type); - return 0; -} - -static PyObject * -TypeTreeNode_getName(TypeTreeNodeObject *self, void *closure) -{ - return PyUnicode_DecodeUTF8(self->m_Name, strlen(self->m_Name), SURROGATEESCAPE); -} - -static int -TypeTreeNode_setName(TypeTreeNodeObject *self, PyObject *value, void *closure) -{ - if (!PyUnicode_Check(value)) - { - PyErr_SetString(PyExc_TypeError, "The name attribute value must be a string"); - return NULL; - } - PyMem_Free(self->m_Name); - char *name = PyUnicode_AsUTF8(value); - self->m_Name = PyMem_Malloc(strlen(name) + 1); - strcpy(self->m_Name, name); - return 0; -} - -static PyMemberDef TypeTreeNode_members[] = { - //{"m_Type", T_STRING, offsetof(TypeTreeNodeObject, m_Type), 0, ""}, - //{"m_Name", T_STRING, offsetof(TypeTreeNodeObject, m_Name), 0, ""}, - {"m_ByteSize", T_INT, offsetof(TypeTreeNodeObject, m_ByteSize), 0, ""}, - {"m_Index", T_INT, offsetof(TypeTreeNodeObject, m_Index), 0, ""}, - {"m_IsArray", T_BOOL, offsetof(TypeTreeNodeObject, m_IsArray), 0, ""}, - {"m_Version", T_SHORT, offsetof(TypeTreeNodeObject, m_Version), 0, ""}, - {"m_MetaFlag", T_INT, offsetof(TypeTreeNodeObject, m_MetaFlag), 0, ""}, - {"m_Level", T_UBYTE, offsetof(TypeTreeNodeObject, m_Level), 0, ""}, - {"m_TypeStrOffset", T_UINT, offsetof(TypeTreeNodeObject, m_TypeStrOffset), 0, ""}, - {"m_NameStrOffset", T_UINT, offsetof(TypeTreeNodeObject, m_NameStrOffset), 0, ""}, - {"m_RefTypeHash", T_ULONGLONG, offsetof(TypeTreeNodeObject, m_RefTypeHash), 0, ""}, - {"m_VariableCount", T_INT, offsetof(TypeTreeNodeObject, m_VariableCount), 0, ""}, - {NULL} /* Sentinel */ -}; - -static PyGetSetDef TypeTreeNode_getsetters[] = { - {"m_Type", (getter)TypeTreeNode_getType, (setter)TypeTreeNode_setType, - "", NULL}, - {"m_Name", (getter)TypeTreeNode_getName, (setter)TypeTreeNode_setName, - "", NULL}, - {NULL} /* Sentinel */ -}; - -static PyObject * -TypeTreeNode_repr(PyObject *self) -{ - TypeTreeNodeObject *node = (TypeTreeNodeObject *)self; - return PyUnicode_FromFormat( - "", - node->m_Level, - node->m_Type, - node->m_Name); -} - -// PyTypeObject TypeTreeNodeType; - -// static int -// TypeTreeNode_set_children(TypeTreeNodeObject *self, PyObject *value, void *closure) -// { -// PyObject *tmp; -// if (value == NULL) -// { -// PyErr_SetString(PyExc_TypeError, "Cannot delete the children attribute"); -// return -1; -// } -// if (!PyList_Check(value)) -// { -// PyErr_SetString(PyExc_TypeError, "The children attribute value must be a list"); -// return -1; -// } -// for (unsigned short i = 0; i < self->children_count; i++) -// { -// Py_DECREF((PyObject*)self->children[i]); -// } -// PyMem_Free(self->children); -// self->children_count = PyList_Size(value); -// self->children = PyMem_Malloc(sizeof(TypeTreeNodeObject *) * self->children_count); -// for (unsigned short i = 0; i < self->children_count; i++) -// { -// tmp = PyList_GetItem(value, i); -// if (!PyObject_TypeCheck(tmp, &TypeTreeNodeType)) -// { -// PyErr_SetString(PyExc_TypeError, "The children attribute value must be a list of TypeTreeNode objects"); -// return -1; -// } -// self->children[i] = (TypeTreeNodeObject *)tmp; -// Py_INCREF(tmp); -// } -// return 0; -// } - -// static PyGetSetDef TypeTreeNode_getsetters[] = { -// {"children", (getter) TypeTreeNode_get_children, (setter) TypeTreeNode_set_children, -// "", NULL}, -// {NULL} /* Sentinel */ -// }; - -PyTypeObject TypeTreeNodeType = { - PyVarObject_HEAD_INIT(NULL, 0) - .tp_name = "UnityPyBoost.TypeTreeNode", - .tp_doc = PyDoc_STR("TypeTreeNode objects"), - .tp_basicsize = sizeof(TypeTreeNodeObject), - .tp_itemsize = 0, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, - .tp_new = TypeTreeNode_new, - .tp_init = (initproc)TypeTreeNode_init, - .tp_dealloc = (destructor)TypeTreeNode_dealloc, - .tp_members = TypeTreeNode_members, - .tp_getset = TypeTreeNode_getsetters, - .tp_repr = (reprfunc)TypeTreeNode_repr, -}; - -int add_typetreenode_to_module(PyObject *m) -{ - if (PyType_Ready(&TypeTreeNodeType) < 0) - return -1; - Py_INCREF(&TypeTreeNodeType); - PyModule_AddObject(m, "TypeTreeNode", (PyObject *)&TypeTreeNodeType); - return 0; -} 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.h b/UnityPyBoost/TypeTreeHelper.h deleted file mode 100644 index 2fe69653b..000000000 --- a/UnityPyBoost/TypeTreeHelper.h +++ /dev/null @@ -1,31 +0,0 @@ -#define PY_SSIZE_T_CLEAN -#pragma once -#include -#include "structmember.h" - -typedef struct TypeTreeNodeObject{ - PyObject_HEAD - short m_Version; - unsigned char m_Level; - char m_IsArray; - int m_ByteSize; - int m_Index; - int m_MetaFlag; - char *m_Type; - char *m_Name; - //unsigned short children_count; - //struct TypeTreeNodeObject **children; - // UnityFS - unsigned int m_TypeStrOffset; - unsigned int m_NameStrOffset; - // UnityFS - version >= 19 - unsigned long long m_RefTypeHash; - // UnityRaw - versin = 2 - int m_VariableCount; - // helper fields - unsigned int typehash; -} TypeTreeNodeObject; - -int add_typetreenode_to_module(PyObject *m); - -PyObject* read_typetree(PyObject *self, PyObject *args); \ No newline at end of file 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.c b/UnityPyBoost/UnityPyBoost.cpp similarity index 50% rename from UnityPyBoost/UnityPyBoost.c rename to UnityPyBoost/UnityPyBoost.cpp index 48745d357..4b3ed8fa0 100644 --- a/UnityPyBoost/UnityPyBoost.c +++ b/UnityPyBoost/UnityPyBoost.cpp @@ -1,33 +1,28 @@ #define PY_SSIZE_T_CLEAN -#pragma once #include -#include "AnimationClip.h" -#include "Mesh.h" -#include "TypeTreeHelper.h" +#include "Mesh.hpp" +#include "TypeTreeHelper.hpp" +#include "ArchiveStorageDecryptor.hpp" /* Mesh.py */ static struct PyMethodDef method_table[] = { - {"unpack_floats", - (PyCFunction)unpack_floats, - METH_VARARGS, - "replacement for PackedFloatVector.unpack_floats"}, - {"unpack_ints", - (PyCFunction)unpack_ints, - METH_VARARGS, - "replacement for PackedIntVector.unpack_ints"}, {"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 TypeTreeHelper.read_typetree"}, - {NULL, - NULL, + "replacement for ArchiveStorageDecryptor.decrypt_block"}, + {nullptr, + nullptr, 0, - NULL} // Sentinel value ending the table + nullptr} // Sentinel value ending the table }; // A struct contains the definition of a module @@ -37,16 +32,16 @@ static PyModuleDef UnityPyBoost_module = { "TODO", -1, // Optional size of the module state memory method_table, - NULL, // Optional slot definitions - NULL, // Optional traversal function - NULL, // Optional clear function - NULL // Optional module deallocation function + 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); + PyObject *module = PyModule_Create(&UnityPyBoost_module); add_typetreenode_to_module(module); return module; -} \ No newline at end of file +} diff --git a/UnityPyBoost/setup.py b/UnityPyBoost/setup.py deleted file mode 100644 index fc197c1fb..000000000 --- a/UnityPyBoost/setup.py +++ /dev/null @@ -1,23 +0,0 @@ -from setuptools import setup, Extension, find_packages -import os -import platform - -local = os.path.dirname(os.path.abspath(__file__)) - -setup( - name="UnityPyBoost", - description="TODO", - author="K0lb3", - version="0.0.3", - ext_modules=[ - Extension( - "UnityPyBoost", - [ - os.path.join(local, f) - for f in os.listdir(local) if f.endswith(".c") - ], - language="c", - include_dirs=[local], - ) - ], -) diff --git a/UnityPyBoost/swap.h b/UnityPyBoost/swap.h deleted file mode 100644 index 5597a2324..000000000 --- a/UnityPyBoost/swap.h +++ /dev/null @@ -1,35 +0,0 @@ -// check if the system is little endian -#define IS_LITTLE_ENDIAN (*(unsigned char *)&(unsigned short){1}) - -// 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 -#define bswap16 ((x) << 8) | ((x) >> 8) -#define bswap32 \ - (((x)&0xFF) << 24) | \ - (((x)&0xFF00) << 8) | \ - (((x) >> 8) & 0xFF00) | \ - (((x) >> 24) & 0xFF) -#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 \ No newline at end of file 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/build_hook.py b/build_hook.py deleted file mode 100644 index 0ca3fe0a8..000000000 --- a/build_hook.py +++ /dev/null @@ -1,90 +0,0 @@ -import os - -from hatchling.builders.hooks.plugin.interface import BuildHookInterface - - -class CustomBuildHook(BuildHookInterface): - def initialize(self, version, build_data): - # set infer_tag to True to let hatchling infer the correct platform tag - build_data["infer_tag"] = True - # - - build_data["pure_python"] = False - - # add fmod lib for the target platform - fmod_lib = get_fmod_library() - if fmod_lib: - print(f"Using fmod lib: {fmod_lib}") - build_data["force_include"][fmod_lib] = fmod_lib - else: - print("No fmod lib found for the target platform") - - # compile and add UnityPyBoost - boost_fp = build_UnityPyBoost(self.root) - build_data["force_include"][boost_fp] = boost_fp - - -def build_UnityPyBoost(build_dir: str) -> str: - from distutils.core import setup, Extension - print("Building UnityPyBoost") - UnityPyBoost_dir = os.path.join(build_dir, "UnityPyBoost") - setup( - name="UnityPy", - packages=["UnityPy"], - script_args=["build_ext", "--inplace"], - ext_modules=[ - Extension( - "UnityPy.UnityPyBoost", - [ - f"UnityPyBoost/{f}" - for f in os.listdir(UnityPyBoost_dir) - if f.endswith(".c") - ], - language="c", - include_dirs=[UnityPyBoost_dir], - ) - ], - ) - print("Done building UnityPyBoost") - # return the path to the built UnityPyBoost - lib_ext = ".pyd" if os.name == "nt" else ".so" - for f in os.listdir(os.path.join(build_dir, "UnityPy")): - if f.endswith(lib_ext): - return f"UnityPy/{f}" - else: - raise Exception("Compiled UnityPyBoost was not found") - - -def get_fmod_library() -> str: - import platform - - # 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] - - lib_name = "" - if system in ["Windows", "Darwin"]: - lib_name = "fmod.dll" if system == "Windows" else "libfmod.dylib" - if arch == "32bit": - arch = "x86" - elif arch == "64bit": - arch = "x64" - elif system == "Linux": - lib_name = "libfmod.so" - # 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": - return None - elif arch == "32bit": - arch = "x86" - elif arch == "64bit": - arch = "x86_64" - else: - return None - - return f"UnityPy/lib/FMOD/{system}/{arch}/{lib_name}" 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/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.deps.json b/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.deps.json deleted file mode 100644 index b1b654e8f..000000000 --- a/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.deps.json +++ /dev/null @@ -1,72 +0,0 @@ -{ - "runtimeTarget": { - "name": ".NETCoreApp,Version=v3.1/win-x64", - "signature": "" - }, - "compilationOptions": {}, - "targets": { - ".NETCoreApp,Version=v3.1": {}, - ".NETCoreApp,Version=v3.1/win-x64": { - "TypeTreeGenerator/1.0.0": { - "dependencies": { - "ILRepack": "2.0.18", - "ILRepack.Lib.MSBuild.Task": "2.0.18.2", - "Mono.Cecil": "0.11.3" - }, - "runtime": { - "TypeTreeGenerator.dll": {} - } - }, - "ILRepack/2.0.18": {}, - "ILRepack.Lib.MSBuild.Task/2.0.18.2": {}, - "Mono.Cecil/0.11.3": { - "runtime": { - "lib/netstandard2.0/Mono.Cecil.Mdb.dll": { - "assemblyVersion": "0.11.3.0", - "fileVersion": "0.11.3.0" - }, - "lib/netstandard2.0/Mono.Cecil.Pdb.dll": { - "assemblyVersion": "0.11.3.0", - "fileVersion": "0.11.3.0" - }, - "lib/netstandard2.0/Mono.Cecil.Rocks.dll": { - "assemblyVersion": "0.11.3.0", - "fileVersion": "0.11.3.0" - }, - "lib/netstandard2.0/Mono.Cecil.dll": { - "assemblyVersion": "0.11.3.0", - "fileVersion": "0.11.3.0" - } - } - } - } - }, - "libraries": { - "TypeTreeGenerator/1.0.0": { - "type": "project", - "serviceable": false, - "sha512": "" - }, - "ILRepack/2.0.18": { - "type": "package", - "serviceable": true, - "sha512": "sha512-sR5Aj3JLDbA8JwESfWYfigSz5k1oNSHPN434W1LX8sboWJYEOSQP/KkvZGmJKPgajzSUKkl2jDar3LPZiMFU4Q==", - "path": "ilrepack/2.0.18", - "hashPath": "ilrepack.2.0.18.nupkg.sha512" - }, - "ILRepack.Lib.MSBuild.Task/2.0.18.2": { - "type": "package", - "serviceable": true, - "sha512": "sha512-lreK19MMDA/ekeqjLY4w171cG7+pHwjeTMHMNq6tghuhTOIOfHT+b/LTMj5jpfbJoR7J6AMD3yOYZoyY/2wtJA==", - "path": "ilrepack.lib.msbuild.task/2.0.18.2", - "hashPath": "ilrepack.lib.msbuild.task.2.0.18.2.nupkg.sha512" - }, - "Mono.Cecil/0.11.3": { - "type": "package", - "serviceable": true, - "sha512": "sha512-DNYE+io5XfEE8+E+5padThTPHJARJHbz1mhbhMPNrrWGKVKKqj/KEeLvbawAmbIcT73NuxLV7itHZaYCZcVWGg==", - "path": "mono.cecil/0.11.3", - "hashPath": "mono.cecil.0.11.3.nupkg.sha512" - } - } -} \ No newline at end of file diff --git a/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.dll b/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.dll deleted file mode 100644 index f9df54396..000000000 Binary files a/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.dll and /dev/null differ diff --git a/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.pdb b/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.pdb deleted file mode 100644 index 527ffa1a2..000000000 Binary files a/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.pdb and /dev/null differ diff --git a/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.runtimeconfig.dev.json b/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.runtimeconfig.dev.json deleted file mode 100644 index 4a6bcc1f4..000000000 --- a/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.runtimeconfig.dev.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "runtimeOptions": { - "additionalProbingPaths": [ - "C:\\Users\\W0lf\\.dotnet\\store\\|arch|\\|tfm|", - "C:\\Users\\W0lf\\.nuget\\packages" - ] - } -} \ No newline at end of file diff --git a/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.runtimeconfig.json b/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.runtimeconfig.json deleted file mode 100644 index bc456d786..000000000 --- a/examples/MonoBehaviourFromAssembly/TypeTreeGenerator/TypeTreeGenerator.runtimeconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "runtimeOptions": { - "tfm": "netcoreapp3.1", - "framework": { - "name": "Microsoft.NETCore.App", - "version": "3.1.0" - } - } -} \ No newline at end of file diff --git a/examples/MonoBehaviourFromAssembly/monobehaviour_from_assembly.py b/examples/MonoBehaviourFromAssembly/monobehaviour_from_assembly.py deleted file mode 100644 index 93c98753f..000000000 --- a/examples/MonoBehaviourFromAssembly/monobehaviour_from_assembly.py +++ /dev/null @@ -1,170 +0,0 @@ -# py3 -# requirements: -# pythonnet 3+ -# pip install git+https://github.com/pythonnet/pythonnet/ -# TypeTreeGenerator -# https://github.com/K0lb3/TypeTreeGenerator -# requires .NET 5.0 SDK -# https://dotnet.microsoft.com/download/dotnet/5.0 -# -# pythonnet 2 and TypeTreeGenerator created with net4.8 works on Windows, -# so it can do without pythonnet_init, -# all other systems need pythonnet 3 and either .net 5 or .net core 3 and pythonnet_init - - -############################ -# -# Warning: This example isn't for beginners -# -############################ - -import os -import UnityPy -from typing import Dict -import json - -ROOT = os.path.dirname(os.path.realpath(__file__)) -TYPETREE_GENERATOR_PATH = os.path.join(ROOT, "TypeTreeGenerator") - -def main(): - # dump the trees for all classes in the assembly - dll_folder = os.path.join(ROOT, "DummyDll") - tree_path = os.path.join(ROOT, "assembly_typetrees.json") - trees = dump_assembly_trees(dll_folder, tree_path) - # by dumping it as json, it can be redistributed, - # so that other people don't have to setup pythonnet3 - # People who don't like to share their decrypted dlls could also share the relevant structures this way. - - export_monobehaviours(asset_path, trees) - -def export_monobehaviours(asset_path: str, trees: dict): - for r, d, fs in os.walk(asset_path): - for f in fs: - try: - env = UnityPy.load(os.path.join(r, f)) - except: - continue - for obj in env.objects: - if obj.type == "MonoBehaviour": - d = obj.read() - if obj.serialized_type and obj.serialized_type.nodes: - tree = obj.read_typetree() - else: - if not d.m_Script: - continue - # RIP, no referenced script - # can only dump raw - script = d.m_Script.read() - # on-demand solution without already dumped tree - #nodes = generate_tree( - # g, script.m_AssemblyName, script.m_ClassName, script.m_Namespace - #) - if script.m_ClassName not in trees: - # class not found in known trees, - # might have to add the classes of the other dlls - continue - nodes = FakeNode(**trees[script.m_ClassName]) - tree = obj.read_typetree(nodes) - - # save tree as json whereever you like - - - -def dump_assembly_trees(dll_folder: str, out_path: str): - # init pythonnet, so that it uses the correct .net for the generator - pythonnet_init() - # create generator - g = create_generator(dll_folder) - - # generate a typetree for all existing classes in the Assembly-CSharp - # while this could also be done dynamically for each required class, - # it's faster and easier overall to just fetch all at once - trees = generate_tree(g, "Assembly-CSharp.dll", "", "") - - if out_path: - with open("typetrees.json", "wt", encoding="utf8") as f: - json.dump(trees, f, ensure_ascii=False) - return trees - - - -def pythonnet_init(): - """correctly sets-up pythonnet for the typetree generator""" - # prepare correct runtime - from clr_loader import get_coreclr - from pythonnet import set_runtime - - rt = get_coreclr( - os.path.join(TYPETREE_GENERATOR_PATH, "TypeTreeGenerator.runtimeconfig.json") - ) - set_runtime(rt) - - -def create_generator(dll_folder: str): - """Loads TypeTreeGenerator library and returns an instance of the Generator class.""" - # temporarily add the typetree generator dir to paths, - # so that pythonnet can find its files - import sys - - sys.path.append(TYPETREE_GENERATOR_PATH) - - # - import clr - - clr.AddReference("TypeTreeGenerator") - - # import Generator class from the loaded library - from Generator import Generator - - # create an instance of the Generator class - g = Generator() - # load the dll folder into the generator - g.loadFolder(dll_folder) - return g - - -class FakeNode: - """A fake/minimal Node class for use in UnityPy.""" - - def __init__(self, **kwargs): - self.__dict__.update(**kwargs) - - -def generate_tree( - g: "Generator", - assembly: str, - class_name: str, - namespace: str, - unity_version=[2018, 4, 3, 1], -) -> Dict[str, Dict]: - """Generates the typetree structure / nodes for the specified class.""" - # C# System - from System import Array - - unity_version_cs = Array[int](unity_version) - - # fetch all type definitions - def_iter = g.getTypeDefs(assembly, class_name, namespace) - - # create the nodes - trees = {} - for d in def_iter: - try: - nodes = g.convertToTypeTreeNodes(d, unity_version_cs) - except Exception as e: - # print(d.Name, e) - continue - trees[d.Name] = [ - { - "level" : node.m_Level, - "type" : node.m_Type, - "name" : node.m_Name, - "meta_flag" : node.m_MetaFlag, - } - for node in nodes - ] - return trees - - -if __name__ == "__main__": - main() diff --git a/examples/rebundle.py b/examples/rebundle.py deleted file mode 100644 index 9e223aa83..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 = r"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.flags = 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(), flags=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() \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 8b24cd415..cad2658f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] -requires = ["hatchling"] -build-backend = "hatchling.build" +requires = ["setuptools", "wheel"] +build-backend = "setuptools.build_meta" [project] name = "UnityPy" @@ -8,13 +8,13 @@ authors = [{ name = "Rudolf Kolbe", email = "rkolbe96@gmail.com" }] description = "A Unity extraction and patching package" readme = "README.md" license = { file = "LICENSE" } -requires-python = ">=3.6" +requires-python = ">=3.8" keywords = [ "python", "unity", "unity-asset", "python3", - "data-minig", + "data-mining", "unitypack", "assetstudio", "unity-asset-extractor", @@ -26,10 +26,13 @@ classifiers = [ "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", + "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", @@ -40,34 +43,49 @@ dependencies = [ "brotli", # WebFile compression # Texture & Sprite handling "Pillow", - "texture2ddecoder", # texture decompression - "etcpak", # ETC & DXT compression - # raw typetree dumping - "tabulate", + "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.hatch.version] -# path to the file containing the version number -path = "UnityPy/__init__.py" +[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.hatch.build.targets.sdist] -# include all files required to build the package -only-include = ["UnityPy", "UnityPyBoost", "tests", "build_hook.py"] -# exclude prebuild files -exclude = ["UnityPy/UnityPyBoost*"] +[tool.cibuildwheel.macos] +archs = ["x86_64", "arm64"] -[tool.hatch.build.targets.wheel] -# UnityPyBoost will be compiled by the hook and added to UnityPy -# therefore UnityPyBoost and hook_build aren't needed -only-include = ["UnityPy", "tests"] -# exclude all prebuild files and all FMOD libs -# the fmod lib for the target platform is force included via the hook -exclude = ["UnityPy/lib/FMOD", "UnityPy/UnityPyBoost*"] +[tool.cibuildwheel.windows] +archs = ["AMD64", "x86", "ARM64"] -[tool.hatch.build.targets.wheel.hooks.custom] -path = "build_hook.py" +[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.py b/setup.py new file mode 100644 index 000000000..5ca47e178 --- /dev/null +++ b/setup.py @@ -0,0 +1,124 @@ +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 index e80f3c811..4c21cba5a 100644 Binary files a/tests/samples/xinzexi_2_n_tex and b/tests/samples/xinzexi_2_n_tex differ diff --git a/tests/samples/xinzexi_2_n_tex_mesh b/tests/samples/xinzexi_2_n_tex_mesh index 0a57a2ec6..785dbd862 100644 --- a/tests/samples/xinzexi_2_n_tex_mesh +++ b/tests/samples/xinzexi_2_n_tex_mesh @@ -1,1812 +1,3 @@ -g xinzexi_2_n-mesh -v -1152 671 0 -v -1152 1343 0 -v -1440 1343 0 -v -1440 671 0 -v -1152 31 0 -v -1152 671 0 -v -1440 671 0 -v -1440 31 0 -v -832 607 0 -v -832 927 0 -v -1152 927 0 -v -1152 607 0 -v -512 607 0 -v -512 927 0 -v -832 927 0 -v -832 607 0 -v -64 319 0 -v -64 607 0 -v -384 607 0 -v -384 319 0 -v -384 319 0 -v -384 607 0 -v -672 607 0 -v -672 319 0 -v -960 927 0 -v -960 1215 0 -v -1152 1215 0 -v -1152 927 0 -v -960 1215 0 -v -960 1503 0 -v -1152 1503 0 -v -1152 1215 0 -v -1440 735 0 -v -1440 991 0 -v -1632 991 0 -v -1632 735 0 -v -1440 991 0 -v -1440 1247 0 -v -1632 1247 0 -v -1632 991 0 -v -704 223 0 -v -704 479 0 -v -832 479 0 -v -832 223 0 -v -1440 1247 0 -v -1440 1407 0 -v -1632 1407 0 -v -1632 1247 0 -v -1152 1343 0 -v -1152 1503 0 -v -1312 1503 0 -v -1312 1343 0 -v -1632 1663 0 -v -1632 2047 0 -v -1696 2047 0 -v -1696 1663 0 -v -1632 1311 0 -v -1632 1663 0 -v -1696 1663 0 -v -1696 1311 0 -v -992 1503 0 -v -992 1599 0 -v -1216 1599 0 -v -1216 1503 0 -v -1440 223 0 -v -1440 447 0 -v -1536 447 0 -v -1536 223 0 -v -800 479 0 -v -800 607 0 -v -960 607 0 -v -960 479 0 -v -800 927 0 -v -800 1055 0 -v -960 1055 0 -v -960 927 0 -v -1024 95 0 -v -1024 255 0 -v -1152 255 0 -v -1152 95 0 -v -1440 31 0 -v -1440 223 0 -v -1536 223 0 -v -1536 31 0 -v -192 191 0 -v -192 255 0 -v -448 255 0 -v -448 191 0 -v -448 191 0 -v -448 255 0 -v -704 255 0 -v -704 191 0 -v -832 1311 0 -v -832 1439 0 -v -928 1439 0 -v -928 1311 0 -v -800 1599 0 -v -800 1727 0 -v -896 1727 0 -v -896 1599 0 -v -1632 1055 0 -v -1632 1183 0 -v -1728 1183 0 -v -1728 1055 0 -v -256 607 0 -v -256 703 0 -v -384 703 0 -v -384 607 0 -v -1632 927 0 -v -1632 1055 0 -v -1728 1055 0 -v -1728 927 0 -v -384 607 0 -v -384 703 0 -v -512 703 0 -v -512 607 0 -v -1600 31 0 -v -1600 159 0 -v -1696 159 0 -v -1696 31 0 -v -1600 159 0 -v -1600 287 0 -v -1696 287 0 -v -1696 159 0 -v -1888 735 0 -v -1888 895 0 -v -1937 895 0 -v -1937 735 0 -v -1760 959 0 -v -1760 1055 0 -v -1856 1055 0 -v -1856 959 0 -v -896 1535 0 -v -896 1631 0 -v -992 1631 0 -v -992 1535 0 -v -128 287 0 -v -128 319 0 -v -416 319 0 -v -416 287 0 -v -416 287 0 -v -416 319 0 -v -704 319 0 -v -704 287 0 -v -1888 607 0 -v -1888 735 0 -v -1937 735 0 -v -1937 607 0 -v -1408 1503 0 -v -1408 1567 0 -v -1536 1567 0 -v -1536 1503 0 -v -1344 1407 0 -v -1344 1535 0 -v -1408 1535 0 -v -1408 1407 0 -v -0 415 0 -v -0 543 0 -v -64 543 0 -v -64 415 0 -v -1088 479 0 -v -1088 607 0 -v -1152 607 0 -v -1152 479 0 -v -1536 31 0 -v -1536 223 0 -v -1568 223 0 -v -1568 31 0 -v -1536 1503 0 -v -1536 1567 0 -v -1632 1567 0 -v -1632 1503 0 -v -1536 223 0 -v -1536 415 0 -v -1568 415 0 -v -1568 223 0 -v -1696 159 0 -v -1696 255 0 -v -1760 255 0 -v -1760 159 0 -v -768 1343 0 -v -768 1407 0 -v -832 1407 0 -v -832 1343 0 -v -448 831 0 -v -448 895 0 -v -512 895 0 -v -512 831 0 -v -1280 0 0 -v -1280 31 0 -v -1408 31 0 -v -1408 0 0 -v -1440 1439 0 -v -1440 1503 0 -v -1504 1503 0 -v -1504 1439 0 -v -1472 671 0 -v -1472 735 0 -v -1536 735 0 -v -1536 671 0 -v -1792 479 0 -v -1792 543 0 -v -1856 543 0 -v -1856 479 0 -v -320 703 0 -v -320 767 0 -v -384 767 0 -v -384 703 0 -v -448 703 0 -v -448 767 0 -v -512 767 0 -v -512 703 0 -v -1568 319 0 -v -1568 447 0 -v -1600 447 0 -v -1600 319 0 -v -1856 863 0 -v -1856 991 0 -v -1888 991 0 -v -1888 863 0 -v -1696 95 0 -v -1696 159 0 -v -1760 159 0 -v -1760 95 0 -v -1568 191 0 -v -1568 319 0 -v -1600 319 0 -v -1600 191 0 -v -160 255 0 -v -160 287 0 -v -288 287 0 -v -288 255 0 -v -832 351 0 -v -832 479 0 -v -864 479 0 -v -864 351 0 -v -288 255 0 -v -288 287 0 -v -416 287 0 -v -416 255 0 -v -672 383 0 -v -672 479 0 -v -704 479 0 -v -704 383 0 -v -1440 639 0 -v -1440 735 0 -v -1472 735 0 -v -1472 639 0 -v -928 1119 0 -v -928 1215 0 -v -960 1215 0 -v -960 1119 0 -v -1856 767 0 -v -1856 863 0 -v -1888 863 0 -v -1888 767 0 -v -1568 63 0 -v -1568 159 0 -v -1600 159 0 -v -1600 63 0 -v -832 255 0 -v -832 351 0 -v -864 351 0 -v -864 255 0 -v -1696 415 0 -v -1696 447 0 -v -1792 447 0 -v -1792 415 0 -v -512 255 0 -v -512 287 0 -v -608 287 0 -v -608 255 0 -v -704 927 0 -v -704 959 0 -v -800 959 0 -v -800 927 0 -v -1600 415 0 -v -1600 447 0 -v -1696 447 0 -v -1696 415 0 -v -608 927 0 -v -608 959 0 -v -704 959 0 -v -704 927 0 -v -1408 0 0 -v -1408 31 0 -v -1504 31 0 -v -1504 0 0 -v -1696 1791 0 -v -1696 1887 0 -v -1728 1887 0 -v -1728 1791 0 -v -1408 1407 0 -v -1408 1503 0 -v -1440 1503 0 -v -1440 1407 0 -v -608 255 0 -v -608 287 0 -v -704 287 0 -v -704 255 0 -v -1696 1887 0 -v -1696 1983 0 -v -1728 1983 0 -v -1728 1887 0 -v -1600 1599 0 -v -1600 1663 0 -v -1632 1663 0 -v -1632 1599 0 -v -768 511 0 -v -768 575 0 -v -800 575 0 -v -800 511 0 -v -1504 1471 0 -v -1504 1503 0 -v -1568 1503 0 -v -1568 1471 0 -v -1216 1567 0 -v -1216 1599 0 -v -1280 1599 0 -v -1280 1567 0 -v -832 1567 0 -v -832 1599 0 -v -896 1599 0 -v -896 1567 0 -v -672 543 0 -v -672 607 0 -v -704 607 0 -v -704 543 0 -v -1440 479 0 -v -1440 543 0 -v -1472 543 0 -v -1472 479 0 -v -416 799 0 -v -416 863 0 -v -448 863 0 -v -448 799 0 -v -384 767 0 -v -384 831 0 -v -416 831 0 -v -416 767 0 -v -1632 383 0 -v -1632 415 0 -v -1696 415 0 -v -1696 383 0 -v -768 1663 0 -v -768 1727 0 -v -800 1727 0 -v -800 1663 0 -v -896 1631 0 -v -896 1695 0 -v -928 1695 0 -v -928 1631 0 -v -1760 223 0 -v -1760 287 0 -v -1792 287 0 -v -1792 223 0 -v -1728 31 0 -v -1728 95 0 -v -1760 95 0 -v -1760 31 0 -v -1120 255 0 -v -1120 319 0 -v -1152 319 0 -v -1152 255 0 -v -1792 223 0 -v -1792 287 0 -v -1824 287 0 -v -1824 223 0 -v -864 415 0 -v -864 479 0 -v -896 479 0 -v -896 415 0 -v -32 351 0 -v -32 415 0 -v -64 415 0 -v -64 351 0 -v -1088 63 0 -v -1088 95 0 -v -1152 95 0 -v -1152 63 0 -v -1600 0 0 -v -1600 31 0 -v -1664 31 0 -v -1664 0 0 -v -960 191 0 -v -960 255 0 -v -992 255 0 -v -992 191 0 -v -480 159 0 -v -480 191 0 -v -544 191 0 -v -544 159 0 -v -1888 543 0 -v -1888 607 0 -v -1920 607 0 -v -1920 543 0 -v -512 1087 0 -v -512 1151 0 -v -544 1151 0 -v -544 1087 0 -v -896 1055 0 -v -896 1119 0 -v -928 1119 0 -v -928 1055 0 -v -1600 1439 0 -v -1600 1503 0 -v -1632 1503 0 -v -1632 1439 0 -v -1632 1247 0 -v -1632 1311 0 -v -1664 1311 0 -v -1664 1247 0 -v -1888 895 0 -v -1888 959 0 -v -1920 959 0 -v -1920 895 0 -v -960 543 0 -v -960 607 0 -v -992 607 0 -v -992 543 0 -v -1760 1055 0 -v -1760 1119 0 -v -1792 1119 0 -v -1792 1055 0 -v -768 959 0 -v -768 1023 0 -v -800 1023 0 -v -800 959 0 -v -1824 895 0 -v -1824 959 0 -v -1856 959 0 -v -1856 895 0 -v -928 1343 0 -v -928 1407 0 -v -960 1407 0 -v -960 1343 0 -v -1504 1407 0 -v -1504 1439 0 -v -1568 1439 0 -v -1568 1407 0 -v -1312 1375 0 -v -1312 1439 0 -v -1344 1439 0 -v -1344 1375 0 -v -928 1407 0 -v -928 1471 0 -v -960 1471 0 -v -960 1407 0 -v -1728 1087 0 -v -1728 1151 0 -v -1760 1151 0 -v -1760 1087 0 -v -192 607 0 -v -192 639 0 -v -256 639 0 -v -256 607 0 -v -1856 575 0 -v -1856 639 0 -v -1888 639 0 -v -1888 575 0 -v -1728 1023 0 -v -1728 1087 0 -v -1760 1087 0 -v -1760 1023 0 -v -128 607 0 -v -128 639 0 -v -192 639 0 -v -192 607 0 -v -1312 1439 0 -v -1312 1503 0 -v -1344 1503 0 -v -1344 1439 0 -v -1440 575 0 -v -1440 639 0 -v -1472 639 0 -v -1472 575 0 -v -672 319 0 -v -672 383 0 -v -704 383 0 -v -704 319 0 -v -1408 1343 0 -v -1408 1407 0 -v -1440 1407 0 -v -1440 1343 0 -v -928 1055 0 -v -928 1119 0 -v -960 1119 0 -v -960 1055 0 -v -1568 0 0 -v -1568 63 0 -v -1600 63 0 -v -1600 0 0 -v -1504 1567 0 -v -1504 1599 0 -v -1568 1599 0 -v -1568 1567 0 -v -1568 1567 0 -v -1568 1599 0 -v -1632 1599 0 -v -1632 1567 0 -v -1696 1631 0 -v -1696 1695 0 -v -1728 1695 0 -v -1728 1631 0 -v -1696 1567 0 -v -1696 1631 0 -v -1728 1631 0 -v -1728 1567 0 -v -1856 511 0 -v -1856 575 0 -v -1888 575 0 -v -1888 511 0 -v -1600 287 0 -v -1600 351 0 -v -1632 351 0 -v -1632 287 0 -v -1568 1407 0 -v -1568 1439 0 -v -1632 1439 0 -v -1632 1407 0 -v -1600 351 0 -v -1600 415 0 -v -1632 415 0 -v -1632 351 0 -v -1152 1599 0 -v -1152 1631 0 -v -1216 1631 0 -v -1216 1599 0 -v -992 159 0 -v -992 223 0 -v -1024 223 0 -v -1024 159 0 -v -992 223 0 -v -992 287 0 -v -1024 287 0 -v -1024 223 0 -v -1760 447 0 -v -1760 479 0 -v -1824 479 0 -v -1824 447 0 -v -1120 351 0 -v -1120 415 0 -v -1152 415 0 -v -1152 351 0 -v -1120 415 0 -v -1120 479 0 -v -1152 479 0 -v -1152 415 0 -v -1696 447 0 -v -1696 479 0 -v -1760 479 0 -v -1760 447 0 -v -448 799 0 -v -448 831 0 -v -480 831 0 -v -480 799 0 -v -288 703 0 -v -288 735 0 -v -320 735 0 -v -320 703 0 -v -736 959 0 -v -736 991 0 -v -768 991 0 -v -768 959 0 -v -1792 927 0 -v -1792 959 0 -v -1824 959 0 -v -1824 927 0 -v -1536 703 0 -v -1536 735 0 -v -1568 735 0 -v -1568 703 0 -v -704 575 0 -v -704 607 0 -v -736 607 0 -v -736 575 0 -v -416 703 0 -v -416 735 0 -v -448 735 0 -v -448 703 0 -v -352 767 0 -v -352 799 0 -v -384 799 0 -v -384 767 0 -v -224 639 0 -v -224 671 0 -v -256 671 0 -v -256 639 0 -v -480 767 0 -v -480 799 0 -v -512 799 0 -v -512 767 0 -v -32 543 0 -v -32 575 0 -v -64 575 0 -v -64 543 0 -v -896 351 0 -v -896 383 0 -v -928 383 0 -v -928 351 0 -v -1472 447 0 -v -1472 479 0 -v -1504 479 0 -v -1504 447 0 -v -1632 287 0 -v -1632 319 0 -v -1664 319 0 -v -1664 287 0 -v -1760 0 0 -v -1760 31 0 -v -1792 31 0 -v -1792 0 0 -v -1024 255 0 -v -1024 287 0 -v -1056 287 0 -v -1056 255 0 -v -1824 543 0 -v -1824 575 0 -v -1856 575 0 -v -1856 543 0 -v -1824 255 0 -v -1824 287 0 -v -1856 287 0 -v -1856 255 0 -v -736 479 0 -v -736 511 0 -v -768 511 0 -v -768 479 0 -v -896 447 0 -v -896 479 0 -v -928 479 0 -v -928 447 0 -v -1760 479 0 -v -1760 511 0 -v -1792 511 0 -v -1792 479 0 -v -1792 1055 0 -v -1792 1087 0 -v -1824 1087 0 -v -1824 1055 0 -v -1440 447 0 -v -1440 479 0 -v -1472 479 0 -v -1472 447 0 -v -768 479 0 -v -768 511 0 -v -800 511 0 -v -800 479 0 -v -1696 383 0 -v -1696 415 0 -v -1728 415 0 -v -1728 383 0 -v -1728 0 0 -v -1728 31 0 -v -1760 31 0 -v -1760 0 0 -v -1760 191 0 -v -1760 223 0 -v -1792 223 0 -v -1792 191 0 -v -1824 863 0 -v -1824 895 0 -v -1856 895 0 -v -1856 863 0 -v -1216 1599 0 -v -1216 1631 0 -v -1248 1631 0 -v -1248 1599 0 -v -416 767 0 -v -416 799 0 -v -448 799 0 -v -448 767 0 -v -672 511 0 -v -672 543 0 -v -704 543 0 -v -704 511 0 -v -384 735 0 -v -384 767 0 -v -416 767 0 -v -416 735 0 -v -928 1631 0 -v -928 1663 0 -v -960 1663 0 -v -960 1631 0 -v -800 1311 0 -v -800 1343 0 -v -832 1343 0 -v -832 1311 0 -v -1376 1343 0 -v -1376 1375 0 -v -1408 1375 0 -v -1408 1343 0 -v -1632 1183 0 -v -1632 1215 0 -v -1664 1215 0 -v -1664 1183 0 -v -864 1055 0 -v -864 1087 0 -v -896 1087 0 -v -896 1055 0 -v -640 1087 0 -v -640 1119 0 -v -672 1119 0 -v -672 1087 0 -v -1248 1535 0 -v -1248 1567 0 -v -1280 1567 0 -v -1280 1535 0 -v -992 1599 0 -v -992 1631 0 -v -1024 1631 0 -v -1024 1599 0 -v -1216 1503 0 -v -1216 1535 0 -v -1248 1535 0 -v -1248 1503 0 -v -1344 1375 0 -v -1344 1407 0 -v -1376 1407 0 -v -1376 1375 0 -v -896 1439 0 -v -896 1471 0 -v -928 1471 0 -v -928 1439 0 -vt 0.0004882812 0.001024604 -vt 0.0004882812 0.6895492 -vt 0.1411133 0.6895492 -vt 0.1411133 0.001024604 -vt 0.1420898 0.001024604 -vt 0.1420898 0.6567623 -vt 0.2827148 0.6567623 -vt 0.2827148 0.001024604 -vt 0.2836914 0.001024604 -vt 0.2836914 0.3288934 -vt 0.4399414 0.3288934 -vt 0.4399414 0.001024604 -vt 0.440918 0.001024604 -vt 0.440918 0.3288934 -vt 0.597168 0.3288934 -vt 0.597168 0.001024604 -vt 0.5981445 0.001024604 -vt 0.5981445 0.2961066 -vt 0.7543945 0.2961066 -vt 0.7543945 0.001024604 -vt 0.7553711 0.001024604 -vt 0.7553711 0.2961066 -vt 0.8959961 0.2961066 -vt 0.8959961 0.001024604 -vt 0.8969727 0.001024604 -vt 0.8969727 0.2961066 -vt 0.9907227 0.2961066 -vt 0.9907227 0.001024604 -vt 0.5981445 0.2981557 -vt 0.5981445 0.5932377 -vt 0.6918945 0.5932377 -vt 0.6918945 0.2981557 -vt 0.6928711 0.2981557 -vt 0.6928711 0.5604508 -vt 0.7866211 0.5604508 -vt 0.7866211 0.2981557 -vt 0.7875977 0.2981557 -vt 0.7875977 0.5604508 -vt 0.8813477 0.5604508 -vt 0.8813477 0.2981557 -vt 0.8823242 0.2981557 -vt 0.8823242 0.5604508 -vt 0.9448242 0.5604508 -vt 0.9448242 0.2981557 -vt 0.2836914 0.3309426 -vt 0.2836914 0.494877 -vt 0.3774414 0.494877 -vt 0.3774414 0.3309426 -vt 0.378418 0.3309426 -vt 0.378418 0.494877 -vt 0.456543 0.494877 -vt 0.456543 0.3309426 -vt 0.9458008 0.2981557 -vt 0.9458008 0.6915984 -vt 0.9770508 0.6915984 -vt 0.9770508 0.2981557 -vt 0.4575195 0.3309426 -vt 0.4575195 0.6915984 -vt 0.4887695 0.6915984 -vt 0.4887695 0.3309426 -vt 0.2836914 0.4969262 -vt 0.2836914 0.5952868 -vt 0.3930664 0.5952868 -vt 0.3930664 0.4969262 -vt 0.4897461 0.3309426 -vt 0.4897461 0.5604508 -vt 0.5366211 0.5604508 -vt 0.5366211 0.3309426 -vt 0.4897461 0.5625 -vt 0.4897461 0.6936475 -vt 0.5678711 0.6936475 -vt 0.5678711 0.5625 -vt 0.6928711 0.5625 -vt 0.6928711 0.6936475 -vt 0.7709961 0.6936475 -vt 0.7709961 0.5625 -vt 0.394043 0.4969262 -vt 0.394043 0.6608607 -vt 0.456543 0.6608607 -vt 0.456543 0.4969262 -vt 0.7719727 0.5625 -vt 0.7719727 0.7592213 -vt 0.8188477 0.7592213 -vt 0.8188477 0.5625 -vt 0.8198242 0.5625 -vt 0.8198242 0.6280738 -vt 0.9448242 0.6280738 -vt 0.9448242 0.5625 -vt 0.8198242 0.630123 -vt 0.8198242 0.6956967 -vt 0.9448242 0.6956967 -vt 0.9448242 0.630123 -vt 0.5981445 0.5952868 -vt 0.5981445 0.7264345 -vt 0.6450195 0.7264345 -vt 0.6450195 0.5952868 -vt 0.2836914 0.5973361 -vt 0.2836914 0.7284836 -vt 0.3305664 0.7284836 -vt 0.3305664 0.5973361 -vt 0.331543 0.5973361 -vt 0.331543 0.7284836 -vt 0.378418 0.7284836 -vt 0.378418 0.5973361 -vt 0.1420898 0.6588115 -vt 0.1420898 0.7571721 -vt 0.2045898 0.7571721 -vt 0.2045898 0.6588115 -vt 0.2055664 0.6588115 -vt 0.2055664 0.789959 -vt 0.2524414 0.789959 -vt 0.2524414 0.6588115 -vt 0.394043 0.6629099 -vt 0.394043 0.7612705 -vt 0.456543 0.7612705 -vt 0.456543 0.6629099 -vt 0.0004882812 0.6915984 -vt 0.0004882812 0.8227459 -vt 0.04736328 0.8227459 -vt 0.04736328 0.6915984 -vt 0.04833984 0.6915984 -vt 0.04833984 0.8227459 -vt 0.09521484 0.8227459 -vt 0.09521484 0.6915984 -vt 0.5688477 0.3309426 -vt 0.5688477 0.494877 -vt 0.5932617 0.494877 -vt 0.5932617 0.3309426 -vt 0.9458008 0.6936475 -vt 0.9458008 0.7920082 -vt 0.9926758 0.7920082 -vt 0.9926758 0.6936475 -vt 0.4897461 0.6956967 -vt 0.4897461 0.7940574 -vt 0.5366211 0.7940574 -vt 0.5366211 0.6956967 -vt 0.5981445 0.7284836 -vt 0.5981445 0.7612705 -vt 0.7387695 0.7612705 -vt 0.7387695 0.7284836 -vt 0.7719727 0.7612705 -vt 0.7719727 0.7940574 -vt 0.9125977 0.7940574 -vt 0.9125977 0.7612705 -vt 0.5688477 0.4969262 -vt 0.5688477 0.6280738 -vt 0.5932617 0.6280738 -vt 0.5932617 0.4969262 -vt 0.2836914 0.7305328 -vt 0.2836914 0.7961066 -vt 0.3461914 0.7961066 -vt 0.3461914 0.7305328 -vt 0.09619141 0.6915984 -vt 0.09619141 0.8227459 -vt 0.1274414 0.8227459 -vt 0.1274414 0.6915984 -vt 0.4575195 0.6936475 -vt 0.4575195 0.8247951 -vt 0.4887695 0.8247951 -vt 0.4887695 0.6936475 -vt 0.5375977 0.6956967 -vt 0.5375977 0.8268443 -vt 0.5688477 0.8268443 -vt 0.5688477 0.6956967 -vt 0.5698242 0.630123 -vt 0.5698242 0.8268443 -vt 0.5854492 0.8268443 -vt 0.5854492 0.630123 -vt 0.1420898 0.7592213 -vt 0.1420898 0.8247951 -vt 0.1889648 0.8247951 -vt 0.1889648 0.7592213 -vt 0.253418 0.6588115 -vt 0.253418 0.8555328 -vt 0.269043 0.8555328 -vt 0.269043 0.6588115 -vt 0.7397461 0.6956967 -vt 0.7397461 0.7940574 -vt 0.7709961 0.7940574 -vt 0.7709961 0.6956967 -vt 0.9135742 0.6977459 -vt 0.9135742 0.7633197 -vt 0.9448242 0.7633197 -vt 0.9448242 0.6977459 -vt 0.347168 0.7305328 -vt 0.347168 0.7961066 -vt 0.378418 0.7961066 -vt 0.378418 0.7305328 -vt 0.394043 0.7633197 -vt 0.394043 0.7961066 -vt 0.456543 0.7961066 -vt 0.456543 0.7633197 -vt 0.5981445 0.7633197 -vt 0.5981445 0.8288934 -vt 0.6293945 0.8288934 -vt 0.6293945 0.7633197 -vt 0.6303711 0.7633197 -vt 0.6303711 0.8288934 -vt 0.6616211 0.8288934 -vt 0.6616211 0.7633197 -vt 0.6625977 0.7633197 -vt 0.6625977 0.8288934 -vt 0.6938477 0.8288934 -vt 0.6938477 0.7633197 -vt 0.6948242 0.7633197 -vt 0.6948242 0.8288934 -vt 0.7260742 0.8288934 -vt 0.7260742 0.7633197 -vt 0.9135742 0.7653688 -vt 0.9135742 0.8309426 -vt 0.9448242 0.8309426 -vt 0.9448242 0.7653688 -vt 0.2055664 0.7920082 -vt 0.2055664 0.9231557 -vt 0.2211914 0.9231557 -vt 0.2211914 0.7920082 -vt 0.222168 0.7920082 -vt 0.222168 0.9231557 -vt 0.237793 0.9231557 -vt 0.237793 0.7920082 -vt 0.9458008 0.7940574 -vt 0.9458008 0.8596312 -vt 0.9770508 0.8596312 -vt 0.9770508 0.7940574 -vt 0.9780273 0.7940574 -vt 0.9780273 0.9252049 -vt 0.9936523 0.9252049 -vt 0.9936523 0.7940574 -vt 0.7397461 0.7961066 -vt 0.7397461 0.8288934 -vt 0.8022461 0.8288934 -vt 0.8022461 0.7961066 -vt 0.4897461 0.7961066 -vt 0.4897461 0.9272541 -vt 0.5053711 0.9272541 -vt 0.5053711 0.7961066 -vt 0.8032227 0.7961066 -vt 0.8032227 0.8288934 -vt 0.8657227 0.8288934 -vt 0.8657227 0.7961066 -vt 0.5063477 0.7961066 -vt 0.5063477 0.8944672 -vt 0.5219727 0.8944672 -vt 0.5219727 0.7961066 -vt 0.8666992 0.7961066 -vt 0.8666992 0.8944672 -vt 0.8823242 0.8944672 -vt 0.8823242 0.7961066 -vt 0.8833008 0.7961066 -vt 0.8833008 0.8944672 -vt 0.8989258 0.8944672 -vt 0.8989258 0.7961066 -vt 0.2836914 0.7981557 -vt 0.2836914 0.8965164 -vt 0.2993164 0.8965164 -vt 0.2993164 0.7981557 -vt 0.300293 0.7981557 -vt 0.300293 0.8965164 -vt 0.315918 0.8965164 -vt 0.315918 0.7981557 -vt 0.3168945 0.7981557 -vt 0.3168945 0.8965164 -vt 0.3325195 0.8965164 -vt 0.3325195 0.7981557 -vt 0.3334961 0.7981557 -vt 0.3334961 0.8309426 -vt 0.3803711 0.8309426 -vt 0.3803711 0.7981557 -vt 0.394043 0.7981557 -vt 0.394043 0.8309426 -vt 0.440918 0.8309426 -vt 0.440918 0.7981557 -vt 0.0004882812 0.8247951 -vt 0.0004882812 0.857582 -vt 0.04736328 0.857582 -vt 0.04736328 0.8247951 -vt 0.04833984 0.8247951 -vt 0.04833984 0.857582 -vt 0.09521484 0.857582 -vt 0.09521484 0.8247951 -vt 0.1420898 0.8268443 -vt 0.1420898 0.8596312 -vt 0.1889648 0.8596312 -vt 0.1889648 0.8268443 -vt 0.5375977 0.8288934 -vt 0.5375977 0.8616803 -vt 0.5844727 0.8616803 -vt 0.5844727 0.8288934 -vt 0.09619141 0.8247951 -vt 0.09619141 0.9231557 -vt 0.1118164 0.9231557 -vt 0.1118164 0.8247951 -vt 0.112793 0.8247951 -vt 0.112793 0.9231557 -vt 0.128418 0.9231557 -vt 0.128418 0.8247951 -vt 0.5981445 0.8309426 -vt 0.5981445 0.8637295 -vt 0.6450195 0.8637295 -vt 0.6450195 0.8309426 -vt 0.4575195 0.8268443 -vt 0.4575195 0.9252049 -vt 0.4731445 0.9252049 -vt 0.4731445 0.8268443 -vt 0.6459961 0.8309426 -vt 0.6459961 0.8965164 -vt 0.6616211 0.8965164 -vt 0.6616211 0.8309426 -vt 0.6625977 0.8309426 -vt 0.6625977 0.8965164 -vt 0.6782227 0.8965164 -vt 0.6782227 0.8309426 -vt 0.6791992 0.8309426 -vt 0.6791992 0.8637295 -vt 0.7104492 0.8637295 -vt 0.7104492 0.8309426 -vt 0.7114258 0.8309426 -vt 0.7114258 0.8637295 -vt 0.7426758 0.8637295 -vt 0.7426758 0.8309426 -vt 0.7436523 0.8309426 -vt 0.7436523 0.8637295 -vt 0.7749023 0.8637295 -vt 0.7749023 0.8309426 -vt 0.7758789 0.8309426 -vt 0.7758789 0.8965164 -vt 0.7915039 0.8965164 -vt 0.7915039 0.8309426 -vt 0.7924805 0.8309426 -vt 0.7924805 0.8965164 -vt 0.8081055 0.8965164 -vt 0.8081055 0.8309426 -vt 0.809082 0.8309426 -vt 0.809082 0.8965164 -vt 0.824707 0.8965164 -vt 0.824707 0.8309426 -vt 0.8256836 0.8309426 -vt 0.8256836 0.8965164 -vt 0.8413086 0.8965164 -vt 0.8413086 0.8309426 -vt 0.3334961 0.8329918 -vt 0.3334961 0.8657787 -vt 0.3647461 0.8657787 -vt 0.3647461 0.8329918 -vt 0.8422852 0.8309426 -vt 0.8422852 0.8965164 -vt 0.8579102 0.8965164 -vt 0.8579102 0.8309426 -vt 0.3657227 0.8329918 -vt 0.3657227 0.8985656 -vt 0.3813477 0.8985656 -vt 0.3813477 0.8329918 -vt 0.394043 0.8329918 -vt 0.394043 0.8985656 -vt 0.409668 0.8985656 -vt 0.409668 0.8329918 -vt 0.4106445 0.8329918 -vt 0.4106445 0.8985656 -vt 0.4262695 0.8985656 -vt 0.4262695 0.8329918 -vt 0.4272461 0.8329918 -vt 0.4272461 0.8985656 -vt 0.4428711 0.8985656 -vt 0.4428711 0.8329918 -vt 0.9135742 0.8329918 -vt 0.9135742 0.8985656 -vt 0.9291992 0.8985656 -vt 0.9291992 0.8329918 -vt 0.253418 0.857582 -vt 0.253418 0.9231557 -vt 0.269043 0.9231557 -vt 0.269043 0.857582 -vt 0.0004882812 0.8596312 -vt 0.0004882812 0.9252049 -vt 0.01611328 0.9252049 -vt 0.01611328 0.8596312 -vt 0.01708984 0.8596312 -vt 0.01708984 0.892418 -vt 0.04833984 0.892418 -vt 0.04833984 0.8596312 -vt 0.04931641 0.8596312 -vt 0.04931641 0.892418 -vt 0.08056641 0.892418 -vt 0.08056641 0.8596312 -vt 0.1420898 0.8616803 -vt 0.1420898 0.9272541 -vt 0.1577148 0.9272541 -vt 0.1577148 0.8616803 -vt 0.1586914 0.8616803 -vt 0.1586914 0.8944672 -vt 0.1899414 0.8944672 -vt 0.1899414 0.8616803 -vt 0.9458008 0.8616803 -vt 0.9458008 0.9272541 -vt 0.9614258 0.9272541 -vt 0.9614258 0.8616803 -vt 0.5375977 0.8637295 -vt 0.5375977 0.9293033 -vt 0.5532227 0.9293033 -vt 0.5532227 0.8637295 -vt 0.5541992 0.8637295 -vt 0.5541992 0.9293033 -vt 0.5698242 0.9293033 -vt 0.5698242 0.8637295 -vt 0.5708008 0.8637295 -vt 0.5708008 0.9293033 -vt 0.5864258 0.9293033 -vt 0.5864258 0.8637295 -vt 0.5981445 0.8657787 -vt 0.5981445 0.9313524 -vt 0.6137695 0.9313524 -vt 0.6137695 0.8657787 -vt 0.6147461 0.8657787 -vt 0.6147461 0.9313524 -vt 0.6303711 0.9313524 -vt 0.6303711 0.8657787 -vt 0.6791992 0.8657787 -vt 0.6791992 0.9313524 -vt 0.6948242 0.9313524 -vt 0.6948242 0.8657787 -vt 0.6958008 0.8657787 -vt 0.6958008 0.9313524 -vt 0.7114258 0.9313524 -vt 0.7114258 0.8657787 -vt 0.7124023 0.8657787 -vt 0.7124023 0.9313524 -vt 0.7280273 0.9313524 -vt 0.7280273 0.8657787 -vt 0.7290039 0.8657787 -vt 0.7290039 0.9313524 -vt 0.7446289 0.9313524 -vt 0.7446289 0.8657787 -vt 0.7456055 0.8657787 -vt 0.7456055 0.9313524 -vt 0.7612305 0.9313524 -vt 0.7612305 0.8657787 -vt 0.3334961 0.8678279 -vt 0.3334961 0.9006147 -vt 0.3647461 0.9006147 -vt 0.3647461 0.8678279 -vt 0.01708984 0.8944672 -vt 0.01708984 0.960041 -vt 0.03271484 0.960041 -vt 0.03271484 0.8944672 -vt 0.03369141 0.8944672 -vt 0.03369141 0.960041 -vt 0.04931641 0.960041 -vt 0.04931641 0.8944672 -vt 0.05029297 0.8944672 -vt 0.05029297 0.960041 -vt 0.06591797 0.960041 -vt 0.06591797 0.8944672 -vt 0.1586914 0.8965164 -vt 0.1586914 0.9293033 -vt 0.1899414 0.9293033 -vt 0.1899414 0.8965164 -vt 0.06689453 0.8944672 -vt 0.06689453 0.960041 -vt 0.08251953 0.960041 -vt 0.08251953 0.8944672 -vt 0.5063477 0.8965164 -vt 0.5063477 0.9620901 -vt 0.5219727 0.9620901 -vt 0.5219727 0.8965164 -vt 0.8666992 0.8965164 -vt 0.8666992 0.9293033 -vt 0.8979492 0.9293033 -vt 0.8979492 0.8965164 -vt 0.2836914 0.8985656 -vt 0.2836914 0.9641393 -vt 0.2993164 0.9641393 -vt 0.2993164 0.8985656 -vt 0.300293 0.8985656 -vt 0.300293 0.9641393 -vt 0.315918 0.9641393 -vt 0.315918 0.8985656 -vt 0.3168945 0.8985656 -vt 0.3168945 0.9641393 -vt 0.3325195 0.9641393 -vt 0.3325195 0.8985656 -vt 0.6459961 0.8985656 -vt 0.6459961 0.9641393 -vt 0.6616211 0.9641393 -vt 0.6616211 0.8985656 -vt 0.6625977 0.8985656 -vt 0.6625977 0.9641393 -vt 0.6782227 0.9641393 -vt 0.6782227 0.8985656 -vt 0.7758789 0.8985656 -vt 0.7758789 0.9641393 -vt 0.7915039 0.9641393 -vt 0.7915039 0.8985656 -vt 0.7924805 0.8985656 -vt 0.7924805 0.9313524 -vt 0.8237305 0.9313524 -vt 0.8237305 0.8985656 -vt 0.824707 0.8985656 -vt 0.824707 0.9313524 -vt 0.855957 0.9313524 -vt 0.855957 0.8985656 -vt 0.3657227 0.9006147 -vt 0.3657227 0.9661885 -vt 0.3813477 0.9661885 -vt 0.3813477 0.9006147 -vt 0.394043 0.9006147 -vt 0.394043 0.9661885 -vt 0.409668 0.9661885 -vt 0.409668 0.9006147 -vt 0.4106445 0.9006147 -vt 0.4106445 0.9661885 -vt 0.4262695 0.9661885 -vt 0.4262695 0.9006147 -vt 0.4272461 0.9006147 -vt 0.4272461 0.9661885 -vt 0.4428711 0.9661885 -vt 0.4428711 0.9006147 -vt 0.9135742 0.9006147 -vt 0.9135742 0.9334016 -vt 0.9448242 0.9334016 -vt 0.9448242 0.9006147 -vt 0.3334961 0.9026639 -vt 0.3334961 0.9682377 -vt 0.3491211 0.9682377 -vt 0.3491211 0.9026639 -vt 0.09619141 0.9252049 -vt 0.09619141 0.9579918 -vt 0.1274414 0.9579918 -vt 0.1274414 0.9252049 -vt 0.2055664 0.9252049 -vt 0.2055664 0.9907787 -vt 0.2211914 0.9907787 -vt 0.2211914 0.9252049 -vt 0.222168 0.9252049 -vt 0.222168 0.9907787 -vt 0.237793 0.9907787 -vt 0.237793 0.9252049 -vt 0.4575195 0.9272541 -vt 0.4575195 0.960041 -vt 0.4887695 0.960041 -vt 0.4887695 0.9272541 -vt 0.253418 0.9252049 -vt 0.253418 0.9907787 -vt 0.269043 0.9907787 -vt 0.269043 0.9252049 -vt 0.0004882812 0.9272541 -vt 0.0004882812 0.9928279 -vt 0.01611328 0.9928279 -vt 0.01611328 0.9272541 -vt 0.9458008 0.9293033 -vt 0.9458008 0.9620901 -vt 0.9770508 0.9620901 -vt 0.9770508 0.9293033 -vt 0.9780273 0.9272541 -vt 0.9780273 0.960041 -vt 0.9936523 0.960041 -vt 0.9936523 0.9272541 -vt 0.1420898 0.9293033 -vt 0.1420898 0.9620901 -vt 0.1577148 0.9620901 -vt 0.1577148 0.9293033 -vt 0.4897461 0.9293033 -vt 0.4897461 0.9620901 -vt 0.5053711 0.9620901 -vt 0.5053711 0.9293033 -vt 0.1586914 0.9313524 -vt 0.1586914 0.9641393 -vt 0.1743164 0.9641393 -vt 0.1743164 0.9313524 -vt 0.175293 0.9313524 -vt 0.175293 0.9641393 -vt 0.190918 0.9641393 -vt 0.190918 0.9313524 -vt 0.5375977 0.9313524 -vt 0.5375977 0.9641393 -vt 0.5532227 0.9641393 -vt 0.5532227 0.9313524 -vt 0.5541992 0.9313524 -vt 0.5541992 0.9641393 -vt 0.5698242 0.9641393 -vt 0.5698242 0.9313524 -vt 0.5708008 0.9313524 -vt 0.5708008 0.9641393 -vt 0.5864258 0.9641393 -vt 0.5864258 0.9313524 -vt 0.8666992 0.9313524 -vt 0.8666992 0.9641393 -vt 0.8823242 0.9641393 -vt 0.8823242 0.9313524 -vt 0.8833008 0.9313524 -vt 0.8833008 0.9641393 -vt 0.8989258 0.9641393 -vt 0.8989258 0.9313524 -vt 0.5981445 0.9334016 -vt 0.5981445 0.9661885 -vt 0.6137695 0.9661885 -vt 0.6137695 0.9334016 -vt 0.6147461 0.9334016 -vt 0.6147461 0.9661885 -vt 0.6303711 0.9661885 -vt 0.6303711 0.9334016 -vt 0.6791992 0.9334016 -vt 0.6791992 0.9661885 -vt 0.6948242 0.9661885 -vt 0.6948242 0.9334016 -vt 0.6958008 0.9334016 -vt 0.6958008 0.9661885 -vt 0.7114258 0.9661885 -vt 0.7114258 0.9334016 -vt 0.7124023 0.9334016 -vt 0.7124023 0.9661885 -vt 0.7280273 0.9661885 -vt 0.7280273 0.9334016 -vt 0.7290039 0.9334016 -vt 0.7290039 0.9661885 -vt 0.7446289 0.9661885 -vt 0.7446289 0.9334016 -vt 0.7456055 0.9334016 -vt 0.7456055 0.9661885 -vt 0.7612305 0.9661885 -vt 0.7612305 0.9334016 -vt 0.7924805 0.9334016 -vt 0.7924805 0.9661885 -vt 0.8081055 0.9661885 -vt 0.8081055 0.9334016 -vt 0.809082 0.9334016 -vt 0.809082 0.9661885 -vt 0.824707 0.9661885 -vt 0.824707 0.9334016 -vt 0.8256836 0.9334016 -vt 0.8256836 0.9661885 -vt 0.8413086 0.9661885 -vt 0.8413086 0.9334016 -vt 0.8422852 0.9334016 -vt 0.8422852 0.9661885 -vt 0.8579102 0.9661885 -vt 0.8579102 0.9334016 -vt 0.9135742 0.9354508 -vt 0.9135742 0.9682377 -vt 0.9291992 0.9682377 -vt 0.9291992 0.9354508 -vt 0.09619141 0.960041 -vt 0.09619141 0.9928279 -vt 0.1118164 0.9928279 -vt 0.1118164 0.960041 -vt 0.112793 0.960041 -vt 0.112793 0.9928279 -vt 0.128418 0.9928279 -vt 0.128418 0.960041 -vt 0.01708984 0.9620901 -vt 0.01708984 0.994877 -vt 0.03271484 0.994877 -vt 0.03271484 0.9620901 -vt 0.03369141 0.9620901 -vt 0.03369141 0.994877 -vt 0.04931641 0.994877 -vt 0.04931641 0.9620901 -vt 0.05029297 0.9620901 -vt 0.05029297 0.994877 -vt 0.06591797 0.994877 -vt 0.06591797 0.9620901 -vt 0.06689453 0.9620901 -vt 0.06689453 0.994877 -vt 0.08251953 0.994877 -vt 0.08251953 0.9620901 -vt 0.4575195 0.9620901 -vt 0.4575195 0.994877 -vt 0.4731445 0.994877 -vt 0.4731445 0.9620901 -vt 0.9780273 0.9620901 -vt 0.9780273 0.994877 -vt 0.9936523 0.994877 -vt 0.9936523 0.9620901 -vt 0.1420898 0.9641393 -vt 0.1420898 0.9969262 -vt 0.1577148 0.9969262 -vt 0.1577148 0.9641393 -vt 0.4897461 0.9641393 -vt 0.4897461 0.9969262 -vt 0.5053711 0.9969262 -vt 0.5053711 0.9641393 -vt 0.5063477 0.9641393 -vt 0.5063477 0.9969262 -vt 0.5219727 0.9969262 -vt 0.5219727 0.9641393 -vt 0.9458008 0.9641393 -vt 0.9458008 0.9969262 -vt 0.9614258 0.9969262 -vt 0.9614258 0.9641393 -vt 0.1586914 0.9661885 -vt 0.1586914 0.9989754 -vt 0.1743164 0.9989754 -vt 0.1743164 0.9661885 -vt 0.175293 0.9661885 -vt 0.175293 0.9989754 -vt 0.190918 0.9989754 -vt 0.190918 0.9661885 -vt 0.2836914 0.9661885 -vt 0.2836914 0.9989754 -vt 0.2993164 0.9989754 -vt 0.2993164 0.9661885 -vt 0.300293 0.9661885 -vt 0.300293 0.9989754 -vt 0.315918 0.9989754 -vt 0.315918 0.9661885 -vt 0.3168945 0.9661885 -vt 0.3168945 0.9989754 -vt 0.3325195 0.9989754 -vt 0.3325195 0.9661885 -vt 0.5375977 0.9661885 -vt 0.5375977 0.9989754 -vt 0.5532227 0.9989754 -vt 0.5532227 0.9661885 -vt 0.5541992 0.9661885 -vt 0.5541992 0.9989754 -vt 0.5698242 0.9989754 -vt 0.5698242 0.9661885 -vt 0.5708008 0.9661885 -vt 0.5708008 0.9989754 -vt 0.5864258 0.9989754 -vt 0.5864258 0.9661885 -vt 0.6459961 0.9661885 -vt 0.6459961 0.9989754 -vt 0.6616211 0.9989754 -vt 0.6616211 0.9661885 -g xinzexi_2_n-mesh_0 -f 3/3/3 2/2/2 1/1/1 -f 1/1/1 4/4/4 3/3/3 -f 7/7/7 6/6/6 5/5/5 -f 5/5/5 8/8/8 7/7/7 -f 11/11/11 10/10/10 9/9/9 -f 9/9/9 12/12/12 11/11/11 -f 15/15/15 14/14/14 13/13/13 -f 13/13/13 16/16/16 15/15/15 -f 19/19/19 18/18/18 17/17/17 -f 17/17/17 20/20/20 19/19/19 -f 23/23/23 22/22/22 21/21/21 -f 21/21/21 24/24/24 23/23/23 -f 27/27/27 26/26/26 25/25/25 -f 25/25/25 28/28/28 27/27/27 -f 31/31/31 30/30/30 29/29/29 -f 29/29/29 32/32/32 31/31/31 -f 35/35/35 34/34/34 33/33/33 -f 33/33/33 36/36/36 35/35/35 -f 39/39/39 38/38/38 37/37/37 -f 37/37/37 40/40/40 39/39/39 -f 43/43/43 42/42/42 41/41/41 -f 41/41/41 44/44/44 43/43/43 -f 47/47/47 46/46/46 45/45/45 -f 45/45/45 48/48/48 47/47/47 -f 51/51/51 50/50/50 49/49/49 -f 49/49/49 52/52/52 51/51/51 -f 55/55/55 54/54/54 53/53/53 -f 53/53/53 56/56/56 55/55/55 -f 59/59/59 58/58/58 57/57/57 -f 57/57/57 60/60/60 59/59/59 -f 63/63/63 62/62/62 61/61/61 -f 61/61/61 64/64/64 63/63/63 -f 67/67/67 66/66/66 65/65/65 -f 65/65/65 68/68/68 67/67/67 -f 71/71/71 70/70/70 69/69/69 -f 69/69/69 72/72/72 71/71/71 -f 75/75/75 74/74/74 73/73/73 -f 73/73/73 76/76/76 75/75/75 -f 79/79/79 78/78/78 77/77/77 -f 77/77/77 80/80/80 79/79/79 -f 83/83/83 82/82/82 81/81/81 -f 81/81/81 84/84/84 83/83/83 -f 87/87/87 86/86/86 85/85/85 -f 85/85/85 88/88/88 87/87/87 -f 91/91/91 90/90/90 89/89/89 -f 89/89/89 92/92/92 91/91/91 -f 95/95/95 94/94/94 93/93/93 -f 93/93/93 96/96/96 95/95/95 -f 99/99/99 98/98/98 97/97/97 -f 97/97/97 100/100/100 99/99/99 -f 103/103/103 102/102/102 101/101/101 -f 101/101/101 104/104/104 103/103/103 -f 107/107/107 106/106/106 105/105/105 -f 105/105/105 108/108/108 107/107/107 -f 111/111/111 110/110/110 109/109/109 -f 109/109/109 112/112/112 111/111/111 -f 115/115/115 114/114/114 113/113/113 -f 113/113/113 116/116/116 115/115/115 -f 119/119/119 118/118/118 117/117/117 -f 117/117/117 120/120/120 119/119/119 -f 123/123/123 122/122/122 121/121/121 -f 121/121/121 124/124/124 123/123/123 -f 127/127/127 126/126/126 125/125/125 -f 125/125/125 128/128/128 127/127/127 -f 131/131/131 130/130/130 129/129/129 -f 129/129/129 132/132/132 131/131/131 -f 135/135/135 134/134/134 133/133/133 -f 133/133/133 136/136/136 135/135/135 -f 139/139/139 138/138/138 137/137/137 -f 137/137/137 140/140/140 139/139/139 -f 143/143/143 142/142/142 141/141/141 -f 141/141/141 144/144/144 143/143/143 -f 147/147/147 146/146/146 145/145/145 -f 145/145/145 148/148/148 147/147/147 -f 151/151/151 150/150/150 149/149/149 -f 149/149/149 152/152/152 151/151/151 -f 155/155/155 154/154/154 153/153/153 -f 153/153/153 156/156/156 155/155/155 -f 159/159/159 158/158/158 157/157/157 -f 157/157/157 160/160/160 159/159/159 -f 163/163/163 162/162/162 161/161/161 -f 161/161/161 164/164/164 163/163/163 -f 167/167/167 166/166/166 165/165/165 -f 165/165/165 168/168/168 167/167/167 -f 171/171/171 170/170/170 169/169/169 -f 169/169/169 172/172/172 171/171/171 -f 175/175/175 174/174/174 173/173/173 -f 173/173/173 176/176/176 175/175/175 -f 179/179/179 178/178/178 177/177/177 -f 177/177/177 180/180/180 179/179/179 -f 183/183/183 182/182/182 181/181/181 -f 181/181/181 184/184/184 183/183/183 -f 187/187/187 186/186/186 185/185/185 -f 185/185/185 188/188/188 187/187/187 -f 191/191/191 190/190/190 189/189/189 -f 189/189/189 192/192/192 191/191/191 -f 195/195/195 194/194/194 193/193/193 -f 193/193/193 196/196/196 195/195/195 -f 199/199/199 198/198/198 197/197/197 -f 197/197/197 200/200/200 199/199/199 -f 203/203/203 202/202/202 201/201/201 -f 201/201/201 204/204/204 203/203/203 -f 207/207/207 206/206/206 205/205/205 -f 205/205/205 208/208/208 207/207/207 -f 211/211/211 210/210/210 209/209/209 -f 209/209/209 212/212/212 211/211/211 -f 215/215/215 214/214/214 213/213/213 -f 213/213/213 216/216/216 215/215/215 -f 219/219/219 218/218/218 217/217/217 -f 217/217/217 220/220/220 219/219/219 -f 223/223/223 222/222/222 221/221/221 -f 221/221/221 224/224/224 223/223/223 -f 227/227/227 226/226/226 225/225/225 -f 225/225/225 228/228/228 227/227/227 -f 231/231/231 230/230/230 229/229/229 -f 229/229/229 232/232/232 231/231/231 -f 235/235/235 234/234/234 233/233/233 -f 233/233/233 236/236/236 235/235/235 -f 239/239/239 238/238/238 237/237/237 -f 237/237/237 240/240/240 239/239/239 -f 243/243/243 242/242/242 241/241/241 -f 241/241/241 244/244/244 243/243/243 -f 247/247/247 246/246/246 245/245/245 -f 245/245/245 248/248/248 247/247/247 -f 251/251/251 250/250/250 249/249/249 -f 249/249/249 252/252/252 251/251/251 -f 255/255/255 254/254/254 253/253/253 -f 253/253/253 256/256/256 255/255/255 -f 259/259/259 258/258/258 257/257/257 -f 257/257/257 260/260/260 259/259/259 -f 263/263/263 262/262/262 261/261/261 -f 261/261/261 264/264/264 263/263/263 -f 267/267/267 266/266/266 265/265/265 -f 265/265/265 268/268/268 267/267/267 -f 271/271/271 270/270/270 269/269/269 -f 269/269/269 272/272/272 271/271/271 -f 275/275/275 274/274/274 273/273/273 -f 273/273/273 276/276/276 275/275/275 -f 279/279/279 278/278/278 277/277/277 -f 277/277/277 280/280/280 279/279/279 -f 283/283/283 282/282/282 281/281/281 -f 281/281/281 284/284/284 283/283/283 -f 287/287/287 286/286/286 285/285/285 -f 285/285/285 288/288/288 287/287/287 -f 291/291/291 290/290/290 289/289/289 -f 289/289/289 292/292/292 291/291/291 -f 295/295/295 294/294/294 293/293/293 -f 293/293/293 296/296/296 295/295/295 -f 299/299/299 298/298/298 297/297/297 -f 297/297/297 300/300/300 299/299/299 -f 303/303/303 302/302/302 301/301/301 -f 301/301/301 304/304/304 303/303/303 -f 307/307/307 306/306/306 305/305/305 -f 305/305/305 308/308/308 307/307/307 -f 311/311/311 310/310/310 309/309/309 -f 309/309/309 312/312/312 311/311/311 -f 315/315/315 314/314/314 313/313/313 -f 313/313/313 316/316/316 315/315/315 -f 319/319/319 318/318/318 317/317/317 -f 317/317/317 320/320/320 319/319/319 -f 323/323/323 322/322/322 321/321/321 -f 321/321/321 324/324/324 323/323/323 -f 327/327/327 326/326/326 325/325/325 -f 325/325/325 328/328/328 327/327/327 -f 331/331/331 330/330/330 329/329/329 -f 329/329/329 332/332/332 331/331/331 -f 335/335/335 334/334/334 333/333/333 -f 333/333/333 336/336/336 335/335/335 -f 339/339/339 338/338/338 337/337/337 -f 337/337/337 340/340/340 339/339/339 -f 343/343/343 342/342/342 341/341/341 -f 341/341/341 344/344/344 343/343/343 -f 347/347/347 346/346/346 345/345/345 -f 345/345/345 348/348/348 347/347/347 -f 351/351/351 350/350/350 349/349/349 -f 349/349/349 352/352/352 351/351/351 -f 355/355/355 354/354/354 353/353/353 -f 353/353/353 356/356/356 355/355/355 -f 359/359/359 358/358/358 357/357/357 -f 357/357/357 360/360/360 359/359/359 -f 363/363/363 362/362/362 361/361/361 -f 361/361/361 364/364/364 363/363/363 -f 367/367/367 366/366/366 365/365/365 -f 365/365/365 368/368/368 367/367/367 -f 371/371/371 370/370/370 369/369/369 -f 369/369/369 372/372/372 371/371/371 -f 375/375/375 374/374/374 373/373/373 -f 373/373/373 376/376/376 375/375/375 -f 379/379/379 378/378/378 377/377/377 -f 377/377/377 380/380/380 379/379/379 -f 383/383/383 382/382/382 381/381/381 -f 381/381/381 384/384/384 383/383/383 -f 387/387/387 386/386/386 385/385/385 -f 385/385/385 388/388/388 387/387/387 -f 391/391/391 390/390/390 389/389/389 -f 389/389/389 392/392/392 391/391/391 -f 395/395/395 394/394/394 393/393/393 -f 393/393/393 396/396/396 395/395/395 -f 399/399/399 398/398/398 397/397/397 -f 397/397/397 400/400/400 399/399/399 -f 403/403/403 402/402/402 401/401/401 -f 401/401/401 404/404/404 403/403/403 -f 407/407/407 406/406/406 405/405/405 -f 405/405/405 408/408/408 407/407/407 -f 411/411/411 410/410/410 409/409/409 -f 409/409/409 412/412/412 411/411/411 -f 415/415/415 414/414/414 413/413/413 -f 413/413/413 416/416/416 415/415/415 -f 419/419/419 418/418/418 417/417/417 -f 417/417/417 420/420/420 419/419/419 -f 423/423/423 422/422/422 421/421/421 -f 421/421/421 424/424/424 423/423/423 -f 427/427/427 426/426/426 425/425/425 -f 425/425/425 428/428/428 427/427/427 -f 431/431/431 430/430/430 429/429/429 -f 429/429/429 432/432/432 431/431/431 -f 435/435/435 434/434/434 433/433/433 -f 433/433/433 436/436/436 435/435/435 -f 439/439/439 438/438/438 437/437/437 -f 437/437/437 440/440/440 439/439/439 -f 443/443/443 442/442/442 441/441/441 -f 441/441/441 444/444/444 443/443/443 -f 447/447/447 446/446/446 445/445/445 -f 445/445/445 448/448/448 447/447/447 -f 451/451/451 450/450/450 449/449/449 -f 449/449/449 452/452/452 451/451/451 -f 455/455/455 454/454/454 453/453/453 -f 453/453/453 456/456/456 455/455/455 -f 459/459/459 458/458/458 457/457/457 -f 457/457/457 460/460/460 459/459/459 -f 463/463/463 462/462/462 461/461/461 -f 461/461/461 464/464/464 463/463/463 -f 467/467/467 466/466/466 465/465/465 -f 465/465/465 468/468/468 467/467/467 -f 471/471/471 470/470/470 469/469/469 -f 469/469/469 472/472/472 471/471/471 -f 475/475/475 474/474/474 473/473/473 -f 473/473/473 476/476/476 475/475/475 -f 479/479/479 478/478/478 477/477/477 -f 477/477/477 480/480/480 479/479/479 -f 483/483/483 482/482/482 481/481/481 -f 481/481/481 484/484/484 483/483/483 -f 487/487/487 486/486/486 485/485/485 -f 485/485/485 488/488/488 487/487/487 -f 491/491/491 490/490/490 489/489/489 -f 489/489/489 492/492/492 491/491/491 -f 495/495/495 494/494/494 493/493/493 -f 493/493/493 496/496/496 495/495/495 -f 499/499/499 498/498/498 497/497/497 -f 497/497/497 500/500/500 499/499/499 -f 503/503/503 502/502/502 501/501/501 -f 501/501/501 504/504/504 503/503/503 -f 507/507/507 506/506/506 505/505/505 -f 505/505/505 508/508/508 507/507/507 -f 511/511/511 510/510/510 509/509/509 -f 509/509/509 512/512/512 511/511/511 -f 515/515/515 514/514/514 513/513/513 -f 513/513/513 516/516/516 515/515/515 -f 519/519/519 518/518/518 517/517/517 -f 517/517/517 520/520/520 519/519/519 -f 523/523/523 522/522/522 521/521/521 -f 521/521/521 524/524/524 523/523/523 -f 527/527/527 526/526/526 525/525/525 -f 525/525/525 528/528/528 527/527/527 -f 531/531/531 530/530/530 529/529/529 -f 529/529/529 532/532/532 531/531/531 -f 535/535/535 534/534/534 533/533/533 -f 533/533/533 536/536/536 535/535/535 -f 539/539/539 538/538/538 537/537/537 -f 537/537/537 540/540/540 539/539/539 -f 543/543/543 542/542/542 541/541/541 -f 541/541/541 544/544/544 543/543/543 -f 547/547/547 546/546/546 545/545/545 -f 545/545/545 548/548/548 547/547/547 -f 551/551/551 550/550/550 549/549/549 -f 549/549/549 552/552/552 551/551/551 -f 555/555/555 554/554/554 553/553/553 -f 553/553/553 556/556/556 555/555/555 -f 559/559/559 558/558/558 557/557/557 -f 557/557/557 560/560/560 559/559/559 -f 563/563/563 562/562/562 561/561/561 -f 561/561/561 564/564/564 563/563/563 -f 567/567/567 566/566/566 565/565/565 -f 565/565/565 568/568/568 567/567/567 -f 571/571/571 570/570/570 569/569/569 -f 569/569/569 572/572/572 571/571/571 -f 575/575/575 574/574/574 573/573/573 -f 573/573/573 576/576/576 575/575/575 -f 579/579/579 578/578/578 577/577/577 -f 577/577/577 580/580/580 579/579/579 -f 583/583/583 582/582/582 581/581/581 -f 581/581/581 584/584/584 583/583/583 -f 587/587/587 586/586/586 585/585/585 -f 585/585/585 588/588/588 587/587/587 -f 591/591/591 590/590/590 589/589/589 -f 589/589/589 592/592/592 591/591/591 -f 595/595/595 594/594/594 593/593/593 -f 593/593/593 596/596/596 595/595/595 -f 599/599/599 598/598/598 597/597/597 -f 597/597/597 600/600/600 599/599/599 -f 603/603/603 602/602/602 601/601/601 -f 601/601/601 604/604/604 603/603/603 -f 607/607/607 606/606/606 605/605/605 -f 605/605/605 608/608/608 607/607/607 -f 611/611/611 610/610/610 609/609/609 -f 609/609/609 612/612/612 611/611/611 -f 615/615/615 614/614/614 613/613/613 -f 613/613/613 616/616/616 615/615/615 -f 619/619/619 618/618/618 617/617/617 -f 617/617/617 620/620/620 619/619/619 -f 623/623/623 622/622/622 621/621/621 -f 621/621/621 624/624/624 623/623/623 -f 627/627/627 626/626/626 625/625/625 -f 625/625/625 628/628/628 627/627/627 -f 631/631/631 630/630/630 629/629/629 -f 629/629/629 632/632/632 631/631/631 -f 635/635/635 634/634/634 633/633/633 -f 633/633/633 636/636/636 635/635/635 -f 639/639/639 638/638/638 637/637/637 -f 637/637/637 640/640/640 639/639/639 -f 643/643/643 642/642/642 641/641/641 -f 641/641/641 644/644/644 643/643/643 -f 647/647/647 646/646/646 645/645/645 -f 645/645/645 648/648/648 647/647/647 -f 651/651/651 650/650/650 649/649/649 -f 649/649/649 652/652/652 651/651/651 -f 655/655/655 654/654/654 653/653/653 -f 653/653/653 656/656/656 655/655/655 -f 659/659/659 658/658/658 657/657/657 -f 657/657/657 660/660/660 659/659/659 -f 663/663/663 662/662/662 661/661/661 -f 661/661/661 664/664/664 663/663/663 -f 667/667/667 666/666/666 665/665/665 -f 665/665/665 668/668/668 667/667/667 -f 671/671/671 670/670/670 669/669/669 -f 669/669/669 672/672/672 671/671/671 -f 675/675/675 674/674/674 673/673/673 -f 673/673/673 676/676/676 675/675/675 -f 679/679/679 678/678/678 677/677/677 -f 677/677/677 680/680/680 679/679/679 -f 683/683/683 682/682/682 681/681/681 -f 681/681/681 684/684/684 683/683/683 -f 687/687/687 686/686/686 685/685/685 -f 685/685/685 688/688/688 687/687/687 -f 691/691/691 690/690/690 689/689/689 -f 689/689/689 692/692/692 691/691/691 -f 695/695/695 694/694/694 693/693/693 -f 693/693/693 696/696/696 695/695/695 -f 699/699/699 698/698/698 697/697/697 -f 697/697/697 700/700/700 699/699/699 -f 703/703/703 702/702/702 701/701/701 -f 701/701/701 704/704/704 703/703/703 -f 707/707/707 706/706/706 705/705/705 -f 705/705/705 708/708/708 707/707/707 -f 711/711/711 710/710/710 709/709/709 -f 709/709/709 712/712/712 711/711/711 -f 715/715/715 714/714/714 713/713/713 -f 713/713/713 716/716/716 715/715/715 -f 719/719/719 718/718/718 717/717/717 -f 717/717/717 720/720/720 719/719/719 -f 723/723/723 722/722/722 721/721/721 -f 721/721/721 724/724/724 723/723/723 +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 129061bac..792e3626d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,7 +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") @@ -9,22 +14,44 @@ 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(): 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() @@ -33,15 +60,30 @@ def test_sprite(): 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(): + 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() + if obj.type.name == "AudioClip": + clip = obj.parse_as_object() assert len(clip.samples) == 1 @@ -50,8 +92,8 @@ def test_mesh(): 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 == "Mesh": - mesh = obj.read() + 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"") @@ -64,6 +106,19 @@ def test_read_typetree(): obj.read_typetree() +def test_save(): + env = UnityPy.load(SAMPLES) + # TODO - check against original + # this only makes sure + # that the save function still produces a readable file + for name, file in env.files.items(): + if isinstance(file, EndianBinaryReader): + continue + save1 = file.save() + save2 = UnityPy.load(save1).file.save() + assert save1 == save2, f"Failed to save {name} correctly" + + if __name__ == "__main__": for x in list(locals()): if str(x)[:4] == "test": 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")