Skip to main content

unity-rs for Python

unity-rs is the native Python distribution for unity-rs; import it as unity_rs. It binds directly to the safe, bounded unity_rs_core::studio::Studio API through PyO3; it does not load or call the legacy C ABI.

The initial API supports:

  • Unity asset, bundle, web-container, split-file, and directory loading;
  • direct bounded in-memory asset, bundle, and web-container loading with UnityRs.from_bytes(...);
  • deterministic lazy or bounded-paged serialized-file and object enumeration;
  • deterministic lazy or bounded-paged external-resource enumeration plus bounded reads by stable index or portable path;
  • safe recursive bundle/web/ZIP/gzip/Brotli extraction with cumulative limits, no-follow paths, and atomic publication;
  • optional user-supplied Oodle block decoding for loading and extraction, exposed as an exact-size Python callable without linking or redistributing a proprietary Oodle library;
  • bounded collection-wide GameObject hierarchy, managed-compatible SplitObjects/Animator candidate planning, and model bindings;
  • bounded ASCII FBX 7.4 generation, including direct or bone-name-hash-recovered skin clusters, static blend shapes, explicit/packed legacy curves, and standard streamed/dense/constant Transform or blend-shape samples;
  • bounded in-memory Live2D MOC/model3/mip-zero PNG packages with schema-verified expression, pose, display-info, physics, and fade-motion files, Animator-bound AnimationClip fallback motions, and explicit or inferred EyeBlink/LipSync parameter groups;
  • bounded raw, TextAsset, managed-compatible Shader text and resident/external-stream Mesh OBJ for standard Unity and non-virtual Tuanjie 2022.3.x meshes, embedded-TypeTree JSON/managed-compatible text dumps, and externally-schemaed stripped MonoBehaviour reads without loading or executing managed DLLs;
  • direct, bounded MonoScript assembly, namespace, class, execution-order, and editor-script metadata for selecting trusted external schemas;
  • bounded BuildSettings scene/level paths and PlayerSettings company/product metadata reads;
  • bounded Unity and Tuanjie AnimationClip curve, muscle, ACL, and external streaming metadata, including the 2022.3.48t3/55t1/55t4/61t1 field gates, plus source-bound ACL 2.x outer-track inspection with hash and output-budget validation, bounded retrieval of the exact compressed blob plus decoder map, and a safe Python callable decoder boundary used by decoded-track metadata, full or selected-model FBX, direct Cubism projection, and Live2D package fallback motions (a bundled pure-Rust Tuanjie ACL decompressor remains an explicit gap);
  • complete bounded Unity/Tuanjie AnimatorController and Avatar parsing, exposed as stable TOS, clip-reference, skeleton, and HumanDescription metadata;
  • bounded embedded-TypeTree CubismExpressionData projection, including the generated exp3.json bytes;
  • bounded embedded-TypeTree Cubism pose/display-info/physics/fade-motion projections plus AnimationClip binding projection and generated JSON bytes;
  • bounded Texture2D mip, ordered Texture2DArray layer, and display-order Sprite decoding to RGBA8 bytes, including legacy/modern tight-mesh masks and resolved SpriteAtlas data, including collection-level atlas backfill and variant replacement;
  • source-bound AudioClip reads with verified WAV output for pre-2.6 PCM16, existing RIFF/WAVE payloads, FSB5 PCM8/16/24/32/float, and pure-Rust FMOD/Xbox IMA-ADPCM, Nintendo DSP/GC-ADPCM, Sony VAG/PS-ADPCM and HEVAG, FMOD FADPCM, MPEG Layer II/III including every 3-16 channel FSB multistream count checked against an independent decoder with FMOD frame padding, and sample-verified standard 48 kHz FSB Opus from mono through 7.1 with every 3-8 channel family-1 multistream mapping checked against an independent decoder and a 312-frame encoder delay, plus pure-Rust FSB Vorbis from mono through 7.1 with every 3-8 channel Vorbis-to-WAVE speaker permutation checked against an independent decoder and the 161-entry FMOD setup-header table; remaining platform codecs are preserved raw until a verified pure-Rust decoder is available;
  • direct embedded Font, legacy MovieTexture, and inline or externally streamed VideoClip payload reads, distinct from serialized wrapper bytes; and
  • structured, order-preserving Material shader references, keywords, tags, texture environments, integer/float/color properties, and duplicate entries;
  • bounded, atomic export to JPEG/PNG/BMP/TGA/lossless WebP/raw RGBA, text, TypeTree dump/JSON, OBJ, and other formats implemented by the Rust Core.
