更加可靠的 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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
90
ModXml.cs
90
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)
|
||||||
@ -186,23 +183,42 @@ namespace HashCalculator.GUI
|
|||||||
}
|
}
|
||||||
catch (FileNotFoundException error)
|
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 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
|
||||||
{
|
{
|
||||||
// I dont know why but looks like it need to be set again
|
var xml = await File.ReadAllBytesAsync(normalizedPath, _token);
|
||||||
// Otherwise it wont work
|
using var xmlStream = new MemoryStream(xml);
|
||||||
XmlResolver = _uriResolver
|
using var reader = new XIncludingReader(normalizedPath, xmlStream, _uriResolver)
|
||||||
};
|
{
|
||||||
reader.MoveToContent();
|
// I dont know why but looks like it need to be set again
|
||||||
return XDocument.Load(reader);
|
// 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)
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,7 +41,7 @@ namespace HashCalculator.GUI
|
|||||||
paths = paths.Select(x => $"{assemblyName.CultureInfo}\\{x}").Concat(paths);
|
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);
|
using var stream = executingAssembly.GetManifestResourceStream(path);
|
||||||
if (stream == null)
|
if (stream == null)
|
||||||
|
@ -1,34 +1,49 @@
|
|||||||
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) =>
|
||||||
{
|
{
|
||||||
var type = $"{t} ".ToLowerInvariant().Substring(0, 4);
|
var type = $"{t} ".ToLowerInvariant().Substring(0, 4);
|
||||||
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