更加可靠的 XML 处理
This commit is contained in:
		
							parent
							
								
									411204b1ac
								
							
						
					
					
						commit
						6ce486945b
					
				@ -18,6 +18,7 @@ namespace HashCalculator.GUI
 | 
			
		||||
        {
 | 
			
		||||
            var core = new TechnologyAssemblerCoreModule();
 | 
			
		||||
            core.Initialize();
 | 
			
		||||
            TracerListener.StartListening();
 | 
			
		||||
            DispatcherUnhandledException += (s, e) => Program.ErrorBox($"SAGE FastHash 计算器遇上了未处理的错误:{e.Exception}");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,6 @@
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Xml.Linq;
 | 
			
		||||
using TechnologyAssembler.Core.Assets;
 | 
			
		||||
 | 
			
		||||
@ -34,6 +33,30 @@ 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}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                return new AssetEntry(element);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception e)
 | 
			
		||||
            {
 | 
			
		||||
                TracerListener.WriteLine($"Failed to parse element: {e}");
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return null;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public AssetEntry(XElement element)
 | 
			
		||||
        {
 | 
			
		||||
            if (element == null)
 | 
			
		||||
 | 
			
		||||
@ -1,14 +1,12 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using TechnologyAssembler.Core.IO;
 | 
			
		||||
using TechnologyAssembler.Core.Assets;
 | 
			
		||||
using TechnologyAssembler.Core.IO;
 | 
			
		||||
using TechnologyAssembler.Core.Language.Providers;
 | 
			
		||||
 | 
			
		||||
namespace HashCalculator.GUI
 | 
			
		||||
@ -29,11 +27,6 @@ namespace HashCalculator.GUI
 | 
			
		||||
            ViewModel = viewModel;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void StartTrace(Action<Action> action)
 | 
			
		||||
        {
 | 
			
		||||
            TracerListener.StartListening(s => action(() => ViewModel.TraceText += s));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task OnMainInputDecided(InputEntry selected)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
@ -41,7 +34,7 @@ namespace HashCalculator.GUI
 | 
			
		||||
                _currentBig?.Dispose();
 | 
			
		||||
                _currentBig = null;
 | 
			
		||||
                ClearEntries();
 | 
			
		||||
                ViewModel.TraceText = string.Empty;
 | 
			
		||||
                ViewModel.ClearTraceText.Execute(null);
 | 
			
		||||
                ViewModel.BigEntryInput.AllManifests = null;
 | 
			
		||||
                ViewModel.IsXml = false;
 | 
			
		||||
 | 
			
		||||
@ -50,7 +43,7 @@ namespace HashCalculator.GUI
 | 
			
		||||
                    case InputEntryType.BinaryFile:
 | 
			
		||||
                        ViewModel.StatusText = "请留意一下弹出的窗口(";
 | 
			
		||||
                        CopyableBox.ShowDialog("SAGE FastHash 计算器", token => CalculateBinaryHash(selected.Value, token));
 | 
			
		||||
                        ViewModel.StatusText = ViewModel.SuggestionString(string.Empty);
 | 
			
		||||
                        ViewModel.StatusText = string.Empty;
 | 
			
		||||
                        return;
 | 
			
		||||
                    case InputEntryType.BigFile:
 | 
			
		||||
                        await LoadBig(selected.Value).ConfigureAwait(true);
 | 
			
		||||
@ -77,6 +70,11 @@ namespace HashCalculator.GUI
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Task<string?> RequestOpenFile(string title, params (string Extension, string? Description)[] filters)
 | 
			
		||||
        {
 | 
			
		||||
            return ViewModel.RequestOpenFile(title, filters);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static Task<string> CalculateBinaryHash(string filePath, CancellationToken cancel)
 | 
			
		||||
        {
 | 
			
		||||
            return Task.Run(() =>
 | 
			
		||||
@ -233,7 +231,12 @@ namespace HashCalculator.GUI
 | 
			
		||||
 | 
			
		||||
        private async Task LoadXmlInternal(string path, CancellationToken token)
 | 
			
		||||
        {
 | 
			
		||||
            var modXml = new ModXml(path, token);
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
@ -380,7 +383,8 @@ namespace HashCalculator.GUI
 | 
			
		||||
        private void AddEntry(AssetEntry entry)
 | 
			
		||||
        {
 | 
			
		||||
            _loadedAssets.Add(entry);
 | 
			
		||||
            ViewModel.AddNewItems(Enumerable.Repeat(entry, 1));
 | 
			
		||||
 | 
			
		||||
            ViewModel.AddNewItems(new[] { entry });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void AddEntries(IEnumerable<AssetEntry> entries)
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
    <UseWPF>true</UseWPF>
 | 
			
		||||
    <OutputType>WinExe</OutputType>
 | 
			
		||||
    <TargetFramework>net5.0-windows</TargetFramework>
 | 
			
		||||
    <LangVersion>8.0</LangVersion>
 | 
			
		||||
    <LangVersion>latest</LangVersion>
 | 
			
		||||
    <Nullable>enable</Nullable>
 | 
			
		||||
    <UseWPF>true</UseWPF>
 | 
			
		||||
    <Platforms>AnyCPU;x86</Platforms>
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,6 @@
 | 
			
		||||
    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"
 | 
			
		||||
    mc:Ignorable="d"
 | 
			
		||||
    Title="SAGE FastHash 哈希计算器" 
 | 
			
		||||
    Height="600" 
 | 
			
		||||
@ -123,11 +122,30 @@
 | 
			
		||||
                Height="25"
 | 
			
		||||
                Margin="0,5,0,0"
 | 
			
		||||
            >
 | 
			
		||||
                <TextBlock   
 | 
			
		||||
                    Text="{Binding StatusText}"
 | 
			
		||||
                <TextBlock
 | 
			
		||||
                    Margin="5,0,0,0"
 | 
			
		||||
                    VerticalAlignment="Center"
 | 
			
		||||
                />
 | 
			
		||||
                >
 | 
			
		||||
                    <TextBlock.Style>
 | 
			
		||||
                        <Style TargetType="TextBlock">
 | 
			
		||||
                            <Setter
 | 
			
		||||
                                Property="Text"
 | 
			
		||||
                                Value="{Binding Path=StatusText}" 
 | 
			
		||||
                            />
 | 
			
		||||
                            <Style.Triggers>
 | 
			
		||||
                                <DataTrigger
 | 
			
		||||
                                    Binding="{Binding Path=StatusText}"
 | 
			
		||||
                                    Value="{x:Null}"
 | 
			
		||||
                                >
 | 
			
		||||
                                    <Setter
 | 
			
		||||
                                        Property="Text"
 | 
			
		||||
                                        Value="{Binding ElementName=Self, Path=SuggestionString}"
 | 
			
		||||
                                    />
 | 
			
		||||
                                </DataTrigger>
 | 
			
		||||
                            </Style.Triggers>
 | 
			
		||||
                        </Style>
 | 
			
		||||
                    </TextBlock.Style>
 | 
			
		||||
                </TextBlock>
 | 
			
		||||
            </Grid>
 | 
			
		||||
            <DockPanel
 | 
			
		||||
                DockPanel.Dock="Top"
 | 
			
		||||
@ -241,25 +259,23 @@
 | 
			
		||||
                    假如对本工具有任何疑问或者建议的话,可以来到<l:ShellLink NavigateUri="https://tieba.baidu.com/ra3">红警3吧</l:ShellLink>发帖寻找岚依(
 | 
			
		||||
                </TextBlock>
 | 
			
		||||
                <TextBlock
 | 
			
		||||
                    x:Name="LogPrefix"
 | 
			
		||||
                    Text="Tracer 输出:"
 | 
			
		||||
                    Margin="0,5,0,0"
 | 
			
		||||
                    Visibility="{Binding TraceText, 
 | 
			
		||||
                                         Converter={StaticResource NotNullToVisibilityConverter}}"
 | 
			
		||||
                    Visibility="Collapsed"
 | 
			
		||||
                />
 | 
			
		||||
                <TextBox 
 | 
			
		||||
                    Text="{Binding TraceText, Mode=OneWay}"
 | 
			
		||||
                    Visibility="{Binding TraceText, 
 | 
			
		||||
                                         Converter={StaticResource NotNullToVisibilityConverter}}"
 | 
			
		||||
                <TextBox
 | 
			
		||||
                    x:Name="LogText"
 | 
			
		||||
                    TextWrapping="Wrap"
 | 
			
		||||
                    IsReadOnly="True"
 | 
			
		||||
                    BorderBrush="Transparent"
 | 
			
		||||
                />
 | 
			
		||||
                <StackPanel 
 | 
			
		||||
                <StackPanel
 | 
			
		||||
                    x:Name="ClearLogs"
 | 
			
		||||
                    FlowDirection="RightToLeft"
 | 
			
		||||
                    Orientation="Horizontal"
 | 
			
		||||
                    Height="25"    
 | 
			
		||||
                    Visibility="{Binding TraceText, 
 | 
			
		||||
                                         Converter={StaticResource NotNullToVisibilityConverter}}"
 | 
			
		||||
                    Height="25"
 | 
			
		||||
                    Visibility="Collapsed"
 | 
			
		||||
                >
 | 
			
		||||
                    <Button
 | 
			
		||||
                        Content="清除输出" 
 | 
			
		||||
@ -270,7 +286,6 @@
 | 
			
		||||
                    <TextBlock
 | 
			
		||||
                        VerticalAlignment="Center"
 | 
			
		||||
                        Margin="10,0"
 | 
			
		||||
                        Visibility="{Binding SuggestClearFilter, Converter={StaticResource BooleanToVisibilityConverter}}"
 | 
			
		||||
                    >
 | 
			
		||||
                        假如觉得有点卡的话,可以试试点击右边的这个按钮
 | 
			
		||||
                    </TextBlock>
 | 
			
		||||
 | 
			
		||||
@ -1,4 +1,8 @@
 | 
			
		||||
using System;
 | 
			
		||||
using Microsoft.Win32;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
using System.Windows;
 | 
			
		||||
using System.Windows.Controls;
 | 
			
		||||
using System.Windows.Threading;
 | 
			
		||||
@ -20,6 +24,25 @@ namespace HashCalculator.GUI
 | 
			
		||||
            {
 | 
			
		||||
                ViewModel.NotifyCsfChange();
 | 
			
		||||
            });
 | 
			
		||||
            ViewModel.OpenFileDialog += ViewModel_OpenFileDialog;
 | 
			
		||||
            ViewModel.ClearTracer += ViewModel_ClearTracer;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ViewModel_OpenFileDialog(object? sender, OpenFileDialogEventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            var sdkRoot = new OpenFileDialog
 | 
			
		||||
            {
 | 
			
		||||
                Title = e.Title,
 | 
			
		||||
                Filter = string.Join('|', e.Filters.Select(t =>
 | 
			
		||||
                {
 | 
			
		||||
                    if (t.Description is null)
 | 
			
		||||
                    {
 | 
			
		||||
                        return $"{t.Extension}|{t.Extension}";
 | 
			
		||||
                    }
 | 
			
		||||
                    return $"{t.Description} ({t.Extension})|{t.Extension}";
 | 
			
		||||
                }))
 | 
			
		||||
            };
 | 
			
		||||
            e.Result = Dispatcher.InvokeAsync(() => sdkRoot.ShowDialog(this) is not true ? null : sdkRoot.FileName).Task;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void OnButtomScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e)
 | 
			
		||||
@ -50,7 +73,26 @@ namespace HashCalculator.GUI
 | 
			
		||||
 | 
			
		||||
        private void OnInitialized(object sender, EventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            ViewModel.StartTracerListener(s => Dispatcher.BeginInvoke(s));
 | 
			
		||||
            TracerListener.DataAvailaible += delegate
 | 
			
		||||
            {
 | 
			
		||||
                Dispatcher.InvokeAsync(() =>
 | 
			
		||||
                {
 | 
			
		||||
                    if (TracerListener.GetText() is not { Length: > 0 } text)
 | 
			
		||||
                    {
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
                    LogPrefix.Visibility = Visibility.Visible;
 | 
			
		||||
                    LogText.AppendText(text);
 | 
			
		||||
                    ClearLogs.Visibility = Visibility.Visible;
 | 
			
		||||
                });
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private void ViewModel_ClearTracer(object? sender, EventArgs e)
 | 
			
		||||
        {
 | 
			
		||||
            LogPrefix.Visibility = Visibility.Collapsed;
 | 
			
		||||
            LogText.Clear();
 | 
			
		||||
            ClearLogs.Visibility = Visibility.Collapsed;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										96
									
								
								ModXml.cs
									
									
									
									
									
								
							
							
						
						
									
										96
									
								
								ModXml.cs
									
									
									
									
									
								
							@ -29,7 +29,6 @@ namespace HashCalculator.GUI
 | 
			
		||||
        };
 | 
			
		||||
        public static XNamespace XInclude { get; } = "http://www.w3.org/2001/XInclude";
 | 
			
		||||
        public static XNamespace EalaAsset { get; } = "uri:ea.com:eala:asset";
 | 
			
		||||
        public bool SdkNotFound { get; }
 | 
			
		||||
        public DirectoryInfo BaseDirectory { get; }
 | 
			
		||||
        public int TotalFilesProcessed => _processed.Count;
 | 
			
		||||
        public int TotalAssets => _assets.Count;
 | 
			
		||||
@ -41,10 +40,10 @@ namespace HashCalculator.GUI
 | 
			
		||||
        private readonly CancellationToken _token;
 | 
			
		||||
        private readonly BufferBlock<AssetEntry> _entries;
 | 
			
		||||
 | 
			
		||||
        public ModXml(string xmlPath, CancellationToken token)
 | 
			
		||||
        public ModXml(string xmlPath, string? sdkRootPath, CancellationToken token)
 | 
			
		||||
        {
 | 
			
		||||
            BaseDirectory = new DirectoryInfo(FindBaseDirectory(xmlPath));
 | 
			
		||||
            var allMods = BaseDirectory.Parent 
 | 
			
		||||
            var allMods = BaseDirectory.Parent
 | 
			
		||||
                ?? throw new ArgumentException($"{nameof(xmlPath)}'s {nameof(BaseDirectory)} doesn't have a parent");
 | 
			
		||||
            if (!allMods.Name.Equals("Mods", StringComparison.OrdinalIgnoreCase))
 | 
			
		||||
            {
 | 
			
		||||
@ -52,12 +51,7 @@ namespace HashCalculator.GUI
 | 
			
		||||
            }
 | 
			
		||||
            var allModsParent = allMods.Parent
 | 
			
		||||
                ?? throw new ArgumentException($"SDK Mods folder doesn't have a parent");
 | 
			
		||||
            using var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
 | 
			
		||||
            if (!(hklm.GetValue(RegistryPath) is string sdkRootPath))
 | 
			
		||||
            {
 | 
			
		||||
                SdkNotFound = true;
 | 
			
		||||
                sdkRootPath = allModsParent.FullName;
 | 
			
		||||
            }
 | 
			
		||||
            sdkRootPath ??= allModsParent.FullName;
 | 
			
		||||
 | 
			
		||||
            IReadOnlyCollection<string> GetPaths(string name) => new string[]
 | 
			
		||||
            {
 | 
			
		||||
@ -81,6 +75,12 @@ namespace HashCalculator.GUI
 | 
			
		||||
            _entries = new BufferBlock<AssetEntry>();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string? LocateSdkFromRegistry()
 | 
			
		||||
        {
 | 
			
		||||
            using var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32);
 | 
			
		||||
            return hklm.GetValue(RegistryPath) as string;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async IAsyncEnumerable<AssetEntry> ProcessDocument()
 | 
			
		||||
        {
 | 
			
		||||
            if (_processed.Any() || _assets.Any())
 | 
			
		||||
@ -88,18 +88,11 @@ namespace HashCalculator.GUI
 | 
			
		||||
                throw new InvalidOperationException();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var task = Task.Run(() =>
 | 
			
		||||
            var task = Task.Run(async () =>
 | 
			
		||||
            {
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var includes = ProcessDocumentInternal(_xmlFullPath);
 | 
			
		||||
                    while (includes.Any())
 | 
			
		||||
                    {
 | 
			
		||||
                        var result = from include in includes.AsParallel()
 | 
			
		||||
                                     from newInclude in ProcessDocumentInternal(include)
 | 
			
		||||
                                     select newInclude;
 | 
			
		||||
                        includes = result.ToArray();
 | 
			
		||||
                    }
 | 
			
		||||
                    await ProcessDocumentInternal(_xmlFullPath);
 | 
			
		||||
                }
 | 
			
		||||
                catch
 | 
			
		||||
                {
 | 
			
		||||
@ -114,33 +107,41 @@ namespace HashCalculator.GUI
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            while(await _entries.OutputAvailableAsync().ConfigureAwait(false))
 | 
			
		||||
            while (await _entries.OutputAvailableAsync())
 | 
			
		||||
            {
 | 
			
		||||
                yield return _entries.Receive();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await task.ConfigureAwait(false);
 | 
			
		||||
            await task;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string[] ProcessDocumentInternal(string fullPath)
 | 
			
		||||
        private async Task ProcessDocumentInternal(string fullPath)
 | 
			
		||||
        {
 | 
			
		||||
            _token.ThrowIfCancellationRequested();
 | 
			
		||||
            if (!_processed.TryAdd(fullPath.ToUpperInvariant(), default))
 | 
			
		||||
            {
 | 
			
		||||
                return Array.Empty<string>();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var document = GetFile(fullPath);
 | 
			
		||||
            var rootName = document.Root?.Name 
 | 
			
		||||
            var document = await GetFileAsync(fullPath);
 | 
			
		||||
            var rootName = document.Root?.Name
 | 
			
		||||
                ?? throw new InvalidDataException("Document doesn't have a root");
 | 
			
		||||
            if (rootName != EalaAsset + "AssetDeclaration" && rootName != "AssetDeclaration")
 | 
			
		||||
            {
 | 
			
		||||
                throw new NotSupportedException();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var includes = from include in document.Root.Elements(EalaAsset + "Includes").Elements()
 | 
			
		||||
                           let includePath = GetIncludePath(include, fullPath)
 | 
			
		||||
                           where includePath != null
 | 
			
		||||
                           select Task.Run(() => ProcessDocumentInternal(includePath), _token);
 | 
			
		||||
            var includesTasks = includes.ToArray();
 | 
			
		||||
 | 
			
		||||
            var items = from element in document.Root.Elements()
 | 
			
		||||
                        where element.Attribute("id") != null
 | 
			
		||||
                        select new AssetEntry(element);
 | 
			
		||||
                        let entry = AssetEntry.TryParse(element)
 | 
			
		||||
                        where entry != null
 | 
			
		||||
                        select entry;
 | 
			
		||||
            foreach (var item in items)
 | 
			
		||||
            {
 | 
			
		||||
                if (_assets.TryAdd(item, fullPath))
 | 
			
		||||
@ -154,11 +155,7 @@ namespace HashCalculator.GUI
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var includes = from include in document.Root.Elements(EalaAsset + "Includes").Elements()
 | 
			
		||||
                           let includePath = GetIncludePath(include, fullPath)
 | 
			
		||||
                           where includePath != null
 | 
			
		||||
                           select includePath;
 | 
			
		||||
            return includes.ToArray();
 | 
			
		||||
            await Task.WhenAll(includesTasks);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private string? GetIncludePath(XElement include, string includerPath)
 | 
			
		||||
@ -186,23 +183,42 @@ namespace HashCalculator.GUI
 | 
			
		||||
            }
 | 
			
		||||
            catch (FileNotFoundException error)
 | 
			
		||||
            {
 | 
			
		||||
                TracerListener.WriteLine($"[ModXml]: {error}");
 | 
			
		||||
                if (!(error.FileName?.StartsWith("ART:", StringComparison.OrdinalIgnoreCase) is true))
 | 
			
		||||
                {
 | 
			
		||||
                    TracerListener.WriteLine($"[ModXml]: {error}");
 | 
			
		||||
                }
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (!File.Exists(includedSource))
 | 
			
		||||
            {
 | 
			
		||||
                TracerListener.WriteLine($"[ModXml]: Warning, include path does not exist! It is: {includedSource}");
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return includedSource;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private XDocument GetFile(string normalizedPath)
 | 
			
		||||
        private async Task<XDocument> GetFileAsync(string normalizedPath)
 | 
			
		||||
        {
 | 
			
		||||
            using var reader = new XIncludingReader(normalizedPath, _uriResolver)
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                // I dont know why but looks like it need to be set again
 | 
			
		||||
                // Otherwise it wont work
 | 
			
		||||
                XmlResolver = _uriResolver
 | 
			
		||||
            };
 | 
			
		||||
            reader.MoveToContent();
 | 
			
		||||
            return XDocument.Load(reader);
 | 
			
		||||
                var xml = await File.ReadAllBytesAsync(normalizedPath, _token);
 | 
			
		||||
                using var xmlStream = new MemoryStream(xml);
 | 
			
		||||
                using var reader = new XIncludingReader(normalizedPath, xmlStream, _uriResolver)
 | 
			
		||||
                {
 | 
			
		||||
                    // I dont know why but looks like it need to be set again
 | 
			
		||||
                    // Otherwise it wont work
 | 
			
		||||
                    XmlResolver = _uriResolver
 | 
			
		||||
                };
 | 
			
		||||
 | 
			
		||||
                reader.MoveToContent();
 | 
			
		||||
                return XDocument.Load(reader);
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception e)
 | 
			
		||||
            {
 | 
			
		||||
                throw new InvalidDataException($"Failed to process XML file: {normalizedPath} - {e.Message}", e);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string FindBaseDirectory(string currentPath)
 | 
			
		||||
@ -254,7 +270,7 @@ namespace HashCalculator.GUI
 | 
			
		||||
            {
 | 
			
		||||
                throw new ArgumentNullException(nameof(relativeUri));
 | 
			
		||||
            }
 | 
			
		||||
            var localPath = Path.GetDirectoryName(baseUri.LocalPath) 
 | 
			
		||||
            var localPath = Path.GetDirectoryName(baseUri.LocalPath)
 | 
			
		||||
                ?? throw new ArgumentException($"{nameof(baseUri)} doesn't have a local path");
 | 
			
		||||
            return new Uri(Path.GetFullPath(ResolvePath(relativeUri, localPath)));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										20
									
								
								OpenFileDialogEventArgs.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								OpenFileDialogEventArgs.cs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Threading.Tasks;
 | 
			
		||||
 | 
			
		||||
namespace HashCalculator.GUI
 | 
			
		||||
{
 | 
			
		||||
    class OpenFileDialogEventArgs : EventArgs
 | 
			
		||||
    {
 | 
			
		||||
        public string Title { get; }
 | 
			
		||||
        public IEnumerable<(string Extension, string? Description)> Filters { get; }
 | 
			
		||||
        public Task<string?> Result { get; set; }
 | 
			
		||||
 | 
			
		||||
        public OpenFileDialogEventArgs(string title, IEnumerable<(string Extension, string? Description)> filters)
 | 
			
		||||
        {
 | 
			
		||||
            Title = title;
 | 
			
		||||
            Filters = filters;
 | 
			
		||||
            Result = Task.FromResult(null as string);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics;
 | 
			
		||||
using System.Globalization;
 | 
			
		||||
using System.IO;
 | 
			
		||||
using System.Linq;
 | 
			
		||||
using System.Reflection;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
@ -17,10 +17,9 @@ namespace HashCalculator.GUI
 | 
			
		||||
                AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
 | 
			
		||||
                App.Main();
 | 
			
		||||
            }
 | 
			
		||||
            catch(Exception exception)
 | 
			
		||||
            catch (Exception exception) when (!Debugger.IsAttached)
 | 
			
		||||
            {
 | 
			
		||||
                ErrorBox($"发生了无法处理的错误:\r\n{exception}\r\n可以尝试在百度红警3吧联系岚依");
 | 
			
		||||
                throw;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -28,7 +27,7 @@ namespace HashCalculator.GUI
 | 
			
		||||
        {
 | 
			
		||||
            var executingAssembly = Assembly.GetExecutingAssembly();
 | 
			
		||||
            var assemblyName = new AssemblyName(args.Name);
 | 
			
		||||
            var nameString = assemblyName.Name 
 | 
			
		||||
            var nameString = assemblyName.Name
 | 
			
		||||
                ?? throw new InvalidOperationException("Assembly does not have an name");
 | 
			
		||||
 | 
			
		||||
            var paths = new[] { $"{nameString}.dll" }.AsEnumerable();
 | 
			
		||||
@ -42,7 +41,7 @@ namespace HashCalculator.GUI
 | 
			
		||||
                paths = paths.Select(x => $"{assemblyName.CultureInfo}\\{x}").Concat(paths);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            foreach(var path in paths)
 | 
			
		||||
            foreach (var path in paths)
 | 
			
		||||
            {
 | 
			
		||||
                using var stream = executingAssembly.GetManifestResourceStream(path);
 | 
			
		||||
                if (stream == null)
 | 
			
		||||
 | 
			
		||||
@ -1,34 +1,49 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
using System.Threading;
 | 
			
		||||
using System.Text;
 | 
			
		||||
using TechnologyAssembler.Core.Diagnostics;
 | 
			
		||||
 | 
			
		||||
namespace HashCalculator.GUI
 | 
			
		||||
{
 | 
			
		||||
    internal static class TracerListener
 | 
			
		||||
    {
 | 
			
		||||
        private static Action<string>? _onData = null;
 | 
			
		||||
        private static readonly object _lock = new();
 | 
			
		||||
        private static readonly StringBuilder _sb = new();
 | 
			
		||||
        public static event EventHandler? DataAvailaible;
 | 
			
		||||
 | 
			
		||||
        [SuppressMessage("Globalization", "CA1308:将字符串规范化为大写", Justification = "<挂起>")]
 | 
			
		||||
        [SuppressMessage("Globalization", "CA1303:请不要将文本作为本地化参数传递", Justification = "<挂起>")]
 | 
			
		||||
        public static void StartListening(Action<string> action)
 | 
			
		||||
        public static void StartListening()
 | 
			
		||||
        {
 | 
			
		||||
            var original = Interlocked.CompareExchange(ref _onData, action, null);
 | 
			
		||||
            if(original != null)
 | 
			
		||||
            lock (_lock)
 | 
			
		||||
            {
 | 
			
		||||
                throw new InvalidOperationException("Action already set");
 | 
			
		||||
            }
 | 
			
		||||
                if (Tracer.TraceWrite != null)
 | 
			
		||||
                {
 | 
			
		||||
                    throw new InvalidOperationException($"{nameof(Tracer.TraceWrite)} already set");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
            Tracer.TraceWrite = new TraceWriteDelegate((s, t, m) =>
 | 
			
		||||
            {
 | 
			
		||||
                var type = $"{t}    ".ToLowerInvariant().Substring(0, 4);
 | 
			
		||||
                WriteLine($"[{s}] {type}: {m}");
 | 
			
		||||
            });
 | 
			
		||||
                Tracer.TraceWrite = new TraceWriteDelegate((s, t, m) =>
 | 
			
		||||
                {
 | 
			
		||||
                    var type = $"{t}    ".ToLowerInvariant().Substring(0, 4);
 | 
			
		||||
                    WriteLine($"[{s}] {type}: {m}");
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static void WriteLine(string message)
 | 
			
		||||
        {
 | 
			
		||||
            Interlocked.CompareExchange(ref _onData, null, null)?.Invoke($"{message}\r\n");
 | 
			
		||||
            lock (_lock)
 | 
			
		||||
            {
 | 
			
		||||
                _sb.AppendLine(message);
 | 
			
		||||
            }
 | 
			
		||||
            DataAvailaible?.Invoke(null, EventArgs.Empty);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string GetText()
 | 
			
		||||
        {
 | 
			
		||||
            lock (_lock)
 | 
			
		||||
            {
 | 
			
		||||
                var result = _sb.ToString();
 | 
			
		||||
                _sb.Clear();
 | 
			
		||||
                return result;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										115
									
								
								ViewModel.cs
									
									
									
									
									
								
							
							
						
						
									
										115
									
								
								ViewModel.cs
									
									
									
									
									
								
							@ -1,5 +1,4 @@
 | 
			
		||||
using Microsoft.Win32;
 | 
			
		||||
using System;
 | 
			
		||||
using System;
 | 
			
		||||
using System.Collections.Generic;
 | 
			
		||||
using System.Collections.ObjectModel;
 | 
			
		||||
using System.ComponentModel;
 | 
			
		||||
@ -44,9 +43,9 @@ namespace HashCalculator.GUI
 | 
			
		||||
    [SuppressMessage("Microsoft.Performance", "CA1812")]
 | 
			
		||||
    internal class ViewModel : NotifyPropertyChanged
 | 
			
		||||
    {
 | 
			
		||||
        private static readonly Random _random = new Random();
 | 
			
		||||
        public Action<Action<Action>> OnStartTraceListener { get; }
 | 
			
		||||
 | 
			
		||||
        private static readonly Random _random = new();
 | 
			
		||||
        public event EventHandler? ClearTracer;
 | 
			
		||||
        public event EventHandler<OpenFileDialogEventArgs>? OpenFileDialog;
 | 
			
		||||
        public MainInputViewModel MainInput { get; }
 | 
			
		||||
        public BigInputViewModel BigEntryInput { get; }
 | 
			
		||||
 | 
			
		||||
@ -78,10 +77,10 @@ namespace HashCalculator.GUI
 | 
			
		||||
        }
 | 
			
		||||
        public ObservableSortedCollection<AssetEntry> DisplayEntries { get; } = new ObservableSortedCollection<AssetEntry>();
 | 
			
		||||
 | 
			
		||||
        private string _statusText = SuggestionString("不知道该显示些什么呢……");
 | 
			
		||||
        private string? _statusText;
 | 
			
		||||
        public string StatusText
 | 
			
		||||
        {
 | 
			
		||||
            get => _statusText;
 | 
			
		||||
            get => SuggestionString(_statusText);
 | 
			
		||||
            set => SetField(ref _statusText, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -101,31 +100,11 @@ namespace HashCalculator.GUI
 | 
			
		||||
        [SuppressMessage("Microsoft.Performance", "CA1822")]
 | 
			
		||||
        public string LoadCsfText => $"{(Translator.HasProvider ? "更换" : "加载")} CSF";
 | 
			
		||||
        public Command<MainWindow> LoadCsf { get; }
 | 
			
		||||
 | 
			
		||||
        private string _traceText = string.Empty;
 | 
			
		||||
        public string TraceText
 | 
			
		||||
        {
 | 
			
		||||
            get => _traceText;
 | 
			
		||||
            set
 | 
			
		||||
            {
 | 
			
		||||
                SetField(ref _traceText, value);
 | 
			
		||||
                SuggestClearFilter = TraceText.Length > 32768;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private bool _suggestClearFilter;
 | 
			
		||||
        public bool SuggestClearFilter
 | 
			
		||||
        {
 | 
			
		||||
            get => _suggestClearFilter;
 | 
			
		||||
            set => SetField(ref _suggestClearFilter, value);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public Command ClearTraceText { get; }
 | 
			
		||||
 | 
			
		||||
        public ViewModel()
 | 
			
		||||
        {
 | 
			
		||||
            var controller = new Controller(this);
 | 
			
		||||
            OnStartTraceListener = controller.StartTrace;
 | 
			
		||||
            MainInput = new MainInputViewModel(controller);
 | 
			
		||||
            BigEntryInput = new BigInputViewModel(controller);
 | 
			
		||||
            CancelXml = new Command(async () =>
 | 
			
		||||
@ -145,15 +124,10 @@ namespace HashCalculator.GUI
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    LoadCsf!.CanExecuteValue = false;
 | 
			
		||||
                    var dialog = new OpenFileDialog
 | 
			
		||||
                    var fileName = await controller.RequestOpenFile(string.Empty, ("*.csf;*.big", "CSF / BIG 文件"));
 | 
			
		||||
                    if (fileName is not null)
 | 
			
		||||
                    {
 | 
			
		||||
                        Multiselect = false,
 | 
			
		||||
                        ValidateNames = true,
 | 
			
		||||
                        Filter = "CSF / BIG 文件|*.csf;*.big"
 | 
			
		||||
                    };
 | 
			
		||||
                    if (dialog.ShowDialog(window) == true)
 | 
			
		||||
                    {
 | 
			
		||||
                        await Controller.LoadCsf(dialog.FileName).ConfigureAwait(true);
 | 
			
		||||
                        await Controller.LoadCsf(fileName).ConfigureAwait(true);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
                finally
 | 
			
		||||
@ -168,37 +142,16 @@ namespace HashCalculator.GUI
 | 
			
		||||
            });
 | 
			
		||||
            ClearTraceText = new Command(() =>
 | 
			
		||||
            {
 | 
			
		||||
                TraceText = string.Empty;
 | 
			
		||||
                ClearTracer?.Invoke(this, EventArgs.Empty);
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public static string SuggestionString(string original)
 | 
			
		||||
        public Task<string?> RequestOpenFile(string title, IEnumerable<(string Extension, string? Description)> filters)
 | 
			
		||||
        {
 | 
			
		||||
            var generated = $"{_random.NextDouble()}";
 | 
			
		||||
            System.Diagnostics.Debug.WriteLine(generated);
 | 
			
		||||
            if (generated.Contains("38") || generated.Contains("16"))
 | 
			
		||||
            {
 | 
			
		||||
                return "你们都是喂鱼的马甲!(";
 | 
			
		||||
            }
 | 
			
		||||
            if (generated.IndexOf('2') == 2)
 | 
			
		||||
            {
 | 
			
		||||
                return "本来以为两小时就能写完这个小工具,没想到写了两个星期,开始怀疑自己的智商orz";
 | 
			
		||||
            }
 | 
			
		||||
            if (generated.IndexOf('7') < 5)
 | 
			
		||||
            {
 | 
			
		||||
                return "小提示:在下方的素材列表里,选择一行或多行之后,直接按 Ctrl+C 就可以复制内容~";
 | 
			
		||||
            }
 | 
			
		||||
            if (generated.IndexOf('2') == 3)
 | 
			
		||||
            {
 | 
			
		||||
                return "温馨提示:请多留意一下自己的重工,不要让它卖自己";
 | 
			
		||||
            }
 | 
			
		||||
            return original;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void StartTracerListener(Action<Action> action)
 | 
			
		||||
        {
 | 
			
		||||
            OnStartTraceListener(action);
 | 
			
		||||
            var data = new OpenFileDialogEventArgs(title, filters);
 | 
			
		||||
            OpenFileDialog?.Invoke(this, data);
 | 
			
		||||
            return data.Result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public void NotifyCsfChange()
 | 
			
		||||
@ -253,6 +206,33 @@ namespace HashCalculator.GUI
 | 
			
		||||
            }
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string SuggestionString(string? text)
 | 
			
		||||
        {
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(text))
 | 
			
		||||
            {
 | 
			
		||||
                return text;
 | 
			
		||||
            }
 | 
			
		||||
            var generated = $"{_random.NextDouble()}";
 | 
			
		||||
            System.Diagnostics.Debug.WriteLine(generated);
 | 
			
		||||
            if (generated.Contains("38") || generated.Contains("16"))
 | 
			
		||||
            {
 | 
			
		||||
                return "你们都是喂鱼的马甲!(";
 | 
			
		||||
            }
 | 
			
		||||
            if (generated.IndexOf('2') == 2)
 | 
			
		||||
            {
 | 
			
		||||
                return "本来以为两小时就能写完这个小工具,没想到写了两个星期,开始怀疑自己的智商orz";
 | 
			
		||||
            }
 | 
			
		||||
            if (generated.IndexOf('7') < 5)
 | 
			
		||||
            {
 | 
			
		||||
                return "小提示:在下方的素材列表里,选择一行或多行之后,直接按 Ctrl+C 就可以复制内容~";
 | 
			
		||||
            }
 | 
			
		||||
            if (generated.IndexOf('2') == 3)
 | 
			
		||||
            {
 | 
			
		||||
                return "温馨提示:请多留意一下自己的重工,不要让它卖自己";
 | 
			
		||||
            }
 | 
			
		||||
            return "不知道该显示些什么呢……";
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    internal class InputBarViewModel : NotifyPropertyChanged
 | 
			
		||||
@ -329,18 +309,13 @@ namespace HashCalculator.GUI
 | 
			
		||||
        public MainInputViewModel(Controller controller)
 | 
			
		||||
        {
 | 
			
		||||
            Items = Enumerable.Empty<InputEntry>();
 | 
			
		||||
            BrowseCommand = new Command<MainWindow>(window =>
 | 
			
		||||
            BrowseCommand = new Command<MainWindow>(async window =>
 | 
			
		||||
            {
 | 
			
		||||
                var dialog = new OpenFileDialog
 | 
			
		||||
                var fileName = await controller.RequestOpenFile(string.Empty);
 | 
			
		||||
                if (fileName is not null)
 | 
			
		||||
                {
 | 
			
		||||
                    Multiselect = false,
 | 
			
		||||
                    ValidateNames = true
 | 
			
		||||
                };
 | 
			
		||||
                if (dialog.ShowDialog(window) == true)
 | 
			
		||||
                {
 | 
			
		||||
                    Text = dialog.FileName;
 | 
			
		||||
                    Text = fileName;
 | 
			
		||||
                }
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            });
 | 
			
		||||
            SelectCommand = new Command<ViewModel>(async viewModel =>
 | 
			
		||||
            {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user