From ce26ef298e851b117d9ee18716b4dd16519a20a5 Mon Sep 17 00:00:00 2001 From: lanyi Date: Tue, 25 Jan 2022 19:05:37 +0100 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=8A=A0=E8=BD=BD=20XSD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AssetEntry.cs | 38 +++- Controller.cs | 62 +++++-- .../MultiBooleanConjunctionConverter.cs | 20 +++ Converters/MultiValueAggregateConverter.cs | 45 +++++ FileSystemSuggestions.cs | 4 +- IModXml.cs | 15 ++ InputEntry.cs | 19 +- MainWindow.xaml | 104 +++++++---- ModXml.cs | 11 +- ModXsd.cs | 164 ++++++++++++++++++ ViewModel.cs | 39 ++++- 11 files changed, 435 insertions(+), 86 deletions(-) create mode 100644 Converters/MultiBooleanConjunctionConverter.cs create mode 100644 Converters/MultiValueAggregateConverter.cs create mode 100644 IModXml.cs create mode 100644 ModXsd.cs diff --git a/AssetEntry.cs b/AssetEntry.cs index 091f075..b85fc1b 100644 --- a/AssetEntry.cs +++ b/AssetEntry.cs @@ -37,11 +37,6 @@ namespace HashCalculator.GUI public static AssetEntry? TryParse(XElement element) { - if (element == null) - { - return null; - } - if (element.Name.Namespace != ModXml.EalaAsset) { TracerListener.WriteLine($"Unknown namespace: {element.Name.Namespace}"); @@ -59,6 +54,27 @@ namespace HashCalculator.GUI return null; } + public static AssetEntry? TryParseXsd(XElement element) + { + if (element.Name.Namespace != ModXsd.Schema) + { + TracerListener.WriteLine($"Unknown namespace: {element.Name.Namespace}"); + } + + try + { + var type = element.Attribute("name")?.Value + ?? throw new NotSupportedException(); + return new AssetEntry(element.Name.LocalName, type); + } + catch (Exception e) + { + TracerListener.WriteLine($"Failed to parse element: {e}"); + } + + return null; + } + public AssetEntry(XElement element) { if (element == null) @@ -107,6 +123,18 @@ namespace HashCalculator.GUI TypeIdString = $"{TypeId:x8} ({TypeId,10})"; } + private AssetEntry(string kind, string type) + { + Type = type; + Name = string.Empty; + NameString = $"<{kind} {Type}>"; + DisplayLabels = Array.Empty(); + InstanceId = 0; + TypeId = SageHash.CalculateBinaryHash(Type); + InstanceIdString = string.Empty; + TypeIdString = $"{TypeId:x8} ({TypeId,10})"; + } + public bool Equals(AssetEntry? entry) { return this == entry; diff --git a/Controller.cs b/Controller.cs index 37d427c..54c57e4 100644 --- a/Controller.cs +++ b/Controller.cs @@ -46,14 +46,14 @@ namespace HashCalculator.GUI ViewModel.StatusText = string.Empty; return; case InputEntryType.BigFile: - await LoadBig(selected.Value).ConfigureAwait(true); + await LoadBig(selected.Value); return; case InputEntryType.ManifestFile: - await LoadManifestFromFile(selected.Value).ConfigureAwait(true); + await LoadManifestFromFile(selected.Value); return; case InputEntryType.XmlFile: - ViewModel.IsXml = true; - await LoadXml(selected.Value).ConfigureAwait(true); + case InputEntryType.XsdFile: + await LoadXml(selected); return; default: throw new NotSupportedException(); @@ -125,9 +125,8 @@ namespace HashCalculator.GUI var first = bigViewModel.AllManifests.FirstOrDefault(); if (first != null) { - var name = VirtualFileSystem.GetFileNameWithoutExtension(first.Value); - if (name.StartsWith("mod", StringComparison.OrdinalIgnoreCase) - || name.StartsWith("mapmetadata", StringComparison.OrdinalIgnoreCase)) + var name = VirtualFileSystem.GetFileNameWithoutExtension(first.Value).ToLowerInvariant(); + if (new[] { "mod", "mapmetadata_mod", "static", "global" }.Any(name.StartsWith)) { bigViewModel.SelectedItem = first; await bigViewModel.SelectCommand.ExecuteTask(ViewModel).ConfigureAwait(true); @@ -189,7 +188,7 @@ namespace HashCalculator.GUI tokenSource.Cancel(); try { - await task.ConfigureAwait(true); + await task; } catch (OperationCanceledException) { @@ -197,11 +196,21 @@ namespace HashCalculator.GUI } } - private async Task LoadXml(string path) + private async Task LoadXml(InputEntry entry) { - await CancelLoadingXml().ConfigureAwait(true); + await CancelLoadingXml(); + var path = entry.Text; + var type = entry.Type; using var tokenSource = new CancellationTokenSource(); - var task = LoadXmlInternal(path, tokenSource.Token); + var modXml = type switch + { + InputEntryType.XmlFile => LoadModXml(path, tokenSource.Token), + InputEntryType.XsdFile => LoadModXsd(path, tokenSource.Token), + _ => throw new NotSupportedException(type.ToString()) + }; + ViewModel.IsXml = true; + ViewModel.IsXsd = type == InputEntryType.XsdFile; + var task = LoadXmlInternal(await modXml, tokenSource.Token); _lastXmlTask = (tokenSource, task).ToTuple(); await ExceptionWrapepr(async () => @@ -229,23 +238,32 @@ namespace HashCalculator.GUI }, "SAGE FastHash 计算器 - XML 加载失败…", "XML 文件加载失败,也许,你选择的 XML 文件并不是红警3使用的那种……\r\n").ConfigureAwait(true); } - private async Task LoadXmlInternal(string path, CancellationToken token) + private async Task LoadModXml(string path, CancellationToken cancelToken) { if (ModXml.LocateSdkFromRegistry() is not { } sdkRoot) { var studio = await RequestOpenFile("选择 Mod Studio", ("EALAModStudio.exe", null)); sdkRoot = Path.GetDirectoryName(studio); } - var modXml = new ModXml(path, sdkRoot, token); - + var modXml = new ModXml(path, sdkRoot, cancelToken); try { - await FindCsf(modXml.BaseDirectory).ConfigureAwait(true); + await FindCsf(modXml.BaseDirectory, cancelToken); } catch (Exception exception) { TracerListener.WriteLine($"[ModXml] Failed to automatically find and load CSF: {exception}"); } + return modXml; + } + + private Task LoadModXsd(string path, CancellationToken token) + { + return Task.FromResult(new ModXsd(path, token)); + } + + private async Task LoadXmlInternal(IModXml modXml, CancellationToken token) + { token.ThrowIfCancellationRequested(); var start = DateTimeOffset.UtcNow; @@ -333,9 +351,9 @@ namespace HashCalculator.GUI }); } - private static Task FindCsf(DirectoryInfo baseDirectory) + private static Task FindCsf(DirectoryInfo baseDirectory, CancellationToken cancelToken) { - return Task.Run(() => + return Task.Run(async () => { if (!baseDirectory.Exists) { @@ -355,6 +373,7 @@ namespace HashCalculator.GUI from data in directories.GetDirectories("Data") from csf in data.GetFiles("*.csf") select csf; + csfs = csfs.AsParallel().WithCancellation(cancelToken); var result = csfs.FirstOrDefault(x => x.Name.Equals("gamestrings.csf", StringComparison.OrdinalIgnoreCase)) ?? csfs.FirstOrDefault(); @@ -363,8 +382,13 @@ namespace HashCalculator.GUI throw new FileNotFoundException(); } - using var fileStream = result.OpenRead(); - Translator.Provider = new CsfTranslationProvider(fileStream); + using var memoryStream = new MemoryStream(); + using (var fileStream = result.OpenRead()) + { + await fileStream.CopyToAsync(memoryStream, cancelToken); + memoryStream.Position = 0; + } + Translator.Provider = new CsfTranslationProvider(memoryStream); TracerListener.WriteLine($"[ModXml]: Automatically loaded csf from `{result.FullName}`"); }); } diff --git a/Converters/MultiBooleanConjunctionConverter.cs b/Converters/MultiBooleanConjunctionConverter.cs new file mode 100644 index 0000000..2e6d3a2 --- /dev/null +++ b/Converters/MultiBooleanConjunctionConverter.cs @@ -0,0 +1,20 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Windows.Data; + +namespace HashCalculator.GUI.Converters +{ + class MultiBooleanConjunctionConverter : IMultiValueConverter + { + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + return values.Cast().Aggregate((a, b) => a && b); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Converters/MultiValueAggregateConverter.cs b/Converters/MultiValueAggregateConverter.cs new file mode 100644 index 0000000..78f79a7 --- /dev/null +++ b/Converters/MultiValueAggregateConverter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Windows.Data; + +namespace HashCalculator.GUI.Converters +{ + class MultiValueAggregateConverter : List, IMultiValueConverter + { + public IMultiValueConverter? Converter { get; set; } + public IValueConverter? PostProcess { get; set; } + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + if (Converter is not { } converter) + { + throw new InvalidOperationException($"{nameof(Converter)} of {nameof(MultiValueAggregateConverter)} is null"); + } + if (Count > 0) + { + values = values.ToArray(); // 复制一份 + var length = Math.Min(Count, values.Length); + for (var i = 0; i < length; ++i) + { + if (this[i] is IValueConverter c) + { + values[i] = c.Convert(values[i], targetType, parameter, culture); + } + } + } + var result = converter.Convert(values, targetType, parameter, culture); + if (PostProcess is not { } postConverter) + { + return result; + } + return postConverter.Convert(result, targetType, parameter, culture); + } + + public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/FileSystemSuggestions.cs b/FileSystemSuggestions.cs index 4801518..6e343c9 100644 --- a/FileSystemSuggestions.cs +++ b/FileSystemSuggestions.cs @@ -8,17 +8,17 @@ namespace HashCalculator.GUI { internal class FileSystemSuggestions { - private static readonly Dictionary Mapping = new Dictionary(StringComparer.OrdinalIgnoreCase) + private static readonly Dictionary Mapping = new(StringComparer.OrdinalIgnoreCase) { { ".big", InputEntryType.BigFile }, { ".xml", InputEntryType.XmlFile }, { ".w3x", InputEntryType.XmlFile }, { ".manifest", InputEntryType.ManifestFile }, + { ".xsd", InputEntryType.XsdFile }, }; private Search _search = new Search(); - [SuppressMessage("Microsoft.Performance", "CA1031")] public IEnumerable ProvideFileSystemSuggestions(string? path) { var empty = Enumerable.Empty(); diff --git a/IModXml.cs b/IModXml.cs new file mode 100644 index 0000000..dab48e6 --- /dev/null +++ b/IModXml.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HashCalculator.GUI +{ + interface IModXml + { + public int TotalFilesProcessed { get; } + public int TotalAssets { get; } + IAsyncEnumerable ProcessDocument(); + } +} diff --git a/InputEntry.cs b/InputEntry.cs index 0fb2335..056680e 100644 --- a/InputEntry.cs +++ b/InputEntry.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; - -using System.Text; -using System.Threading.Tasks; -using TechnologyAssembler.Core.IO; +using System.Collections.Generic; namespace HashCalculator.GUI { @@ -13,18 +8,20 @@ namespace HashCalculator.GUI BigFile, ManifestFile, XmlFile, + XsdFile, BinaryFile, Path, } internal sealed class InputEntry { - public static IReadOnlyDictionary Descriptions = new Dictionary + public static IReadOnlyDictionary Descriptions = new Dictionary { { InputEntryType.BigFile, "可以尝试读取这个 big 文件里的 manifest 文件" }, { InputEntryType.BinaryFile, "可以计算这个文件的哈希值" }, { InputEntryType.ManifestFile, "可以尝试读取这个 manifest 文件,显示各个素材的哈希值" }, - { InputEntryType.XmlFile, "可以尝试读取这个XML,显示 XML 里定义的各个素材的哈希值" }, + { InputEntryType.XmlFile, "可以尝试读取这个 XML,显示 XML 里定义的各个素材的哈希值" }, + { InputEntryType.XsdFile, "可以尝试读取 XSD,显示 XSD 里定义的各个素材的哈希值" }, { InputEntryType.Path, string.Empty }, }; @@ -33,13 +30,13 @@ namespace HashCalculator.GUI public string Text { get; } public string Details { - get + get { - if(Type == InputEntryType.Text) + if (Type == InputEntryType.Text) { var hash = SageHash.CalculateLowercaseHash(Value); var binaryHash = SageHash.CalculateBinaryHash(Value); - return hash == binaryHash + return hash == binaryHash ? $"这段文字的哈希值:{hash:x8} ({hash})" : $"这段文字的哈希值:{hash:x8};大小写敏感哈希值 {binaryHash:x8}"; } diff --git a/MainWindow.xaml b/MainWindow.xaml index 78e9d30..d2338d9 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -6,6 +6,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:l="clr-namespace:HashCalculator.GUI" + xmlns:c="clr-namespace:HashCalculator.GUI.Converters" + xmlns:sys="clr-namespace:System;assembly=System.Runtime" mc:Ignorable="d" Title="SAGE FastHash 哈希计算器" Height="600" @@ -152,47 +154,54 @@ Height="25" Margin="0,0,0,5" > -