The format: gzip + tar, GUID directories

avatar unitypackage info avatar.unitypackage avatar unitypackage list avatar.unitypackage avatar unitypackage extract avatar.unitypackage -o proj avatar unitypackage testbed avatar.unitypackage world.unitypackage --strict

A .unitypackage is a gzip-compressed tar. There is no manifest; every asset is a top-level directory named by its Unity GUID (32 hex chars), containing:

MemberContents
pathnameThe project-relative path the asset had on export, e.g. Assets/Avatar/final.fbx. First non-empty line is the path; Unity occasionally appends trailing lines, which are ignored.
assetThe raw asset bytes. Absent for folder entries (a directory has a pathname and a .meta but no asset).
asset.metaThe Unity .meta sidecar (YAML) — same guid, plus import settings.
preview.pngOptional thumbnail. Ignored.

The reader loads the whole archive into memory and indexes it by GUID — built for one-shot CLI runs, not streaming. A large world export can be ~1 GB uncompressed; the testbed cross-check of a ~90 MB avatar against a ~700 MB world peaks around 1.1 GB RSS.

Summarizing — avatar unitypackage info

UnityPackage::summary() reports entry/file/folder counts, total asset bytes, a size breakdown by extension, and heuristic PackageTraits:

  • vrc_sdkSdk2, Sdk3Avatars, Sdk3Worlds, Unknown, or none. Determined from the runtime plugin DLLs (VRCSDK2.dll vs VRCSDK3*.dll) and VPM package paths (com.vrchat.avatars / com.vrchat.worlds) — not from VRCSDK/version.txt, which is date-based and whose 2021-era bundle shipped SDK2 and SDK3 editor scripts in one folder, making the source tree alone ambiguous.
  • looks_like_avatar — a VRChat avatar prefab (prefab-id-v1_avtr_*.prefab) is present.
  • looks_like_world — scenes present and no avatar prefab.

Extracting — avatar unitypackage extract

extract reconstructs a normal Unity project tree: each asset at <dest>/<pathname> with its .meta beside it. Because the lint/stats and FBX/armature tools only need files on disk plus the .meta GUID index, an extracted package is immediately consumable:

avatar unitypackage extract avatar.unitypackage -o /tmp/proj avatar lint /tmp/proj avatar stats /tmp/proj/Assets/Avatar/.../final.fbx avatar armature check /tmp/proj/Assets/Avatar/.../final.fbx
Non-project paths are refused
Legitimate assets are always project-relative (Assets/, Packages/, ProjectSettings/, …), but old SDK exports sometimes leak absolute paths to bundled editor DLLs. The extractor rejects absolute POSIX paths, Windows drive letters (C:/… — important on non-Windows hosts, where the std path parser treats C: as an ordinary segment), UNC roots, and .. traversal; these are counted in ExtractReport::skipped_unsafe and not written.

The avatar-in-world testbed

avatar unitypackage testbed avatar.unitypackage world.unitypackage

Testing an avatar "in a map" offline is, at the file level, the question: if I import both packages into one Unity project, what breaks? UnityPackage::overlap answers it:

  • GUID collisions — the same GUID in both packages. Flagged identical when the asset bytes match (a harmless duplicate); otherwise Unity keeps whichever was imported last, silently changing one package's asset — e.g. two shader or SDK versions fighting.
  • Path collisions — different GUIDs claiming the same Assets/... path. The second import overwrites the first file on disk while keeping a different GUID, so references can dangle.

--strict exits non-zero when any different-bytes GUID collision or path collision exists, for gating; --json emits both package summaries and the full overlap. A clean result means the avatar can be dropped into the world's project to preview scale, lighting, and placement without clobbering the world's assets.

Cross-checking two platform variants of one world (a PC vs Quest export) is expected to show large overlap with many content-conflicting GUIDs — platform-recompressed textures/materials and serialized Udon programs.

Notes

SDK2 avatars surface their age through the rest of the toolchain naturally: avatar lint reports VRC001 (no com.vrchat.avatars package) and finds no SDK3 descriptor, and avatar stats on the project finds no avatar because the prefab carries the SDK2 VRC_AvatarDescriptor. Run avatar stats <fbx> directly for the geometry rank regardless — or take the SDK2 package through avatar migrate sdk3.