| Crates.io | steam-vdf-parser |
| lib.rs | steam-vdf-parser |
| version | 0.1.1 |
| created_at | 2026-01-17 21:16:28.013581+00 |
| updated_at | 2026-01-18 12:01:42.08072+00 |
| description | Zero-copy parser for Steam's VDF (Valve Data Format) files |
| homepage | |
| repository | https://github.com/mexus/steam-vdf-parser |
| max_upload_size | |
| id | 2051180 |
| size | 257,222 |
A blazing fast, zero-copy parser for Steam's VDF (Valve Data Format) files in Rust.
Supports both text and binary formats used by Steam, including shortcuts.vdf, appinfo.vdf, and packageinfo.vdf.
no_std compatible — works without the standard library, requires only allocappinfo.vdf v40 (null-terminated keys) and v41 (string table)serde-free — simple, direct data structures with no hidden allocationsuse steam_vdf_parser::parse_text;
let input = r#""root"
{
"key" "value"
"nested"
{
"subkey" "subvalue"
}
}"#;
let vdf = parse_text(input)?;
assert_eq!(vdf.key(), "root");
// Access nested values
let obj = vdf.as_obj().unwrap();
let value = obj.get("key").and_then(|v| v.as_str()).unwrap();
assert_eq!(value, "value");
// Path-based access
let subvalue = vdf.get_str(&["nested", "subkey"]).unwrap();
assert_eq!(subvalue, "subvalue");
use steam_vdf_parser::parse_binary;
let data = read_vdf_data_somehow()?;
let vdf = parse_binary(&data)?;
// For data that needs to outlive the input:
let owned = vdf.into_owned();
use steam_vdf_parser::{parse_appinfo, parse_packageinfo};
// appinfo.vdf (auto-detects v40/v41)
let data = read_vdf_data_somehow()?;
let vdf = parse_appinfo(&data)?;
// packageinfo.vdf
let data = read_vdf_data_somehow()?;
let vdf = parse_packageinfo(&data)?;
no_std SupportThis library is no_std compatible and only requires the alloc crate. Enable it in your Cargo.toml:
[dependencies]
steam-vdf-parser = "0.1"
For no_std environments, make sure to have a global allocator configured:
extern crate alloc;
use steam_vdf_parser::parse_text;
// Your parsing code here
Vdf<'text>The top-level VDF document. A VDF document is essentially a single key-value pair at the root level.
let vdf = parse_text(input)?;
// Access root key and value
let key: &str = vdf.key();
let value: &Value = vdf.value();
// Check if root is an object
if vdf.is_obj() {
let obj = vdf.as_obj().unwrap();
}
// Direct nested access
let nested = vdf.get("key"); // Option<&Value>
// Path-based traversal
let deep = vdf.get_path(&["nested", "deep", "value"]);
let name = vdf.get_str(&["config", "name"]);
let count = vdf.get_i32(&["settings", "count"]);
Value<'text> Enum| Variant | Rust Type | Type Check | Accessor |
|---|---|---|---|
Str |
Cow<'text, str> |
is_str() |
as_str() |
Obj |
Obj<'text> |
is_obj() |
as_obj(), as_obj_mut() |
I32 |
i32 |
is_i32() |
as_i32() |
U64 |
u64 |
is_u64() |
as_u64() |
Float |
f32 |
is_float() |
as_float() |
Pointer |
u32 |
is_pointer() |
as_pointer() |
Color |
[u8; 4] |
is_color() |
as_color() |
Path-based access methods are also available on Value:
// Traverse nested objects
let deep = value.get_path(&["nested", "key"]);
// Typed path access
let name = value.get_str(&["config", "name"]);
let obj = value.get_obj(&["settings"]);
let count = value.get_i32(&["stats", "count"]);
let id = value.get_u64(&["user", "id"]);
let ratio = value.get_float(&["metrics", "ratio"]);
Obj<'text>A HashMap-backed object (using hashbrown for no_std compatibility) with O(1) lookup:
let obj = vdf.as_obj().unwrap();
// Basic access
let len = obj.len();
let is_empty = obj.is_empty();
let value = obj.get("key"); // Option<&Value>
let exists = obj.contains_key("key"); // bool
// Iteration
for (key, value) in obj.iter() {
println!("{}: {}", key, value);
}
for key in obj.keys() {
println!("Key: {}", key);
}
for value in obj.values() {
println!("Value: {}", value);
}
// Mutation
let mut obj = Obj::new();
obj.insert("key", Value::Str("value".into()));
obj.get_mut("key"); // Option<&mut Value>
obj.remove("key"); // Option<Value>
Parsing functions return Vdf<'_> with strings borrowed from input where possible. Use .into_owned() to convert to Vdf<'static>:
let borrowed: Vdf<'_> = parse_text(input)?;
let owned: Vdf<'static> = borrowed.into_owned();
All binary VDF formats use type byte prefixes:
| Byte | Name | Description |
|---|---|---|
0x00 |
None/Object | Start of an object (followed by key) |
0x01 |
String | Null-terminated UTF-8 string value |
0x02 |
Int32 | 4-byte little-endian signed integer |
0x03 |
Float | 4-byte little-endian IEEE-754 float |
0x04 |
Pointer | 4-byte pointer value (stored as u32) |
0x05 |
WString | Null-terminated UTF-16LE string (0x00 0x00 terminator) |
0x06 |
Color | 4 bytes RGBA |
0x07 |
UInt64 | 8-byte little-endian unsigned integer |
0x08 |
ObjectEnd | End of current object |
[TypeByte] [Key] [Value...]
Simple binary format. Keys are null-terminated UTF-8 strings. Ends at EOF.
| Offset | Size | Field | Description |
|---|---|---|---|
0x00 |
4 | Magic | 0x07564428 (v40) or 0x07564429 (v41) |
0x04 |
4 | Universe | Always 1 (public) |
0x08 |
8 | String Table Offset | Present only in v41 |
| Offset | Size | Field | Description |
|---|---|---|---|
0x00 |
4 | App ID | Steam application ID |
0x04 |
4 | Size | Size of remaining data (60 bytes + VDF payload) |
0x08 |
4 | Info State | Flags (e.g., 2 = available) |
0x0C |
4 | Last Updated | Unix timestamp |
0x10 |
8 | PICS Token | Access token for PICS API |
0x18 |
20 | SHA1 | Hash of VDF payload |
0x2C |
4 | Change Number | Sequence number |
0x30 |
20 | Binary SHA1 | Hash of binary VDF data |
VDF data starts at offset 0x44 (68 bytes). Length = Size - 60.
u32 indices into the string tableLocated at String Table Offset:
| Offset | Size | Field | Description |
|---|---|---|---|
0x00 |
4 | String Count | Number of strings (little-endian) |
0x04 |
- | Strings | Null-terminated UTF-8 strings |
| Offset | Size | Field | Description |
|---|---|---|---|
0x00 |
4 | Magic | Upper 3 bytes: 0x065655, lower byte: version (27 = v39, 28 = v40) |
0x04 |
4 | Universe | Always 1 (public) |
| Offset | Size | Field | Description |
|---|---|---|---|
0x00 |
4 | Package ID | 0xFFFFFFFF marks end of file |
0x04 |
20 | SHA-1 | Hash of VDF payload |
0x18 |
4 | Change Number | Sequence number |
0x1C |
8 | PICS Token | Present only in v40 |
Followed by binary VDF blob (null-terminated keys, like shortcuts.vdf).
Text parsing is zero-copy when strings contain no escape sequences — Cow::Borrowed refers directly into the input. Escape sequences and wide strings (UTF-16) cause allocation (Cow::Owned).
Binary parsing is also zero-copy for string values. In appinfo v41, root keys (app IDs) are owned due to integer-to-string conversion, but nested values remain borrowed from the string table or input buffer.
Licensed under either of:
at your option.