aboutsummaryrefslogtreecommitdiff
path: root/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value
diff options
context:
space:
mode:
authorSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-17 00:53:20 -0500
committerSulfurNitride <SulfurNitride@users.noreply.github.com>2026-04-17 00:53:20 -0500
commit06c396fd27f55929b40161e587cb9b596b549f3f (patch)
tree40fdb465d2fe6e7d4487bbd237582868c8332459 /libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value
parente583d6dda3cde9e8bccee313fe1ad69634593708 (diff)
Symlink saves, rank prefixes by Steam app type, ntsync safeguards
- Replace copy-in/copy-out save deployment with a direct symlink from the prefix's __MO_Saves to the profile's saves/. Writes land in the profile immediately; afterRun reverts the symlink + prefix INI so a vanilla launch outside MO2 uses the default Saves dir. - Parse Steam's binary appinfo.vdf (v41) via a new Rust FFI crate backed by steam-vdf-parser. Rank detected games by common.type so real Games beat Tools/Editors when multiple prefixes share a My Games folder name (fixes Creation Kit shadowing Skyrim SE). Top-ranked candidate overwrites stale symlinks from earlier runs. - Switch bundled Steam Linux Runtime downloader from sniper (steamrt3) to steamrt4 so Proton 11+ works out of the box. steamrt4 omits xrandr so we inject it from the Debian x11-xserver-utils .deb into a dedicated dir and prepend it to PATH via `env PATH=...` inside the pressure-vessel container (PATH env doesn't propagate). - Auto-kill stale wineboot/wineserver/pv-adverb processes still bound to the prefix before starting a new wineboot -u; match by /proc/<pid>/environ WINEPREFIX / STEAM_COMPAT_DATA_PATH since Wine cmdlines are Windows-style. Same sweep runs in destroyPrefix so a delete actually frees file handles. - Blacklist Proton 11 (Steam-bundled) — its current Wine tree deadlocks during wineboot -u on modern kernels via ntsync / futex_wait_multiple races. Use "waitforexitandrun" (not "run") for prefix init so use_sessions=1 Protons don't fork into a persistent session manager. - Drop WINEDEBUG=-all during prefix init so wineboot progress is visible. Suppress focus-stealing by setting WA_ShowWithoutActivating on the PrefixSetupDialog and SLR download progress dialogs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value')
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/hasher.rs127
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/impls.rs222
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/mod.rs10
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/tests.rs802
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/types.rs346
5 files changed, 1507 insertions, 0 deletions
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/hasher.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/hasher.rs
new file mode 100644
index 0000000..126ea86
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/hasher.rs
@@ -0,0 +1,127 @@
+//! Default hasher for IndexMap.
+
+use core::fmt::Debug;
+use core::hash::{BuildHasher, Hasher};
+use foldhash::fast::RandomState;
+use static_assertions::assert_impl_all;
+
+/// Default hash builder for IndexMap.
+#[derive(Clone, Debug, Default)]
+pub struct DefaultHashBuilder {
+ inner: RandomState,
+}
+
+impl BuildHasher for DefaultHashBuilder {
+ type Hasher = DefaultHasher;
+
+ #[inline(always)]
+ fn build_hasher(&self) -> Self::Hasher {
+ DefaultHasher {
+ inner: self.inner.build_hasher(),
+ }
+ }
+}
+
+/// Default hasher.
+#[derive(Clone)]
+pub struct DefaultHasher {
+ inner: <RandomState as BuildHasher>::Hasher,
+}
+
+impl Hasher for DefaultHasher {
+ #[inline(always)]
+ fn write(&mut self, bytes: &[u8]) {
+ self.inner.write(bytes);
+ }
+
+ #[inline(always)]
+ fn write_u8(&mut self, i: u8) {
+ self.inner.write_u8(i);
+ }
+
+ #[inline(always)]
+ fn write_u16(&mut self, i: u16) {
+ self.inner.write_u16(i);
+ }
+
+ #[inline(always)]
+ fn write_u32(&mut self, i: u32) {
+ self.inner.write_u32(i);
+ }
+
+ #[inline(always)]
+ fn write_u64(&mut self, i: u64) {
+ self.inner.write_u64(i);
+ }
+
+ #[inline(always)]
+ fn write_u128(&mut self, i: u128) {
+ self.inner.write_u128(i);
+ }
+
+ #[inline(always)]
+ fn write_usize(&mut self, i: usize) {
+ self.inner.write_usize(i);
+ }
+
+ #[inline(always)]
+ fn write_i8(&mut self, i: i8) {
+ self.inner.write_i8(i);
+ }
+
+ #[inline(always)]
+ fn write_i16(&mut self, i: i16) {
+ self.inner.write_i16(i);
+ }
+
+ #[inline(always)]
+ fn write_i32(&mut self, i: i32) {
+ self.inner.write_i32(i);
+ }
+
+ #[inline(always)]
+ fn write_i64(&mut self, i: i64) {
+ self.inner.write_i64(i);
+ }
+
+ #[inline(always)]
+ fn write_i128(&mut self, i: i128) {
+ self.inner.write_i128(i);
+ }
+
+ #[inline(always)]
+ fn write_isize(&mut self, i: isize) {
+ self.inner.write_isize(i);
+ }
+
+ #[inline(always)]
+ fn finish(&self) -> u64 {
+ self.inner.finish()
+ }
+}
+
+assert_impl_all!(DefaultHashBuilder: Clone, Debug, Default, BuildHasher);
+assert_impl_all!(DefaultHasher: Clone, Hasher);
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use core::hash::{BuildHasher, Hasher};
+
+ #[test]
+ fn write_order_affects_hash() {
+ let builder = DefaultHashBuilder::default();
+ let hasher = builder.build_hasher();
+
+ let mut h1 = hasher.clone();
+ let mut h2 = hasher.clone();
+
+ h1.write(b"a");
+ h1.write(b"b");
+
+ h2.write(b"b");
+ h2.write(b"a");
+
+ assert_ne!(h1.finish(), h2.finish());
+ }
+}
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/impls.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/impls.rs
new file mode 100644
index 0000000..422c6f7
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/impls.rs
@@ -0,0 +1,222 @@
+//! Trait implementations for VDF types.
+
+use alloc::borrow::Cow;
+use alloc::string::String;
+
+use super::types::{Key, Obj, Value, Vdf};
+use core::fmt;
+use core::fmt::Write as _;
+use core::ops::Index;
+
+// ============================================================================
+// Pretty-print helper functions
+// ============================================================================
+
+/// Write a quoted and escaped string to the formatter.
+///
+/// Escapes special characters: `\n`, `\t`, `\r`, `\\`, `"`
+fn write_quoted_str(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result {
+ f.write_char('"')?;
+ for c in s.chars() {
+ match c {
+ '\n' => f.write_str("\\n")?,
+ '\t' => f.write_str("\\t")?,
+ '\r' => f.write_str("\\r")?,
+ '\\' => f.write_str("\\\\")?,
+ '"' => f.write_str("\\\"")?,
+ c => f.write_char(c)?,
+ }
+ }
+ f.write_char('"')
+}
+
+/// Write indentation (tabs) to the formatter.
+fn write_indent(f: &mut fmt::Formatter<'_>, level: usize) -> fmt::Result {
+ for _ in 0..level {
+ f.write_char('\t')?;
+ }
+ Ok(())
+}
+
+/// Helper struct for pretty-printing an Obj with a specific indent level.
+struct PrettyObj<'a, 'text> {
+ obj: &'a Obj<'text>,
+ indent: usize,
+}
+
+impl fmt::Display for PrettyObj<'_, '_> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ writeln!(f, "{{")?;
+ for (key, value) in self.obj.inner.iter() {
+ write_indent(f, self.indent + 1)?;
+ write_quoted_str(f, key)?;
+ if let Value::Obj(inner_obj) = value {
+ // Object value: key on one line, value (with braces) on next lines
+ writeln!(f)?;
+ write_indent(f, self.indent + 1)?;
+ write!(
+ f,
+ "{}",
+ PrettyObj {
+ obj: inner_obj,
+ indent: self.indent + 1
+ }
+ )?;
+ writeln!(f)?;
+ } else {
+ // Scalar value: key<tab>value on same line
+ write!(f, "\t")?;
+ write!(f, "{:#}", value)?;
+ writeln!(f)?;
+ }
+ }
+ write_indent(f, self.indent)?;
+ write!(f, "}}")
+ }
+}
+
+// ============================================================================
+// From implementations for Value
+// ============================================================================
+
+impl<'text> From<&'text str> for Value<'text> {
+ fn from(s: &'text str) -> Self {
+ Value::Str(Cow::Borrowed(s))
+ }
+}
+
+impl From<String> for Value<'static> {
+ fn from(s: String) -> Self {
+ Value::Str(Cow::Owned(s))
+ }
+}
+
+impl From<i32> for Value<'static> {
+ fn from(n: i32) -> Self {
+ Value::I32(n)
+ }
+}
+
+impl From<u64> for Value<'static> {
+ fn from(n: u64) -> Self {
+ Value::U64(n)
+ }
+}
+
+impl From<f32> for Value<'static> {
+ fn from(n: f32) -> Self {
+ Value::Float(n)
+ }
+}
+
+impl From<u32> for Value<'static> {
+ fn from(n: u32) -> Self {
+ Value::Pointer(n)
+ }
+}
+
+impl From<[u8; 4]> for Value<'static> {
+ fn from(color: [u8; 4]) -> Self {
+ Value::Color(color)
+ }
+}
+
+impl<'text> From<Obj<'text>> for Value<'text> {
+ fn from(obj: Obj<'text>) -> Self {
+ Value::Obj(obj)
+ }
+}
+
+impl<'text> fmt::Display for Value<'text> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ match self {
+ Value::Str(s) => write_quoted_str(f, s),
+ Value::Obj(obj) => write!(f, "{}", obj),
+ Value::I32(n) => write!(f, "\"{}\"", n),
+ Value::U64(n) => write!(f, "\"{}\"", n),
+ Value::Float(n) => write!(f, "\"{}\"", n),
+ Value::Pointer(n) => write!(f, "\"0x{:08x}\"", n),
+ Value::Color(c) => write!(f, "\"{} {} {} {}\"", c[0], c[1], c[2], c[3]),
+ }
+ }
+}
+
+impl<'text> Default for Obj<'text> {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+impl<'text> fmt::Display for Obj<'text> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ PrettyObj {
+ obj: self,
+ indent: 0
+ }
+ )
+ }
+}
+
+impl<'text> fmt::Display for Vdf<'text> {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write_quoted_str(f, self.key())?;
+ writeln!(f)?;
+ write!(f, "{}", self.value())
+ }
+}
+
+// ============================================================================
+// IntoIterator implementations for Obj
+// ============================================================================
+
+impl<'text> IntoIterator for Obj<'text> {
+ type Item = (Key<'text>, Value<'text>);
+ type IntoIter = indexmap::map::IntoIter<Key<'text>, Value<'text>>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.inner.into_iter()
+ }
+}
+
+impl<'a, 'text> IntoIterator for &'a Obj<'text> {
+ type Item = (&'a Key<'text>, &'a Value<'text>);
+ type IntoIter = indexmap::map::Iter<'a, Key<'text>, Value<'text>>;
+
+ fn into_iter(self) -> Self::IntoIter {
+ self.inner.iter()
+ }
+}
+
+// ============================================================================
+// Index implementations for Value and Obj
+// ============================================================================
+
+impl<'text> Index<&str> for Value<'text> {
+ type Output = Value<'text>;
+
+ /// Returns a reference to the value at the given key.
+ ///
+ /// # Panics
+ ///
+ /// Panics if this is not an object or if the key doesn't exist.
+ /// Use `get()` for non-panicking access.
+ fn index(&self, key: &str) -> &Self::Output {
+ self.get(key).expect("key not found in Value")
+ }
+}
+
+impl<'text> Index<&str> for Obj<'text> {
+ type Output = Value<'text>;
+
+ /// Returns a reference to the value at the given key.
+ ///
+ /// # Panics
+ ///
+ /// Panics if the key doesn't exist. Use `get()` for non-panicking access.
+ fn index(&self, key: &str) -> &Self::Output {
+ self.get(key).expect("key not found in Obj")
+ }
+}
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/mod.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/mod.rs
new file mode 100644
index 0000000..2518957
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/mod.rs
@@ -0,0 +1,10 @@
+//! Core data structures for VDF representation.
+
+mod hasher;
+mod impls;
+mod types;
+
+#[cfg(test)]
+mod tests;
+
+pub use types::{Obj, Value, Vdf};
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/tests.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/tests.rs
new file mode 100644
index 0000000..5dda1e9
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/tests.rs
@@ -0,0 +1,802 @@
+//! Unit tests for VDF types.
+
+use super::types::{Obj, Value, Vdf};
+use alloc::borrow::Cow;
+use alloc::format;
+use alloc::string::{String, ToString};
+
+// ============================================================================
+// From implementation tests
+// ============================================================================
+
+#[test]
+fn test_value_from_str() {
+ let value: Value = "hello".into();
+ assert!(value.is_str());
+ assert_eq!(value.as_str(), Some("hello"));
+}
+
+#[test]
+fn test_value_from_string() {
+ let value: Value = String::from("hello").into();
+ assert!(value.is_str());
+ assert_eq!(value.as_str(), Some("hello"));
+}
+
+#[test]
+fn test_value_from_i32() {
+ let value: Value = 42i32.into();
+ assert!(value.is_i32());
+ assert_eq!(value.as_i32(), Some(42));
+}
+
+#[test]
+fn test_value_from_u64() {
+ let value: Value = 123u64.into();
+ assert!(value.is_u64());
+ assert_eq!(value.as_u64(), Some(123));
+}
+
+#[test]
+fn test_value_from_f32() {
+ let value: Value = 2.5f32.into();
+ assert!(value.is_float());
+ assert_eq!(value.as_float(), Some(2.5));
+}
+
+#[test]
+fn test_value_from_u32() {
+ let value: Value = 0x12345678u32.into();
+ assert!(value.is_pointer());
+ assert_eq!(value.as_pointer(), Some(0x12345678));
+}
+
+#[test]
+fn test_value_from_color() {
+ let value: Value = [255u8, 0, 128, 64].into();
+ assert!(value.is_color());
+ assert_eq!(value.as_color(), Some([255, 0, 128, 64]));
+}
+
+#[test]
+fn test_value_from_obj() {
+ let obj = Obj::new();
+ let value: Value = obj.into();
+ assert!(value.is_obj());
+}
+
+// ============================================================================
+// Type check tests
+// ============================================================================
+
+#[test]
+fn test_value_is_methods() {
+ let v = Value::I32(42);
+ assert!(v.is_i32());
+ assert!(!v.is_str());
+ assert!(!v.is_obj());
+}
+
+#[test]
+fn test_value_is_str() {
+ let v = Value::Str(Cow::Borrowed("test"));
+ assert!(v.is_str());
+ assert!(!v.is_i32());
+}
+
+#[test]
+fn test_value_is_obj() {
+ let v = Value::Obj(Obj::new());
+ assert!(v.is_obj());
+ assert!(!v.is_str());
+}
+
+#[test]
+fn test_value_is_float() {
+ let v = Value::Float(1.0);
+ assert!(v.is_float());
+ assert!(!v.is_i32());
+}
+
+#[test]
+fn test_value_is_u64() {
+ let v = Value::U64(100);
+ assert!(v.is_u64());
+ assert!(!v.is_i32());
+}
+
+#[test]
+fn test_value_is_pointer() {
+ let v = Value::Pointer(0x12345678);
+ assert!(v.is_pointer());
+ assert!(!v.is_i32());
+}
+
+#[test]
+fn test_value_is_color() {
+ let v = Value::Color([255, 0, 0, 255]);
+ assert!(v.is_color());
+ assert!(!v.is_i32());
+}
+
+#[test]
+fn test_value_as_methods() {
+ let v = Value::I32(42);
+ assert_eq!(v.as_i32(), Some(42));
+ assert_eq!(v.as_str(), None);
+ assert_eq!(v.as_obj(), None);
+}
+
+#[test]
+fn test_value_as_str() {
+ let v = Value::Str(Cow::Borrowed("test"));
+ assert_eq!(v.as_str(), Some("test"));
+}
+
+#[test]
+fn test_value_as_obj() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("key"), Value::I32(42));
+ let v = Value::Obj(obj);
+ assert!(v.as_obj().is_some());
+}
+
+#[test]
+fn test_value_as_float() {
+ let v = Value::Float(1.5);
+ assert_eq!(v.as_float(), Some(1.5));
+}
+
+#[test]
+fn test_value_as_u64() {
+ let v = Value::U64(123456789);
+ assert_eq!(v.as_u64(), Some(123456789));
+}
+
+#[test]
+fn test_value_as_pointer() {
+ let v = Value::Pointer(0xABCDEF01);
+ assert_eq!(v.as_pointer(), Some(0xABCDEF01));
+}
+
+#[test]
+fn test_value_as_color() {
+ let v = Value::Color([255, 128, 64, 32]);
+ assert_eq!(v.as_color(), Some([255, 128, 64, 32]));
+}
+
+#[test]
+fn test_value_display_i32() {
+ assert_eq!(format!("{}", Value::I32(42)), "\"42\"");
+ assert_eq!(format!("{}", Value::I32(-42)), "\"-42\"");
+}
+
+#[test]
+fn test_value_display_u64() {
+ assert_eq!(format!("{}", Value::U64(100)), "\"100\"");
+}
+
+#[test]
+fn test_value_display_str() {
+ assert_eq!(format!("{}", Value::Str(Cow::Borrowed("test"))), "\"test\"");
+}
+
+#[test]
+fn test_value_display_obj() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("key"), Value::I32(42));
+ let v = Value::Obj(obj);
+ assert!(format!("{}", v).contains("\"key\""));
+ assert!(format!("{}", v).contains("\"42\""));
+}
+
+#[test]
+fn test_value_display_float() {
+ let v = Value::Float(1.5);
+ assert_eq!(format!("{}", v), "\"1.5\"");
+}
+
+#[test]
+fn test_value_display_pointer() {
+ assert_eq!(format!("{}", Value::Pointer(0x12345678)), "\"0x12345678\"");
+}
+
+#[test]
+fn test_value_display_color() {
+ assert_eq!(
+ format!("{}", Value::Color([255, 0, 0, 255])),
+ "\"255 0 0 255\""
+ );
+}
+
+#[test]
+fn test_obj_new_is_empty() {
+ let obj = Obj::new();
+ assert!(obj.is_empty());
+ assert_eq!(obj.len(), 0);
+}
+
+#[test]
+fn test_obj_get() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("key"), Value::I32(42));
+ assert_eq!(obj.get("key").and_then(|v| v.as_i32()), Some(42));
+ assert_eq!(obj.get("missing"), None);
+}
+
+#[test]
+fn test_obj_len() {
+ let mut obj = Obj::new();
+ assert_eq!(obj.len(), 0);
+ obj.insert(Cow::Borrowed("key1"), Value::I32(1));
+ assert_eq!(obj.len(), 1);
+ obj.insert(Cow::Borrowed("key2"), Value::I32(2));
+ assert_eq!(obj.len(), 2);
+}
+
+#[test]
+fn test_obj_iter() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("key1"), Value::I32(1));
+ obj.insert(Cow::Borrowed("key2"), Value::I32(2));
+ let mut iter = obj.iter();
+ assert!(iter.next().is_some());
+ assert!(iter.next().is_some());
+ assert!(iter.next().is_none());
+}
+
+#[test]
+fn test_obj_default() {
+ let obj = Obj::default();
+ assert!(obj.is_empty());
+}
+
+#[test]
+fn test_vdf_new() {
+ let vdf = Vdf::new("root", Value::Obj(Obj::new()));
+ assert_eq!(vdf.key(), "root");
+ assert!(vdf.is_obj());
+}
+
+#[test]
+fn test_vdf_is_obj() {
+ let vdf = Vdf::new(Cow::Borrowed("root"), Value::Obj(Obj::new()));
+ assert!(vdf.is_obj());
+ let vdf2 = Vdf::new(Cow::Borrowed("root"), Value::I32(42));
+ assert!(!vdf2.is_obj());
+}
+
+#[test]
+fn test_vdf_as_obj() {
+ let vdf = Vdf::new(Cow::Borrowed("root"), Value::Obj(Obj::new()));
+ assert!(vdf.as_obj().is_some());
+ let vdf2 = Vdf::new(Cow::Borrowed("root"), Value::I32(42));
+ assert!(vdf2.as_obj().is_none());
+}
+
+#[test]
+fn test_vdf_display() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("key"), Value::I32(42));
+ let vdf = Vdf::new(Cow::Borrowed("root"), Value::Obj(obj));
+ let s = format!("{}", vdf);
+ assert!(s.contains("root"));
+}
+
+#[test]
+fn test_into_owned_value_str() {
+ let value = Value::Str(Cow::Borrowed("test"));
+ let owned = value.into_owned();
+ assert_eq!(owned, Value::Str(Cow::Owned("test".to_string())));
+}
+
+#[test]
+fn test_into_owned_value_str_already_owned() {
+ let value = Value::Str(Cow::Owned("test".to_string()));
+ let owned = value.into_owned();
+ assert_eq!(owned, Value::Str(Cow::Owned("test".to_string())));
+}
+
+#[test]
+fn test_into_owned_value_obj() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("key"), Value::I32(42));
+ let value = Value::Obj(obj);
+ let owned = value.into_owned();
+ assert!(owned.is_obj());
+}
+
+#[test]
+fn test_into_owned_value_numeric() {
+ assert_eq!(Value::I32(42).into_owned(), Value::I32(42));
+ assert_eq!(Value::U64(100).into_owned(), Value::U64(100));
+ assert_eq!(Value::Float(1.5).into_owned(), Value::Float(1.5));
+ assert_eq!(Value::Pointer(123).into_owned(), Value::Pointer(123));
+ assert_eq!(
+ Value::Color([1, 2, 3, 4]).into_owned(),
+ Value::Color([1, 2, 3, 4])
+ );
+}
+
+#[test]
+fn test_into_owned_obj() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("key"), Value::I32(42));
+ let owned = obj.into_owned();
+ assert_eq!(owned.len(), 1);
+ assert_eq!(owned.get("key").and_then(|v| v.as_i32()), Some(42));
+}
+
+#[test]
+fn test_into_owned_obj_nested() {
+ let mut inner = Obj::new();
+ inner.insert(
+ Cow::Borrowed("inner_key"),
+ Value::Str(Cow::Borrowed("value")),
+ );
+ let mut outer = Obj::new();
+ outer.insert(Cow::Borrowed("outer_key"), Value::Obj(inner));
+ let owned = outer.into_owned();
+ let inner_obj = owned.get("outer_key").and_then(|v| v.as_obj()).unwrap();
+ assert_eq!(
+ inner_obj.get("inner_key").and_then(|v| v.as_str()),
+ Some("value")
+ );
+}
+
+#[test]
+fn test_into_owned_vdf() {
+ let vdf = Vdf::new("root", Value::I32(42));
+ let owned = vdf.into_owned();
+ assert_eq!(owned.key(), "root");
+ assert_eq!(owned.value(), &Value::I32(42));
+}
+
+// Tests for Value::get and get_path methods
+
+#[test]
+fn test_value_get_on_obj() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("key"), Value::I32(42));
+ let value = Value::Obj(obj);
+ assert_eq!(value.get("key").and_then(|v| v.as_i32()), Some(42));
+ assert_eq!(value.get("missing"), None);
+}
+
+#[test]
+fn test_value_get_on_non_obj() {
+ let value = Value::I32(42);
+ assert_eq!(value.get("key"), None);
+}
+
+#[test]
+fn test_value_get_path_single_key() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("key"), Value::I32(42));
+ let value = Value::Obj(obj);
+ assert_eq!(value.get_path(&["key"]).and_then(|v| v.as_i32()), Some(42));
+}
+
+#[test]
+fn test_value_get_path_empty() {
+ let value = Value::I32(42);
+ assert_eq!(value.get_path(&[]).and_then(|v| v.as_i32()), Some(42));
+}
+
+#[test]
+fn test_value_get_path_nested() {
+ let mut inner = Obj::new();
+ inner.insert(Cow::Borrowed("c"), Value::Str(Cow::Borrowed("found")));
+ let mut middle = Obj::new();
+ middle.insert(Cow::Borrowed("b"), Value::Obj(inner));
+ let mut outer = Obj::new();
+ outer.insert(Cow::Borrowed("a"), Value::Obj(middle));
+ let value = Value::Obj(outer);
+
+ assert_eq!(
+ value.get_path(&["a", "b", "c"]).and_then(|v| v.as_str()),
+ Some("found")
+ );
+}
+
+#[test]
+fn test_value_get_path_missing_intermediate() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("a"), Value::I32(42));
+ let value = Value::Obj(obj);
+ assert_eq!(value.get_path(&["a", "b"]), None);
+}
+
+#[test]
+fn test_value_get_path_non_obj_intermediate() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("a"), Value::I32(42));
+ let value = Value::Obj(obj);
+ assert_eq!(value.get_path(&["a", "b"]), None);
+}
+
+#[test]
+fn test_value_get_str() {
+ let mut inner = Obj::new();
+ inner.insert(Cow::Borrowed("name"), Value::Str(Cow::Borrowed("test")));
+ let mut outer = Obj::new();
+ outer.insert(Cow::Borrowed("data"), Value::Obj(inner));
+ let value = Value::Obj(outer);
+
+ assert_eq!(value.get_str(&["data", "name"]), Some("test"));
+ assert_eq!(value.get_str(&["data", "missing"]), None);
+}
+
+#[test]
+fn test_value_get_obj() {
+ let mut inner = Obj::new();
+ inner.insert(Cow::Borrowed("key"), Value::I32(1));
+ let mut outer = Obj::new();
+ outer.insert(Cow::Borrowed("nested"), Value::Obj(inner));
+ let value = Value::Obj(outer);
+
+ let obj = value.get_obj(&["nested"]).unwrap();
+ assert_eq!(obj.len(), 1);
+}
+
+#[test]
+fn test_value_get_i32() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("num"), Value::I32(123));
+ let value = Value::Obj(obj);
+
+ assert_eq!(value.get_i32(&["num"]), Some(123));
+ assert_eq!(value.get_i32(&["missing"]), None);
+}
+
+#[test]
+fn test_value_get_u64() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("big"), Value::U64(9999999999));
+ let value = Value::Obj(obj);
+
+ assert_eq!(value.get_u64(&["big"]), Some(9999999999));
+}
+
+#[test]
+fn test_value_get_float() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("ratio"), Value::Float(2.5));
+ let value = Value::Obj(obj);
+
+ assert_eq!(value.get_float(&["ratio"]), Some(2.5));
+}
+
+// Tests for Vdf delegation methods
+
+#[test]
+fn test_vdf_get() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("key"), Value::I32(42));
+ let vdf = Vdf::new(Cow::Borrowed("root"), Value::Obj(obj));
+
+ assert_eq!(vdf.get("key").and_then(|v| v.as_i32()), Some(42));
+ assert_eq!(vdf.get("missing"), None);
+}
+
+#[test]
+fn test_vdf_get_path() {
+ let mut inner = Obj::new();
+ inner.insert(Cow::Borrowed("value"), Value::Str(Cow::Borrowed("found")));
+ let mut outer = Obj::new();
+ outer.insert(Cow::Borrowed("nested"), Value::Obj(inner));
+ let vdf = Vdf::new(Cow::Borrowed("root"), Value::Obj(outer));
+
+ assert_eq!(
+ vdf.get_path(&["nested", "value"]).and_then(|v| v.as_str()),
+ Some("found")
+ );
+}
+
+#[test]
+fn test_vdf_get_str() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("name"), Value::Str(Cow::Borrowed("test")));
+ let vdf = Vdf::new(Cow::Borrowed("root"), Value::Obj(obj));
+
+ assert_eq!(vdf.get_str(&["name"]), Some("test"));
+}
+
+#[test]
+fn test_vdf_get_obj() {
+ let mut inner = Obj::new();
+ inner.insert(Cow::Borrowed("k"), Value::I32(1));
+ let mut outer = Obj::new();
+ outer.insert(Cow::Borrowed("inner"), Value::Obj(inner));
+ let vdf = Vdf::new(Cow::Borrowed("root"), Value::Obj(outer));
+
+ assert!(vdf.get_obj(&["inner"]).is_some());
+}
+
+#[test]
+fn test_vdf_get_i32() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("num"), Value::I32(42));
+ let vdf = Vdf::new(Cow::Borrowed("root"), Value::Obj(obj));
+
+ assert_eq!(vdf.get_i32(&["num"]), Some(42));
+}
+
+#[test]
+fn test_vdf_get_u64() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("big"), Value::U64(12345678901234));
+ let vdf = Vdf::new(Cow::Borrowed("root"), Value::Obj(obj));
+
+ assert_eq!(vdf.get_u64(&["big"]), Some(12345678901234));
+}
+
+#[test]
+fn test_vdf_get_float() {
+ let mut obj = Obj::new();
+ obj.insert(Cow::Borrowed("f"), Value::Float(2.5));
+ let vdf = Vdf::new(Cow::Borrowed("root"), Value::Obj(obj));
+
+ assert_eq!(vdf.get_float(&["f"]), Some(2.5));
+}
+
+// ============================================================================
+// IntoIterator tests
+// ============================================================================
+
+#[test]
+fn test_obj_into_iter_owned() {
+ let mut obj = Obj::new();
+ obj.insert("key1", "value1".into());
+ obj.insert("key2", "value2".into());
+
+ let mut count = 0;
+ for (key, value) in obj {
+ count += 1;
+ assert!(key == "key1" || key == "key2");
+ assert!(value.is_str());
+ }
+ assert_eq!(count, 2);
+}
+
+#[test]
+fn test_obj_into_iter_ref() {
+ let mut obj = Obj::new();
+ obj.insert("key1", "value1".into());
+ obj.insert("key2", "value2".into());
+
+ let mut count = 0;
+ for (key, value) in &obj {
+ count += 1;
+ assert!(key.as_ref() == "key1" || key.as_ref() == "key2");
+ assert!(value.is_str());
+ }
+ assert_eq!(count, 2);
+ // obj is still usable after iterating by reference
+ assert_eq!(obj.len(), 2);
+}
+
+// ============================================================================
+// Index trait tests
+// ============================================================================
+
+#[test]
+fn test_value_index_existing_key() {
+ let mut obj = Obj::new();
+ obj.insert("name", "Alice".into());
+ let value = Value::Obj(obj);
+
+ assert_eq!(value["name"].as_str(), Some("Alice"));
+}
+
+#[test]
+#[should_panic(expected = "key not found in Value")]
+fn test_value_index_missing_key() {
+ let mut obj = Obj::new();
+ obj.insert("name", "Alice".into());
+ let value = Value::Obj(obj);
+
+ // This should panic because the key doesn't exist
+ let _ = &value["nonexistent"];
+}
+
+#[test]
+#[should_panic(expected = "key not found in Value")]
+fn test_value_index_on_non_obj() {
+ let value = Value::Str("not an object".into());
+
+ // This should panic because indexing a non-object fails
+ let _ = &value["any"];
+}
+
+#[test]
+fn test_obj_index_existing_key() {
+ let mut obj = Obj::new();
+ obj.insert("count", 42i32.into());
+
+ assert_eq!(obj["count"].as_i32(), Some(42));
+}
+
+#[test]
+#[should_panic(expected = "key not found in Obj")]
+fn test_obj_index_missing_key() {
+ let obj = Obj::new();
+
+ // This should panic because the key doesn't exist
+ let _ = &obj["nonexistent"];
+}
+
+#[test]
+fn test_index_chained_access() {
+ let mut inner = Obj::new();
+ inner.insert("value", "found".into());
+
+ let mut outer = Obj::new();
+ outer.insert("inner", Value::Obj(inner));
+
+ let value = Value::Obj(outer);
+
+ // Chained indexing works when keys exist
+ assert_eq!(value["inner"]["value"].as_str(), Some("found"));
+}
+
+#[test]
+#[should_panic(expected = "key not found in Value")]
+fn test_index_chained_access_missing_key() {
+ let mut inner = Obj::new();
+ inner.insert("value", "found".into());
+
+ let mut outer = Obj::new();
+ outer.insert("inner", Value::Obj(inner));
+
+ let value = Value::Obj(outer);
+
+ // This should panic because "missing" doesn't exist
+ let _ = &value["missing"]["anything"];
+}
+
+// ============================================================================
+// Pretty-print Display tests
+// ============================================================================
+
+#[test]
+fn test_value_pretty_str() {
+ let value = Value::Str(Cow::Borrowed("hello"));
+ assert_eq!(format!("{:#}", value), "\"hello\"");
+}
+
+#[test]
+fn test_value_pretty_str_with_escapes() {
+ let value = Value::Str(Cow::Borrowed("line1\nline2\ttab\\backslash\"quote"));
+ assert_eq!(
+ format!("{:#}", value),
+ "\"line1\\nline2\\ttab\\\\backslash\\\"quote\""
+ );
+}
+
+#[test]
+fn test_value_pretty_i32() {
+ let value = Value::I32(42);
+ assert_eq!(format!("{:#}", value), "\"42\"");
+}
+
+#[test]
+fn test_value_pretty_u64() {
+ let value = Value::U64(123456789);
+ assert_eq!(format!("{:#}", value), "\"123456789\"");
+}
+
+#[test]
+fn test_value_pretty_float() {
+ let value = Value::Float(1.5);
+ assert_eq!(format!("{:#}", value), "\"1.5\"");
+}
+
+#[test]
+fn test_value_pretty_pointer() {
+ let value = Value::Pointer(0x12345678);
+ assert_eq!(format!("{:#}", value), "\"0x12345678\"");
+}
+
+#[test]
+fn test_value_pretty_color() {
+ let value = Value::Color([255, 0, 128, 64]);
+ assert_eq!(format!("{:#}", value), "\"255 0 128 64\"");
+}
+
+#[test]
+fn test_obj_pretty_simple() {
+ let mut obj = Obj::new();
+ obj.insert("key", "value".into());
+ let output = format!("{:#}", obj);
+ // Should contain proper VDF structure
+ assert!(output.starts_with("{\n"));
+ assert!(output.ends_with("}"));
+ assert!(output.contains("\"key\""));
+ assert!(output.contains("\"value\""));
+}
+
+#[test]
+fn test_obj_pretty_nested() {
+ let mut inner = Obj::new();
+ inner.insert("inner_key", "inner_value".into());
+ let mut outer = Obj::new();
+ outer.insert("nested", Value::Obj(inner));
+
+ let output = format!("{:#}", outer);
+ // Should have nested structure with proper indentation
+ assert!(output.contains("\"nested\""));
+ assert!(output.contains("\"inner_key\""));
+ assert!(output.contains("\"inner_value\""));
+ // Check for proper indentation (tabs)
+ assert!(output.contains("\t\"nested\""));
+ assert!(output.contains("\t\t\"inner_key\""));
+}
+
+#[test]
+fn test_vdf_pretty() {
+ let mut obj = Obj::new();
+ obj.insert("key", "value".into());
+ let vdf = Vdf::new("root", Value::Obj(obj));
+
+ let output = format!("{:#}", vdf);
+ // Should start with quoted key followed by newline and brace
+ assert!(output.starts_with("\"root\"\n{"));
+ assert!(output.contains("\"key\"\t\"value\""));
+}
+
+#[test]
+fn test_vdf_pretty_complex() {
+ let mut inner = Obj::new();
+ inner.insert("value", "found".into());
+ inner.insert("number", Value::I32(42));
+
+ let mut outer = Obj::new();
+ outer.insert("nested", Value::Obj(inner));
+ outer.insert("simple", "string".into());
+
+ let vdf = Vdf::new("TestVdf", Value::Obj(outer));
+
+ let output = format!("{:#}", vdf);
+ // Verify structure
+ assert!(output.starts_with("\"TestVdf\"\n{"), "output: {}", output);
+ // The closing brace may or may not have a trailing newline depending on
+ // whether the last element is a nested object, so just check it ends with }
+ assert!(output.trim_end().ends_with("}"), "output: {}", output);
+ // Verify content is present (order may vary due to HashMap)
+ assert!(output.contains("\"nested\""), "output: {}", output);
+ assert!(
+ output.contains("\"simple\"\t\"string\""),
+ "output: {}",
+ output
+ );
+ assert!(
+ output.contains("\"value\"\t\"found\""),
+ "output: {}",
+ output
+ );
+ assert!(output.contains("\"number\"\t\"42\""), "output: {}", output);
+}
+
+#[test]
+fn test_value_display_produces_vdf_format() {
+ // Display always produces valid VDF text format
+ let value = Value::Str(Cow::Borrowed("test"));
+ assert_eq!(format!("{}", value), "\"test\"");
+ // Alternate format is the same
+ assert_eq!(format!("{:#}", value), "\"test\"");
+}
+
+#[test]
+fn test_obj_display_produces_vdf_format() {
+ let mut obj = Obj::new();
+ obj.insert("k", "v".into());
+
+ // Display produces VDF format with newlines and proper quoting
+ let output = format!("{}", obj);
+ assert!(output.contains('\n'));
+ assert!(output.contains("\"k\""));
+ assert!(output.contains("\"v\""));
+ // Alternate format is the same
+ assert_eq!(format!("{:#}", obj), output);
+}
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/types.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/types.rs
new file mode 100644
index 0000000..2b3a77f
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value/types.rs
@@ -0,0 +1,346 @@
+//! Type definitions for VDF values.
+
+use alloc::borrow::Cow;
+use indexmap::IndexMap;
+
+use super::hasher::DefaultHashBuilder;
+
+/// A key in VDF - zero-copy when possible
+pub(crate) type Key<'text> = Cow<'text, str>;
+
+/// VDF Value - can be a string, number, object, or other types
+#[derive(Clone, Debug, PartialEq)]
+pub enum Value<'text> {
+ /// A string value (text format and WideString from binary)
+ Str(Cow<'text, str>),
+ /// An object containing nested key-value pairs
+ Obj(Obj<'text>),
+ /// A 32-bit signed integer (binary Int32 type)
+ I32(i32),
+ /// A 64-bit unsigned integer (binary UInt64 type)
+ U64(u64),
+ /// A 32-bit float (binary Float type)
+ Float(f32),
+ /// A pointer value (binary Ptr type, stored as u32)
+ Pointer(u32),
+ /// A color value (binary Color type, RGBA)
+ Color([u8; 4]),
+}
+
+impl<'text> Value<'text> {
+ /// Returns `true` if this value is a string.
+ pub fn is_str(&self) -> bool {
+ matches!(self, Value::Str(_))
+ }
+
+ /// Returns `true` if this value is an object.
+ pub fn is_obj(&self) -> bool {
+ matches!(self, Value::Obj(_))
+ }
+
+ /// Returns `true` if this value is an i32.
+ pub fn is_i32(&self) -> bool {
+ matches!(self, Value::I32(_))
+ }
+
+ /// Returns `true` if this value is a u64.
+ pub fn is_u64(&self) -> bool {
+ matches!(self, Value::U64(_))
+ }
+
+ /// Returns `true` if this value is a float.
+ pub fn is_float(&self) -> bool {
+ matches!(self, Value::Float(_))
+ }
+
+ /// Returns `true` if this value is a pointer.
+ pub fn is_pointer(&self) -> bool {
+ matches!(self, Value::Pointer(_))
+ }
+
+ /// Returns `true` if this value is a color.
+ pub fn is_color(&self) -> bool {
+ matches!(self, Value::Color(_))
+ }
+
+ /// Returns a reference to the string value if this is a string.
+ pub fn as_str(&self) -> Option<&str> {
+ match self {
+ Value::Str(s) => Some(s.as_ref()),
+ _ => None,
+ }
+ }
+
+ /// Returns a reference to the object if this is an object.
+ pub fn as_obj(&self) -> Option<&Obj<'text>> {
+ match self {
+ Value::Obj(obj) => Some(obj),
+ _ => None,
+ }
+ }
+
+ /// Returns a mutable reference to the object if this is an object.
+ pub fn as_obj_mut(&mut self) -> Option<&mut Obj<'text>> {
+ match self {
+ Value::Obj(obj) => Some(obj),
+ _ => None,
+ }
+ }
+
+ /// Returns the i32 value if this is an i32.
+ pub fn as_i32(&self) -> Option<i32> {
+ match self {
+ Value::I32(n) => Some(*n),
+ _ => None,
+ }
+ }
+
+ /// Returns the u64 value if this is a u64.
+ pub fn as_u64(&self) -> Option<u64> {
+ match self {
+ Value::U64(n) => Some(*n),
+ _ => None,
+ }
+ }
+
+ /// Returns the float value if this is a float.
+ pub fn as_float(&self) -> Option<f32> {
+ match self {
+ Value::Float(n) => Some(*n),
+ _ => None,
+ }
+ }
+
+ /// Returns the pointer value if this is a pointer.
+ pub fn as_pointer(&self) -> Option<u32> {
+ match self {
+ Value::Pointer(n) => Some(*n),
+ _ => None,
+ }
+ }
+
+ /// Returns the color value if this is a color.
+ pub fn as_color(&self) -> Option<[u8; 4]> {
+ match self {
+ Value::Color(c) => Some(*c),
+ _ => None,
+ }
+ }
+
+ /// Returns a reference to a nested value by key.
+ ///
+ /// Shorthand for `self.as_obj()?.get(key)`.
+ pub fn get(&self, key: &str) -> Option<&Value<'text>> {
+ self.as_obj()?.get(key)
+ }
+
+ /// Traverse nested objects by path.
+ ///
+ /// Returns `None` if any segment doesn't exist or isn't an object.
+ pub fn get_path(&self, path: &[&str]) -> Option<&Value<'text>> {
+ let mut current = self;
+ for key in path {
+ current = current.get(key)?;
+ }
+ Some(current)
+ }
+
+ /// Get a string at the given path.
+ pub fn get_str(&self, path: &[&str]) -> Option<&str> {
+ self.get_path(path)?.as_str()
+ }
+
+ /// Get an object at the given path.
+ pub fn get_obj(&self, path: &[&str]) -> Option<&Obj<'text>> {
+ self.get_path(path)?.as_obj()
+ }
+
+ /// Get an i32 at the given path.
+ pub fn get_i32(&self, path: &[&str]) -> Option<i32> {
+ self.get_path(path)?.as_i32()
+ }
+
+ /// Get a u64 at the given path.
+ pub fn get_u64(&self, path: &[&str]) -> Option<u64> {
+ self.get_path(path)?.as_u64()
+ }
+
+ /// Get a float at the given path.
+ pub fn get_float(&self, path: &[&str]) -> Option<f32> {
+ self.get_path(path)?.as_float()
+ }
+}
+
+/// Object - map from keys to values
+///
+/// Uses `IndexMap` for O(1) lookup while preserving insertion order.
+/// Binary VDF doesn't have duplicate keys, and for text VDF we use
+/// "last value wins" semantics.
+#[derive(Clone, Debug, PartialEq)]
+pub struct Obj<'text> {
+ pub(crate) inner: IndexMap<Key<'text>, Value<'text>, DefaultHashBuilder>,
+}
+
+impl<'text> Obj<'text> {
+ /// Creates a new empty VDF object.
+ pub fn new() -> Self {
+ Self {
+ inner: IndexMap::with_hasher(DefaultHashBuilder::default()),
+ }
+ }
+
+ /// Returns the number of key-value pairs in the object.
+ pub fn len(&self) -> usize {
+ self.inner.len()
+ }
+
+ /// Returns `true` if the object contains no key-value pairs.
+ pub fn is_empty(&self) -> bool {
+ self.inner.is_empty()
+ }
+
+ /// Returns a reference to the value corresponding to the key.
+ pub fn get(&self, key: &str) -> Option<&Value<'text>> {
+ self.inner.get(key)
+ }
+
+ /// Returns an iterator over the key-value pairs.
+ pub fn iter(&self) -> impl Iterator<Item = (&Key<'text>, &Value<'text>)> {
+ self.inner.iter()
+ }
+
+ /// Returns an iterator over the keys.
+ pub fn keys(&self) -> impl Iterator<Item = &str> {
+ self.inner.keys().map(|k| k.as_ref())
+ }
+
+ /// Returns an iterator over the values.
+ pub fn values(&self) -> impl Iterator<Item = &Value<'text>> {
+ self.inner.values()
+ }
+
+ /// Returns `true` if the object contains the given key.
+ pub fn contains_key(&self, key: &str) -> bool {
+ self.inner.contains_key(key)
+ }
+
+ /// Returns a mutable reference to the value corresponding to the key.
+ pub fn get_mut(&mut self, key: &str) -> Option<&mut Value<'text>> {
+ self.inner.get_mut(key)
+ }
+
+ /// Inserts a key-value pair into the object.
+ ///
+ /// Returns the previous value if one existed for this key.
+ pub fn insert(
+ &mut self,
+ key: impl Into<Key<'text>>,
+ value: Value<'text>,
+ ) -> Option<Value<'text>> {
+ self.inner.insert(key.into(), value)
+ }
+
+ /// Removes a key from the object, preserving insertion order.
+ ///
+ /// This is O(n) as it shifts subsequent elements. Use [`swap_remove`](Self::swap_remove)
+ /// for O(1) removal when order doesn't matter.
+ ///
+ /// Returns the value if the key was present.
+ pub fn remove(&mut self, key: &str) -> Option<Value<'text>> {
+ self.inner.shift_remove(key)
+ }
+
+ /// Removes a key from the object by swapping with the last element.
+ ///
+ /// This is O(1) but does not preserve insertion order.
+ /// Use [`remove`](Self::remove) if order preservation is needed.
+ ///
+ /// Returns the value if the key was present.
+ pub fn swap_remove(&mut self, key: &str) -> Option<Value<'text>> {
+ self.inner.swap_remove(key)
+ }
+}
+
+/// Top-level VDF document
+///
+/// A VDF document is essentially a single key-value pair at the root level.
+#[derive(Clone, Debug, PartialEq)]
+pub struct Vdf<'text> {
+ key: Key<'text>,
+ value: Value<'text>,
+}
+
+impl<'text> Vdf<'text> {
+ /// Creates a new VDF document.
+ pub fn new(key: impl Into<Key<'text>>, value: Value<'text>) -> Self {
+ Self {
+ key: key.into(),
+ value,
+ }
+ }
+
+ /// Returns the root key.
+ pub fn key(&self) -> &str {
+ &self.key
+ }
+
+ /// Returns a reference to the root value.
+ pub fn value(&self) -> &Value<'text> {
+ &self.value
+ }
+
+ /// Returns a mutable reference to the root value.
+ pub fn value_mut(&mut self) -> &mut Value<'text> {
+ &mut self.value
+ }
+
+ /// Consumes the Vdf and returns its parts.
+ pub fn into_parts(self) -> (Cow<'text, str>, Value<'text>) {
+ (self.key, self.value)
+ }
+
+ /// Returns `true` if the root value is an object.
+ pub fn is_obj(&self) -> bool {
+ self.value.is_obj()
+ }
+
+ /// Returns a reference to the root object if it is one.
+ pub fn as_obj(&self) -> Option<&Obj<'text>> {
+ self.value.as_obj()
+ }
+
+ /// Returns a reference to a nested value by key.
+ pub fn get(&self, key: &str) -> Option<&Value<'text>> {
+ self.value.get(key)
+ }
+
+ /// Traverse nested objects by path from the root value.
+ pub fn get_path(&self, path: &[&str]) -> Option<&Value<'text>> {
+ self.value.get_path(path)
+ }
+
+ /// Get a string at the given path.
+ pub fn get_str(&self, path: &[&str]) -> Option<&str> {
+ self.value.get_str(path)
+ }
+
+ /// Get an object at the given path.
+ pub fn get_obj(&self, path: &[&str]) -> Option<&Obj<'text>> {
+ self.value.get_obj(path)
+ }
+
+ /// Get an i32 at the given path.
+ pub fn get_i32(&self, path: &[&str]) -> Option<i32> {
+ self.value.get_i32(path)
+ }
+
+ /// Get a u64 at the given path.
+ pub fn get_u64(&self, path: &[&str]) -> Option<u64> {
+ self.value.get_u64(path)
+ }
+
+ /// Get a float at the given path.
+ pub fn get_float(&self, path: &[&str]) -> Option<f32> {
+ self.value.get_float(path)
+ }
+}