finish!
This commit is contained in:
parent
97067a68a5
commit
1e8889aecc
204
App.xaml
204
App.xaml
@ -5,6 +5,7 @@
|
||||
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
|
||||
StartupUri="MainWindow.xaml">
|
||||
<Application.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
|
||||
<c:ValueConverterAggregate x:Key="ValidInputEntryToVisibilityConverter">
|
||||
<c:ValidInputEntryTypeToBooleanConverter />
|
||||
<BooleanToVisibilityConverter />
|
||||
@ -38,10 +39,6 @@
|
||||
Value="#202020"
|
||||
/>
|
||||
</Style>
|
||||
<Style
|
||||
BasedOn="{StaticResource CommonStyle}"
|
||||
TargetType="l:MainWindow"
|
||||
/>
|
||||
<Style
|
||||
TargetType="l:ShellLink"
|
||||
>
|
||||
@ -126,10 +123,6 @@
|
||||
BasedOn="{StaticResource ButtonStyle}"
|
||||
TargetType="Button"
|
||||
/>
|
||||
<Style
|
||||
BasedOn="{StaticResource CommonStyle}"
|
||||
TargetType="CheckBox"
|
||||
/>
|
||||
<Style
|
||||
BasedOn="{StaticResource CommonStyle}"
|
||||
TargetType="{x:Type DataGrid}"
|
||||
@ -163,5 +156,200 @@
|
||||
>
|
||||
<Setter Property="Background" Value="#181818"/>
|
||||
</Style>
|
||||
<Style
|
||||
BasedOn="{StaticResource CommonStyle}"
|
||||
TargetType="CheckBox"
|
||||
>
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="{x:Type CheckBox}">
|
||||
<Grid
|
||||
x:Name="TemplateRoot"
|
||||
Background="Transparent"
|
||||
SnapsToDevicePixels="True"
|
||||
>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<Border
|
||||
x:Name="CheckBoxBorder"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
Background="{TemplateBinding Background}"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
Margin="1"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
>
|
||||
<Grid x:Name="MarkGrid">
|
||||
<Path
|
||||
x:Name="OptionMark"
|
||||
Data="F1M9.97498,1.22334L4.6983,9.09834 4.52164,9.09834 0,5.19331 1.27664,3.52165 4.255,6.08833 8.33331,1.52588E-05 9.97498,1.22334z"
|
||||
Fill="LightGray"
|
||||
Margin="1"
|
||||
Opacity="0"
|
||||
Stretch="None"
|
||||
/>
|
||||
<Rectangle
|
||||
x:Name="IndeterminateMark"
|
||||
Fill="LightGray"
|
||||
Margin="2"
|
||||
Opacity="0"
|
||||
/>
|
||||
</Grid>
|
||||
</Border>
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
Content="{TemplateBinding Content}"
|
||||
Grid.Column="1"
|
||||
ContentStringFormat="{TemplateBinding ContentStringFormat}"
|
||||
Focusable="False"
|
||||
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
RecognizesAccessKey="True"
|
||||
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
/>
|
||||
</Grid>
|
||||
<ControlTemplate.Triggers>
|
||||
<Trigger
|
||||
Property="HasContent"
|
||||
Value="True"
|
||||
>
|
||||
<Setter Property="FocusVisualStyle">
|
||||
<Setter.Value>
|
||||
<Style>
|
||||
<Setter Property="Control.Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate>
|
||||
<Rectangle
|
||||
Margin="14,0,0,0"
|
||||
SnapsToDevicePixels="True"
|
||||
Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"
|
||||
StrokeThickness="1"
|
||||
StrokeDashArray="1 2"
|
||||
/>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
<Setter
|
||||
Property="Padding"
|
||||
Value="4,-1,0,0"
|
||||
/>
|
||||
</Trigger>
|
||||
<Trigger
|
||||
Property="IsMouseOver"
|
||||
Value="True"
|
||||
>
|
||||
<Setter
|
||||
Property="Background"
|
||||
TargetName="CheckBoxBorder"
|
||||
Value="#80F3F9FF"
|
||||
/>
|
||||
<Setter
|
||||
Property="BorderBrush"
|
||||
TargetName="CheckBoxBorder"
|
||||
Value="#FF5593FF"
|
||||
/>
|
||||
<Setter
|
||||
Property="Fill"
|
||||
TargetName="OptionMark"
|
||||
Value="LightGray"
|
||||
/>
|
||||
<Setter
|
||||
Property="Fill"
|
||||
TargetName="IndeterminateMark"
|
||||
Value="LightGray"
|
||||
/>
|
||||
</Trigger>
|
||||
<Trigger
|
||||
Property="IsEnabled"
|
||||
Value="False"
|
||||
>
|
||||
<Setter
|
||||
Property="Background"
|
||||
TargetName="CheckBoxBorder"
|
||||
Value="#FFE6E6E6"
|
||||
/>
|
||||
<Setter
|
||||
Property="BorderBrush"
|
||||
TargetName="CheckBoxBorder"
|
||||
Value="#FFBCBCBC"
|
||||
/>
|
||||
<Setter
|
||||
Property="Fill"
|
||||
TargetName="OptionMark"
|
||||
Value="#FF707070"
|
||||
/>
|
||||
<Setter
|
||||
Property="Fill"
|
||||
TargetName="IndeterminateMark"
|
||||
Value="#FF707070"
|
||||
/>
|
||||
</Trigger>
|
||||
<Trigger
|
||||
Property="IsPressed"
|
||||
Value="True"
|
||||
>
|
||||
<Setter
|
||||
Property="Background"
|
||||
TargetName="CheckBoxBorder"
|
||||
Value="#80D9ECFF"
|
||||
/>
|
||||
<Setter
|
||||
Property="BorderBrush"
|
||||
TargetName="CheckBoxBorder"
|
||||
Value="#FF3C77DD"
|
||||
/>
|
||||
<Setter
|
||||
Property="Fill"
|
||||
TargetName="OptionMark"
|
||||
Value="LightGray"
|
||||
/>
|
||||
<Setter
|
||||
Property="Fill"
|
||||
TargetName="IndeterminateMark"
|
||||
Value="LightGray"
|
||||
/>
|
||||
</Trigger>
|
||||
<Trigger
|
||||
Property="IsChecked"
|
||||
Value="True"
|
||||
>
|
||||
<Setter
|
||||
Property="Opacity"
|
||||
TargetName="OptionMark"
|
||||
Value="1"
|
||||
/>
|
||||
<Setter
|
||||
Property="Opacity"
|
||||
TargetName="IndeterminateMark"
|
||||
Value="0"
|
||||
/>
|
||||
</Trigger>
|
||||
<Trigger
|
||||
Property="IsChecked"
|
||||
Value="{x:Null}"
|
||||
>
|
||||
<Setter
|
||||
Property="Opacity"
|
||||
TargetName="OptionMark"
|
||||
Value="0"
|
||||
/>
|
||||
<Setter
|
||||
Property="Opacity"
|
||||
TargetName="IndeterminateMark"
|
||||
Value="1"
|
||||
/>
|
||||
</Trigger>
|
||||
</ControlTemplate.Triggers>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
@ -18,6 +18,7 @@ namespace HashCalculator.GUI
|
||||
{
|
||||
var core = new TechnologyAssemblerCoreModule();
|
||||
core.Initialize();
|
||||
DispatcherUnhandledException += (s, e) => Program.ErrorBox($"SAGE FastHash 计算器遇上了未处理的错误:{e.Exception}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,20 +2,37 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using TechnologyAssembler.Core.Assets;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
public class AssetEntry : IEquatable<AssetEntry>
|
||||
public class AssetEntry : IEquatable<AssetEntry>, IComparable<AssetEntry>
|
||||
{
|
||||
public string Type { get; }
|
||||
public string Name { get; }
|
||||
public string NameString { get; }
|
||||
public IEnumerable<string> DisplayLabels { get; }
|
||||
public uint InstanceId => SageHash.CalculateLowercaseHash(Name);
|
||||
|
||||
public string NameString => $"{Type}:{Name}";
|
||||
public string InstanceIdString => $"{InstanceId:X8} ({InstanceId})";
|
||||
public string InstanceIdString => $"{InstanceId:x8} ({InstanceId,10})";
|
||||
|
||||
public string LocalizedNames
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!Translator.HasProvider)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
if (!DisplayLabels.Any())
|
||||
{
|
||||
return "N/A";
|
||||
}
|
||||
return DisplayLabels.Select(Translator.Translate).Aggregate((x, y) => $"{x} {y}");
|
||||
}
|
||||
}
|
||||
|
||||
public AssetEntry(XElement element)
|
||||
{
|
||||
@ -32,6 +49,7 @@ namespace HashCalculator.GUI
|
||||
Type = element.Name.LocalName;
|
||||
var id = element.Attribute("id")?.Value;
|
||||
Name = id ?? throw new NotSupportedException();
|
||||
NameString = $"{Type}:{Name}";
|
||||
|
||||
var labels = from name in element.Elements(ModXml.EalaAsset + "DisplayName")
|
||||
select name.Value;
|
||||
@ -42,14 +60,15 @@ namespace HashCalculator.GUI
|
||||
|
||||
public AssetEntry(Asset asset)
|
||||
{
|
||||
if(asset is null)
|
||||
if (asset is null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(asset)} is null");
|
||||
}
|
||||
Type = asset.TypeName;
|
||||
Name = asset.InstanceName;
|
||||
DisplayLabels = Enumerable.Empty<string>();
|
||||
if(InstanceId != asset.InstanceId || SageHash.CalculateBinaryHash(Type) != asset.TypeId)
|
||||
NameString = $"{Type}:{Name}";
|
||||
DisplayLabels = Array.Empty<string>();
|
||||
if (InstanceId != asset.InstanceId || SageHash.CalculateBinaryHash(Type) != asset.TypeId)
|
||||
{
|
||||
throw new InvalidDataException();
|
||||
}
|
||||
@ -103,32 +122,34 @@ namespace HashCalculator.GUI
|
||||
{
|
||||
return HashCode.Combine(Type, InstanceId);
|
||||
}
|
||||
}
|
||||
|
||||
internal class DisplayAssetEntry : IComparable<DisplayAssetEntry>
|
||||
{
|
||||
public string Name { get; }
|
||||
public string InstanceId { get; }
|
||||
|
||||
public DisplayAssetEntry(AssetEntry entry)
|
||||
public int CompareTo(AssetEntry other)
|
||||
{
|
||||
Name = entry.NameString;
|
||||
InstanceId = entry.InstanceIdString;
|
||||
if (other is null)
|
||||
{
|
||||
throw new ArgumentNullException($"{nameof(other)} is null");
|
||||
}
|
||||
return string.CompareOrdinal(NameString, other.NameString);
|
||||
}
|
||||
|
||||
public int CompareTo(DisplayAssetEntry other)
|
||||
public static bool operator <(AssetEntry left, AssetEntry right)
|
||||
{
|
||||
return string.CompareOrdinal(Name, other.Name);
|
||||
return left is null ? right is object : left.CompareTo(right) < 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal class LocalizedDisplayAssetEntry : DisplayAssetEntry
|
||||
{
|
||||
public string LocalizedNames { get; }
|
||||
|
||||
public LocalizedDisplayAssetEntry(AssetEntry entry) : base(entry)
|
||||
public static bool operator <=(AssetEntry left, AssetEntry right)
|
||||
{
|
||||
LocalizedNames = entry.DisplayLabels.Aggregate((x, y) => $"{x} {y}");
|
||||
return left is null || left.CompareTo(right) <= 0;
|
||||
}
|
||||
|
||||
public static bool operator >(AssetEntry left, AssetEntry right)
|
||||
{
|
||||
return left is object && left.CompareTo(right) > 0;
|
||||
}
|
||||
|
||||
public static bool operator >=(AssetEntry left, AssetEntry right)
|
||||
{
|
||||
return left is null ? right is null : left.CompareTo(right) >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,12 +98,12 @@ namespace HashCalculator.GUI
|
||||
});
|
||||
}
|
||||
|
||||
public bool CanExecute(object parameter)
|
||||
public bool CanExecute(object? parameter)
|
||||
{
|
||||
return _canExecute;
|
||||
}
|
||||
|
||||
public void Execute(object parameter)
|
||||
public void Execute(object? parameter)
|
||||
{
|
||||
if (!_canExecute)
|
||||
{
|
||||
|
313
Controller.cs
313
Controller.cs
@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@ -8,6 +9,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using TechnologyAssembler.Core.IO;
|
||||
using TechnologyAssembler.Core.Assets;
|
||||
using TechnologyAssembler.Core.Language.Providers;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
@ -19,28 +21,36 @@ namespace HashCalculator.GUI
|
||||
|
||||
private ViewModel ViewModel { get; }
|
||||
private BigFileSystemProvider? _currentBig = null;
|
||||
private readonly HashSet<AssetEntry> _loadedAssets = new HashSet<AssetEntry>();
|
||||
private Tuple<CancellationTokenSource, Task>? _lastXmlTask = null;
|
||||
|
||||
public Controller(ViewModel viewModel)
|
||||
{
|
||||
ViewModel = viewModel;
|
||||
}
|
||||
|
||||
public void StartTrace(Action<Action> action)
|
||||
{
|
||||
TracerListener.StartListening(s => action(() => ViewModel.TraceText += s));
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
||||
public async Task OnMainInputDecided(InputEntry selected)
|
||||
{
|
||||
_currentBig?.Dispose();
|
||||
_currentBig = null;
|
||||
ViewModel.Entries.Clear();
|
||||
ViewModel.TraceText = string.Empty;
|
||||
ViewModel.BigEntryInput.AllManifests = null;
|
||||
|
||||
try
|
||||
{
|
||||
_currentBig?.Dispose();
|
||||
_currentBig = null;
|
||||
ClearEntries();
|
||||
ViewModel.TraceText = string.Empty;
|
||||
ViewModel.BigEntryInput.AllManifests = null;
|
||||
ViewModel.IsXml = false;
|
||||
|
||||
switch (selected.Type)
|
||||
{
|
||||
case InputEntryType.BinaryFile:
|
||||
ViewModel.StatusText = "请留意一下弹出的窗口(";
|
||||
CopyableBox.ShowDialog(token => CalculateBinaryHash(selected.Value, token));
|
||||
CopyableBox.ShowDialog("SAGE FastHash 计算器", token => CalculateBinaryHash(selected.Value, token));
|
||||
ViewModel.StatusText = ViewModel.SuggestionString(string.Empty);
|
||||
return;
|
||||
case InputEntryType.BigFile:
|
||||
@ -50,7 +60,9 @@ namespace HashCalculator.GUI
|
||||
await LoadManifestFromFile(selected.Value).ConfigureAwait(true);
|
||||
return;
|
||||
case InputEntryType.XmlFile:
|
||||
throw new NotImplementedException();
|
||||
ViewModel.IsXml = true;
|
||||
await LoadXml(selected.Value).ConfigureAwait(true);
|
||||
return;
|
||||
default:
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
@ -58,7 +70,7 @@ namespace HashCalculator.GUI
|
||||
catch (Exception error)
|
||||
{
|
||||
ViewModel.StatusText = "失败…";
|
||||
CopyableBox.ShowDialog(_ =>
|
||||
CopyableBox.ShowDialog("SAGE FastHash 计算器 - 失败!", _ =>
|
||||
{
|
||||
return Task.FromResult($"在尝试加载 {selected.Type} `{selected.Value}` 时发生错误:\r\n{error}");
|
||||
});
|
||||
@ -90,10 +102,10 @@ namespace HashCalculator.GUI
|
||||
SageHash.CalculateLauncherBinaryHash
|
||||
}.AsParallel().Select(fn => fn(array)).ToArray();
|
||||
|
||||
return $"使用 SAGE FastHash 计算出的哈希值:{hashes[0]:X8} (十进制 {hashes[0]})\r\n"
|
||||
return $"使用 SAGE FastHash 计算出的哈希值:{hashes[0]:x8} (十进制 {hashes[0]})\r\n"
|
||||
+ "注意这是以大小写敏感模式计算出的哈希值,与素材ID的哈希值(转换成小写后计算的哈希)一般是不一样的\r\n\r\n"
|
||||
+ "使用 SAGE Launcher FastHash(比如说 RA3.exe 用来校验更新补丁文件的哈希)计算出的哈希值:"
|
||||
+ $"{hashes[1]:X8} (十进制 {hashes[1]})";
|
||||
+ $"{hashes[1]:x8} (十进制 {hashes[1]})";
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
@ -134,73 +146,252 @@ namespace HashCalculator.GUI
|
||||
|
||||
private async Task LoadManifestFromFile(string path)
|
||||
{
|
||||
using var provider = new FileSystemProvider(SelectedFileSystemRootPath, null);
|
||||
await ProcessManifest(path).ConfigureAwait(true);
|
||||
await ExceptionWrapepr(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using var provider = new FileSystemProvider(SelectedFileSystemRootPath, null);
|
||||
path = VirtualFileSystem.Combine(SelectedFileSystemRootPath, path);
|
||||
await ProcessManifest(path).ConfigureAwait(true);
|
||||
}
|
||||
catch
|
||||
{
|
||||
ViewModel.StatusText = "失败…";
|
||||
throw;
|
||||
}
|
||||
|
||||
}, "SAGE FastHash 计算器 - Manifest 加载失败…", "Manifest 文件加载失败,也许,你选择的 manifest 文件并不是红警3使用的那种……\r\n").ConfigureAwait(true);
|
||||
}
|
||||
|
||||
public async Task ProcessManifest(string path)
|
||||
{
|
||||
ViewModel.Entries.Clear();
|
||||
ViewModel.TraceText = string.Empty;
|
||||
ViewModel.StatusText = "正在加载 manifest 文件…";
|
||||
ClearEntries();
|
||||
ViewModel.StatusText = "正在读取 manifest 文件…";
|
||||
var entries = await Task.Run(() =>
|
||||
{
|
||||
if (path.EndsWith(ManifestExtension, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
path = path.Remove(path.Length - ManifestExtension.Length);
|
||||
}
|
||||
using var manifest = Manifest.Load(path);
|
||||
using var manifest = Manifest.Load(path, true);
|
||||
var assets = from asset in manifest.Assets.Values
|
||||
select new DisplayAssetEntry(new AssetEntry(asset));
|
||||
select new AssetEntry(asset);
|
||||
return assets.ToArray();
|
||||
}).ConfigureAwait(true);
|
||||
await AddEntries(entries).ConfigureAwait(true);
|
||||
ViewModel.StatusText = $"总共加载了{ViewModel.Entries.Count}个素材";
|
||||
AddEntries(entries);
|
||||
ViewModel.StatusText = $"Manifest文件读取完毕,加载了{_loadedAssets.Count}个素材";
|
||||
}
|
||||
|
||||
public async Task AddEntries(IEnumerable<DisplayAssetEntry> entries)
|
||||
public async Task CancelLoadingXml()
|
||||
{
|
||||
var target = ViewModel.Entries;
|
||||
|
||||
int BinarySearch(DisplayAssetEntry entry, int? hint = null)
|
||||
if (_lastXmlTask is null)
|
||||
{
|
||||
var begin = hint.GetValueOrDefault(0);
|
||||
var end = target.Count;
|
||||
while (begin < end)
|
||||
return;
|
||||
}
|
||||
|
||||
var (tokenSource, task) = _lastXmlTask;
|
||||
tokenSource.Cancel();
|
||||
try
|
||||
{
|
||||
await task.ConfigureAwait(true);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
ViewModel.StatusText = "已经取消了加载";
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("Reliability", "CA2000:丢失范围之前释放对象", Justification = "<挂起>")]
|
||||
private async Task LoadXml(string path)
|
||||
{
|
||||
await CancelLoadingXml().ConfigureAwait(true);
|
||||
using var tokenSource = new CancellationTokenSource();
|
||||
var task = LoadXmlInternal(path, tokenSource.Token);
|
||||
_lastXmlTask = (tokenSource, task).ToTuple();
|
||||
|
||||
await ExceptionWrapepr(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var middle = begin + (end - begin) / 2;
|
||||
var result = entry.CompareTo(target[middle]);
|
||||
if (result == 0)
|
||||
{
|
||||
return middle;
|
||||
}
|
||||
else if (result < 0)
|
||||
{
|
||||
end = middle;
|
||||
}
|
||||
else if (result > 0)
|
||||
{
|
||||
begin = middle + 1;
|
||||
}
|
||||
ViewModel.IsLoadingXml = true;
|
||||
await task.ConfigureAwait(true);
|
||||
}
|
||||
return end;
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
ViewModel.StatusText = "已经取消了加载";
|
||||
}
|
||||
catch
|
||||
{
|
||||
ViewModel.StatusText = "失败…";
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ViewModel.IsLoadingXml = false;
|
||||
_lastXmlTask = null;
|
||||
}
|
||||
|
||||
}, "SAGE FastHash 计算器 - XML 加载失败…", "XML 文件加载失败,也许,你选择的 XML 文件并不是红警3使用的那种……\r\n").ConfigureAwait(true);
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
||||
private async Task LoadXmlInternal(string path, CancellationToken token)
|
||||
{
|
||||
var modXml = new ModXml(path, token);
|
||||
|
||||
try
|
||||
{
|
||||
await FindCsf(modXml.BaseDirectory).ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
TracerListener.WriteLine($"[ModXml] Failed to automatically find and load CSF: {exception}");
|
||||
}
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var start = DateTimeOffset.UtcNow;
|
||||
var lastMoment = start;
|
||||
var previousCount = 0;
|
||||
var preivousSpeed = 0.0;
|
||||
await foreach (var entry in modXml.ProcessDocument())
|
||||
{
|
||||
var now = DateTimeOffset.Now;
|
||||
var total = modXml.TotalFilesProcessed;
|
||||
var time = now - start;
|
||||
var speed = total / time.TotalSeconds;
|
||||
|
||||
var dt = (now - lastMoment).TotalSeconds;
|
||||
double instantSpeed;
|
||||
if (dt <= 0.1)
|
||||
{
|
||||
instantSpeed = preivousSpeed;
|
||||
}
|
||||
else
|
||||
{
|
||||
var dx = total - previousCount;
|
||||
instantSpeed = dx / dt;
|
||||
lastMoment = now;
|
||||
previousCount = total;
|
||||
preivousSpeed = instantSpeed;
|
||||
}
|
||||
ViewModel.StatusText = $"已读取{total}个文件,平均读取速度{speed,8:N2}文件/秒,瞬时速度{instantSpeed,8:N2}文件/秒";
|
||||
AddEntry(entry);
|
||||
}
|
||||
UpdateEntries();
|
||||
var totalTime = DateTimeOffset.UtcNow - start;
|
||||
var fileSpeed = modXml.TotalFilesProcessed / totalTime.TotalSeconds;
|
||||
var assetSpeed = modXml.TotalAssets / totalTime.TotalSeconds;
|
||||
var statistics = $"耗时{totalTime:mm\\:ss},共读取{modXml.TotalFilesProcessed}个文件({fileSpeed,1:N2}文件/秒;{assetSpeed,1:N2}素材/秒)";
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
ViewModel.StatusText = $"ModXml 的读取已被取消,共{statistics}";
|
||||
return;
|
||||
}
|
||||
|
||||
var ordered = await Task.Run(() =>
|
||||
ViewModel.StatusText = $"ModXml 读取完毕~ {statistics}";
|
||||
if (_loadedAssets.Count != modXml.TotalAssets)
|
||||
{
|
||||
var array = entries.ToArray();
|
||||
Array.Sort(array);
|
||||
return array;
|
||||
}).ConfigureAwait(true);
|
||||
ViewModel.StatusText = $"正在处理刚刚读取出来的 {ordered.Length} 个素材";
|
||||
var hint = 0;
|
||||
foreach (var entry in ordered)
|
||||
{
|
||||
var index = hint = BinarySearch(entry, hint);
|
||||
target.Insert(index, entry);
|
||||
var message = $"_loadedAssets.Count ({_loadedAssets.Count}) != modXml.TotalAssets ({modXml.TotalAssets})";
|
||||
throw new InvalidDataException(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task LoadCsf(string filePath)
|
||||
{
|
||||
await ExceptionWrapepr(async () =>
|
||||
{
|
||||
switch (Path.GetExtension(filePath).ToUpperInvariant())
|
||||
{
|
||||
case ".BIG":
|
||||
await LoadCsfFromBig(filePath).ConfigureAwait(true);
|
||||
return;
|
||||
case ".CSF":
|
||||
await Task.Run(() =>
|
||||
{
|
||||
using var stream = File.OpenRead(filePath);
|
||||
Translator.Provider = new CsfTranslationProvider(stream);
|
||||
}).ConfigureAwait(true);
|
||||
return;
|
||||
}
|
||||
}, "SAGE FastHash 计算器 - CSF 加载失败…", "CSF 加载失败:").ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private static Task LoadCsfFromBig(string bigPath)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
using var big = new BigFile(bigPath);
|
||||
var files = big.GetFiles(string.Empty, "*.csf", VirtualSearchOptionType.AllDirectories);
|
||||
var file =
|
||||
files.FirstOrDefault(x => x.Equals("gamestrings.csf", StringComparison.OrdinalIgnoreCase))
|
||||
?? files.FirstOrDefault();
|
||||
if (file == null)
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
using var stream = big.OpenStream(file);
|
||||
Translator.Provider = new CsfTranslationProvider(stream);
|
||||
});
|
||||
}
|
||||
|
||||
private static Task FindCsf(DirectoryInfo baseDirectory)
|
||||
{
|
||||
return Task.Run(() =>
|
||||
{
|
||||
if (!baseDirectory.Exists)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
baseDirectory.GetFiles("*.csf");
|
||||
var parent = baseDirectory.Parent;
|
||||
var searchDirectories =
|
||||
parent.GetDirectories($"{baseDirectory.Name[0]}*")
|
||||
.Prepend(parent.Parent)
|
||||
.Prepend(baseDirectory);
|
||||
searchDirectories = searchDirectories
|
||||
.Concat(searchDirectories.SelectMany(x => x.GetDirectories("Additional")));
|
||||
var csfs = from directories in searchDirectories
|
||||
from data in directories.GetDirectories("Data")
|
||||
from csf in data.GetFiles("*.csf")
|
||||
select csf;
|
||||
var result =
|
||||
csfs.FirstOrDefault(x => x.Name.Equals("gamestrings.csf", StringComparison.OrdinalIgnoreCase))
|
||||
?? csfs.FirstOrDefault();
|
||||
if (result == null)
|
||||
{
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
|
||||
using var fileStream = result.OpenRead();
|
||||
Translator.Provider = new CsfTranslationProvider(fileStream);
|
||||
TracerListener.WriteLine($"[ModXml]: Automatically loaded csf from `{result.FullName}`");
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateEntries()
|
||||
{
|
||||
ViewModel.FilterCollection(_loadedAssets);
|
||||
}
|
||||
|
||||
private void ClearEntries()
|
||||
{
|
||||
_loadedAssets.Clear();
|
||||
UpdateEntries();
|
||||
}
|
||||
|
||||
private void AddEntry(AssetEntry entry)
|
||||
{
|
||||
_loadedAssets.Add(entry);
|
||||
ViewModel.AddNewItems(Enumerable.Repeat(entry, 1));
|
||||
}
|
||||
|
||||
private void AddEntries(IEnumerable<AssetEntry> entries)
|
||||
{
|
||||
_loadedAssets.UnionWith(entries);
|
||||
UpdateEntries();
|
||||
}
|
||||
|
||||
public static IEnumerable<InputEntry> FindManifests()
|
||||
{
|
||||
var manifests = VirtualFileSystem.ListFiles(SelectedFileSystemRootPath, "*.manifest", VirtualSearchOptionType.AllDirectories);
|
||||
@ -223,5 +414,21 @@ namespace HashCalculator.GUI
|
||||
{
|
||||
return VirtualFileSystem.GetFileName(path).StartsWith(what, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
||||
private static async Task ExceptionWrapepr(Func<Task> action, string errorTitle, string preErrorMessage)
|
||||
{
|
||||
try
|
||||
{
|
||||
await action().ConfigureAwait(true);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
CopyableBox.ShowDialog(errorTitle, _ =>
|
||||
{
|
||||
return Task.FromResult(preErrorMessage + exception);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ namespace HashCalculator.GUI.Converters
|
||||
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
||||
internal class ValueConverterAggregate : List<IValueConverter>, IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
public object? Convert(object? value, Type targetType, object parameter, CultureInfo culture)
|
||||
{
|
||||
return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, culture));
|
||||
}
|
||||
|
@ -8,8 +8,9 @@
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:local="clr-namespace:HashCalculator.GUI"
|
||||
mc:Ignorable="d"
|
||||
Title="CopyableBox" Height="350" Width="600"
|
||||
Title="{Binding Title}" Height="350" Width="600"
|
||||
Closing="ClosingHandler"
|
||||
Style="{StaticResource CommonStyle}"
|
||||
>
|
||||
<Window.DataContext>
|
||||
<local:CopyableBoxViewModel />
|
||||
|
@ -27,10 +27,11 @@ namespace HashCalculator.GUI
|
||||
{
|
||||
private CopyableBoxViewModel ViewModel => (CopyableBoxViewModel)DataContext;
|
||||
|
||||
public static void ShowDialog(Func<CancellationToken, Task<string>> action)
|
||||
public static void ShowDialog(string title, Func<CancellationToken, Task<string>> action)
|
||||
{
|
||||
using var box = new CopyableBox();
|
||||
box.ViewModel.Initialize(action, box.Dispatcher);
|
||||
box.ViewModel.Initialize(title, action, box.Dispatcher);
|
||||
box.Owner = App.Current.MainWindow;
|
||||
box.ShowDialog();
|
||||
}
|
||||
|
||||
@ -67,7 +68,14 @@ namespace HashCalculator.GUI
|
||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||
private Task? _task;
|
||||
|
||||
public const string InitialMessage = "正在加载…… 关闭窗口就可以取消加载(";
|
||||
private string _title = "我居然没有名字qwq";
|
||||
public string Title
|
||||
{
|
||||
get => _title;
|
||||
private set => SetField(ref _title, value);
|
||||
}
|
||||
|
||||
private const string InitialMessage = "正在加载…… 关闭窗口就可以取消加载(";
|
||||
|
||||
private string _text = InitialMessage;
|
||||
public string Text
|
||||
@ -121,8 +129,9 @@ namespace HashCalculator.GUI
|
||||
_cancellationTokenSource.Dispose();
|
||||
}
|
||||
|
||||
public void Initialize(Func<CancellationToken, Task<string>> action, Dispatcher dispatcher)
|
||||
public void Initialize(string title, Func<CancellationToken, Task<string>> action, Dispatcher dispatcher)
|
||||
{
|
||||
Title = title;
|
||||
_task = InitializationTask(action, dispatcher);
|
||||
}
|
||||
|
||||
|
@ -9,22 +9,32 @@
|
||||
<UseWPF>true</UseWPF>
|
||||
<Platforms>AnyCPU;x86</Platforms>
|
||||
<UserSecretsId>bf77c300-44f6-46ea-be94-f50d6993b55b</UserSecretsId>
|
||||
<StartupObject>HashCalculator.GUI.Program</StartupObject>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Mvp.Xml" Version="2.3.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TechnologyAssembler.Core\TechnologyAssembler.Core.csproj" />
|
||||
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.11.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="System.Windows.Presentation" />
|
||||
<Reference Include="TechnologyAssembler.Core">
|
||||
<HintPath>TechnologyAssembler.Core.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="CustomAfterResolveReferences" AfterTargets="AfterResolveReferences">
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="@(ReferenceCopyLocalPaths)" Condition="'%(ReferenceCopyLocalPaths.Extension)' == '.dll'">
|
||||
<LogicalName>%(ReferenceCopyLocalPaths.DestinationSubDirectory)%(ReferenceCopyLocalPaths.Filename)%(ReferenceCopyLocalPaths.Extension)</LogicalName>
|
||||
</EmbeddedResource>
|
||||
</ItemGroup>
|
||||
</Target>
|
||||
</Project>
|
@ -12,11 +12,6 @@
|
||||
d:DesignHeight="100" d:DesignWidth="800"
|
||||
>
|
||||
<UserControl.Resources>
|
||||
<c:ValueConverterAggregate x:Key="NullToVisibilityConverter">
|
||||
<c:NullToBooleanConverter />
|
||||
<c:BooleanInvertConverter />
|
||||
<BooleanToVisibilityConverter />
|
||||
</c:ValueConverterAggregate>
|
||||
<Style
|
||||
x:Key="BlankListBoxContainerStyle"
|
||||
TargetType="{x:Type ListBoxItem}"
|
||||
|
204
MainWindow.xaml
204
MainWindow.xaml
@ -10,8 +10,9 @@
|
||||
mc:Ignorable="d"
|
||||
Title="SAGE FastHash 哈希计算器"
|
||||
Height="600"
|
||||
Width="600"
|
||||
Width="600"
|
||||
Style="{StaticResource CommonStyle}"
|
||||
Initialized="OnInitialized"
|
||||
>
|
||||
<Window.DataContext>
|
||||
<l:ViewModel />
|
||||
@ -43,7 +44,8 @@
|
||||
Content="浏览文件"
|
||||
Command="{Binding MainInput.BrowseCommand}"
|
||||
CommandParameter="{Binding ElementName=Self}"
|
||||
Visibility="{Binding MainInput.SelectedItem, Converter={StaticResource InvalidInputEntryToVisibilityConverter}}"
|
||||
Visibility="{Binding MainInput.SelectedItem,
|
||||
Converter={StaticResource InvalidInputEntryToVisibilityConverter}}"
|
||||
Grid.Column="1"
|
||||
Margin="0"
|
||||
/>
|
||||
@ -51,7 +53,8 @@
|
||||
Content="确认"
|
||||
Command="{Binding MainInput.SelectCommand}"
|
||||
CommandParameter="{Binding}"
|
||||
Visibility="{Binding MainInput.SelectedItem, Converter={StaticResource ValidInputEntryToVisibilityConverter}}"
|
||||
Visibility="{Binding MainInput.SelectedItem,
|
||||
Converter={StaticResource ValidInputEntryToVisibilityConverter}}"
|
||||
Grid.Column="1"
|
||||
Margin="0"
|
||||
Foreground="#20FF30"
|
||||
@ -62,7 +65,8 @@
|
||||
DockPanel.Dock="Top"
|
||||
Height="25"
|
||||
Margin="0,10,0,0"
|
||||
Visibility="{Binding BigEntryInput.AllManifests, Converter={StaticResource NotNullToVisibilityConverter}}"
|
||||
Visibility="{Binding BigEntryInput.AllManifests,
|
||||
Converter={StaticResource NotNullToVisibilityConverter}}"
|
||||
>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
@ -116,98 +120,162 @@
|
||||
</Grid>
|
||||
<Grid
|
||||
DockPanel.Dock="Top"
|
||||
Height="50"
|
||||
Margin="0,10"
|
||||
Height="25"
|
||||
Margin="0,5,0,0"
|
||||
>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
<TextBlock
|
||||
Text="{Binding StatusText}"
|
||||
Grid.Row="0"
|
||||
Margin="10,0"
|
||||
Margin="5,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
/>
|
||||
</Grid>
|
||||
<DockPanel
|
||||
DockPanel.Dock="Top"
|
||||
Height="25"
|
||||
Margin="0,0,0,5"
|
||||
>
|
||||
<Grid
|
||||
DockPanel.Dock="Right"
|
||||
Width="190"
|
||||
Visibility="{Binding IsXml,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
<Button
|
||||
Content="{Binding LoadCsfText}"
|
||||
Command="{Binding LoadCsf}"
|
||||
CommandParameter="{Binding ElementName=Self}"
|
||||
Width="92"
|
||||
HorizontalAlignment="Left"
|
||||
/>
|
||||
<Button
|
||||
Content="取消加载"
|
||||
Command="{Binding CancelXml}"
|
||||
Width="92"
|
||||
HorizontalAlignment="Right"
|
||||
Visibility="{Binding IsLoadingXml,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</Grid>
|
||||
<CheckBox
|
||||
DockPanel.Dock="Right"
|
||||
Content="只显示 GameObject"
|
||||
IsChecked="{Binding GameObjectOnly}"
|
||||
Command="{Binding FilterDisplayEntries}"
|
||||
Width="155"
|
||||
VerticalAlignment="Center"
|
||||
/>
|
||||
<TextBlock
|
||||
Text="{Binding Entries.Count, StringFormat=目前加载了{0}个素材}"
|
||||
Grid.Row="1"
|
||||
Margin="10,5,0,5"
|
||||
Text="{Binding DisplayEntries.Count, StringFormat=列表里显示了{0}个素材}"
|
||||
Margin="5,0,0,0"
|
||||
Width="160"
|
||||
HorizontalAlignment="Left"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding Entries.Count, Converter={StaticResource NonZeroToVisibilityConverter}}"
|
||||
Visibility="{Binding DisplayEntries.Count,
|
||||
Converter={StaticResource NonZeroToVisibilityConverter}}"
|
||||
/>
|
||||
|
||||
<CheckBox
|
||||
Content="只显示 GameObject"
|
||||
IsChecked="True"
|
||||
Grid.Row="1"
|
||||
Margin="0,5,242,5"
|
||||
Width="155"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center"
|
||||
/>
|
||||
<Button
|
||||
Content="加载 csf/mod.str"
|
||||
Grid.Row="1"
|
||||
Width="140"
|
||||
Margin="0,0,97,0"
|
||||
HorizontalAlignment="Right"
|
||||
/>
|
||||
<Button
|
||||
Content="取消加载"
|
||||
Grid.Row="1"
|
||||
Width="92"
|
||||
HorizontalAlignment="Right"
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
</DockPanel>
|
||||
<Grid
|
||||
DockPanel.Dock="Top"
|
||||
Height="25"
|
||||
>
|
||||
<TextBox></TextBox>
|
||||
<TextBox
|
||||
Text="{Binding Filter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
VerticalContentAlignment="Center"
|
||||
/>
|
||||
<TextBlock
|
||||
Text="过滤Asset ID(可选)"
|
||||
Text="可以按照素材名称或 Instance ID 来查找素材,加载 CSF 之后还可以按照单位名称来查找"
|
||||
Foreground="#B0B0B0"
|
||||
Padding="5,0"
|
||||
VerticalAlignment="Center"
|
||||
Visibility="{Binding Filter,
|
||||
Converter={StaticResource NullToVisibilityConverter}}"
|
||||
IsHitTestVisible="False"
|
||||
/>
|
||||
</Grid>
|
||||
<FrameworkElement x:Name="ReferenceProvider" Visibility="Collapsed"/>
|
||||
<DataGrid
|
||||
ItemsSource="{Binding Entries}"
|
||||
x:Name="DataGrid"
|
||||
ItemsSource="{Binding DisplayEntries}"
|
||||
IsReadOnly="True"
|
||||
AutoGenerateColumns="False"
|
||||
ScrollViewer.CanContentScroll="True"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
EnableRowVirtualization="True"
|
||||
VirtualizingPanel.VirtualizationMode="Recycling"
|
||||
/>
|
||||
>
|
||||
<DataGrid.Columns>
|
||||
<DataGridTextColumn
|
||||
Header="类型/素材名称"
|
||||
Binding="{Binding NameString}"
|
||||
/>
|
||||
<DataGridTextColumn
|
||||
Header="哈希(InstanceId)"
|
||||
Binding="{Binding InstanceIdString}"
|
||||
FontFamily="Courier New"
|
||||
/>
|
||||
<DataGridTextColumn
|
||||
Header="名称"
|
||||
Binding="{Binding LocalizedNames}"
|
||||
Visibility="{Binding Source={x:Reference Name=ReferenceProvider},
|
||||
Path=DataContext.IsXml,
|
||||
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
/>
|
||||
</DataGrid.Columns>
|
||||
</DataGrid>
|
||||
</DockPanel>
|
||||
<StackPanel
|
||||
<ScrollViewer
|
||||
Grid.Row="1"
|
||||
Margin="0,10,0,0"
|
||||
ScrollViewer.CanContentScroll="True"
|
||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto"
|
||||
ScrollChanged="OnButtomScrollViewerScrollChanged"
|
||||
>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
本工具基于 <l:ShellLink NavigateUri="https://github.com/Qibbi">Qibbi</l:ShellLink> 提供的 TechnologyAssembler 制作<LineBreak />
|
||||
此外使用了 <l:ShellLink NavigateUri="https://github.com/bgrainger">Bradley Grainger</l:ShellLink> 的
|
||||
<l:ShellLink NavigateUri="https://github.com/bgrainger/IndexRange">IndexRange</l:ShellLink>
|
||||
从而能在 .NET Standard 2.0 上使用 C# 8.0 的末尾索引操作符<LineBreak />
|
||||
<LineBreak />
|
||||
假如你对本工具有任何疑问或者建议的话,可以来到<l:ShellLink NavigateUri="https://tieba.baidu.com/ra3">红警3吧</l:ShellLink>发帖寻找岚依(
|
||||
</TextBlock>
|
||||
<TextBlock />
|
||||
<StackPanel
|
||||
Height="25"
|
||||
HorizontalAlignment="Right"
|
||||
>
|
||||
<Button
|
||||
Content="清除输出"
|
||||
Width="92"
|
||||
Grid.Row="1"
|
||||
<StackPanel>
|
||||
<TextBlock TextWrapping="Wrap">
|
||||
本工具基于 <l:ShellLink NavigateUri="https://github.com/Qibbi">Qibbi</l:ShellLink> 提供的 TechnologyAssembler 制作<LineBreak />
|
||||
使用了 <l:ShellLink NavigateUri="https://github.com/bgrainger">Bradley Grainger</l:ShellLink> 的
|
||||
<l:ShellLink NavigateUri="https://github.com/bgrainger/IndexRange">IndexRange</l:ShellLink>
|
||||
从而能在 .NET Standard 2.0 上使用 C# 8.0 的末尾索引操作符<LineBreak />
|
||||
此外还使用了 <l:ShellLink NavigateUri="http://mvpxml.codeplex.com/">Mvp.Xml</l:ShellLink> 来解析 XML 文件<LineBreak />
|
||||
<LineBreak />
|
||||
假如对本工具有任何疑问或者建议的话,可以来到<l:ShellLink NavigateUri="https://tieba.baidu.com/ra3">红警3吧</l:ShellLink>发帖寻找岚依(
|
||||
</TextBlock>
|
||||
<TextBlock
|
||||
Text="Tracer 输出:"
|
||||
Margin="0,5,0,0"
|
||||
Visibility="{Binding TraceText,
|
||||
Converter={StaticResource NotNullToVisibilityConverter}}"
|
||||
/>
|
||||
<TextBox
|
||||
Text="{Binding TraceText, Mode=OneWay}"
|
||||
Visibility="{Binding TraceText,
|
||||
Converter={StaticResource NotNullToVisibilityConverter}}"
|
||||
TextWrapping="Wrap"
|
||||
IsReadOnly="True"
|
||||
BorderBrush="Transparent"
|
||||
/>
|
||||
<StackPanel
|
||||
FlowDirection="RightToLeft"
|
||||
Orientation="Horizontal"
|
||||
Height="25"
|
||||
Visibility="{Binding TraceText,
|
||||
Converter={StaticResource NotNullToVisibilityConverter}}"
|
||||
>
|
||||
<Button
|
||||
Content="清除输出"
|
||||
Command="{Binding ClearTraceText}"
|
||||
Width="92"
|
||||
Grid.Row="1"
|
||||
/>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Margin="10,0"
|
||||
Visibility="{Binding SuggestClearFilter, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||
>
|
||||
假如觉得有点卡的话,可以试试点击右边的这个按钮
|
||||
</TextBlock>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</Window>
|
||||
|
@ -1,17 +1,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using System.Windows.Documents;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
@ -26,11 +16,15 @@ namespace HashCalculator.GUI
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
Translator.ProviderChanged += (s, e) => Dispatcher.Invoke(() =>
|
||||
{
|
||||
ViewModel.NotifyCsfChange();
|
||||
});
|
||||
}
|
||||
|
||||
private void OnButtomScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||
{
|
||||
var scrollViewer = (ScrollViewer)e.Source;
|
||||
var scrollViewer = (ScrollViewer)sender;
|
||||
// User scroll event : set or unset auto-scroll mode
|
||||
if (e.ExtentHeightChange == 0)
|
||||
{ // Content unchanged : user scroll event
|
||||
@ -53,5 +47,10 @@ namespace HashCalculator.GUI
|
||||
scrollViewer.ScrollToVerticalOffset(scrollViewer.ExtentHeight);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnInitialized(object sender, EventArgs e)
|
||||
{
|
||||
ViewModel.StartTracerListener(s => Dispatcher.BeginInvoke(s));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
74
ModXml.cs
74
ModXml.cs
@ -4,7 +4,9 @@ using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading.Tasks.Dataflow;
|
||||
using System.Xml;
|
||||
using System.Xml.Linq;
|
||||
using Microsoft.Win32;
|
||||
@ -28,17 +30,21 @@ namespace HashCalculator.GUI
|
||||
public static XNamespace XInclude { get; } = "http://www.w3.org/2001/XInclude";
|
||||
public static XNamespace EalaAsset { get; } = "uri:ea.com:eala:asset";
|
||||
public bool SdkNotFound { get; }
|
||||
public IReadOnlyCollection<string> Errors => _errors;
|
||||
public DirectoryInfo BaseDirectory { get; }
|
||||
public int TotalFilesProcessed => _processed.Count;
|
||||
public int TotalAssets => _assets.Count;
|
||||
private readonly ConcurrentDictionary<string, byte> _processed = new ConcurrentDictionary<string, byte>();
|
||||
private readonly ConcurrentDictionary<AssetEntry, byte> _assets = new ConcurrentDictionary<AssetEntry, byte>();
|
||||
private readonly ConcurrentQueue<string> _errors = new ConcurrentQueue<string>();
|
||||
private readonly ConcurrentDictionary<AssetEntry, string> _assets = new ConcurrentDictionary<AssetEntry, string>();
|
||||
private readonly ModUriResolver _uriResolver;
|
||||
private readonly string _xmlFullPath;
|
||||
|
||||
public ModXml(string xmlPath)
|
||||
private readonly CancellationToken _token;
|
||||
private readonly BufferBlock<AssetEntry> _entries;
|
||||
|
||||
public ModXml(string xmlPath, CancellationToken token)
|
||||
{
|
||||
var baseDirectory = new DirectoryInfo(FindBaseDirectory(xmlPath));
|
||||
var allMods = baseDirectory.Parent;
|
||||
BaseDirectory = new DirectoryInfo(FindBaseDirectory(xmlPath));
|
||||
var allMods = BaseDirectory.Parent;
|
||||
if (!allMods.Name.Equals("Mods", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
throw new DirectoryNotFoundException();
|
||||
@ -55,7 +61,7 @@ namespace HashCalculator.GUI
|
||||
{
|
||||
sdkRootPath,
|
||||
allModsParent.FullName,
|
||||
Path.Combine(baseDirectory.FullName, name),
|
||||
Path.Combine(BaseDirectory.FullName, name),
|
||||
Path.Combine(sdkRootPath, "Mods"),
|
||||
allMods.FullName,
|
||||
Path.Combine(sdkRootPath, name.Equals("Data", StringComparison.OrdinalIgnoreCase) ? "SageXml" : name)
|
||||
@ -69,31 +75,54 @@ namespace HashCalculator.GUI
|
||||
});
|
||||
|
||||
_xmlFullPath = Path.GetFullPath(xmlPath);
|
||||
_token = token;
|
||||
_entries = new BufferBlock<AssetEntry>();
|
||||
}
|
||||
|
||||
public Task<ICollection<AssetEntry>> ProcessDocument()
|
||||
public async IAsyncEnumerable<AssetEntry> ProcessDocument()
|
||||
{
|
||||
if (_processed.Any() || _assets.Any() || _errors.Any())
|
||||
if (_processed.Any() || _assets.Any())
|
||||
{
|
||||
throw new InvalidOperationException();
|
||||
}
|
||||
|
||||
return Task.Run(() =>
|
||||
var task = Task.Run(() =>
|
||||
{
|
||||
var includes = ProcessDocumentInternal(_xmlFullPath).AsParallel();
|
||||
while (includes.Any())
|
||||
try
|
||||
{
|
||||
includes = from include in includes
|
||||
from newInclude in ProcessDocumentInternal(include)
|
||||
select newInclude;
|
||||
var includes = ProcessDocumentInternal(_xmlFullPath);
|
||||
while (includes.Any())
|
||||
{
|
||||
var result = from include in includes.AsParallel()
|
||||
from newInclude in ProcessDocumentInternal(include)
|
||||
select newInclude;
|
||||
includes = result.ToArray();
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (!_token.IsCancellationRequested)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
_entries.Complete();
|
||||
}
|
||||
|
||||
return _assets.Keys;
|
||||
});
|
||||
|
||||
while(await _entries.OutputAvailableAsync().ConfigureAwait(false))
|
||||
{
|
||||
yield return _entries.Receive();
|
||||
}
|
||||
|
||||
await task.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private string[] ProcessDocumentInternal(string fullPath)
|
||||
{
|
||||
_token.ThrowIfCancellationRequested();
|
||||
if (!_processed.TryAdd(fullPath.ToUpperInvariant(), default))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
@ -110,9 +139,14 @@ namespace HashCalculator.GUI
|
||||
select new AssetEntry(element);
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (!_assets.TryAdd(item, default))
|
||||
if (_assets.TryAdd(item, fullPath))
|
||||
{
|
||||
_errors.Enqueue($"Attempted to add item `{item.Type}:{item.Name}` multiple times");
|
||||
_entries.Post(item);
|
||||
}
|
||||
else
|
||||
{
|
||||
var previousPath = _assets[item];
|
||||
TracerListener.WriteLine($"[ModXml] `{fullPath}`: Attempted to add item `{item.Type}:{item.Name}` multiple times - already processed in `{previousPath}`");
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +182,7 @@ namespace HashCalculator.GUI
|
||||
}
|
||||
catch (FileNotFoundException error)
|
||||
{
|
||||
_errors.Enqueue(error.Message);
|
||||
TracerListener.WriteLine($"[ModXml]: {error}");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
75
Program.cs
Normal file
75
Program.cs
Normal file
@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
public static class Program
|
||||
{
|
||||
[STAThread]
|
||||
public static void Main()
|
||||
{
|
||||
try
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyResolve += OnResolveAssembly;
|
||||
App.Main();
|
||||
}
|
||||
catch(Exception exception)
|
||||
{
|
||||
ErrorBox($"发生了无法处理的错误:\r\n{exception.ToString()}\r\n可以尝试在百度红警3吧联系岚依");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static Assembly OnResolveAssembly(object sender, ResolveEventArgs args)
|
||||
{
|
||||
var executingAssembly = Assembly.GetExecutingAssembly();
|
||||
var assemblyName = new AssemblyName(args.Name);
|
||||
var nameString = assemblyName.Name;
|
||||
|
||||
var paths = new[] { $"{nameString}.dll" }.AsEnumerable();
|
||||
const string resourceExtenstion = ".resources";
|
||||
if (nameString.EndsWith(resourceExtenstion, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
paths = paths.Append(nameString.Insert(nameString.Length - resourceExtenstion.Length, ".g"));
|
||||
}
|
||||
if (!assemblyName.CultureInfo.Equals(CultureInfo.InvariantCulture))
|
||||
{
|
||||
paths = paths.Select(x => $"{assemblyName.CultureInfo}\\{x}").Concat(paths);
|
||||
}
|
||||
|
||||
foreach(var path in paths)
|
||||
{
|
||||
using var stream = executingAssembly.GetManifestResourceStream(path);
|
||||
if (stream == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
using (stream)
|
||||
{
|
||||
var assemblyRawBytes = new byte[stream.Length];
|
||||
stream.Read(assemblyRawBytes, 0, assemblyRawBytes.Length);
|
||||
return Assembly.Load(assemblyRawBytes);
|
||||
}
|
||||
}
|
||||
|
||||
var errorMessage = $"Failed to load assembly {nameString}";
|
||||
throw new ApplicationException(errorMessage);
|
||||
}
|
||||
|
||||
public static void ErrorBox(string text)
|
||||
{
|
||||
_ = NativeMethods.MessageBox(IntPtr.Zero, text, null, 0x00000010L);
|
||||
}
|
||||
}
|
||||
|
||||
internal static class NativeMethods
|
||||
{
|
||||
[DllImport("User32.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Unicode)]
|
||||
public static extern int MessageBox(IntPtr hWnd, string lpText, string? lpCaption, ulong uType);
|
||||
}
|
||||
}
|
Binary file not shown.
34
TracerListener.cs
Normal file
34
TracerListener.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using TechnologyAssembler.Core.Diagnostics;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
internal static class TracerListener
|
||||
{
|
||||
private static Action<string>? _onData = null;
|
||||
|
||||
[SuppressMessage("Globalization", "CA1308:将字符串规范化为大写", Justification = "<挂起>")]
|
||||
[SuppressMessage("Globalization", "CA1303:请不要将文本作为本地化参数传递", Justification = "<挂起>")]
|
||||
public static void StartListening(Action<string> action)
|
||||
{
|
||||
var original = Interlocked.CompareExchange(ref _onData, action, null);
|
||||
if(original != null)
|
||||
{
|
||||
throw new InvalidOperationException("Action already set");
|
||||
}
|
||||
|
||||
Tracer.TraceWrite = new TraceWriteDelegate((s, t, m) =>
|
||||
{
|
||||
var type = $"{t} ".ToLowerInvariant().Substring(0, 4);
|
||||
WriteLine($"[{s}] {type}: {m}");
|
||||
});
|
||||
}
|
||||
|
||||
public static void WriteLine(string message)
|
||||
{
|
||||
Interlocked.CompareExchange(ref _onData, null, null)?.Invoke($"{message}\r\n");
|
||||
}
|
||||
}
|
||||
}
|
66
Translator.cs
Normal file
66
Translator.cs
Normal file
@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using TechnologyAssembler.Core.Language;
|
||||
|
||||
namespace HashCalculator.GUI
|
||||
{
|
||||
internal static class Translator
|
||||
{
|
||||
private static ITranslationProvider? _provider;
|
||||
public static ITranslationProvider? Provider
|
||||
{
|
||||
get => Interlocked.CompareExchange(ref _provider, null, null);
|
||||
set
|
||||
{
|
||||
var provider = value is null
|
||||
? null
|
||||
: new CachedCsfTranslationProvider(value);
|
||||
Interlocked.Exchange(ref _provider, provider);
|
||||
ProviderChanged?.Invoke(null, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool HasProvider => Provider != null;
|
||||
public static EventHandler? ProviderChanged;
|
||||
|
||||
public static string Translate(string what)
|
||||
{
|
||||
var provider = Provider;
|
||||
if(provider is null)
|
||||
{
|
||||
return $"NoProvider:{what}";
|
||||
}
|
||||
return provider.GetString(what);
|
||||
}
|
||||
}
|
||||
|
||||
internal class CachedCsfTranslationProvider : ITranslationProvider
|
||||
{
|
||||
private readonly ITranslationProvider _provider;
|
||||
private readonly ConcurrentDictionary<string, string> _cache = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public string Name => $"Cached{_provider.Name}";
|
||||
|
||||
public CachedCsfTranslationProvider(ITranslationProvider provider)
|
||||
{
|
||||
_provider = provider;
|
||||
}
|
||||
|
||||
public string GetString(string str)
|
||||
{
|
||||
if(!_cache.TryGetValue(str, out var value))
|
||||
{
|
||||
value = _provider.GetString(str);
|
||||
_cache.TryAdd(str, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public string GetPluralString(string str, string strPlural, long count)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
256
ViewModel.cs
256
ViewModel.cs
@ -1,15 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows;
|
||||
using Microsoft.Win32;
|
||||
using TechnologyAssembler.Core.IO;
|
||||
|
||||
@ -48,16 +46,38 @@ namespace HashCalculator.GUI
|
||||
internal class ViewModel : NotifyPropertyChanged
|
||||
{
|
||||
private static readonly Random _random = new Random();
|
||||
public Action<Action<Action>> OnStartTraceListener { get; }
|
||||
|
||||
public MainInputViewModel MainInput { get; }
|
||||
public BigInputViewModel BigEntryInput { get; }
|
||||
|
||||
private ObservableCollection<DisplayAssetEntry> _entries = new ObservableCollection<DisplayAssetEntry>();
|
||||
public ObservableCollection<DisplayAssetEntry> Entries
|
||||
private string? _filter;
|
||||
public string? Filter
|
||||
{
|
||||
get => _entries;
|
||||
set => SetField(ref _entries, value);
|
||||
get => _filter;
|
||||
set
|
||||
{
|
||||
SetField(ref _filter, value);
|
||||
FilterDisplayEntries.Execute(null);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _gameObjectOnly;
|
||||
public bool GameObjectOnly
|
||||
{
|
||||
get => _gameObjectOnly;
|
||||
set => SetField(ref _gameObjectOnly, value);
|
||||
}
|
||||
|
||||
public Command FilterDisplayEntries { get; }
|
||||
|
||||
private int _totalCount;
|
||||
public int TotalCount
|
||||
{
|
||||
get => _totalCount;
|
||||
set => SetField(ref _totalCount, value);
|
||||
}
|
||||
public ObservableSortedCollection<AssetEntry> DisplayEntries { get; } = new ObservableSortedCollection<AssetEntry>();
|
||||
|
||||
private string _statusText = SuggestionString("不知道该显示些什么呢……");
|
||||
public string StatusText
|
||||
@ -66,18 +86,92 @@ namespace HashCalculator.GUI
|
||||
set => SetField(ref _statusText, value);
|
||||
}
|
||||
|
||||
private bool _isXml;
|
||||
public bool IsXml
|
||||
{
|
||||
get => _isXml;
|
||||
set => SetField(ref _isXml, value);
|
||||
}
|
||||
private bool _isLoadingXml;
|
||||
public bool IsLoadingXml
|
||||
{
|
||||
get => _isLoadingXml;
|
||||
set => SetField(ref _isLoadingXml, value);
|
||||
}
|
||||
public Command CancelXml { get; }
|
||||
[SuppressMessage("Microsoft.Performance", "CA1822")]
|
||||
public string LoadCsfText => $"{(Translator.HasProvider ? "更换" : "加载")} CSF";
|
||||
public Command<MainWindow> LoadCsf { get; }
|
||||
|
||||
private string _traceText = string.Empty;
|
||||
public string TraceText
|
||||
{
|
||||
get => _traceText;
|
||||
set => SetField(ref _traceText, value);
|
||||
set
|
||||
{
|
||||
SetField(ref _traceText, value);
|
||||
SuggestClearFilter = TraceText.Length > 32768;
|
||||
}
|
||||
}
|
||||
|
||||
private bool _suggestClearFilter;
|
||||
public bool SuggestClearFilter
|
||||
{
|
||||
get => _suggestClearFilter;
|
||||
set => SetField(ref _suggestClearFilter, value);
|
||||
}
|
||||
|
||||
public Command ClearTraceText { get; }
|
||||
|
||||
public ViewModel()
|
||||
{
|
||||
var controller = new Controller(this);
|
||||
OnStartTraceListener = controller.StartTrace;
|
||||
MainInput = new MainInputViewModel(controller);
|
||||
BigEntryInput = new BigInputViewModel(controller);
|
||||
CancelXml = new Command(async () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
CancelXml.CanExecuteValue = false;
|
||||
await controller.CancelLoadingXml().ConfigureAwait(true);
|
||||
}
|
||||
finally
|
||||
{
|
||||
CancelXml.CanExecuteValue = true;
|
||||
}
|
||||
});
|
||||
LoadCsf = new Command<MainWindow>(async window =>
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadCsf.CanExecuteValue = false;
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Multiselect = false,
|
||||
ValidateNames = true,
|
||||
Filter = "CSF / BIG 文件|*.csf;*.big"
|
||||
};
|
||||
if (dialog.ShowDialog(window) == true)
|
||||
{
|
||||
await Controller.LoadCsf(dialog.FileName).ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
LoadCsf.CanExecuteValue = true;
|
||||
}
|
||||
});
|
||||
FilterDisplayEntries = new Command(() =>
|
||||
{
|
||||
controller.UpdateEntries();
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
ClearTraceText = new Command(() =>
|
||||
{
|
||||
TraceText = string.Empty;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
}
|
||||
|
||||
public static string SuggestionString(string original)
|
||||
@ -92,8 +186,74 @@ namespace HashCalculator.GUI
|
||||
{
|
||||
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()
|
||||
{
|
||||
Notify(nameof(LoadCsfText));
|
||||
FilterDisplayEntries.Execute(null);
|
||||
}
|
||||
|
||||
public void AddNewItems(IEnumerable<AssetEntry> items)
|
||||
{
|
||||
DisplayEntries.SortedAddItems(items.Where(FilterDisplayEntry));
|
||||
}
|
||||
|
||||
public void FilterCollection(HashSet<AssetEntry> orderedSource)
|
||||
{
|
||||
DisplayEntries.SortedRemoveItems(x => !orderedSource.Contains(x) || !FilterDisplayEntry(x));
|
||||
DisplayEntries.SortedAddItems(orderedSource.Where(FilterDisplayEntry));
|
||||
}
|
||||
|
||||
private bool FilterDisplayEntry(AssetEntry entry)
|
||||
{
|
||||
if(GameObjectOnly)
|
||||
{
|
||||
if(entry.Type != "GameObject")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(Filter))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var chunk in Filter!.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
if (entry.NameString.IndexOf(chunk, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (entry.InstanceIdString.IndexOf(chunk, StringComparison.OrdinalIgnoreCase) != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
if (Translator.HasProvider)
|
||||
{
|
||||
if (entry.LocalizedNames.IndexOf(chunk, StringComparison.CurrentCultureIgnoreCase) != -1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal class InputBarViewModel : NotifyPropertyChanged
|
||||
@ -257,7 +417,10 @@ namespace HashCalculator.GUI
|
||||
base.Text = value;
|
||||
if (value != SelectedItem?.ToString())
|
||||
{
|
||||
UpdateList(value);
|
||||
if(AllManifests != null)
|
||||
{
|
||||
UpdateList(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -277,6 +440,7 @@ namespace HashCalculator.GUI
|
||||
{
|
||||
SetField(ref _allManifests, value);
|
||||
Items = value;
|
||||
SelectedItem = null;
|
||||
LastProcessedManifest = null;
|
||||
Text = null;
|
||||
}
|
||||
@ -300,7 +464,7 @@ namespace HashCalculator.GUI
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
CopyableBox.ShowDialog(_ =>
|
||||
CopyableBox.ShowDialog("SAGE FastHash 计算器的错误" , _ =>
|
||||
{
|
||||
return Task.FromResult($"在加载 manifest 时发生错误:{exception}");
|
||||
});
|
||||
@ -332,4 +496,76 @@ namespace HashCalculator.GUI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class ObservableSortedCollection<T> : ObservableCollection<T> where T : IComparable<T>
|
||||
{
|
||||
private readonly HashSet<T> _set = new HashSet<T>();
|
||||
|
||||
public void SortedAddItems(IEnumerable<T> items)
|
||||
{
|
||||
try
|
||||
{
|
||||
items = from item in items
|
||||
where !_set.Contains(item)
|
||||
select item;
|
||||
foreach (var item in items)
|
||||
{
|
||||
_set.Add(item);
|
||||
base.Insert(BinarySearch(item), item);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_set.Clear();
|
||||
base.Clear();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void SortedRemoveItems(Func<T, bool> ifRemove)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach(var i in Enumerable.Range(0, base.Count).Reverse())
|
||||
{
|
||||
var item = base[i];
|
||||
if (ifRemove(item))
|
||||
{
|
||||
_set.Remove(item);
|
||||
base.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
_set.Clear();
|
||||
base.Clear();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private int BinarySearch(T entry)
|
||||
{
|
||||
var begin = 0;
|
||||
var end = base.Count;
|
||||
while (begin < end)
|
||||
{
|
||||
var middle = begin + (end - begin) / 2;
|
||||
var result = entry.CompareTo(base[middle]);
|
||||
if (result == 0)
|
||||
{
|
||||
return middle;
|
||||
}
|
||||
else if (result < 0)
|
||||
{
|
||||
end = middle;
|
||||
}
|
||||
else if (result > 0)
|
||||
{
|
||||
begin = middle + 1;
|
||||
}
|
||||
}
|
||||
return end;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user