from pathlib import Path

from unity_rs import (
    UnityRs,
    CubismMotionTargets,
    ExportLimits,
    ExtractionLimits,
    MonoBehaviourSchema,
    MonoBehaviourSchemas,
    extract,
)

studio = UnityRs(
    "game_Data",
    maximum_input_files=100_000,
    maximum_input_directories=100_000,
    maximum_directory_entries=200_000,
    maximum_path_bytes=1_048_576,
    maximum_total_path_bytes=67_108_864,
    maximum_diagnostic_bytes=256 * 1024 * 1024,
    skip_unreadable_inputs=True,
)

memory_studio = UnityRs.from_bytes(downloaded_bundle, name="download.bundle")
memory_collection = UnityRs.from_memory_files(
    [
        ("sharedassets0.assets", downloaded_assets),
        ("sharedassets0.resource", downloaded_resource),
    ]
)

# Oodle remains opt-in. The callback receives one compressed block and the
# exact expected output length; returning any other type or length is rejected.
def decode_oodle(block: bytes, expected_size: int) -> bytes:
    return my_licensed_oodle_wrapper.decompress(block, expected_size)

oodle_studio = UnityRs("oodle.bundle", oodle_decoder=decode_oodle)

for obj in studio.iter_objects():
    print(obj.file_index, obj.path_id, obj.class_id, obj.name)

for resource in studio.iter_resources():
    print(resource.index, resource.path, resource.byte_size)
resource_bytes = studio.read_resource_by_path("sharedassets0.resource")
header = studio.read_resource_range(0, offset=0, length=4096)

# Tolerant loads retain only bounded skipped-input metadata and expose it by
# page instead of copying the whole diagnostic table at once.
print(studio.load_diagnostic_count)
for diagnostic in studio.load_diagnostic_page(offset=0, limit=4096):
    print(diagnostic.path, diagnostic.message)

# Page one serialized file without copying the rest of its object table.
page = studio.object_page(0, offset=0, limit=4096)

extraction = extract(
    "bundle.ab",
    "unpacked",
    limits=ExtractionLimits(
        maximum_output_bytes=4 * 1024 * 1024 * 1024,
        maximum_total_path_bytes=64 * 1024 * 1024,
        maximum_metadata_bytes=256 * 1024 * 1024,
    ),
)
# The same callback can be supplied to recursive extraction.
oodle_extraction = extract(
    "oodle.bundle",
    "oodle-unpacked",
    oodle_decoder=decode_oodle,
)
for failure in extraction.failures:
    print(failure.source, failure.error)

# maximum_total_path_bytes bounds traversal and recursive labels;
# maximum_metadata_bytes separately bounds the retained success, skip, and
# failure report strings returned to Python.

# Supply a complete Unity object tree produced by a trusted offline schema
# tool. The assembly name is an identity only; no DLL is opened or executed.
schema = MonoBehaviourSchema(
    "Assembly-CSharp.dll",
    "Stats",
    [
        ("MonoBehaviour", "Base", 0, False),
        ("PPtr<GameObject>", "m_GameObject", 1, False),
        ("int", "m_FileID", 2, False),
        ("SInt64", "m_PathID", 2, False),
        ("UInt8", "m_Enabled", 1, True),
        ("PPtr<MonoScript>", "m_Script", 1, False),
        ("int", "m_FileID", 2, False),
        ("SInt64", "m_PathID", 2, False),
        ("string", "m_Name", 1, False),
        ("Array", "Array", 2, True),
        ("int", "size", 3, False),
        ("char", "data", 3, False),
        ("SInt32", "score", 1, False),
    ],
    namespace="Game",
)
stats = studio.read_mono_behaviour_json(0, 114, schema)

