//! Unreal Engine C++ extractor. //! //! Detects security and architecture issues in Unreal Engine C++ code, such as: //! - Exposed Exec functions (can be called from console by users/cheaters) //! - Unprotected replication variables //! - Hardcoded asset paths (fragile and hard to maintain) #![allow(clippy::too_many_arguments)] use regex::Regex; use stemedb_core::types::ObjectValue; use super::Extractor; use crate::types::{Language, Observation}; /// Extractor for Unreal Engine C++ patterns. pub struct UnrealCppExtractor { /// UFUNCTION(Exec) detection exec_function: Regex, /// UPROPERTY(Replicated) without condition unconditional_replication: Regex, /// Hardcoded asset paths (TEXT("/Game/...") or TEXT("/Engine/...")) hardcoded_asset_path: Regex, } impl Default for UnrealCppExtractor { fn default() -> Self { Self::new() } } impl UnrealCppExtractor { /// Create a new Unreal C++ extractor with compiled regexes. /// /// # Panics /// Panics if regex patterns are invalid. #[allow(clippy::expect_used)] pub fn new() -> Self { Self { exec_function: Regex::new(r"UFUNCTION\s*\((?:[^)]*,\s*)?Exec(?:,\s*[^)]*)?\)") .expect("valid regex"), unconditional_replication: Regex::new( r"UPROPERTY\s*\((?:[^)]*,\s*)?Replicated(?:\s*)\)", ) .expect("valid regex"), hardcoded_asset_path: Regex::new(r#"TEXT\s*\(\s*['"]/(Game|Engine)/[^'"]+['"]\s*\)"#) .expect("valid regex"), } } fn check_pattern( &self, content: &str, pattern: &Regex, path_segments: &[String], file: &str, category: &str, leaf: &str, desc_template: &str, ) -> Vec { let mut claims = Vec::new(); for (line_idx, line) in content.lines().enumerate() { if let Some(matched) = pattern.find(line) { let mut concept_path = path_segments.to_vec(); concept_path.push("unreal".to_string()); concept_path.push(category.to_string()); concept_path.push(leaf.to_string()); claims.push(Observation { concept_path: format!("code://{}", concept_path.join("/")), predicate: "exposed".to_string(), // Default predicate value: ObjectValue::Boolean(true), file: file.to_string(), line: line_idx + 1, matched_text: matched.as_str().to_string(), confidence: 0.9, description: desc_template.to_string(), }); } } claims } } impl Extractor for UnrealCppExtractor { fn name(&self) -> &str { "unreal_cpp" } fn languages(&self) -> &[Language] { &[Language::Cpp] } fn extract( &self, path_segments: &[String], content: &str, language: Language, file: &str, ) -> Vec { if language != Language::Cpp { return vec![]; } let mut claims = Vec::new(); // Check for Exec functions claims.extend(self.check_pattern( content, &self.exec_function, path_segments, file, "security", "exec_function", "UFUNCTION(Exec) exposes this function to the console", )); // Check for Unconditional Replication claims.extend(self.check_pattern( content, &self.unconditional_replication, path_segments, file, "security", "replication", "UPROPERTY(Replicated) used without condition", )); // Check for Hardcoded Asset Paths claims.extend(self.check_pattern( content, &self.hardcoded_asset_path, path_segments, file, "assets", "hardcoded_path", "Hardcoded asset path found in C++. Use SoftObjectPtr or UPROPERTY(Config) instead.", )); claims } } #[cfg(test)] mod tests { use super::*; #[test] fn test_exec_function_detection() { let extractor = UnrealCppExtractor::new(); let content = r#" UCLASS() class AMyActor : public AActor { GENERATED_BODY() UFUNCTION(Exec) void CheatGiveMoney(); }; "#; let claims = extractor.extract( &["cpp".to_string()], content, Language::Cpp, "Source/MyGame/MyActor.h", ); assert_eq!(claims.len(), 1); assert_eq!(claims[0].concept_path, "code://cpp/unreal/security/exec_function"); } #[test] fn test_hardcoded_asset_path_detection() { let extractor = UnrealCppExtractor::new(); let content = r#" static ConstructorHelpers::FObjectFinder Logo(TEXT("/Game/UI/Logo")); static ConstructorHelpers::FClassFinder Pawn(TEXT("/Engine/BasicShapes/Cube")); "#; let claims = extractor.extract( &["cpp".to_string()], content, Language::Cpp, "Source/MyGame/MyActor.cpp", ); assert_eq!(claims.len(), 2); assert_eq!(claims[0].concept_path, "code://cpp/unreal/assets/hardcoded_path"); assert_eq!(claims[1].concept_path, "code://cpp/unreal/assets/hardcoded_path"); } }