guide
anatomy of a .veado file

this one is a bit technical! if you’re only looking into recovering images from your .veado file, use the export image option in veadotube mini.

a .veado file contains an avatar, as well as the images that come with it! it can be a mini avatar or a full version avatar.

the current .veado file format

it’s a simple chunk-based file format! things are stored in little-endian, text is UTF-8, that kinda stuff.

we also use some varints, which are 7-bit encoded variable-length integers, done the usual .NET way – when something is said to be a string, assume it’s a UTF-8 string of bytes prefixed by a varint containing the text length.

it starts with the ASCII characters for VEADOTUBE and then for each chunk:

  • 4 bytes: unsigned integer for the chunk ID, unique in the file;
  • 4 bytes: FourCC code for the chunk type;
  • 4 bytes: unsigned integer for the chunk length = N;
  • N bytes: the chunk content itself.

the file might end with 12 bytes filled with zero – that’s a way to identify if the file has ended!

metadata chunks

the META chunk contains metadata about the avatar.

  • string: the software used to create this file;
  • string: credits, aka the author of this file;
  • string: a custom description written by the author of this file.

the THMB chunk contains PNG data for a preview of the avatar.

asset chunks

assets are saved in a folder-like structure. there can be multiple root folders, identified by their root code – for example, mini saves all the avatar images in a root folder with the code MINI.

the folder chunk is ASFD:

  • 4 bytes: the root code for the folder;
  • then for each entry until the end of the chunk:
    • string: string for the entry name;
    • 4 bytes: unsigned integer for the chunk ID of the entry;
    • varint: unsigned integer for the number of metadata entries;
    • then for each metadata entry:
      • 4 bytes: FourCC code for the metadata type;
      • varint: unsigned integer for the length of the metadata = N;
      • N bytes: metadata

the only current asset type is an image asset! it describes which bitmaps are used in the image (for animated images there’ll be multiple bitmaps), and how they’re positioned (as bitmaps can be cropped to save space).

the image asset is described by a AIMG chunk:

  • 4 bytes: unsigned integer for the image width;
  • 4 bytes: unsigned integer for the image height;
  • varint: unsigned integer for the frame count = N;
  • if N > 1, varint: unsigned integer for the loop count;
  • then for each frame:
    • 4 bytes: unsigned integer for the bitmap chunk ID;
    • 4 bytes: signed integer for the frame offset in X;
    • 4 bytes: signed integer for the frame offset in Y;
    • 8 bytes: double-precision floating number for the frame duration

a bitmap is described by a ABMP chunk:

  • 4 bytes: unsigned integer for the bitmap width;
  • 4 bytes: unsigned integer for the bitmap height;
  • 4 bytes: FourCC describing the bitmap format;
  • remaining bytes: bitmap data

the two supported bitmap formats are RAW. and VDD. – respectively, raw and VeadoDelta. the raw bitmap is simply a sequence of 8-bit RGBA bytes (bottom to top, left to right).

VeadoDelta is a simple delta-encoding format, hence the name! it’s meant to be fast to read with support for multithreading and all that fancy stuff. compression levels are pretty ok, not too worse than PNG, good for transparent images, a bit heavy for photos. internally, veadotube falls back to the raw bitmap if things get too bad.

no we still don’t have proper test results to show the performance of VeadoDelta <3 it’s good enough trust me bro

each channel is encoded separately, compressing delta values with prefix codes. the compressed image is a 8-bit RGBA as well (so channels go from 0 to 255).

in order to compress a channel, it must first take the original values and apply the difference between the previous value and the current, keeping the first value intact. like this:

original:  6   7   8   7   11  14  14  14  14  14
   delta:  6   1   1  -1   4   3   0   0   0   0

these deltas are stored as signed bytes, meaning that they range from -128 to 127 and take advantage of byte overflows/underflows:

original:  0   255 0   240
   delta:  0   -1  1  -16

the delta values are then encoded as prefix codes of variable bit length:

abs value    | sequence
x = 0        | 1 0
x = 1 ~ 2    | 1 1 [1 bit, x - 1] [sign bit]
x = 3 ~ 4    | 0 1 0 [1 bit, x - 3] [sign bit]
x = 5 ~ 8    | 0 1 1 [2 bits, x - 5] [sign bit]
x = 9 ~ 16   | 0 0 1 0 [3 bits, x - 9] [sign bit]
x = 17 ~ 32  | 0 0 1 1 [4 bits, x - 17] [sign bit]
x = 33 ~ 64  | 0 0 0 1 0 [5 bits, x - 33] [sign bit]
x = 65 ~ 128 | 0 0 0 1 1 [6 bits, x - 65] [sign bit]

the sign bit is 1 for positive, and 0 for negative. there’s also a special bit sequence that must be used when 0 is repeated at least 7 times, with a maximum value of 262:

0 0 0 0 [8 bits, x - 7]

these are all stored in 4-byte integers, so the result ends up being padded by 4 bytes.

