diff --git a/BannerlordModEditor.Common.Tests/Services/ProjectTypeDetectorTests.cs b/BannerlordModEditor.Common.Tests/Services/ProjectTypeDetectorTests.cs new file mode 100644 index 00000000..9bee05f1 --- /dev/null +++ b/BannerlordModEditor.Common.Tests/Services/ProjectTypeDetectorTests.cs @@ -0,0 +1,146 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Xunit; +using BannerlordModEditor.Common.Services; + +namespace BannerlordModEditor.Common.Tests.Services +{ + public class ProjectTypeDetectorTests + { + private readonly ProjectTypeDetector _detector; + + public ProjectTypeDetectorTests() + { + _detector = new ProjectTypeDetector(); + } + + [Fact] + public void DetectProjectType_ReturnsUnknownForNonExistentPath() + { + var result = _detector.DetectProjectType("/nonexistent/path"); + Assert.Equal(ProjectType.Unknown, result.Type); + } + + [Fact] + public void DetectProjectType_ReturnsNativeModForSubmoduleOnly() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + File.WriteAllText(Path.Combine(tempDir, "SubModule.xml"), ""); + + var result = _detector.DetectProjectType(tempDir); + Assert.Equal(ProjectType.NativeMod, result.Type); + Assert.True(result.HasSubmoduleXml); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void DetectProjectType_ReturnsCustomTemplateForSubmoduleAndCsproj() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + File.WriteAllText(Path.Combine(tempDir, "SubModule.xml"), ""); + File.WriteAllText(Path.Combine(tempDir, "TestMod.csproj"), ""); + + var result = _detector.DetectProjectType(tempDir); + Assert.Equal(ProjectType.CustomTemplate, result.Type); + Assert.True(result.HasSubmoduleXml); + Assert.True(result.HasCsprojFile); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void DetectProjectType_ReturnsButrTemplateForButrMarker() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var butrDir = Path.Combine(tempDir, "BUTR"); + Directory.CreateDirectory(butrDir); + + try + { + File.WriteAllText(Path.Combine(tempDir, "SubModule.xml"), ""); + File.WriteAllText(Path.Combine(tempDir, "TestMod.csproj"), ""); + File.WriteAllText(Path.Combine(butrDir, "Bannerlord.ButterLib.dll"), ""); + + var result = _detector.DetectProjectType(tempDir); + Assert.Equal(ProjectType.ButrTemplate, result.Type); + Assert.True(result.HasButrMarker); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void IsButrTemplateProject_ReturnsTrueForButrProject() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var butrDir = Path.Combine(tempDir, "BUTR"); + Directory.CreateDirectory(butrDir); + + try + { + File.WriteAllText(Path.Combine(butrDir, "marker.txt"), "BUTR marker"); + + var result = _detector.IsButrTemplateProject(tempDir); + Assert.True(result); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void GetModName_ReturnsCsprojName() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(tempDir); + + try + { + File.WriteAllText(Path.Combine(tempDir, "MyCoolMod.csproj"), ""); + + var result = _detector.GetModName(tempDir); + Assert.Equal("MyCoolMod", result); + } + finally + { + Directory.Delete(tempDir, true); + } + } + + [Fact] + public void GetModName_ReturnsDirectoryNameWhenNoCsproj() + { + var tempDir = Path.Combine(Path.GetTempPath(), "TestModProject"); + Directory.CreateDirectory(tempDir); + + try + { + var result = _detector.GetModName(tempDir); + Assert.Equal("TestModProject", result); + } + finally + { + Directory.Delete(tempDir, true); + } + } + } +} diff --git a/BannerlordModEditor.Common/Services/ProjectTypeDetector.cs b/BannerlordModEditor.Common/Services/ProjectTypeDetector.cs new file mode 100644 index 00000000..ddc4d53f --- /dev/null +++ b/BannerlordModEditor.Common/Services/ProjectTypeDetector.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace BannerlordModEditor.Common.Services +{ + public enum ProjectType + { + Unknown, + ButrTemplate, + CustomTemplate, + NativeMod + } + + public class ProjectTypeInfo + { + public ProjectType Type { get; set; } + public string ProjectPath { get; set; } = string.Empty; + public bool HasSubmoduleXml { get; set; } + public bool HasCsprojFile { get; set; } + public bool HasButrMarker { get; set; } + public string? ModName { get; set; } + public string? ModuleDataPath { get; set; } + } + + public interface IProjectTypeDetector + { + ProjectTypeInfo DetectProjectType(string projectPath); + bool IsButrTemplateProject(string projectPath); + string? GetModName(string projectPath); + } + + public class ProjectTypeDetector : IProjectTypeDetector + { + private static readonly string[] ButrMarkers = new[] + { + "Bannerlord.ButterLib.dll", + "Bannerlord.ButterLib", + "BUTR", + "BUTRLoadingScreen" + }; + + private static readonly string[] ButrDirectories = new[] + { + "BUTR", + "ButterLib", + "LoadingScreen" + }; + + public ProjectTypeInfo DetectProjectType(string projectPath) + { + var info = new ProjectTypeInfo + { + ProjectPath = projectPath, + Type = ProjectType.Unknown + }; + + if (!Directory.Exists(projectPath)) + return info; + + info.HasSubmoduleXml = File.Exists(Path.Combine(projectPath, "SubModule.xml")); + info.HasCsprojFile = Directory.GetFiles(projectPath, "*.csproj").Any(); + info.HasButrMarker = CheckForButrMarkers(projectPath); + info.ModName = GetModName(projectPath); + + var moduleDataDir = Path.Combine(projectPath, "ModuleData"); + if (Directory.Exists(moduleDataDir)) + { + info.ModuleDataPath = moduleDataDir; + } + + if (info.HasButrMarker) + { + info.Type = ProjectType.ButrTemplate; + } + else if (info.HasSubmoduleXml && info.HasCsprojFile) + { + info.Type = ProjectType.CustomTemplate; + } + else if (info.HasSubmoduleXml) + { + info.Type = ProjectType.NativeMod; + } + + return info; + } + + public bool IsButrTemplateProject(string projectPath) + { + return DetectProjectType(projectPath).Type == ProjectType.ButrTemplate; + } + + public string? GetModName(string projectPath) + { + if (!Directory.Exists(projectPath)) + return null; + + var csprojFiles = Directory.GetFiles(projectPath, "*.csproj"); + if (csprojFiles.Any()) + { + var csprojPath = csprojFiles.First(); + var csprojName = Path.GetFileNameWithoutExtension(csprojPath); + return csprojName; + } + + var dirName = new DirectoryInfo(projectPath).Name; + return dirName; + } + + private bool CheckForButrMarkers(string projectPath) + { + var allFiles = GetAllFiles(projectPath); + + foreach (var file in allFiles) + { + var fileName = Path.GetFileName(file); + if (ButrMarkers.Any(marker => fileName.Contains(marker, StringComparison.OrdinalIgnoreCase))) + return true; + + var content = SafeReadFile(file); + if (content != null && content.Contains("BUTR", StringComparison.OrdinalIgnoreCase)) + return true; + } + + var directories = Directory.GetDirectories(projectPath, "*", SearchOption.AllDirectories); + foreach (var dir in directories) + { + var dirName = new DirectoryInfo(dir).Name; + if (ButrDirectories.Any(marker => dirName.Contains(marker, StringComparison.OrdinalIgnoreCase))) + return true; + } + + return false; + } + + private IEnumerable GetAllFiles(string directory) + { + var files = new List(); + + try + { + files.AddRange(Directory.GetFiles(directory, "*.*", SearchOption.TopDirectoryOnly)); + + foreach (var subDir in Directory.GetDirectories(directory)) + { + var dirName = new DirectoryInfo(subDir).Name; + if (dirName != "bin" && dirName != "obj" && dirName != ".git") + { + files.AddRange(GetAllFiles(subDir)); + } + } + } + catch + { + } + + return files; + } + + private string? SafeReadFile(string filePath) + { + try + { + return File.ReadAllText(filePath); + } + catch + { + return null; + } + } + } +} diff --git a/BannerlordModEditor.UI/App.axaml.cs b/BannerlordModEditor.UI/App.axaml.cs index 028d620f..620bb523 100644 --- a/BannerlordModEditor.UI/App.axaml.cs +++ b/BannerlordModEditor.UI/App.axaml.cs @@ -71,6 +71,8 @@ private IServiceCollection ConfigureServices() // 注册Common层服务 services.AddTransient(); + services.AddSingleton(); + services.AddSingleton(); // 注册所有编辑器ViewModel和View services.AddTransient();