# Discover the managed identity used to select that schema. This reads Unity
# metadata only; the named assembly is never opened or executed.
script = studio.read_mono_script(0, 115)
print(script.assembly_name, script.namespace, script.class_name)

# Package planning accepts many independently generated schemas and still
# never opens the managed assemblies named by those schemas.
schemas = MonoBehaviourSchemas([schema])
stats = studio.read_mono_behaviour_json_with_schemas(0, 114, schemas)

image = studio.read_texture(0, 1234, maximum_bytes=256 * 1024 * 1024)
assert len(image.rgba) == image.width * image.height * 4
layers = studio.read_texture_array(0, 187, maximum_bytes=256 * 1024 * 1024)
assert all(len(layer.rgba) == layer.width * layer.height * 4 for layer in layers)
sprite = studio.read_sprite(0, 213, maximum_bytes=256 * 1024 * 1024)
assert len(sprite.rgba) == sprite.width * sprite.height * 4

audio = studio.read_audio_clip(0, 83, format="auto")
Path("audio" + audio.extension).write_bytes(audio.data)

font = studio.read_font(0, 128)
Path("font" + font.extension).write_bytes(font.data)
movie = studio.read_movie_texture(0, 152)
Path("movie" + movie.extension).write_bytes(movie.data)
video = studio.read_video_clip(0, 329)
Path("video" + video.extension).write_bytes(video.data)

material = studio.read_material(0, 21)
print(material.shader, material.texture_environments, material.colors)

shader = studio.read_shader(0, 48, maximum_bytes=256 * 1024 * 1024)
Path("shader.shader").write_bytes(shader)

mesh_obj = studio.read_mesh_obj(0, 43, maximum_bytes=256 * 1024 * 1024)
Path("mesh.obj").write_bytes(mesh_obj)

clip = studio.read_animation_clip(0, 74)
print(clip.name, clip.muscle_present, clip.acl_present, clip.streaming_path)
controller = studio.read_animator_controller(0, 91)
avatar = studio.read_avatar(0, 90)
print(controller.animation_clips, avatar.paths)

for candidate in studio.split_object_fbx_candidates():
    fbx = studio.read_game_object_fbx(
        candidate.file_index,
        candidate.path_id,
        include_animations=False,
    )
    Path(candidate.name + ".fbx").write_bytes(fbx)

dump = studio.read_type_tree_dump(0, 1234)
assert "\r\n" in dump

build = studio.read_build_settings(0, 141)
print(build.scenes or build.levels)
player = studio.read_player_settings(0, 129)
print(player.company_name, player.product_name)
expression = studio.read_cubism_expression(0, 114)
print(expression.source_name, expression.parameters[0].blend)
physics = studio.read_cubism_physics(0, 115)
motion = studio.read_cubism_fade_motion(0, 116)
clip_motion = studio.read_cubism_clip_motion(
    0,
    117,
    targets=CubismMotionTargets(
        parameters=["ParamAngleX"],
        parts=["PartBody"],
    ),
)

live2d = studio.read_live2d_packages(
    schemas=schemas,
    maximum_total_bytes=2 * 1024 * 1024 * 1024,
)
for package in live2d.packages:
    print(package.directory_name, package.moc_file_name)

report = studio.export(
    "exported",
    image_format="png",
    limits=ExportLimits(
        maximum_objects=100_000,
        maximum_total_output_bytes=4 * 1024 * 1024 * 1024,
        maximum_metadata_bytes=256 * 1024 * 1024,
    ),
)
for failure in report.failures:
    print(failure)

# JPEG is lossy, discards alpha like the managed exporter, and accepts quality
# values from 1 through 100. PNG remains the default.
jpeg_report = studio.export("jpeg-export", image_format="jpeg", jpeg_quality=90)

