更加可靠的 XML 处理

This commit is contained in:
lanyi 2022-01-25 13:34:27 +01:00
parent 411204b1ac
commit 6ce486945b
11 changed files with 272 additions and 162 deletions

View File

@ -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}");
} }
} }

View File

@ -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)

View File

@ -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)

View File

@ -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>

View File

@ -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>

View File

@ -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;
} }
} }
} }

View File

@ -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)
{ {

View 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);
}
}
}

View File

@ -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;
} }
} }

View File

@ -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;
}
} }
} }
} }

View File

@ -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 =>
{ {