using System;
using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json.Linq;
using UnityEditor;
using UnityEngine;
#if UNITY_VFX_GRAPH
using UnityEngine.VFX;
#endif
namespace MCPForUnity.Editor.Tools.Vfx
{
///
/// Asset management operations for VFX Graph.
/// Handles creating, assigning, and listing VFX assets.
/// Requires com.unity.visualeffectgraph package and UNITY_VFX_GRAPH symbol.
///
internal static class VfxGraphAssets
{
#if !UNITY_VFX_GRAPH
public static object CreateAsset(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object AssignAsset(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object ListTemplates(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
public static object ListAssets(JObject @params)
{
return new { success = false, message = "VFX Graph package (com.unity.visualeffectgraph) not installed" };
}
#else
private static readonly string[] SupportedVfxGraphVersions = { "12.1" };
///
/// Creates a new VFX Graph asset file from a template.
///
public static object CreateAsset(JObject @params)
{
string assetName = @params["assetName"]?.ToString();
string folderPath = @params["folderPath"]?.ToString() ?? "Assets/VFX";
string template = @params["template"]?.ToString() ?? "empty";
if (string.IsNullOrEmpty(assetName))
{
return new { success = false, message = "assetName is required" };
}
string versionError = ValidateVfxGraphVersion();
if (!string.IsNullOrEmpty(versionError))
{
return new { success = false, message = versionError };
}
// Ensure folder exists
if (!AssetDatabase.IsValidFolder(folderPath))
{
string[] folders = folderPath.Split('/');
string currentPath = folders[0];
for (int i = 1; i < folders.Length; i++)
{
string newPath = currentPath + "/" + folders[i];
if (!AssetDatabase.IsValidFolder(newPath))
{
AssetDatabase.CreateFolder(currentPath, folders[i]);
}
currentPath = newPath;
}
}
string assetPath = $"{folderPath}/{assetName}.vfx";
// Check if asset already exists
if (AssetDatabase.LoadAssetAtPath(assetPath) != null)
{
bool overwrite = @params["overwrite"]?.ToObject() ?? false;
if (!overwrite)
{
return new { success = false, message = $"Asset already exists at {assetPath}. Set overwrite=true to replace." };
}
AssetDatabase.DeleteAsset(assetPath);
}
// Find template asset and copy it
string templatePath = FindTemplate(template);
string templateAssetPath = TryGetAssetPathFromFileSystem(templatePath);
VisualEffectAsset newAsset = null;
if (!string.IsNullOrEmpty(templateAssetPath))
{
// Copy the asset to create a new VFX Graph asset
if (!AssetDatabase.CopyAsset(templateAssetPath, assetPath))
{
return new { success = false, message = $"Failed to copy VFX template from {templateAssetPath}" };
}
AssetDatabase.Refresh();
newAsset = AssetDatabase.LoadAssetAtPath(assetPath);
}
else
{
return new { success = false, message = "VFX template not found. Add a .vfx template asset or install VFX Graph templates." };
}
if (newAsset == null)
{
return new { success = false, message = "Failed to create VFX asset. Try using a template from list_templates." };
}
return new
{
success = true,
message = $"Created VFX asset: {assetPath}",
data = new
{
assetPath = assetPath,
assetName = newAsset.name,
template = template
}
};
}
///
/// Finds VFX template path by name.
///
private static string FindTemplate(string templateName)
{
// Get the actual filesystem path for the VFX Graph package using PackageManager API
var packageInfo = UnityEditor.PackageManager.PackageInfo.FindForAssetPath("Packages/com.unity.visualeffectgraph");
var searchPaths = new List();
if (packageInfo != null)
{
// Use the resolved path from PackageManager (handles Library/PackageCache paths)
searchPaths.Add(System.IO.Path.Combine(packageInfo.resolvedPath, "Editor/Templates"));
searchPaths.Add(System.IO.Path.Combine(packageInfo.resolvedPath, "Samples"));
}
// Also search project-local paths
searchPaths.Add("Assets/VFX/Templates");
string[] templatePatterns = new[]
{
$"{templateName}.vfx",
$"VFX{templateName}.vfx",
$"Simple{templateName}.vfx",
$"{templateName}VFX.vfx"
};
foreach (string basePath in searchPaths)
{
string searchRoot = basePath;
if (basePath.StartsWith("Assets/"))
{
searchRoot = System.IO.Path.Combine(UnityEngine.Application.dataPath, basePath.Substring("Assets/".Length));
}
if (!System.IO.Directory.Exists(searchRoot))
{
continue;
}
foreach (string pattern in templatePatterns)
{
string[] files = System.IO.Directory.GetFiles(searchRoot, pattern, System.IO.SearchOption.AllDirectories);
if (files.Length > 0)
{
return files[0];
}
}
// Also search by partial match
try
{
string[] allVfxFiles = System.IO.Directory.GetFiles(searchRoot, "*.vfx", System.IO.SearchOption.AllDirectories);
foreach (string file in allVfxFiles)
{
if (System.IO.Path.GetFileNameWithoutExtension(file).ToLower().Contains(templateName.ToLower()))
{
return file;
}
}
}
catch (Exception ex)
{
Debug.LogWarning($"Failed to search VFX templates under '{searchRoot}': {ex.Message}");
}
}
// Search in project assets
string[] guids = AssetDatabase.FindAssets("t:VisualEffectAsset " + templateName);
if (guids.Length > 0)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
// Convert asset path (e.g., "Assets/...") to absolute filesystem path
if (!string.IsNullOrEmpty(assetPath) && assetPath.StartsWith("Assets/"))
{
return System.IO.Path.Combine(UnityEngine.Application.dataPath, assetPath.Substring("Assets/".Length));
}
if (!string.IsNullOrEmpty(assetPath) && assetPath.StartsWith("Packages/"))
{
var info = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(assetPath);
if (info != null)
{
string relPath = assetPath.Substring(("Packages/" + info.name + "/").Length);
return System.IO.Path.Combine(info.resolvedPath, relPath);
}
}
return null;
}
return null;
}
///
/// Assigns a VFX asset to a VisualEffect component.
///
public static object AssignAsset(JObject @params)
{
VisualEffect vfx = VfxGraphCommon.FindVisualEffect(@params);
if (vfx == null)
{
return new { success = false, message = "VisualEffect component not found" };
}
string assetPath = @params["assetPath"]?.ToString();
if (string.IsNullOrEmpty(assetPath))
{
return new { success = false, message = "assetPath is required" };
}
// Validate and normalize path
// Reject absolute paths, parent directory traversal, and backslashes
if (assetPath.Contains("\\") || assetPath.Contains("..") || System.IO.Path.IsPathRooted(assetPath))
{
return new { success = false, message = "Invalid assetPath: traversal and absolute paths are not allowed" };
}
if (assetPath.StartsWith("Packages/"))
{
return new { success = false, message = "Invalid assetPath: VFX assets must live under Assets/." };
}
if (!assetPath.StartsWith("Assets/"))
{
assetPath = "Assets/" + assetPath;
}
if (!assetPath.EndsWith(".vfx"))
{
assetPath += ".vfx";
}
// Verify the normalized path doesn't escape the project
string fullPath = System.IO.Path.Combine(UnityEngine.Application.dataPath, assetPath.Substring("Assets/".Length));
string canonicalProjectRoot = System.IO.Path.GetFullPath(UnityEngine.Application.dataPath);
string canonicalAssetPath = System.IO.Path.GetFullPath(fullPath);
if (!canonicalAssetPath.StartsWith(canonicalProjectRoot + System.IO.Path.DirectorySeparatorChar) &&
canonicalAssetPath != canonicalProjectRoot)
{
return new { success = false, message = "Invalid assetPath: would escape project directory" };
}
var asset = AssetDatabase.LoadAssetAtPath(assetPath);
if (asset == null)
{
// Try searching by name
string searchName = System.IO.Path.GetFileNameWithoutExtension(assetPath);
string[] guids = AssetDatabase.FindAssets($"t:VisualEffectAsset {searchName}");
if (guids.Length > 0)
{
assetPath = AssetDatabase.GUIDToAssetPath(guids[0]);
asset = AssetDatabase.LoadAssetAtPath(assetPath);
}
}
if (asset == null)
{
return new { success = false, message = $"VFX asset not found: {assetPath}" };
}
Undo.RecordObject(vfx, "Assign VFX Asset");
vfx.visualEffectAsset = asset;
EditorUtility.SetDirty(vfx);
return new
{
success = true,
message = $"Assigned VFX asset '{asset.name}' to {vfx.gameObject.name}",
data = new
{
gameObject = vfx.gameObject.name,
assetName = asset.name,
assetPath = assetPath
}
};
}
///
/// Lists available VFX templates.
///
public static object ListTemplates(JObject @params)
{
var templates = new List