更加可靠的 XML 处理
This commit is contained in:
		
							parent
							
								
									411204b1ac
								
							
						
					
					
						commit
						6ce486945b
					
				| @ -18,6 +18,7 @@ namespace HashCalculator.GUI | |||||||
|         { |         { | ||||||
|             var core = new TechnologyAssemblerCoreModule(); |             var core = new TechnologyAssemblerCoreModule(); | ||||||
|             core.Initialize(); |             core.Initialize(); | ||||||
|  |             TracerListener.StartListening(); | ||||||
|             DispatcherUnhandledException += (s, e) => Program.ErrorBox($"SAGE FastHash 计算器遇上了未处理的错误:{e.Exception}"); |             DispatcherUnhandledException += (s, e) => Program.ErrorBox($"SAGE FastHash 计算器遇上了未处理的错误:{e.Exception}"); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ | |||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Threading; |  | ||||||
| using System.Xml.Linq; | using System.Xml.Linq; | ||||||
| using TechnologyAssembler.Core.Assets; | 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) |         public AssetEntry(XElement element) | ||||||
|         { |         { | ||||||
|             if (element == null) |             if (element == null) | ||||||
|  | |||||||
| @ -1,14 +1,12 @@ | |||||||
| using System; | using System; | ||||||
| using System.Collections.Generic; | using System.Collections.Generic; | ||||||
| using System.Diagnostics; |  | ||||||
| using System.Diagnostics.CodeAnalysis; | using System.Diagnostics.CodeAnalysis; | ||||||
| using System.IO; | using System.IO; | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Text; |  | ||||||
| using System.Threading; | using System.Threading; | ||||||
| using System.Threading.Tasks; | using System.Threading.Tasks; | ||||||
| using TechnologyAssembler.Core.IO; |  | ||||||
| using TechnologyAssembler.Core.Assets; | using TechnologyAssembler.Core.Assets; | ||||||
|  | using TechnologyAssembler.Core.IO; | ||||||
| using TechnologyAssembler.Core.Language.Providers; | using TechnologyAssembler.Core.Language.Providers; | ||||||
| 
 | 
 | ||||||
| namespace HashCalculator.GUI | namespace HashCalculator.GUI | ||||||
| @ -29,11 +27,6 @@ namespace HashCalculator.GUI | |||||||
|             ViewModel = viewModel; |             ViewModel = viewModel; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public void StartTrace(Action<Action> action) |  | ||||||
|         { |  | ||||||
|             TracerListener.StartListening(s => action(() => ViewModel.TraceText += s)); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         public async Task OnMainInputDecided(InputEntry selected) |         public async Task OnMainInputDecided(InputEntry selected) | ||||||
|         { |         { | ||||||
|             try |             try | ||||||
| @ -41,7 +34,7 @@ namespace HashCalculator.GUI | |||||||
|                 _currentBig?.Dispose(); |                 _currentBig?.Dispose(); | ||||||
|                 _currentBig = null; |                 _currentBig = null; | ||||||
|                 ClearEntries(); |                 ClearEntries(); | ||||||
|                 ViewModel.TraceText = string.Empty; |                 ViewModel.ClearTraceText.Execute(null); | ||||||
|                 ViewModel.BigEntryInput.AllManifests = null; |                 ViewModel.BigEntryInput.AllManifests = null; | ||||||
|                 ViewModel.IsXml = false; |                 ViewModel.IsXml = false; | ||||||
| 
 | 
 | ||||||
| @ -50,7 +43,7 @@ namespace HashCalculator.GUI | |||||||
|                     case InputEntryType.BinaryFile: |                     case InputEntryType.BinaryFile: | ||||||
|                         ViewModel.StatusText = "请留意一下弹出的窗口("; |                         ViewModel.StatusText = "请留意一下弹出的窗口("; | ||||||
|                         CopyableBox.ShowDialog("SAGE FastHash 计算器", token => CalculateBinaryHash(selected.Value, token)); |                         CopyableBox.ShowDialog("SAGE FastHash 计算器", token => CalculateBinaryHash(selected.Value, token)); | ||||||
|                         ViewModel.StatusText = ViewModel.SuggestionString(string.Empty); |                         ViewModel.StatusText = string.Empty; | ||||||
|                         return; |                         return; | ||||||
|                     case InputEntryType.BigFile: |                     case InputEntryType.BigFile: | ||||||
|                         await LoadBig(selected.Value).ConfigureAwait(true); |                         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) |         private static Task<string> CalculateBinaryHash(string filePath, CancellationToken cancel) | ||||||
|         { |         { | ||||||
|             return Task.Run(() => |             return Task.Run(() => | ||||||
| @ -233,7 +231,12 @@ namespace HashCalculator.GUI | |||||||
| 
 | 
 | ||||||
|         private async Task LoadXmlInternal(string path, CancellationToken token) |         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 |             try | ||||||
|             { |             { | ||||||
| @ -380,7 +383,8 @@ namespace HashCalculator.GUI | |||||||
|         private void AddEntry(AssetEntry entry) |         private void AddEntry(AssetEntry entry) | ||||||
|         { |         { | ||||||
|             _loadedAssets.Add(entry); |             _loadedAssets.Add(entry); | ||||||
|             ViewModel.AddNewItems(Enumerable.Repeat(entry, 1)); | 
 | ||||||
|  |             ViewModel.AddNewItems(new[] { entry }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private void AddEntries(IEnumerable<AssetEntry> entries) |         private void AddEntries(IEnumerable<AssetEntry> entries) | ||||||
|  | |||||||
| @ -3,7 +3,7 @@ | |||||||
|     <UseWPF>true</UseWPF> |     <UseWPF>true</UseWPF> | ||||||
|     <OutputType>WinExe</OutputType> |     <OutputType>WinExe</OutputType> | ||||||
|     <TargetFramework>net5.0-windows</TargetFramework> |     <TargetFramework>net5.0-windows</TargetFramework> | ||||||
|     <LangVersion>8.0</LangVersion> |     <LangVersion>latest</LangVersion> | ||||||
|     <Nullable>enable</Nullable> |     <Nullable>enable</Nullable> | ||||||
|     <UseWPF>true</UseWPF> |     <UseWPF>true</UseWPF> | ||||||
|     <Platforms>AnyCPU;x86</Platforms> |     <Platforms>AnyCPU;x86</Platforms> | ||||||
|  | |||||||
| @ -6,7 +6,6 @@ | |||||||
|     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |     xmlns:d="http://schemas.microsoft.com/expression/blend/2008" | ||||||
|     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" | ||||||
|     xmlns:l="clr-namespace:HashCalculator.GUI" |     xmlns:l="clr-namespace:HashCalculator.GUI" | ||||||
|     xmlns:c="clr-namespace:HashCalculator.GUI.Converters" |  | ||||||
|     mc:Ignorable="d" |     mc:Ignorable="d" | ||||||
|     Title="SAGE FastHash 哈希计算器"  |     Title="SAGE FastHash 哈希计算器"  | ||||||
|     Height="600"  |     Height="600"  | ||||||
| @ -124,10 +123,29 @@ | |||||||
|                 Margin="0,5,0,0" |                 Margin="0,5,0,0" | ||||||
|             > |             > | ||||||
|                 <TextBlock |                 <TextBlock | ||||||
|                     Text="{Binding StatusText}" |  | ||||||
|                     Margin="5,0,0,0" |                     Margin="5,0,0,0" | ||||||
|                     VerticalAlignment="Center" |                     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> |             </Grid> | ||||||
|             <DockPanel |             <DockPanel | ||||||
|                 DockPanel.Dock="Top" |                 DockPanel.Dock="Top" | ||||||
| @ -241,25 +259,23 @@ | |||||||
|                     假如对本工具有任何疑问或者建议的话,可以来到<l:ShellLink NavigateUri="https://tieba.baidu.com/ra3">红警3吧</l:ShellLink>发帖寻找岚依( |                     假如对本工具有任何疑问或者建议的话,可以来到<l:ShellLink NavigateUri="https://tieba.baidu.com/ra3">红警3吧</l:ShellLink>发帖寻找岚依( | ||||||
|                 </TextBlock> |                 </TextBlock> | ||||||
|                 <TextBlock |                 <TextBlock | ||||||
|  |                     x:Name="LogPrefix" | ||||||
|                     Text="Tracer 输出:" |                     Text="Tracer 输出:" | ||||||
|                     Margin="0,5,0,0" |                     Margin="0,5,0,0" | ||||||
|                     Visibility="{Binding TraceText,  |                     Visibility="Collapsed" | ||||||
|                                          Converter={StaticResource NotNullToVisibilityConverter}}" |  | ||||||
|                 /> |                 /> | ||||||
|                 <TextBox |                 <TextBox | ||||||
|                     Text="{Binding TraceText, Mode=OneWay}" |                     x:Name="LogText" | ||||||
|                     Visibility="{Binding TraceText,  |  | ||||||
|                                          Converter={StaticResource NotNullToVisibilityConverter}}" |  | ||||||
|                     TextWrapping="Wrap" |                     TextWrapping="Wrap" | ||||||
|                     IsReadOnly="True" |                     IsReadOnly="True" | ||||||
|                     BorderBrush="Transparent" |                     BorderBrush="Transparent" | ||||||
|                 /> |                 /> | ||||||
|                 <StackPanel |                 <StackPanel | ||||||
|  |                     x:Name="ClearLogs" | ||||||
|                     FlowDirection="RightToLeft" |                     FlowDirection="RightToLeft" | ||||||
|                     Orientation="Horizontal" |                     Orientation="Horizontal" | ||||||
|                     Height="25" |                     Height="25" | ||||||
|                     Visibility="{Binding TraceText,  |                     Visibility="Collapsed" | ||||||
|                                          Converter={StaticResource NotNullToVisibilityConverter}}" |  | ||||||
|                 > |                 > | ||||||
|                     <Button |                     <Button | ||||||
|                         Content="清除输出"  |                         Content="清除输出"  | ||||||
| @ -270,7 +286,6 @@ | |||||||
|                     <TextBlock |                     <TextBlock | ||||||
|                         VerticalAlignment="Center" |                         VerticalAlignment="Center" | ||||||
|                         Margin="10,0" |                         Margin="10,0" | ||||||
|                         Visibility="{Binding SuggestClearFilter, Converter={StaticResource BooleanToVisibilityConverter}}" |  | ||||||
|                     > |                     > | ||||||
|                         假如觉得有点卡的话,可以试试点击右边的这个按钮 |                         假如觉得有点卡的话,可以试试点击右边的这个按钮 | ||||||
|                     </TextBlock> |                     </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; | ||||||
| using System.Windows.Controls; | using System.Windows.Controls; | ||||||
| using System.Windows.Threading; | using System.Windows.Threading; | ||||||
| @ -20,6 +24,25 @@ namespace HashCalculator.GUI | |||||||
|             { |             { | ||||||
|                 ViewModel.NotifyCsfChange(); |                 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) |         private void OnButtomScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e) | ||||||
| @ -50,7 +73,26 @@ namespace HashCalculator.GUI | |||||||
| 
 | 
 | ||||||
|         private void OnInitialized(object sender, EventArgs e) |         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; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										76
									
								
								ModXml.cs
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								ModXml.cs
									
									
									
									
									
								
							| @ -29,7 +29,6 @@ namespace HashCalculator.GUI | |||||||
|         }; |         }; | ||||||
|         public static XNamespace XInclude { get; } = "http://www.w3.org/2001/XInclude"; |         public static XNamespace XInclude { get; } = "http://www.w3.org/2001/XInclude"; | ||||||
|         public static XNamespace EalaAsset { get; } = "uri:ea.com:eala:asset"; |         public static XNamespace EalaAsset { get; } = "uri:ea.com:eala:asset"; | ||||||
|         public bool SdkNotFound { get; } |  | ||||||
|         public DirectoryInfo BaseDirectory { get; } |         public DirectoryInfo BaseDirectory { get; } | ||||||
|         public int TotalFilesProcessed => _processed.Count; |         public int TotalFilesProcessed => _processed.Count; | ||||||
|         public int TotalAssets => _assets.Count; |         public int TotalAssets => _assets.Count; | ||||||
| @ -41,7 +40,7 @@ namespace HashCalculator.GUI | |||||||
|         private readonly CancellationToken _token; |         private readonly CancellationToken _token; | ||||||
|         private readonly BufferBlock<AssetEntry> _entries; |         private readonly BufferBlock<AssetEntry> _entries; | ||||||
| 
 | 
 | ||||||
|         public ModXml(string xmlPath, CancellationToken token) |         public ModXml(string xmlPath, string? sdkRootPath, CancellationToken token) | ||||||
|         { |         { | ||||||
|             BaseDirectory = new DirectoryInfo(FindBaseDirectory(xmlPath)); |             BaseDirectory = new DirectoryInfo(FindBaseDirectory(xmlPath)); | ||||||
|             var allMods = BaseDirectory.Parent |             var allMods = BaseDirectory.Parent | ||||||
| @ -52,12 +51,7 @@ namespace HashCalculator.GUI | |||||||
|             } |             } | ||||||
|             var allModsParent = allMods.Parent |             var allModsParent = allMods.Parent | ||||||
|                 ?? throw new ArgumentException($"SDK Mods folder doesn't have a parent"); |                 ?? throw new ArgumentException($"SDK Mods folder doesn't have a parent"); | ||||||
|             using var hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32); |             sdkRootPath ??= allModsParent.FullName; | ||||||
|             if (!(hklm.GetValue(RegistryPath) is string sdkRootPath)) |  | ||||||
|             { |  | ||||||
|                 SdkNotFound = true; |  | ||||||
|                 sdkRootPath = allModsParent.FullName; |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             IReadOnlyCollection<string> GetPaths(string name) => new string[] |             IReadOnlyCollection<string> GetPaths(string name) => new string[] | ||||||
|             { |             { | ||||||
| @ -81,6 +75,12 @@ namespace HashCalculator.GUI | |||||||
|             _entries = new BufferBlock<AssetEntry>(); |             _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() |         public async IAsyncEnumerable<AssetEntry> ProcessDocument() | ||||||
|         { |         { | ||||||
|             if (_processed.Any() || _assets.Any()) |             if (_processed.Any() || _assets.Any()) | ||||||
| @ -88,18 +88,11 @@ namespace HashCalculator.GUI | |||||||
|                 throw new InvalidOperationException(); |                 throw new InvalidOperationException(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             var task = Task.Run(() => |             var task = Task.Run(async () => | ||||||
|             { |             { | ||||||
|                 try |                 try | ||||||
|                 { |                 { | ||||||
|                     var includes = ProcessDocumentInternal(_xmlFullPath); |                     await ProcessDocumentInternal(_xmlFullPath); | ||||||
|                     while (includes.Any()) |  | ||||||
|                     { |  | ||||||
|                         var result = from include in includes.AsParallel() |  | ||||||
|                                      from newInclude in ProcessDocumentInternal(include) |  | ||||||
|                                      select newInclude; |  | ||||||
|                         includes = result.ToArray(); |  | ||||||
|                     } |  | ||||||
|                 } |                 } | ||||||
|                 catch |                 catch | ||||||
|                 { |                 { | ||||||
| @ -114,23 +107,23 @@ namespace HashCalculator.GUI | |||||||
|                 } |                 } | ||||||
|             }); |             }); | ||||||
| 
 | 
 | ||||||
|             while(await _entries.OutputAvailableAsync().ConfigureAwait(false)) |             while (await _entries.OutputAvailableAsync()) | ||||||
|             { |             { | ||||||
|                 yield return _entries.Receive(); |                 yield return _entries.Receive(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             await task.ConfigureAwait(false); |             await task; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private string[] ProcessDocumentInternal(string fullPath) |         private async Task ProcessDocumentInternal(string fullPath) | ||||||
|         { |         { | ||||||
|             _token.ThrowIfCancellationRequested(); |             _token.ThrowIfCancellationRequested(); | ||||||
|             if (!_processed.TryAdd(fullPath.ToUpperInvariant(), default)) |             if (!_processed.TryAdd(fullPath.ToUpperInvariant(), default)) | ||||||
|             { |             { | ||||||
|                 return Array.Empty<string>(); |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             var document = GetFile(fullPath); |             var document = await GetFileAsync(fullPath); | ||||||
|             var rootName = document.Root?.Name |             var rootName = document.Root?.Name | ||||||
|                 ?? throw new InvalidDataException("Document doesn't have a root"); |                 ?? throw new InvalidDataException("Document doesn't have a root"); | ||||||
|             if (rootName != EalaAsset + "AssetDeclaration" && rootName != "AssetDeclaration") |             if (rootName != EalaAsset + "AssetDeclaration" && rootName != "AssetDeclaration") | ||||||
| @ -138,9 +131,17 @@ namespace HashCalculator.GUI | |||||||
|                 throw new NotSupportedException(); |                 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() |             var items = from element in document.Root.Elements() | ||||||
|                         where element.Attribute("id") != null |                         where element.Attribute("id") != null | ||||||
|                         select new AssetEntry(element); |                         let entry = AssetEntry.TryParse(element) | ||||||
|  |                         where entry != null | ||||||
|  |                         select entry; | ||||||
|             foreach (var item in items) |             foreach (var item in items) | ||||||
|             { |             { | ||||||
|                 if (_assets.TryAdd(item, fullPath)) |                 if (_assets.TryAdd(item, fullPath)) | ||||||
| @ -154,11 +155,7 @@ namespace HashCalculator.GUI | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             var includes = from include in document.Root.Elements(EalaAsset + "Includes").Elements() |             await Task.WhenAll(includesTasks); | ||||||
|                            let includePath = GetIncludePath(include, fullPath) |  | ||||||
|                            where includePath != null |  | ||||||
|                            select includePath; |  | ||||||
|             return includes.ToArray(); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private string? GetIncludePath(XElement include, string includerPath) |         private string? GetIncludePath(XElement include, string includerPath) | ||||||
| @ -185,25 +182,44 @@ namespace HashCalculator.GUI | |||||||
|                 includedSource = _uriResolver.ResolveUri(new Uri(includerPath), source).LocalPath; |                 includedSource = _uriResolver.ResolveUri(new Uri(includerPath), source).LocalPath; | ||||||
|             } |             } | ||||||
|             catch (FileNotFoundException error) |             catch (FileNotFoundException error) | ||||||
|  |             { | ||||||
|  |                 if (!(error.FileName?.StartsWith("ART:", StringComparison.OrdinalIgnoreCase) is true)) | ||||||
|                 { |                 { | ||||||
|                     TracerListener.WriteLine($"[ModXml]: {error}"); |                     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 null; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return includedSource; |             return includedSource; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         private XDocument GetFile(string normalizedPath) |         private async Task<XDocument> GetFileAsync(string normalizedPath) | ||||||
|         { |         { | ||||||
|             using var reader = new XIncludingReader(normalizedPath, _uriResolver) |             try | ||||||
|  |             { | ||||||
|  |                 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 |                     // I dont know why but looks like it need to be set again | ||||||
|                     // Otherwise it wont work |                     // Otherwise it wont work | ||||||
|                     XmlResolver = _uriResolver |                     XmlResolver = _uriResolver | ||||||
|                 }; |                 }; | ||||||
|  | 
 | ||||||
|                 reader.MoveToContent(); |                 reader.MoveToContent(); | ||||||
|                 return XDocument.Load(reader); |                 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) |         private static string FindBaseDirectory(string currentPath) | ||||||
|         { |         { | ||||||
|  | |||||||
							
								
								
									
										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; | ||||||
|  | using System.Diagnostics; | ||||||
| using System.Globalization; | using System.Globalization; | ||||||
| using System.IO; |  | ||||||
| using System.Linq; | using System.Linq; | ||||||
| using System.Reflection; | using System.Reflection; | ||||||
| using System.Runtime.InteropServices; | using System.Runtime.InteropServices; | ||||||
| @ -17,10 +17,9 @@ namespace HashCalculator.GUI | |||||||
|                 AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly; |                 AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly; | ||||||
|                 App.Main(); |                 App.Main(); | ||||||
|             } |             } | ||||||
|             catch(Exception exception) |             catch (Exception exception) when (!Debugger.IsAttached) | ||||||
|             { |             { | ||||||
|                 ErrorBox($"发生了无法处理的错误:\r\n{exception}\r\n可以尝试在百度红警3吧联系岚依"); |                 ErrorBox($"发生了无法处理的错误:\r\n{exception}\r\n可以尝试在百度红警3吧联系岚依"); | ||||||
|                 throw; |  | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,22 +1,22 @@ | |||||||
| using System; | using System; | ||||||
| using System.Diagnostics.CodeAnalysis; | using System.Text; | ||||||
| using System.Threading; |  | ||||||
| using TechnologyAssembler.Core.Diagnostics; | using TechnologyAssembler.Core.Diagnostics; | ||||||
| 
 | 
 | ||||||
| namespace HashCalculator.GUI | namespace HashCalculator.GUI | ||||||
| { | { | ||||||
|     internal static class TracerListener |     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 = "<挂起>")] |         public static void StartListening() | ||||||
|         [SuppressMessage("Globalization", "CA1303:请不要将文本作为本地化参数传递", Justification = "<挂起>")] |  | ||||||
|         public static void StartListening(Action<string> action) |  | ||||||
|         { |         { | ||||||
|             var original = Interlocked.CompareExchange(ref _onData, action, null); |             lock (_lock) | ||||||
|             if(original != null) |  | ||||||
|             { |             { | ||||||
|                 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) => |                 Tracer.TraceWrite = new TraceWriteDelegate((s, t, m) => | ||||||
| @ -25,10 +25,25 @@ namespace HashCalculator.GUI | |||||||
|                     WriteLine($"[{s}] {type}: {m}"); |                     WriteLine($"[{s}] {type}: {m}"); | ||||||
|                 }); |                 }); | ||||||
|             } |             } | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         public static void WriteLine(string message) |         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.Generic; | ||||||
| using System.Collections.ObjectModel; | using System.Collections.ObjectModel; | ||||||
| using System.ComponentModel; | using System.ComponentModel; | ||||||
| @ -44,9 +43,9 @@ namespace HashCalculator.GUI | |||||||
|     [SuppressMessage("Microsoft.Performance", "CA1812")] |     [SuppressMessage("Microsoft.Performance", "CA1812")] | ||||||
|     internal class ViewModel : NotifyPropertyChanged |     internal class ViewModel : NotifyPropertyChanged | ||||||
|     { |     { | ||||||
|         private static readonly Random _random = new Random(); |         private static readonly Random _random = new(); | ||||||
|         public Action<Action<Action>> OnStartTraceListener { get; } |         public event EventHandler? ClearTracer; | ||||||
| 
 |         public event EventHandler<OpenFileDialogEventArgs>? OpenFileDialog; | ||||||
|         public MainInputViewModel MainInput { get; } |         public MainInputViewModel MainInput { get; } | ||||||
|         public BigInputViewModel BigEntryInput { get; } |         public BigInputViewModel BigEntryInput { get; } | ||||||
| 
 | 
 | ||||||
| @ -78,10 +77,10 @@ namespace HashCalculator.GUI | |||||||
|         } |         } | ||||||
|         public ObservableSortedCollection<AssetEntry> DisplayEntries { get; } = new ObservableSortedCollection<AssetEntry>(); |         public ObservableSortedCollection<AssetEntry> DisplayEntries { get; } = new ObservableSortedCollection<AssetEntry>(); | ||||||
| 
 | 
 | ||||||
|         private string _statusText = SuggestionString("不知道该显示些什么呢……"); |         private string? _statusText; | ||||||
|         public string StatusText |         public string StatusText | ||||||
|         { |         { | ||||||
|             get => _statusText; |             get => SuggestionString(_statusText); | ||||||
|             set => SetField(ref _statusText, value); |             set => SetField(ref _statusText, value); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
| @ -101,31 +100,11 @@ namespace HashCalculator.GUI | |||||||
|         [SuppressMessage("Microsoft.Performance", "CA1822")] |         [SuppressMessage("Microsoft.Performance", "CA1822")] | ||||||
|         public string LoadCsfText => $"{(Translator.HasProvider ? "更换" : "加载")} CSF"; |         public string LoadCsfText => $"{(Translator.HasProvider ? "更换" : "加载")} CSF"; | ||||||
|         public Command<MainWindow> LoadCsf { get; } |         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 Command ClearTraceText { get; } | ||||||
| 
 | 
 | ||||||
|         public ViewModel() |         public ViewModel() | ||||||
|         { |         { | ||||||
|             var controller = new Controller(this); |             var controller = new Controller(this); | ||||||
|             OnStartTraceListener = controller.StartTrace; |  | ||||||
|             MainInput = new MainInputViewModel(controller); |             MainInput = new MainInputViewModel(controller); | ||||||
|             BigEntryInput = new BigInputViewModel(controller); |             BigEntryInput = new BigInputViewModel(controller); | ||||||
|             CancelXml = new Command(async () => |             CancelXml = new Command(async () => | ||||||
| @ -145,15 +124,10 @@ namespace HashCalculator.GUI | |||||||
|                 try |                 try | ||||||
|                 { |                 { | ||||||
|                     LoadCsf!.CanExecuteValue = false; |                     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, |                         await Controller.LoadCsf(fileName).ConfigureAwait(true); | ||||||
|                         ValidateNames = true, |  | ||||||
|                         Filter = "CSF / BIG 文件|*.csf;*.big" |  | ||||||
|                     }; |  | ||||||
|                     if (dialog.ShowDialog(window) == true) |  | ||||||
|                     { |  | ||||||
|                         await Controller.LoadCsf(dialog.FileName).ConfigureAwait(true); |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 finally |                 finally | ||||||
| @ -168,37 +142,16 @@ namespace HashCalculator.GUI | |||||||
|             }); |             }); | ||||||
|             ClearTraceText = new Command(() => |             ClearTraceText = new Command(() => | ||||||
|             { |             { | ||||||
|                 TraceText = string.Empty; |                 ClearTracer?.Invoke(this, EventArgs.Empty); | ||||||
|                 return Task.CompletedTask; |                 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()}"; |             var data = new OpenFileDialogEventArgs(title, filters); | ||||||
|             System.Diagnostics.Debug.WriteLine(generated); |             OpenFileDialog?.Invoke(this, data); | ||||||
|             if (generated.Contains("38") || generated.Contains("16")) |             return data.Result; | ||||||
|             { |  | ||||||
|                 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); |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         public void NotifyCsfChange() |         public void NotifyCsfChange() | ||||||
| @ -253,6 +206,33 @@ namespace HashCalculator.GUI | |||||||
|             } |             } | ||||||
|             return false; |             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 |     internal class InputBarViewModel : NotifyPropertyChanged | ||||||
| @ -329,18 +309,13 @@ namespace HashCalculator.GUI | |||||||
|         public MainInputViewModel(Controller controller) |         public MainInputViewModel(Controller controller) | ||||||
|         { |         { | ||||||
|             Items = Enumerable.Empty<InputEntry>(); |             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, |                     Text = fileName; | ||||||
|                     ValidateNames = true |  | ||||||
|                 }; |  | ||||||
|                 if (dialog.ShowDialog(window) == true) |  | ||||||
|                 { |  | ||||||
|                     Text = dialog.FileName; |  | ||||||
|                 } |                 } | ||||||
|                 return Task.CompletedTask; |  | ||||||
|             }); |             }); | ||||||
|             SelectCommand = new Command<ViewModel>(async viewModel => |             SelectCommand = new Command<ViewModel>(async viewModel => | ||||||
|             { |             { | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user