diff options
| author | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-17 00:53:20 -0500 |
|---|---|---|
| committer | SulfurNitride <SulfurNitride@users.noreply.github.com> | 2026-04-17 00:53:20 -0500 |
| commit | 06c396fd27f55929b40161e587cb9b596b549f3f (patch) | |
| tree | 40fdb465d2fe6e7d4487bbd237582868c8332459 /libs/steam_appinfo_ffi/vendor/steam-vdf-parser/src/value | |
| parent | e583d6dda3cde9e8bccee313fe1ad69634593708 (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')
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) + } +} |