# The original tab-indented CRLF TypeTree text is distinct from JSON.
dump_report = studio.export("dumped", mode="dump_text")

All materializing operations accept explicit limits or use conservative defaults. Bulk export additionally limits the object count and cumulative published bytes. iter_files() and iter_objects() are lazy and keep their originating UnityRs alive; files() and objects() remain convenience lists with a one-million-entry safety ceiling. CPU-heavy parsing and export release Python's GIL. The pure-Rust validation, node conversion, and registry construction performed by MonoBehaviourSchema do too. Its Python-list length and cumulative UTF-8 budget are checked before copying the input; only this bounded conversion of the caller's Python strings and tuples remains under the GIL. Export never follows symbolic-link destinations and publishes files atomically; by default it does not overwrite existing files.

Caller-controlled lists are preflighted before conversion into owned Rust vectors. This includes in-memory file tables, schema collections, Cubism target names, and ACL decoder output; file counts, UTF-8/byte totals, and ACL frame/curve/value limits are checked before their elements are copied. Core also rejects request-declared ACL work beyond those limits before invoking the Python callback at all.

This package is beta while the separately maintained .NET implementation remains an optional format oracle. GUI support and further C ABI parity are not package goals.

Local development

With Rust 1.88+, Python 3.9+, and Maturin installed:

cd crates/unity-rs-python
python -m venv .venv
source .venv/bin/activate
maturin develop --locked
python tests/python_api.py

Release packages

The cp39-abi3 wheel is built as an optimized release artifact and is usable with CPython 3.9 and newer. CI builds native wheels for x86-64 and ARM64 Linux, Windows, and macOS targets, installs every wheel into its build interpreter, then installs the same wheel into CPython 3.14 and reruns the complete API fixture. The Linux x86-64 job also builds the wheel through the generated source distribution so missing workspace or Python-package files fail before publication.

To reproduce the dependency-locked release wheel locally:

