//! Parse Cursebreaker Resource Prefabs //! //! This example demonstrates: //! 1. Parsing Cursebreaker prefab files directly //! 2. Finding Interactable_Resource components in prefabs //! 3. Extracting typeId and maxHealth data //! 4. Writing resource data to an output file //! //! Note: The 10_3.unity scene uses prefab instances, and the current parser //! doesn't yet support resolving components from nested prefabs. This example //! parses the prefab files directly instead. use unity_parser::{GuidResolver, UnityComponent, UnityFile}; use std::fs::File; use std::io::Write; use std::path::Path; use walkdir::WalkDir; /// Interactable_Resource component from Cursebreaker /// /// C# definition from Interactable_Resource.cs: /// ```csharp /// public class Interactable_Resource : Interactable /// { /// public int health; /// public int maxHealth; /// public int typeId; /// // ... other fields /// } /// ``` #[derive(Debug, Clone, UnityComponent)] #[unity_class("Interactable_Resource")] pub struct InteractableResource { #[unity_field("maxHealth")] pub max_health: i64, #[unity_field("typeId")] pub type_id: i64, } fn main() -> Result<(), Box> { println!("🎮 Cursebreaker - Resource Prefab Parser"); println!("{}", "=".repeat(70)); println!(); // Build GUID resolver for the project let project_path = Path::new("/home/connor/repos/CBAssets"); println!("📦 Building GUID resolver for project: {}", project_path.display()); let resolver = match GuidResolver::from_project(project_path) { Ok(r) => { println!(" ✅ GUID resolver built successfully ({} mappings)", r.len()); // Debug: Check if we have Interactable_Resource if let Some(class) = r.resolve_class_name("d39ddbf1c2c3d1a4baa070e5e76548bd") { println!(" ✅ Found Interactable_Resource in resolver: {}", class); } else { println!(" ⚠️ Interactable_Resource NOT found in resolver"); // Try to find what we did find related to "Interactable" println!(" Searching for similar class names..."); } Some(r) } Err(e) => { eprintln!(" ❌ Failed to build GUID resolver: {}", e); None } }; println!(); let harvestables_dir = Path::new("/home/connor/repos/CBAssets/_GameAssets/Prefabs/Harvestables"); if !harvestables_dir.exists() { eprintln!("❌ Error: Harvestables directory not found at {}", harvestables_dir.display()); return Err("Harvestables directory not found".into()); } println!("📁 Scanning for harvestable prefabs in:"); println!(" {}", harvestables_dir.display()); println!(); // Find all prefab files let mut prefab_files = Vec::new(); for entry in WalkDir::new(harvestables_dir) .follow_links(false) .into_iter() .filter_map(|e| e.ok()) { let path = entry.path(); if path.extension().and_then(|s| s.to_str()) == Some("prefab") { prefab_files.push(path.to_path_buf()); } } println!("📄 Found {} prefab file(s)", prefab_files.len()); println!(); let mut all_resources = Vec::new(); // Parse each prefab using the GUID resolver we built for prefab_path in &prefab_files { println!("🔍 Parsing: {}", prefab_path.file_name().unwrap().to_string_lossy()); // For prefabs, we need to manually parse and check documents // since prefabs don't have an ECS world like scenes do match UnityFile::from_path(prefab_path) { Ok(UnityFile::Prefab(prefab)) => { // Search through YAML documents for Interactable_Resource components let mut found_in_prefab = false; for doc in &prefab.documents { // Check if this document is a MonoBehaviour if doc.class_name == "MonoBehaviour" { // Try to extract the m_Script GUID if let Some(m_script) = doc.yaml.get("m_Script").and_then(|v| v.as_mapping()) { if let Some(guid_val) = m_script.get("guid").and_then(|v| v.as_str()) { // Resolve GUID to class name if let Some(ref res) = resolver { if let Some(class_name) = res.resolve_class_name(guid_val) { // Debug: print what we found if prefab_path.file_name().unwrap().to_string_lossy().contains("Copper Ore") { eprintln!("DEBUG: Found class '{}' in Copper Ore prefab", class_name); } if class_name == "Interactable_Resource" { // Extract fields let type_id = doc.yaml.get("typeId") .and_then(|v| v.as_i64()) .unwrap_or(0); let max_health = doc.yaml.get("maxHealth") .and_then(|v| v.as_i64()) .unwrap_or(0); let prefab_name = prefab_path .file_stem() .and_then(|s| s.to_str()) .unwrap_or("unknown"); all_resources.push(( prefab_name.to_string(), type_id, max_health, )); found_in_prefab = true; } } else if prefab_path.file_name().unwrap().to_string_lossy().contains("Copper Ore") { eprintln!("DEBUG: Could not resolve GUID '{}' in Copper Ore prefab", guid_val); } } } } } } if found_in_prefab { println!(" ✅ Found Interactable_Resource"); } else { println!(" ⊘ No Interactable_Resource found"); } } Ok(_) => { println!(" ⊘ Not a prefab file"); } Err(e) => { println!(" ❌ Parse error: {}", e); } } } println!(); println!("{}", "=".repeat(70)); println!("📊 Summary: Found {} resource(s)", all_resources.len()); println!("{}", "=".repeat(70)); println!(); if !all_resources.is_empty() { // Display resources for (name, type_id, max_health) in &all_resources { println!(" 📦 Prefab: \"{}\"", name); println!(" • typeId: {}", type_id); println!(" • maxHealth: {}", max_health); println!(); } // Write to output file let output_path = "resource_prefabs_output.txt"; let mut output_file = File::create(output_path)?; writeln!(output_file, "Cursebreaker Resource Prefabs")?; writeln!(output_file, "{}", "=".repeat(70))?; writeln!(output_file)?; writeln!(output_file, "Total resources found: {}", all_resources.len())?; writeln!(output_file)?; writeln!(output_file, "{}", "-".repeat(70))?; writeln!(output_file)?; for (name, type_id, max_health) in &all_resources { writeln!(output_file, "Prefab: {}", name)?; writeln!(output_file, " TypeID: {}", type_id)?; writeln!(output_file, " MaxHealth: {}", max_health)?; writeln!(output_file)?; } writeln!(output_file, "{}", "=".repeat(70))?; writeln!(output_file, "End of resource data")?; println!("📝 Resource data written to: {}", output_path); } println!(); println!("{}", "=".repeat(70)); println!("✅ Parsing complete!"); println!("{}", "=".repeat(70)); Ok(()) }