the entire VeadoDelta data buffer is saved like so:

  • 4 bytes: unsigned integer for the number of 4-byte integers of the alpha channel = A;
  • 4 bytes: unsigned integer for the number of 4-byte integers of the red channel = R;
  • 4 bytes: unsigned integer for the number of 4-byte integers of the green channel = G;
  • 4 bytes: unsigned integer for the number of 4-byte integers of the blue channel = B;
  • A * 4 bytes: encoded alpha channel
  • R * 4 bytes: encoded red channel
  • G * 4 bytes: encoded green channel
  • B * 4 bytes: encoded blue channel

with one caveat: if a channel length is 0xFFFFFF00 or larger, it’s assumed that the length is actually zero, and the least significant byte here is the constant value for this channel. for example, if A is 0xFFFFFFFF, alpha is always 255, and thus the image is opaque.

mini avatar chunks

mini avatars are those that veadotube mini can edit!

to identify if a .veado file contains a mini avatar, it must have a MLST chunk, which is a list of 4 byte unsigned integers, each describing the chunk ID for the avatar state.

the avatar state chunk itself is a MSTA chunk:

  • string: state name
  • 4 bytes: unsigned integer with state flags
    • 0x1: pixelated image mode
    • 0x2: blink sync
    • 0x4: play images from the start
  • 4 bytes: chunk ID for the thumbnail for the closed mouth image asset
  • 4 bytes: chunk ID for the thumbnail for the open mouth image asset
  • 4 bytes: chunk ID for the thumbnail for the blinking closed mouth image asset
  • 4 bytes: chunk ID for the thumbnail for the blinking open mouth image asset
  • 4 bytes: chunk ID for the closed mouth image asset
  • 4 bytes: chunk ID for the open mouth image asset
  • 4 bytes: chunk ID for the blinking closed mouth image asset
  • 4 bytes: chunk ID for the blinking open mouth image asset
  • 8 bytes: double-precision floating number for the duration of the blink frame
  • 8 bytes: double-precision floating number for the min blinking interval
  • 8 bytes: double-precision floating number for the max blinking interval
  • closed mouth effects
  • open mouth effects
  • on open mouth effects
  • on close mouth effects
  • varint: unsigned integer for the number of signals assigned to the shortcut
  • then for each signal:
    • string: signal source
    • string: signal name
  • 4 bytes: FourCC for the shortcut mode
    • PRES: on press
    • RLSE: on release
    • HOLD: while pressed

effects are saved like so:

  • varint: unsigned integer for the number of effects
  • then for each effect:
    • string: effect id
    • 1 byte: effect flags
      • 0x1: is active
      • 0x2: uses preset
      • 0x4: uses preset chunk
    • if flags contains 0x4:
      • 4 bytes: chunk ID for the preset
    • else if flags contains 0x2:
      • string: built-in preset id
    • varint: unsigned integer for the number of values
    • then for each value:
      • 8 bytes: double-precision floating number for the value

preset chunks are user-defined presets, stored in MEPR chunks:

  • 1 byte: effect type (0 = state, 1 = transition)
  • string: effect id
  • string: preset name
  • varint: unsigned integer for the number of values
  • then for each value:
    • 8 bytes: double-precision floating number for the value

the old .veadomini file format

veadotube mini 1.4 used .veadomini files, which are essentially renamed .zip files! their contents were:

  • a folder for each state, each folder containing the respective imported images for all mouth and blink states ;
  • an avatar.yaml file, with a list of states, including the name of the folder containing its images, its motion/transition animations, hotkeys, so on.

this means that you can essentially create and edit .veadomini files using a zip program and a text editor, and those can be imported in more recent versions as well :]

the even older .veadomini format

before becoming a renamed .zip file, .veadomini was a bit of a mess written with C#’s BinaryWriter. it can be identified with the ASCII characters for VEADOMINI and a byte describing the file format version:

  • 0x1: veadotube mini 1.1-pre
  • 0x2: veadotube mini 1.1 / 1.1a
  • 0x3: veadotube mini 1.2
  • 0x4: veadotube mini 1.3 / 1.3a / 1.3b
  • 0x5: veadotube mini 1.3c

then there’s a 4-byte integer for the number of states in the file. then for each state:

  • version 0x4 or 0x5: 4 varint strings to identify the hotkey
  • version 0x2 or 0x3: one varint string to identify the hotkey
  • version 0x2 or 0x3 or 0x4 or 0x5: a 4-byte integer for the state transition
    • 0x0: None
    • 0x1: Jump
  • closed mouth data
  • open mouth data

for each mouth data:

  • a 4-byte integer for the state motion
    • 0x0: Static
    • 0x1: Vibing
    • 0x2: Shaking
    • 0x3: ShakingMore
    • 0x4: Bouncy
    • 0x5: Excited
    • 0x6: Nervous
  • non-blinking image
  • version 0x3 or 0x4 or 0x5: blinking image

for each image:

  • version 0x1 or 0x2 or 0x4: varint string with the name of the image or just the extension itself
  • 4-byte integer for the image data length
  • the image data, in any format

the really old, patreon-exclusive .veado file format

if you somehow still have access to this file, it’s a renamed .zip! it’s similar to how the 1.4 .veadomini file structures things. no versions of veadotube currently open this format, and currently there are no plans to support it again.