cd crates/unity-rs-python
maturin build --release --locked --compatibility pypi --out dist
python -m pip install --force-reinstall --no-deps dist/*.whl
python -I tests/python_api.py

Build and compile-check the source distribution separately:

maturin build --release --sdist --compatibility pypi --out sdist-dist
python tests/sdist_contents.py sdist-dist

Maturin deliberately prunes the workspace inside the source distribution to Core + Python. Its internal source-build step therefore cannot use --locked: Cargo first removes CLI/Node-only entries copied from the full workspace lock. The wheel published by CI is still built from the checked-in lock file, while the second command proves that the source distribution is complete and builds successfully in its pruned workspace.

Release files for unity-rs 0.4.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for unity-rs 0.4.0
File Size Uploaded
unity_rs-0.4.0.tar.gz 1.9 MB Details

Built distributions (wheels)

Table of built distributions (wheels) for unity-rs 0.4.0
File
unity_rs-0.4.0-cp39-abi3-win_arm64.whl CPython 3.9 abi3 Windows ARM64 Details
unity_rs-0.4.0-cp39-abi3-win_amd64.whl CPython 3.9 abi3 Windows x86-64 Details
unity_rs-0.4.0-cp39-abi3-manylinux_2_28_x86_64.whl CPython 3.9 abi3 Linux glibc 2.28+ x86-64 Details
unity_rs-0.4.0-cp39-abi3-manylinux_2_28_aarch64.whl CPython 3.9 abi3 Linux glibc 2.28+ ARM64 Details
unity_rs-0.4.0-cp39-abi3-macosx_11_0_arm64.whl CPython 3.9 abi3 macOS 11.0+ ARM64 Details
unity_rs-0.4.0-cp39-abi3-macosx_10_12_x86_64.whl CPython 3.9 abi3 macOS 10.12+ x86-64 Details

Total release size:18.2 MB

Release files / unity_rs-0.4.0.tar.gz

Download URL unity_rs-0.4.0.tar.gz
Size 1.9 MB
Tags Source
SHA-256 checksum
How to use checksums
458af5dd03450f168d4aafe6acd0c6d2851db97a99ac6790fb42ea3aaefbd124
BLAKE2b-256 checksum
How to use checksums
34b25abe01c8bc6255513fa9da3d290e2bb7d323b9450784a2a6d4e514f891f4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.

Transparency log

Release files / unity_rs-0.4.0-cp39-abi3-win_arm64.whl

Download URL unity_rs-0.4.0-cp39-abi3-win_arm64.whl
Size 2.6 MB
Tags CPython 3.9 Windows ARM64 abi3
SHA-256 checksum
How to use checksums
d019afbbe8c4d61e2f4b66f73248f1accf9b4a4e277c3025af2ad867abd7a1c9
BLAKE2b-256 checksum
How to use checksums
0b6d8e550f2f1827b9fe0bf96c78b18d49c58a3265dd529d84f385ca16f78cc5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.

Transparency log

Release files / unity_rs-0.4.0-cp39-abi3-win_amd64.whl

Download URL unity_rs-0.4.0-cp39-abi3-win_amd64.whl
Size 2.8 MB
Tags CPython 3.9 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
031c87746b2e799ede3da85dd4c6ec416c6bec53e5e014bdbebf21ab85d2bdcc
BLAKE2b-256 checksum
How to use checksums
664f662117f7923f28adb0b1a5f82b5e5745a3833318ad1ac1ebff963375ec25
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.

Transparency log

Release files / unity_rs-0.4.0-cp39-abi3-manylinux_2_28_x86_64.whl

Download URL unity_rs-0.4.0-cp39-abi3-manylinux_2_28_x86_64.whl
Size 2.9 MB
Tags CPython 3.9 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
4087cef6f7cce1542d6facbe2eec7a6b8ad6ae1e706df5c5649cfc79830cc1af
BLAKE2b-256 checksum
How to use checksums
4d23715880d2c571d1b5f0124e0b249adb036365d42d6aa154e235ceacfd1cf7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.

Transparency log

Release files / unity_rs-0.4.0-cp39-abi3-manylinux_2_28_aarch64.whl

Download URL unity_rs-0.4.0-cp39-abi3-manylinux_2_28_aarch64.whl
Size 2.7 MB
Tags CPython 3.9 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
36842c6576eea00b95bb893341d2bab10f40aa3d8ad8490ba09e3dd35a9a4d47
BLAKE2b-256 checksum
How to use checksums
accd0b4aa33a9331ac29caa9a5eef8a2e888771f0d858816cb4a19959b5f1a8e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.

Transparency log

Release files / unity_rs-0.4.0-cp39-abi3-macosx_11_0_arm64.whl

Download URL unity_rs-0.4.0-cp39-abi3-macosx_11_0_arm64.whl
Size 2.6 MB
Tags CPython 3.9 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
9c91f8bbd3c9efb207862d79616d560f7d9a25d4438b79963eb28c86d8f28cc1
BLAKE2b-256 checksum
How to use checksums
0ce4e46148b1e897823edcb674a25c8312337d5ed5beb6a2974943df9bf070c0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.

Transparency log

Release files / unity_rs-0.4.0-cp39-abi3-macosx_10_12_x86_64.whl

Download URL unity_rs-0.4.0-cp39-abi3-macosx_10_12_x86_64.whl
Size 2.7 MB
Tags CPython 3.9 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
990ccc838f219b0fee9deb57d1faf161b89f67ed8ec2bcf0fe17fb40a6997853
BLAKE2b-256 checksum
How to use checksums
4c1c51e687dd8170c86be41c679c89a5d7ed62c5a61b5e3f4f8c0326cddf2af4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Aug 27, 2026.

Transparency log

Release history Release notifications | RSS feed

0.5.1

7 release files

0.5.0

7 release files

This release

0.4.0 This release

7 release files

0.3.0

7 release files

0.2.0

7 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page