diff options
Diffstat (limited to 'libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/lib.rs')
| -rw-r--r-- | libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/lib.rs | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/lib.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/lib.rs new file mode 100644 index 0000000..edf4307 --- /dev/null +++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/lib.rs @@ -0,0 +1,220 @@ +//! Blazing fast VDF (Valve Data Format) parser. +//! +//! This library provides parsers for both text and binary VDF formats used by Steam and +//! other Valve Software products. +//! +//! # Features +//! +//! - **`no_std` compatible** — works without the standard library, requires only `alloc` +//! - **Zero-copy parsing** for text format when possible (no escape sequences) +//! - **Binary format support** for Steam's appinfo.vdf, shortcuts.vdf, and packageinfo.vdf +//! - **Winnow-powered** text parser for maximum performance +//! +//! # Example +//! +//! ``` +//! use steam_vdf_parser::parse_text; +//! +//! let input = r#""root" +//! { +//! "key" "value" +//! "nested" +//! { +//! "subkey" "subvalue" +//! } +//! }"#; +//! +//! let vdf = parse_text(input).unwrap(); +//! ``` + +#![no_std] +#![warn(missing_docs)] + +extern crate alloc; + +use alloc::borrow::Cow; +use alloc::string::ToString; + +pub mod binary; +pub mod error; +pub mod text; +pub mod value; + +pub use error::{Error, Result}; +pub use value::{Obj, Value, Vdf}; + +// Re-export commonly used functions +pub use text::parse_text; + +/// Parse VDF from binary format (autodetects shortcuts or appinfo format). +/// +/// This function returns zero-copy data where possible - strings are borrowed +/// from the input buffer. Use `.into_owned()` to convert to an owned `Vdf<'static>`. +pub fn parse_binary(input: &[u8]) -> Result<Vdf<'_>> { + binary::parse(input) +} + +/// Parse a shortcuts.vdf format binary file. +/// +/// This function returns zero-copy data where possible - strings are borrowed +/// from the input buffer. Use `.into_owned()` to convert to an owned `Vdf<'static>`. +pub fn parse_shortcuts(input: &[u8]) -> Result<Vdf<'_>> { + binary::parse_shortcuts(input) +} + +/// Parse an appinfo.vdf format binary file. +/// +/// This function returns zero-copy data where possible - strings are borrowed +/// from the input buffer. Use `.into_owned()` to convert to an owned `Vdf<'static>`. +pub fn parse_appinfo(input: &[u8]) -> Result<Vdf<'_>> { + binary::parse_appinfo(input) +} + +/// Parse a packageinfo.vdf format binary file. +/// +/// This function returns zero-copy data where possible - strings are borrowed +/// from the input buffer. Use `.into_owned()` to convert to an owned `Vdf<'static>`. +pub fn parse_packageinfo(input: &[u8]) -> Result<Vdf<'_>> { + binary::parse_packageinfo(input) +} + +// Convert from borrowed to owned +impl Vdf<'_> { + /// Convert to an owned version (with 'static lifetime). + /// + /// This creates a new `Vdf<'static>` with all strings owned, allowing the + /// data to outlive the original input. + pub fn into_owned(self) -> Vdf<'static> { + let (key, value) = self.into_parts(); + let owned_key: Cow<'static, str> = match key { + Cow::Borrowed(s) => Cow::Owned(s.to_string()), + Cow::Owned(s) => Cow::Owned(s), + }; + Vdf::new(owned_key, value.into_owned()) + } +} + +impl Value<'_> { + /// Convert to an owned version (with 'static lifetime). + pub fn into_owned(self) -> Value<'static> { + match self { + Value::Str(s) => Value::Str(match s { + Cow::Borrowed(b) => b.to_string().into(), + Cow::Owned(o) => o.into(), + }), + Value::Obj(obj) => Value::Obj(obj.into_owned()), + Value::I32(n) => Value::I32(n), + Value::U64(n) => Value::U64(n), + Value::Float(n) => Value::Float(n), + Value::Pointer(n) => Value::Pointer(n), + Value::Color(c) => Value::Color(c), + } + } +} + +impl Obj<'_> { + /// Convert to an owned version (with 'static lifetime). + pub fn into_owned(self) -> Obj<'static> { + let mut new = Obj::new(); + for (k, v) in self.iter() { + let owned_key: Cow<'static, str> = match k { + Cow::Borrowed(b) => Cow::Owned(b.to_string()), + Cow::Owned(o) => Cow::Owned(o.clone()), + }; + // Clone the value since we're iterating + new.insert(owned_key, v.clone().into_owned()); + } + new + } +} + +#[cfg(test)] +mod tests { + use super::*; + use alloc::vec; + + // Simple shortcuts.vdf format test data (object start, key, string value, object end) + const SHORTCUTS_VDF: &[u8] = &[ + 0x00, // Object start + b't', b'e', b's', b't', 0x00, // Key "test" + 0x01, // String type + b'k', b'e', b'y', 0x00, // Nested key "key" + b'v', b'a', b'l', b'u', b'e', 0x00, // Value "value" + 0x08, // Object end + ]; + + #[test] + fn test_parse_binary() { + let vdf = parse_binary(SHORTCUTS_VDF).unwrap(); + assert!(vdf.as_obj().is_some()); + assert_eq!(vdf.key(), "root"); + let obj = vdf.as_obj().unwrap(); + let test_obj = obj.get("test").and_then(|v| v.as_obj()).unwrap(); + assert_eq!(test_obj.get("key").and_then(|v| v.as_str()), Some("value")); + } + + #[test] + fn test_parse_shortcuts() { + let vdf = parse_shortcuts(SHORTCUTS_VDF).unwrap(); + assert!(vdf.as_obj().is_some()); + assert_eq!(vdf.key(), "root"); + let obj = vdf.as_obj().unwrap(); + let test_obj = obj.get("test").and_then(|v| v.as_obj()).unwrap(); + assert_eq!(test_obj.get("key").and_then(|v| v.as_str()), Some("value")); + } + + #[test] + fn test_parse_appinfo() { + // appinfo v40 magic + terminator (no apps) + // Need 8 bytes (magic + universe) + 68 bytes (APPINFO_ENTRY_HEADER_SIZE) = 76 bytes total + let mut input = vec![ + 0x28, 0x44, 0x56, 0x07, // magic: 0x07564428 (APPINFO_MAGIC_40) + 0x20, 0x00, 0x00, 0x00, // universe: 32 + 0x00, 0x00, 0x00, 0x00, // app_id = 0 (terminator) + ]; + // Pad to 76 bytes total (8 + APPINFO_ENTRY_HEADER_SIZE) + input.resize(76, 0); + let result = parse_appinfo(&input); + if let Err(e) = &result { + panic!("parse_appinfo failed: {:?}", e); + } + assert!(result.is_ok()); + let vdf = result.unwrap(); + assert!(vdf.key().starts_with("appinfo_universe_")); + } + + #[test] + fn test_into_owned_vdf() { + let input = r#""root" + { + "key" "value" + }"#; + let borrowed = parse_text(input).unwrap(); + let owned = borrowed.into_owned(); + assert_eq!(owned.key(), "root"); + } + + #[test] + fn test_into_owned_value_str() { + let borrowed = Value::Str("test".into()); + let owned = borrowed.into_owned(); + assert!(matches!(owned, Value::Str(Cow::Owned(_)))); + } + + #[test] + fn test_into_owned_value_obj() { + let mut obj = Obj::new(); + obj.insert("key", Value::Str("value".into())); + let borrowed = Value::Obj(obj); + let owned = borrowed.into_owned(); + assert!(matches!(owned, Value::Obj(_))); + } + + #[test] + fn test_into_owned_obj() { + let mut obj = Obj::new(); + obj.insert("key", Value::Str("value".into())); + let owned = obj.into_owned(); + assert!(owned.get("key").is_some()); + } +} |
