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 channelR * 4
bytes: encoded red channelG * 4
bytes: encoded green channelB * 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 mode0x2
: blink sync0x4
: 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 pressRLSE
: on releaseHOLD
: 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 active0x2
: uses preset0x4
: 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-pre0x2
: veadotube mini 1.1 / 1.1a0x3
: veadotube mini 1.20x4
: veadotube mini 1.3 / 1.3a / 1.3b0x5
: 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
or0x5
: 4 varint strings to identify the hotkey - version
0x2
or0x3
: one varint string to identify the hotkey - version
0x2
or0x3
or0x4
or0x5
: a 4-byte integer for the state transition0x0
: None0x1
: Jump
- closed mouth data
- open mouth data
for each mouth data:
- a 4-byte integer for the state motion
0x0
: Static0x1
: Vibing0x2
: Shaking0x3
: ShakingMore0x4
: Bouncy0x5
: Excited0x6
: Nervous
- non-blinking image
- version
0x3
or0x4
or0x5
: blinking image
for each image:
- version
0x1
or0x2
or0x4
: 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.