Skip to main content
The database downloader fetches and processes three binary VRChat avatar database files — PC, Quest, and iOS — decrypts them, and writes the result to a local cache for the avatar browser to stream.

Download sources

The app fetches from GitHub Gist primary URLs, with automatic fallback to prismic.net mirror URLs if a primary request fails.
DatabasePrimary URLBackup URL
PC (main)gist.githubusercontent.com/Mwr247/.../pasavtrdb.txtprismic.net/vrc/pasavtrdb.txt
Questgist.githubusercontent.com/Mwr247/.../pasavtrdb_qst.txtprismic.net/vrc/pasavtrdb_qst.txt
iOSgist.githubusercontent.com/Mwr247/.../pasavtrdb_ios.txtprismic.net/vrc/pasavtrdb.txt

Download steps

1

Downloading main database

Fetches pasavtrdb.txt — the primary PC avatar database. This file contains all avatar metadata: IDs, names, descriptions, authors, and tags.
2

Downloading Quest database

Fetches pasavtrdb_qst.txt — a compact auxiliary file containing only the avatar IDs available on Quest. These IDs are cross-referenced against the main database to set the quest flag on matching entries.
3

Downloading iOS database

Fetches pasavtrdb_ios.txt — the same structure as the Quest auxiliary file, used to set the ios flag on matching entries.
4

Processing data

Parses and decrypts all three binary files, merges the platform flags into each entry, and streams the results line-by-line into cache/avatar_entries.ndjson. Progress is reported every 10,000 entries.

Binary format and decryption

All three database files share a custom binary format with a PAS magic header. The main database uses a Reader struct for sequential byte-level parsing. Avatar IDs are stored as 16-byte encrypted blocks. Decryption works by XOR-combining 16 random bytes read from the file header with a compile-time constant:
const STATIC_BYTES: [u8; 16] = [
    208, 29, 107, 36, 251, 69, 122, 14,
    67, 204, 171, 246, 106, 38, 183, 224
];

// dynamic_bytes = random_bytes XOR STATIC_BYTES
let dynamic_bytes: Vec<u8> = random_bytes
    .iter()
    .zip(STATIC_BYTES.iter())
    .map(|(a, b)| a ^ b)
    .collect();
Each 16-byte avatar ID block is then decrypted using a chained XOR pass with nibble-swapping, producing a UUID formatted as avtr_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. The Reader struct exposes typed reads over the raw byte slice:
  • read_byte() — reads a single byte
  • read_bytes(n) — reads n bytes
  • read_int24() — reads a 3-byte big-endian integer
  • read_int_array(n) — reads n little-endian i32 values
Author names and avatar name/description strings are stored reversed and separated by \r characters inside a newline-delimited string block at the end of the file.

Data structures

After parsing, each avatar is represented as an AvatarEntry:
{
  "name": "Cozy Fox",
  "author": "SomeCreator",
  "description": "A fluffy avatar with custom shaders.",
  "avatar_id": "avtr_a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "tags": ["furry", "full-body"],
  "quest": true,
  "ios": false
}
FieldTypeDescription
nameStringDisplay name of the avatar
authorStringVRChat display name of the creator
descriptionStringAvatar description text
avatar_idStringFull VRChat avatar ID (avtr_...)
tagsVec<String>Comma-separated tags extracted from the database
questboolWhether the avatar has a Quest-compatible build
iosboolWhether the avatar has an iOS-compatible build
The aggregate AvatarData struct records database-level statistics:
FieldTypeDescription
avatar_countu32Total avatar count from the file header
author_countu32Unique author count, computed during processing
last_updateStringDatabase date, decoded from a packed 2-byte field (YYYY-MM-DD)
entriesVec<AvatarEntry>Avatar entries (empty in cached summary; full data is in the NDJSON stream)

Output files

All output is written to the cache/ directory:
FileDescription
cache/avatar_entries.ndjsonStreaming NDJSON file — one AvatarEntry JSON object per line. Read by the avatar browser.
cache/cache_meta.jsonStores the gist commit version hash and a saved_at timestamp.

Smart caching

Before downloading, the app queries the GitHub Gist API to retrieve the latest commit version for the main database gist:
GET https://api.github.com/gists/{gist_id}/commits
The version hash from the first commit is compared against the gist_version stored in cache/cache_meta.json. If they match, the download is skipped entirely and the existing avatar_entries.ndjson is used. If the version has changed or no cache exists, all three files are re-downloaded and processed.

Build docs developers (and LLMs) love