mirror of
https://github.com/waynebian01/Shigure.git
synced 2026-09-02 22:39:01 +08:00
- Introduced a new markdown file detailing the texture sorting mechanism for ClassBlocks. - Explained the allocation of states, auras, spells, and group pixels, along with the independent layout of auxiliary bars. - Provided a comprehensive overview of the output areas, sorting order, and pixel encoding. - Included examples and guidelines for external reading and integration with existing systems.
147 lines
5.1 KiB
C#
147 lines
5.1 KiB
C#
using System.Security.Cryptography;
|
|
|
|
namespace Shigure;
|
|
|
|
/// <summary>
|
|
/// 将程序目录中的 Fuyutsui 单向部署到当前目标游戏。项目目录始终是权威源。
|
|
/// </summary>
|
|
internal sealed class FuyutsuiAddonSyncService
|
|
{
|
|
private const string AddonDirectoryName = "Fuyutsui";
|
|
private readonly string _sourceRoot;
|
|
private readonly WowProcessLocator _processLocator;
|
|
|
|
public FuyutsuiAddonSyncService(string sourceRoot, WowProcessLocator processLocator)
|
|
{
|
|
_sourceRoot = Path.GetFullPath(sourceRoot);
|
|
_processLocator = processLocator;
|
|
}
|
|
|
|
public string SourceRoot => _sourceRoot;
|
|
|
|
public FuyutsuiAddonSyncResult SynchronizeAll()
|
|
{
|
|
if (!Directory.Exists(_sourceRoot))
|
|
{
|
|
throw new DirectoryNotFoundException($"找不到项目 Fuyutsui 目录: {_sourceRoot}");
|
|
}
|
|
|
|
var targetRoot = ResolveTargetRoot();
|
|
if (targetRoot is null)
|
|
{
|
|
return FuyutsuiAddonSyncResult.TargetNotFound(_sourceRoot);
|
|
}
|
|
|
|
var copied = new List<string>();
|
|
var skipped = new List<string>();
|
|
var failures = new List<FuyutsuiAddonSyncFailure>();
|
|
foreach (var sourcePath in Directory.EnumerateFiles(_sourceRoot, "*", SearchOption.AllDirectories))
|
|
{
|
|
var relativePath = Path.GetRelativePath(_sourceRoot, sourcePath);
|
|
SynchronizeCore(sourcePath, relativePath, targetRoot, copied, skipped, failures);
|
|
}
|
|
|
|
return new FuyutsuiAddonSyncResult(_sourceRoot, targetRoot, copied, skipped, failures, null);
|
|
}
|
|
|
|
public FuyutsuiAddonSyncResult SynchronizeFile(string sourcePath)
|
|
{
|
|
var fullSourcePath = Path.GetFullPath(sourcePath);
|
|
var relativePath = Path.GetRelativePath(_sourceRoot, fullSourcePath);
|
|
if (Path.IsPathRooted(relativePath)
|
|
|| relativePath.Equals("..", StringComparison.Ordinal)
|
|
|| relativePath.StartsWith($"..{Path.DirectorySeparatorChar}", StringComparison.Ordinal))
|
|
{
|
|
throw new InvalidOperationException($"待同步文件不在项目 Fuyutsui 目录内: {fullSourcePath}");
|
|
}
|
|
|
|
if (!File.Exists(fullSourcePath))
|
|
{
|
|
throw new FileNotFoundException("找不到待同步的项目插件文件。", fullSourcePath);
|
|
}
|
|
|
|
var targetRoot = ResolveTargetRoot();
|
|
if (targetRoot is null)
|
|
{
|
|
return FuyutsuiAddonSyncResult.TargetNotFound(_sourceRoot);
|
|
}
|
|
|
|
var copied = new List<string>();
|
|
var skipped = new List<string>();
|
|
var failures = new List<FuyutsuiAddonSyncFailure>();
|
|
SynchronizeCore(fullSourcePath, relativePath, targetRoot, copied, skipped, failures);
|
|
return new FuyutsuiAddonSyncResult(_sourceRoot, targetRoot, copied, skipped, failures, null);
|
|
}
|
|
|
|
private string? ResolveTargetRoot()
|
|
{
|
|
var addOnsDirectory = WowAddonLocator.FindAddOnsDirectory(_processLocator);
|
|
return string.IsNullOrWhiteSpace(addOnsDirectory)
|
|
? null
|
|
: Path.Combine(addOnsDirectory, AddonDirectoryName);
|
|
}
|
|
|
|
private static void SynchronizeCore(
|
|
string sourcePath,
|
|
string relativePath,
|
|
string targetRoot,
|
|
ICollection<string> copied,
|
|
ICollection<string> skipped,
|
|
ICollection<FuyutsuiAddonSyncFailure> failures)
|
|
{
|
|
try
|
|
{
|
|
var targetPath = Path.Combine(targetRoot, relativePath);
|
|
if (File.Exists(targetPath) && FilesHaveSameHash(sourcePath, targetPath))
|
|
{
|
|
skipped.Add(relativePath);
|
|
return;
|
|
}
|
|
|
|
var targetDirectory = Path.GetDirectoryName(targetPath);
|
|
if (!string.IsNullOrWhiteSpace(targetDirectory))
|
|
{
|
|
Directory.CreateDirectory(targetDirectory);
|
|
}
|
|
|
|
File.Copy(sourcePath, targetPath, overwrite: true);
|
|
copied.Add(relativePath);
|
|
}
|
|
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or CryptographicException)
|
|
{
|
|
failures.Add(new FuyutsuiAddonSyncFailure(relativePath, ex.Message));
|
|
}
|
|
}
|
|
|
|
private static bool FilesHaveSameHash(string firstPath, string secondPath)
|
|
{
|
|
using var first = File.OpenRead(firstPath);
|
|
using var second = File.OpenRead(secondPath);
|
|
var firstHash = SHA256.HashData(first);
|
|
var secondHash = SHA256.HashData(second);
|
|
return firstHash.AsSpan().SequenceEqual(secondHash);
|
|
}
|
|
}
|
|
|
|
internal sealed record FuyutsuiAddonSyncFailure(string RelativePath, string Message);
|
|
|
|
internal sealed record FuyutsuiAddonSyncResult(
|
|
string SourceRoot,
|
|
string? TargetRoot,
|
|
IReadOnlyList<string> CopiedFiles,
|
|
IReadOnlyList<string> SkippedFiles,
|
|
IReadOnlyList<FuyutsuiAddonSyncFailure> Failures,
|
|
string? SkippedReason)
|
|
{
|
|
public bool TargetFound => TargetRoot is not null;
|
|
public bool CompletedSuccessfully => TargetFound && Failures.Count == 0;
|
|
|
|
public static FuyutsuiAddonSyncResult TargetNotFound(string sourceRoot) => new(
|
|
sourceRoot,
|
|
null,
|
|
[],
|
|
[],
|
|
[],
|
|
"未找到目标游戏进程,已跳过游戏插件同步。");
|
|
}
|