aboutsummaryrefslogtreecommitdiff
path: root/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples
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/examples
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/examples')
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/appid_to_name.rs103
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/compactify_vdf.rs242
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/dump_vdf.rs106
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/print.rs59
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/test_parse_real.rs41
-rw-r--r--libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/verify_compactified.rs45
6 files changed, 596 insertions, 0 deletions
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/appid_to_name.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/appid_to_name.rs
new file mode 100644
index 0000000..6393869
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/appid_to_name.rs
@@ -0,0 +1,103 @@
+// Example: Extract AppId to game name mapping from Steam's appinfo.vdf
+//
+// Usage:
+// cargo run --example appid_to_name -- path/to/appinfo.vdf
+//
+// The appinfo.vdf file is typically located at:
+// - Windows: C:\Program Files (x86)\Steam\appcache\appinfo.vdf
+// - Linux: ~/.steam/steam/appcache/appinfo.vdf
+// - macOS: ~/Library/Application Support/Steam/appcache/appinfo.vdf
+
+use std::env;
+use std::fs;
+use std::process::ExitCode;
+
+use steam_vdf_parser::parse_appinfo;
+
+fn main() -> ExitCode {
+ let args: Vec<String> = env::args().collect();
+
+ if args.len() < 2 {
+ eprintln!("Usage: {} <path/to/appinfo.vdf>", args[0]);
+ eprintln!();
+ eprintln!("Extracts AppId to game name mappings from Steam's appinfo.vdf file.");
+ eprintln!();
+ eprintln!("The appinfo.vdf file is typically located at:");
+ eprintln!(" Windows: C:\\Program Files (x86)\\Steam\\appcache\\appinfo.vdf");
+ eprintln!(" Linux: ~/.steam/steam/appcache/appinfo.vdf");
+ eprintln!(" macOS: ~/Library/Application Support/Steam/appcache/appinfo.vdf");
+ return ExitCode::FAILURE;
+ }
+
+ let path = &args[1];
+
+ // Read the file
+ let data = match fs::read(path) {
+ Ok(data) => data,
+ Err(e) => {
+ eprintln!("Error reading file '{}': {}", path, e);
+ return ExitCode::FAILURE;
+ }
+ };
+
+ // Parse the appinfo.vdf file
+ let vdf = match parse_appinfo(&data) {
+ Ok(vdf) => vdf.into_owned(),
+ Err(e) => {
+ eprintln!("Error parsing appinfo.vdf: {}", e);
+ return ExitCode::FAILURE;
+ }
+ };
+
+ // Get the root object containing all apps
+ let root = match vdf.as_obj() {
+ Some(obj) => obj,
+ None => {
+ eprintln!("Error: root is not an object");
+ return ExitCode::FAILURE;
+ }
+ };
+
+ // Iterate through all apps (keyed by AppID as string)
+ let mut apps = Vec::new();
+
+ for (app_id_str, app_value) in root.iter() {
+ // Skip non-numeric keys (metadata entries)
+ if app_id_str.parse::<u32>().is_err() {
+ continue;
+ }
+
+ let app_obj = match app_value.as_obj() {
+ Some(obj) => obj,
+ None => continue,
+ };
+
+ // Navigate the nested structure: appinfo -> common -> name
+ let name = app_obj
+ .get("appinfo")
+ .and_then(|v| v.as_obj())
+ .and_then(|appinfo| appinfo.get("common"))
+ .and_then(|common| common.as_obj())
+ .and_then(|common| common.get("name"))
+ .and_then(|v| v.as_str());
+
+ if let (Some(name), Ok(app_id)) = (name, app_id_str.parse::<u32>()) {
+ apps.push((app_id, name.to_string()));
+ }
+ }
+
+ // Sort by AppID
+ apps.sort_by_key(|(id, _)| *id);
+
+ // Print the results
+ println!("AppId\tName");
+ println!("------\t{}", "-".repeat(80));
+ for (app_id, name) in &apps {
+ println!("{}\t{}", app_id, name);
+ }
+
+ println!();
+ println!("Total games: {}", apps.len());
+
+ ExitCode::SUCCESS
+}
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/compactify_vdf.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/compactify_vdf.rs
new file mode 100644
index 0000000..64a94ee
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/compactify_vdf.rs
@@ -0,0 +1,242 @@
+//! Compactify an appinfo.vdf file by keeping only the first N apps.
+//!
+//! Usage:
+//! cargo run --example compactify_vdf <input.vdf> <output.vdf> [--count N]
+//!
+//! Default count is 5.
+
+use std::env;
+use std::fs;
+use std::path::Path;
+
+use steam_vdf_parser::binary::{
+ APPINFO_MAGIC_40, APPINFO_MAGIC_41, read_u32_le_at, read_u64_le_at,
+};
+
+// App entry header size
+const APPINFO_ENTRY_HEADER_SIZE: usize = 68;
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+
+ if args.len() < 3 {
+ eprintln!("Usage: {} <input.vdf> <output.vdf> [--count N]", args[0]);
+ eprintln!(" --count N: Keep only the first N apps (default: 5)");
+ std::process::exit(1);
+ }
+
+ let input_path = Path::new(&args[1]);
+ let output_path = Path::new(&args[2]);
+
+ // Parse count argument
+ let mut count: usize = 5;
+ if args.len() >= 4 {
+ if args[3] == "--count" {
+ if args.len() < 5 {
+ eprintln!("Error: --count requires a number");
+ std::process::exit(1);
+ }
+ count = match args[4].parse::<usize>() {
+ Ok(n) if n > 0 => n,
+ _ => {
+ eprintln!("Error: count must be a positive integer");
+ std::process::exit(1);
+ }
+ };
+ } else {
+ eprintln!("Error: unknown argument {}", args[3]);
+ std::process::exit(1);
+ }
+ }
+
+ // Read input file
+ let data = match fs::read(input_path) {
+ Ok(d) => d,
+ Err(e) => {
+ eprintln!("Error reading input file: {}", e);
+ std::process::exit(1);
+ }
+ };
+
+ // Parse header
+ if data.len() < 16 {
+ eprintln!("Error: file too small to be a valid appinfo.vdf");
+ std::process::exit(1);
+ }
+
+ let magic = match read_u32_le_at(&data, 0) {
+ Some(m) => m,
+ None => {
+ eprintln!("Error: cannot read magic number");
+ std::process::exit(1);
+ }
+ };
+
+ let universe = match read_u32_le_at(&data, 4) {
+ Some(u) => u,
+ None => {
+ eprintln!("Error: cannot read universe");
+ std::process::exit(1);
+ }
+ };
+
+ let (is_v41, string_table_offset) = match magic {
+ APPINFO_MAGIC_40 => (false, None),
+ APPINFO_MAGIC_41 => {
+ let offset = read_u64_le_at(&data, 8);
+ (true, offset.map(|o| o as usize))
+ }
+ _ => {
+ eprintln!(
+ "Error: invalid magic number {:08x}, expected appinfo.vdf format",
+ magic
+ );
+ std::process::exit(1);
+ }
+ };
+
+ if is_v41 && string_table_offset.is_none() {
+ eprintln!("Error: cannot read string table offset for v41 format");
+ std::process::exit(1);
+ }
+
+ println!(
+ "Detected appinfo.vdf version: {}",
+ if is_v41 { 41 } else { 40 }
+ );
+ println!("Universe: {}", universe);
+ if let Some(offset) = string_table_offset {
+ println!("String table offset: {}", offset);
+ }
+
+ // Find app entries to keep
+ // App entries start at offset 16 (after magic + universe + optional string table offset)
+ const HEADER_SIZE: usize = 16;
+
+ let mut apps_end = data.len();
+ if let Some(offset) = string_table_offset {
+ apps_end = offset;
+ }
+
+ let mut current_offset = HEADER_SIZE;
+ let mut selected_apps: Vec<(usize, usize)> = Vec::new(); // (start, size) for each app
+
+ for _ in 0..count {
+ if current_offset >= apps_end {
+ break;
+ }
+
+ // Check we have enough data for the entry header
+ if current_offset + APPINFO_ENTRY_HEADER_SIZE > data.len() {
+ eprintln!("Warning: incomplete app entry at offset {}", current_offset);
+ break;
+ }
+
+ // Read app ID
+ let app_id = match read_u32_le_at(&data, current_offset) {
+ Some(id) => id,
+ None => {
+ eprintln!("Error: cannot read app_id at offset {}", current_offset);
+ std::process::exit(1);
+ }
+ };
+
+ // Check for terminator
+ if app_id == 0 {
+ println!(
+ "Reached terminator (app_id == 0) at offset {}",
+ current_offset
+ );
+ break;
+ }
+
+ // Read size field (at offset 4)
+ let entry_size = match read_u32_le_at(&data, current_offset + 4) {
+ Some(s) => s as usize,
+ None => {
+ eprintln!("Error: cannot read size at offset {}", current_offset + 4);
+ std::process::exit(1);
+ }
+ };
+
+ // Total size of this app entry = header (8) + size field value
+ // The size field includes APPINFO_HEADER_AFTER_SIZE (60) + VDF data
+ let total_entry_size = 8 + entry_size;
+
+ // Verify we have enough data
+ if current_offset + total_entry_size > apps_end {
+ eprintln!(
+ "Warning: app entry extends past string table/EOF at offset {}",
+ current_offset
+ );
+ break;
+ }
+
+ println!(
+ "Selecting app_id {} at offset {}, size {}",
+ app_id, current_offset, total_entry_size
+ );
+
+ selected_apps.push((current_offset, total_entry_size));
+ current_offset += total_entry_size;
+ }
+
+ if selected_apps.is_empty() {
+ eprintln!("Error: no app entries found in file");
+ std::process::exit(1);
+ }
+
+ println!("Selected {} app entries", selected_apps.len());
+
+ // Build output file
+ let mut output = Vec::new();
+
+ // Write header
+ output.extend_from_slice(&data[0..HEADER_SIZE]);
+
+ // Update string table offset for v41 (will be calculated later)
+ let string_table_offset_placeholder = if is_v41 { Some(output.len() - 8) } else { None };
+
+ // Write selected app entries
+ for (offset, size) in &selected_apps {
+ output.extend_from_slice(&data[*offset..*offset + *size]);
+ }
+
+ // For v41: copy string table
+ // For v40: add terminator (4 bytes of 0x00)
+ if is_v41 {
+ let string_table_offset = string_table_offset.unwrap();
+ let string_table_data = &data[string_table_offset..];
+
+ // Update string table offset in header
+ let new_offset = output.len() as u64;
+ let offset_bytes = new_offset.to_le_bytes();
+ if let Some(pos) = string_table_offset_placeholder {
+ output[pos..pos + 8].copy_from_slice(&offset_bytes);
+ }
+
+ println!(
+ "String table at original offset {}, new offset {}",
+ string_table_offset, new_offset
+ );
+
+ // Copy string table
+ output.extend_from_slice(string_table_data);
+ } else {
+ // v40: add terminator
+ output.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]);
+ }
+
+ // Write output file
+ if let Err(e) = fs::write(output_path, &output) {
+ eprintln!("Error writing output file: {}", e);
+ std::process::exit(1);
+ }
+
+ println!(
+ "Wrote {} bytes (original: {} bytes, reduction: {}%)",
+ output.len(),
+ data.len(),
+ (data.len() - output.len()) * 100 / data.len()
+ );
+}
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/dump_vdf.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/dump_vdf.rs
new file mode 100644
index 0000000..d0ee2e4
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/dump_vdf.rs
@@ -0,0 +1,106 @@
+use std::env;
+use std::path::Path;
+use steam_vdf_parser::{binary, parse_binary, parse_text};
+
+fn dump_value(value: &steam_vdf_parser::Value, indent: usize) -> String {
+ let indent_str = " ".repeat(indent);
+ match value {
+ steam_vdf_parser::Value::Str(s) => format!("{}\"{}\"", indent_str, s),
+ steam_vdf_parser::Value::I32(n) => format!("{}{}", indent_str, n),
+ steam_vdf_parser::Value::U64(n) => format!("{}{}", indent_str, n),
+ steam_vdf_parser::Value::Float(n) => format!("{}{}", indent_str, n),
+ steam_vdf_parser::Value::Pointer(n) => format!("{}(pointer: {})", indent_str, n),
+ steam_vdf_parser::Value::Color(c) => format!(
+ "{}(color: #{:02x}{:02x}{:02x}{:02x})",
+ indent_str, c[0], c[1], c[2], c[3]
+ ),
+ steam_vdf_parser::Value::Obj(obj) => {
+ let mut out = format!("{}{{\n", indent_str);
+ for (k, v) in obj.iter() {
+ out.push_str(&format!("{}\"\"{}\"\": ", indent_str, k));
+ match v {
+ steam_vdf_parser::Value::Obj(_) => {
+ out.push_str(&dump_value(v, indent + 1));
+ }
+ steam_vdf_parser::Value::Str(s) => out.push_str(&format!("\"{}\"\n", s)),
+ steam_vdf_parser::Value::I32(n) => out.push_str(&format!("{}\n", n)),
+ steam_vdf_parser::Value::U64(n) => out.push_str(&format!("{}\n", n)),
+ steam_vdf_parser::Value::Float(n) => out.push_str(&format!("{}\n", n)),
+ steam_vdf_parser::Value::Pointer(n) => {
+ out.push_str(&format!("(pointer: {})\n", n))
+ }
+ steam_vdf_parser::Value::Color(c) => out.push_str(&format!(
+ "(color: #{:02x}{:02x}{:02x}{:02x})\n",
+ c[0], c[1], c[2], c[3]
+ )),
+ }
+ }
+ out.push_str(&format!("{}}}\n", indent_str));
+ out
+ }
+ }
+}
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+ if args.len() < 2 {
+ eprintln!("Usage: {} <vdf_file> [--text]", args[0]);
+ eprintln!(
+ " --text: force text format parsing (default: auto-detect based on file extension)"
+ );
+ std::process::exit(1);
+ }
+
+ let path = Path::new(&args[1]);
+ let force_text = args.len() > 2 && args[2] == "--text";
+
+ let result = if force_text || path.extension().is_some_and(|e| e == "vdf") {
+ // Try text first for .vdf files
+ let content = std::fs::read_to_string(path);
+ if let Ok(content) = content {
+ parse_text(&content).map(|v| v.into_owned())
+ } else {
+ // Fall back to binary
+ let data = std::fs::read(path).expect("Failed to read file");
+ if path
+ .file_name()
+ .is_some_and(|n| n.to_str().is_some_and(|s| s.contains("packageinfo")))
+ {
+ binary::parse_packageinfo(&data).map(|v| v.into_owned())
+ } else if path
+ .file_name()
+ .is_some_and(|n| n.to_str().is_some_and(|s| s.contains("appinfo")))
+ {
+ binary::parse_appinfo(&data).map(|v| v.into_owned())
+ } else {
+ parse_binary(&data).map(|v| v.into_owned())
+ }
+ }
+ } else {
+ // Binary parsing
+ let data = std::fs::read(path).expect("Failed to read file");
+ if path
+ .file_name()
+ .is_some_and(|n| n.to_str().is_some_and(|s| s.contains("packageinfo")))
+ {
+ binary::parse_packageinfo(&data).map(|v| v.into_owned())
+ } else if path
+ .file_name()
+ .is_some_and(|n| n.to_str().is_some_and(|s| s.contains("appinfo")))
+ {
+ binary::parse_appinfo(&data).map(|v| v.into_owned())
+ } else {
+ parse_binary(&data).map(|v| v.into_owned())
+ }
+ };
+
+ match result {
+ Ok(vdf) => {
+ println!("\"{}\" {}", vdf.key(), dump_value(vdf.value(), 0));
+ }
+ Err(e) => {
+ eprintln!("Error parsing VDF: {:?}", e);
+ std::process::exit(1);
+ }
+ }
+}
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/print.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/print.rs
new file mode 100644
index 0000000..b022a1f
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/print.rs
@@ -0,0 +1,59 @@
+//! Pretty-print a VDF file using the `{:#}` Display format.
+//!
+//! Usage: cargo run --example pretty_print <vdf_file>
+//!
+//! This example demonstrates the pretty-print Display implementation that
+//! outputs valid VDF text format with proper indentation and escaping.
+
+use std::env;
+use std::path::Path;
+use steam_vdf_parser::{binary, parse_binary, parse_text};
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+ if args.len() < 2 {
+ eprintln!("Usage: {} <vdf_file>", args[0]);
+ eprintln!();
+ eprintln!("Pretty-prints a VDF file to stdout in valid VDF text format.");
+ eprintln!(
+ "Supports both text (.vdf) and binary (appinfo.vdf, packageinfo.vdf, shortcuts.vdf) formats."
+ );
+ std::process::exit(1);
+ }
+
+ let path = Path::new(&args[1]);
+ let data = std::fs::read(path).expect("Failed to read file");
+
+ // Try to detect format and parse accordingly
+ let result = if let Ok(text) = std::str::from_utf8(&data) {
+ // Looks like text, try text parser first
+ parse_text(text)
+ .map(|v| v.into_owned())
+ .or_else(|_| parse_binary(&data).map(|v| v.into_owned()))
+ } else {
+ // Binary data - detect format from filename
+ let filename = path
+ .file_name()
+ .and_then(|n| n.to_str())
+ .unwrap_or_default();
+
+ if filename.contains("packageinfo") {
+ binary::parse_packageinfo(&data).map(|v| v.into_owned())
+ } else if filename.contains("appinfo") {
+ binary::parse_appinfo(&data).map(|v| v.into_owned())
+ } else {
+ parse_binary(&data).map(|v| v.into_owned())
+ }
+ };
+
+ match result {
+ Ok(vdf) => {
+ // Use the pretty-print Display implementation
+ println!("{:#}", vdf);
+ }
+ Err(e) => {
+ eprintln!("Error parsing VDF: {:?}", e);
+ std::process::exit(1);
+ }
+ }
+}
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/test_parse_real.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/test_parse_real.rs
new file mode 100644
index 0000000..97752d4
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/test_parse_real.rs
@@ -0,0 +1,41 @@
+use std::fs;
+use steam_vdf_parser::{parse_binary, parse_text};
+
+fn main() {
+ // Test localconfig.vdf (text format)
+ println!("=== Parsing localconfig.vdf (text format) ===");
+ let localconfig = fs::read_to_string(
+ "/home/mexus/.local/share/Steam/userdata/127648749/config/localconfig.vdf",
+ );
+ match localconfig {
+ Ok(content) => match parse_text(&content) {
+ Ok(vdf) => {
+ println!("Success!");
+ println!("Root key: {}", vdf.key());
+ let obj = vdf.as_obj().unwrap();
+ println!("Root has {} keys", obj.len());
+ }
+ Err(e) => {
+ println!("Parse error: {:?}", e);
+ }
+ },
+ Err(e) => println!("Error reading: {}", e),
+ }
+
+ println!("\n=== Parsing appinfo.vdf (binary format) ===");
+ let appinfo = fs::read("/home/mexus/.local/share/Steam/appcache/appinfo.vdf");
+ match appinfo {
+ Ok(data) => match parse_binary(&data) {
+ Ok(vdf) => {
+ println!("Success!");
+ println!("Root key: {}", vdf.key());
+ let obj = vdf.as_obj().unwrap();
+ println!("Root has {} keys", obj.len());
+ }
+ Err(e) => {
+ println!("Parse error: {:?}", e);
+ }
+ },
+ Err(e) => println!("Error reading: {}", e),
+ }
+}
diff --git a/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/verify_compactified.rs b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/verify_compactified.rs
new file mode 100644
index 0000000..c36f917
--- /dev/null
+++ b/libs/steam_appinfo_ffi/vendor/steam-vdf-parser/examples/verify_compactified.rs
@@ -0,0 +1,45 @@
+//! Verify that a compactified appinfo.vdf file parses correctly.
+
+use std::env;
+use steam_vdf_parser::binary::parse_appinfo;
+
+fn main() {
+ let args: Vec<String> = env::args().collect();
+
+ if args.len() < 2 {
+ eprintln!("Usage: {} <vdf_file>", args[0]);
+ std::process::exit(1);
+ }
+
+ let path = &args[1];
+
+ // Read the file
+ let data = match std::fs::read(path) {
+ Ok(d) => d,
+ Err(e) => {
+ eprintln!("Error reading file: {}", e);
+ std::process::exit(1);
+ }
+ };
+
+ // Parse it
+ match parse_appinfo(&data) {
+ Ok(vdf) => {
+ println!("Successfully parsed {}", path);
+ println!("Root key: {}", vdf.key());
+ if let Some(obj) = vdf.as_obj() {
+ println!("Number of apps: {}", obj.len());
+ for (key, _) in obj.iter().take(5) {
+ println!(" - app_id: {}", key);
+ }
+ if obj.len() > 5 {
+ println!(" ... and {} more", obj.len() - 5);
+ }
+ }
+ }
+ Err(e) => {
+ eprintln!("Error parsing file: {:?}", e);
+ std::process::exit(1);
+ }
+ }
+}