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"
|
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
|
||||||
StartupUri="MainWindow.xaml">
|
StartupUri="MainWindow.xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
|
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
|
||||||
<c:ValueConverterAggregate x:Key="ValidInputEntryToVisibilityConverter">
|
<c:ValueConverterAggregate x:Key="ValidInputEntryToVisibilityConverter">
|
||||||
<c:ValidInputEntryTypeToBooleanConverter />
|
<c:ValidInputEntryTypeToBooleanConverter />
|
||||||
<BooleanToVisibilityConverter />
|
<BooleanToVisibilityConverter />
|
||||||
@ -38,10 +39,6 @@
|
|||||||
Value="#202020"
|
Value="#202020"
|
||||||
/>
|
/>
|
||||||
</Style>
|
</Style>
|
||||||
<Style
|
|
||||||
BasedOn="{StaticResource CommonStyle}"
|
|
||||||
TargetType="l:MainWindow"
|
|
||||||
/>
|
|
||||||
<Style
|
<Style
|
||||||
TargetType="l:ShellLink"
|
TargetType="l:ShellLink"
|
||||||
>
|
>
|
||||||
@ -126,10 +123,6 @@
|
|||||||
BasedOn="{StaticResource ButtonStyle}"
|
BasedOn="{StaticResource ButtonStyle}"
|
||||||
TargetType="Button"
|
TargetType="Button"
|
||||||
/>
|
/>
|
||||||
<Style
|
|
||||||
BasedOn="{StaticResource CommonStyle}"
|
|
||||||
TargetType="CheckBox"
|
|
||||||
/>
|
|
||||||
<Style
|
<Style
|
||||||
BasedOn="{StaticResource CommonStyle}"
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
TargetType="{x:Type DataGrid}"
|
TargetType="{x:Type DataGrid}"
|
||||||
@ -163,5 +156,200 @@
|
|||||||
>
|
>
|
||||||
<Setter Property="Background" Value="#181818"/>
|
<Setter Property="Background" Value="#181818"/>
|
||||||
</Style>
|
</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.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
|
@ -18,6 +18,7 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
var core = new TechnologyAssemblerCoreModule();
|
var core = new TechnologyAssemblerCoreModule();
|
||||||
core.Initialize();
|
core.Initialize();
|
||||||
|
DispatcherUnhandledException += (s, e) => Program.ErrorBox($"SAGE FastHash 计算器遇上了未处理的错误:{e.Exception}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,20 +2,37 @@
|
|||||||
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;
|
||||||
|
|
||||||
namespace HashCalculator.GUI
|
namespace HashCalculator.GUI
|
||||||
{
|
{
|
||||||
public class AssetEntry : IEquatable<AssetEntry>
|
public class AssetEntry : IEquatable<AssetEntry>, IComparable<AssetEntry>
|
||||||
{
|
{
|
||||||
public string Type { get; }
|
public string Type { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
|
public string NameString { get; }
|
||||||
public IEnumerable<string> DisplayLabels { get; }
|
public IEnumerable<string> DisplayLabels { get; }
|
||||||
public uint InstanceId => SageHash.CalculateLowercaseHash(Name);
|
public uint InstanceId => SageHash.CalculateLowercaseHash(Name);
|
||||||
|
|
||||||
public string NameString => $"{Type}:{Name}";
|
public string InstanceIdString => $"{InstanceId:x8} ({InstanceId,10})";
|
||||||
public string InstanceIdString => $"{InstanceId:X8} ({InstanceId})";
|
|
||||||
|
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)
|
public AssetEntry(XElement element)
|
||||||
{
|
{
|
||||||
@ -32,6 +49,7 @@ namespace HashCalculator.GUI
|
|||||||
Type = element.Name.LocalName;
|
Type = element.Name.LocalName;
|
||||||
var id = element.Attribute("id")?.Value;
|
var id = element.Attribute("id")?.Value;
|
||||||
Name = id ?? throw new NotSupportedException();
|
Name = id ?? throw new NotSupportedException();
|
||||||
|
NameString = $"{Type}:{Name}";
|
||||||
|
|
||||||
var labels = from name in element.Elements(ModXml.EalaAsset + "DisplayName")
|
var labels = from name in element.Elements(ModXml.EalaAsset + "DisplayName")
|
||||||
select name.Value;
|
select name.Value;
|
||||||
@ -42,14 +60,15 @@ namespace HashCalculator.GUI
|
|||||||
|
|
||||||
public AssetEntry(Asset asset)
|
public AssetEntry(Asset asset)
|
||||||
{
|
{
|
||||||
if(asset is null)
|
if (asset is null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException($"{nameof(asset)} is null");
|
throw new ArgumentNullException($"{nameof(asset)} is null");
|
||||||
}
|
}
|
||||||
Type = asset.TypeName;
|
Type = asset.TypeName;
|
||||||
Name = asset.InstanceName;
|
Name = asset.InstanceName;
|
||||||
DisplayLabels = Enumerable.Empty<string>();
|
NameString = $"{Type}:{Name}";
|
||||||
if(InstanceId != asset.InstanceId || SageHash.CalculateBinaryHash(Type) != asset.TypeId)
|
DisplayLabels = Array.Empty<string>();
|
||||||
|
if (InstanceId != asset.InstanceId || SageHash.CalculateBinaryHash(Type) != asset.TypeId)
|
||||||
{
|
{
|
||||||
throw new InvalidDataException();
|
throw new InvalidDataException();
|
||||||
}
|
}
|
||||||
@ -103,32 +122,34 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
return HashCode.Combine(Type, InstanceId);
|
return HashCode.Combine(Type, InstanceId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int CompareTo(AssetEntry other)
|
||||||
|
{
|
||||||
|
if (other is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException($"{nameof(other)} is null");
|
||||||
|
}
|
||||||
|
return string.CompareOrdinal(NameString, other.NameString);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class DisplayAssetEntry : IComparable<DisplayAssetEntry>
|
public static bool operator <(AssetEntry left, AssetEntry right)
|
||||||
{
|
{
|
||||||
public string Name { get; }
|
return left is null ? right is object : left.CompareTo(right) < 0;
|
||||||
public string InstanceId { get; }
|
|
||||||
|
|
||||||
public DisplayAssetEntry(AssetEntry entry)
|
|
||||||
{
|
|
||||||
Name = entry.NameString;
|
|
||||||
InstanceId = entry.InstanceIdString;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int CompareTo(DisplayAssetEntry other)
|
public static bool operator <=(AssetEntry left, AssetEntry right)
|
||||||
{
|
{
|
||||||
return string.CompareOrdinal(Name, other.Name);
|
return left is null || left.CompareTo(right) <= 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class LocalizedDisplayAssetEntry : DisplayAssetEntry
|
public static bool operator >(AssetEntry left, AssetEntry right)
|
||||||
{
|
{
|
||||||
public string LocalizedNames { get; }
|
return left is object && left.CompareTo(right) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
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 ? 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;
|
return _canExecute;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Execute(object parameter)
|
public void Execute(object? parameter)
|
||||||
{
|
{
|
||||||
if (!_canExecute)
|
if (!_canExecute)
|
||||||
{
|
{
|
||||||
|
311
Controller.cs
311
Controller.cs
@ -1,5 +1,6 @@
|
|||||||
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;
|
||||||
@ -8,6 +9,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using TechnologyAssembler.Core.IO;
|
using TechnologyAssembler.Core.IO;
|
||||||
using TechnologyAssembler.Core.Assets;
|
using TechnologyAssembler.Core.Assets;
|
||||||
|
using TechnologyAssembler.Core.Language.Providers;
|
||||||
|
|
||||||
namespace HashCalculator.GUI
|
namespace HashCalculator.GUI
|
||||||
{
|
{
|
||||||
@ -19,28 +21,36 @@ namespace HashCalculator.GUI
|
|||||||
|
|
||||||
private ViewModel ViewModel { get; }
|
private ViewModel ViewModel { get; }
|
||||||
private BigFileSystemProvider? _currentBig = null;
|
private BigFileSystemProvider? _currentBig = null;
|
||||||
|
private readonly HashSet<AssetEntry> _loadedAssets = new HashSet<AssetEntry>();
|
||||||
|
private Tuple<CancellationTokenSource, Task>? _lastXmlTask = null;
|
||||||
|
|
||||||
public Controller(ViewModel viewModel)
|
public Controller(ViewModel viewModel)
|
||||||
{
|
{
|
||||||
ViewModel = viewModel;
|
ViewModel = viewModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void StartTrace(Action<Action> action)
|
||||||
|
{
|
||||||
|
TracerListener.StartListening(s => action(() => ViewModel.TraceText += s));
|
||||||
|
}
|
||||||
|
|
||||||
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
||||||
public async Task OnMainInputDecided(InputEntry selected)
|
public async Task OnMainInputDecided(InputEntry selected)
|
||||||
{
|
{
|
||||||
_currentBig?.Dispose();
|
|
||||||
_currentBig = null;
|
|
||||||
ViewModel.Entries.Clear();
|
|
||||||
ViewModel.TraceText = string.Empty;
|
|
||||||
ViewModel.BigEntryInput.AllManifests = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
_currentBig?.Dispose();
|
||||||
|
_currentBig = null;
|
||||||
|
ClearEntries();
|
||||||
|
ViewModel.TraceText = string.Empty;
|
||||||
|
ViewModel.BigEntryInput.AllManifests = null;
|
||||||
|
ViewModel.IsXml = false;
|
||||||
|
|
||||||
switch (selected.Type)
|
switch (selected.Type)
|
||||||
{
|
{
|
||||||
case InputEntryType.BinaryFile:
|
case InputEntryType.BinaryFile:
|
||||||
ViewModel.StatusText = "请留意一下弹出的窗口(";
|
ViewModel.StatusText = "请留意一下弹出的窗口(";
|
||||||
CopyableBox.ShowDialog(token => CalculateBinaryHash(selected.Value, token));
|
CopyableBox.ShowDialog("SAGE FastHash 计算器", token => CalculateBinaryHash(selected.Value, token));
|
||||||
ViewModel.StatusText = ViewModel.SuggestionString(string.Empty);
|
ViewModel.StatusText = ViewModel.SuggestionString(string.Empty);
|
||||||
return;
|
return;
|
||||||
case InputEntryType.BigFile:
|
case InputEntryType.BigFile:
|
||||||
@ -50,7 +60,9 @@ namespace HashCalculator.GUI
|
|||||||
await LoadManifestFromFile(selected.Value).ConfigureAwait(true);
|
await LoadManifestFromFile(selected.Value).ConfigureAwait(true);
|
||||||
return;
|
return;
|
||||||
case InputEntryType.XmlFile:
|
case InputEntryType.XmlFile:
|
||||||
throw new NotImplementedException();
|
ViewModel.IsXml = true;
|
||||||
|
await LoadXml(selected.Value).ConfigureAwait(true);
|
||||||
|
return;
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException();
|
throw new NotSupportedException();
|
||||||
}
|
}
|
||||||
@ -58,7 +70,7 @@ namespace HashCalculator.GUI
|
|||||||
catch (Exception error)
|
catch (Exception error)
|
||||||
{
|
{
|
||||||
ViewModel.StatusText = "失败…";
|
ViewModel.StatusText = "失败…";
|
||||||
CopyableBox.ShowDialog(_ =>
|
CopyableBox.ShowDialog("SAGE FastHash 计算器 - 失败!", _ =>
|
||||||
{
|
{
|
||||||
return Task.FromResult($"在尝试加载 {selected.Type} `{selected.Value}` 时发生错误:\r\n{error}");
|
return Task.FromResult($"在尝试加载 {selected.Type} `{selected.Value}` 时发生错误:\r\n{error}");
|
||||||
});
|
});
|
||||||
@ -90,10 +102,10 @@ namespace HashCalculator.GUI
|
|||||||
SageHash.CalculateLauncherBinaryHash
|
SageHash.CalculateLauncherBinaryHash
|
||||||
}.AsParallel().Select(fn => fn(array)).ToArray();
|
}.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"
|
+ "注意这是以大小写敏感模式计算出的哈希值,与素材ID的哈希值(转换成小写后计算的哈希)一般是不一样的\r\n\r\n"
|
||||||
+ "使用 SAGE Launcher FastHash(比如说 RA3.exe 用来校验更新补丁文件的哈希)计算出的哈希值:"
|
+ "使用 SAGE Launcher FastHash(比如说 RA3.exe 用来校验更新补丁文件的哈希)计算出的哈希值:"
|
||||||
+ $"{hashes[1]:X8} (十进制 {hashes[1]})";
|
+ $"{hashes[1]:x8} (十进制 {hashes[1]})";
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
@ -133,72 +145,251 @@ namespace HashCalculator.GUI
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task LoadManifestFromFile(string path)
|
private async Task LoadManifestFromFile(string path)
|
||||||
|
{
|
||||||
|
await ExceptionWrapepr(async () =>
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
using var provider = new FileSystemProvider(SelectedFileSystemRootPath, null);
|
using var provider = new FileSystemProvider(SelectedFileSystemRootPath, null);
|
||||||
|
path = VirtualFileSystem.Combine(SelectedFileSystemRootPath, path);
|
||||||
await ProcessManifest(path).ConfigureAwait(true);
|
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)
|
public async Task ProcessManifest(string path)
|
||||||
{
|
{
|
||||||
ViewModel.Entries.Clear();
|
ClearEntries();
|
||||||
ViewModel.TraceText = string.Empty;
|
ViewModel.StatusText = "正在读取 manifest 文件…";
|
||||||
ViewModel.StatusText = "正在加载 manifest 文件…";
|
|
||||||
var entries = await Task.Run(() =>
|
var entries = await Task.Run(() =>
|
||||||
{
|
{
|
||||||
if (path.EndsWith(ManifestExtension, StringComparison.OrdinalIgnoreCase))
|
if (path.EndsWith(ManifestExtension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
path = path.Remove(path.Length - ManifestExtension.Length);
|
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
|
var assets = from asset in manifest.Assets.Values
|
||||||
select new DisplayAssetEntry(new AssetEntry(asset));
|
select new AssetEntry(asset);
|
||||||
return assets.ToArray();
|
return assets.ToArray();
|
||||||
}).ConfigureAwait(true);
|
}).ConfigureAwait(true);
|
||||||
await AddEntries(entries).ConfigureAwait(true);
|
AddEntries(entries);
|
||||||
ViewModel.StatusText = $"总共加载了{ViewModel.Entries.Count}个素材";
|
ViewModel.StatusText = $"Manifest文件读取完毕,加载了{_loadedAssets.Count}个素材";
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task AddEntries(IEnumerable<DisplayAssetEntry> entries)
|
public async Task CancelLoadingXml()
|
||||||
{
|
{
|
||||||
var target = ViewModel.Entries;
|
if (_lastXmlTask is null)
|
||||||
|
|
||||||
int BinarySearch(DisplayAssetEntry entry, int? hint = null)
|
|
||||||
{
|
{
|
||||||
var begin = hint.GetValueOrDefault(0);
|
return;
|
||||||
var end = target.Count;
|
|
||||||
while (begin < end)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return end;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ordered = await Task.Run(() =>
|
var (tokenSource, task) = _lastXmlTask;
|
||||||
|
tokenSource.Cancel();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var array = entries.ToArray();
|
await task.ConfigureAwait(true);
|
||||||
Array.Sort(array);
|
}
|
||||||
return array;
|
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
|
||||||
|
{
|
||||||
|
ViewModel.IsLoadingXml = true;
|
||||||
|
await task.ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
ViewModel.StatusText = $"ModXml 读取完毕~ {statistics}";
|
||||||
|
if (_loadedAssets.Count != modXml.TotalAssets)
|
||||||
|
{
|
||||||
|
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);
|
}).ConfigureAwait(true);
|
||||||
ViewModel.StatusText = $"正在处理刚刚读取出来的 {ordered.Length} 个素材";
|
return;
|
||||||
var hint = 0;
|
|
||||||
foreach (var entry in ordered)
|
|
||||||
{
|
|
||||||
var index = hint = BinarySearch(entry, hint);
|
|
||||||
target.Insert(index, entry);
|
|
||||||
}
|
}
|
||||||
|
}, "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()
|
public static IEnumerable<InputEntry> FindManifests()
|
||||||
@ -223,5 +414,21 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
return VirtualFileSystem.GetFileName(path).StartsWith(what, StringComparison.OrdinalIgnoreCase);
|
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")]
|
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
||||||
internal class ValueConverterAggregate : List<IValueConverter>, IValueConverter
|
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));
|
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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
xmlns:local="clr-namespace:HashCalculator.GUI"
|
xmlns:local="clr-namespace:HashCalculator.GUI"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="CopyableBox" Height="350" Width="600"
|
Title="{Binding Title}" Height="350" Width="600"
|
||||||
Closing="ClosingHandler"
|
Closing="ClosingHandler"
|
||||||
|
Style="{StaticResource CommonStyle}"
|
||||||
>
|
>
|
||||||
<Window.DataContext>
|
<Window.DataContext>
|
||||||
<local:CopyableBoxViewModel />
|
<local:CopyableBoxViewModel />
|
||||||
|
@ -27,10 +27,11 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
private CopyableBoxViewModel ViewModel => (CopyableBoxViewModel)DataContext;
|
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();
|
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();
|
box.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +68,14 @@ namespace HashCalculator.GUI
|
|||||||
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
|
||||||
private Task? _task;
|
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;
|
private string _text = InitialMessage;
|
||||||
public string Text
|
public string Text
|
||||||
@ -121,8 +129,9 @@ namespace HashCalculator.GUI
|
|||||||
_cancellationTokenSource.Dispose();
|
_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);
|
_task = InitializationTask(action, dispatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,22 +9,32 @@
|
|||||||
<UseWPF>true</UseWPF>
|
<UseWPF>true</UseWPF>
|
||||||
<Platforms>AnyCPU;x86</Platforms>
|
<Platforms>AnyCPU;x86</Platforms>
|
||||||
<UserSecretsId>bf77c300-44f6-46ea-be94-f50d6993b55b</UserSecretsId>
|
<UserSecretsId>bf77c300-44f6-46ea-be94-f50d6993b55b</UserSecretsId>
|
||||||
|
<StartupObject>HashCalculator.GUI.Program</StartupObject>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
|
<PackageReference Include="Microsoft.Bcl.HashCode" Version="1.1.0" />
|
||||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
|
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.9.8">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Mvp.Xml" Version="2.3.0" />
|
<PackageReference Include="Mvp.Xml" Version="2.3.0" />
|
||||||
</ItemGroup>
|
<PackageReference Include="System.Threading.Tasks.Dataflow" Version="4.11.0" />
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\TechnologyAssembler.Core\TechnologyAssembler.Core.csproj" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System.Windows.Presentation" />
|
<Reference Include="System.Windows.Presentation" />
|
||||||
|
<Reference Include="TechnologyAssembler.Core">
|
||||||
|
<HintPath>TechnologyAssembler.Core.dll</HintPath>
|
||||||
|
</Reference>
|
||||||
</ItemGroup>
|
</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>
|
</Project>
|
@ -12,11 +12,6 @@
|
|||||||
d:DesignHeight="100" d:DesignWidth="800"
|
d:DesignHeight="100" d:DesignWidth="800"
|
||||||
>
|
>
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
<c:ValueConverterAggregate x:Key="NullToVisibilityConverter">
|
|
||||||
<c:NullToBooleanConverter />
|
|
||||||
<c:BooleanInvertConverter />
|
|
||||||
<BooleanToVisibilityConverter />
|
|
||||||
</c:ValueConverterAggregate>
|
|
||||||
<Style
|
<Style
|
||||||
x:Key="BlankListBoxContainerStyle"
|
x:Key="BlankListBoxContainerStyle"
|
||||||
TargetType="{x:Type ListBoxItem}"
|
TargetType="{x:Type ListBoxItem}"
|
||||||
|
164
MainWindow.xaml
164
MainWindow.xaml
@ -12,6 +12,7 @@
|
|||||||
Height="600"
|
Height="600"
|
||||||
Width="600"
|
Width="600"
|
||||||
Style="{StaticResource CommonStyle}"
|
Style="{StaticResource CommonStyle}"
|
||||||
|
Initialized="OnInitialized"
|
||||||
>
|
>
|
||||||
<Window.DataContext>
|
<Window.DataContext>
|
||||||
<l:ViewModel />
|
<l:ViewModel />
|
||||||
@ -43,7 +44,8 @@
|
|||||||
Content="浏览文件"
|
Content="浏览文件"
|
||||||
Command="{Binding MainInput.BrowseCommand}"
|
Command="{Binding MainInput.BrowseCommand}"
|
||||||
CommandParameter="{Binding ElementName=Self}"
|
CommandParameter="{Binding ElementName=Self}"
|
||||||
Visibility="{Binding MainInput.SelectedItem, Converter={StaticResource InvalidInputEntryToVisibilityConverter}}"
|
Visibility="{Binding MainInput.SelectedItem,
|
||||||
|
Converter={StaticResource InvalidInputEntryToVisibilityConverter}}"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
/>
|
/>
|
||||||
@ -51,7 +53,8 @@
|
|||||||
Content="确认"
|
Content="确认"
|
||||||
Command="{Binding MainInput.SelectCommand}"
|
Command="{Binding MainInput.SelectCommand}"
|
||||||
CommandParameter="{Binding}"
|
CommandParameter="{Binding}"
|
||||||
Visibility="{Binding MainInput.SelectedItem, Converter={StaticResource ValidInputEntryToVisibilityConverter}}"
|
Visibility="{Binding MainInput.SelectedItem,
|
||||||
|
Converter={StaticResource ValidInputEntryToVisibilityConverter}}"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
Foreground="#20FF30"
|
Foreground="#20FF30"
|
||||||
@ -62,7 +65,8 @@
|
|||||||
DockPanel.Dock="Top"
|
DockPanel.Dock="Top"
|
||||||
Height="25"
|
Height="25"
|
||||||
Margin="0,10,0,0"
|
Margin="0,10,0,0"
|
||||||
Visibility="{Binding BigEntryInput.AllManifests, Converter={StaticResource NotNullToVisibilityConverter}}"
|
Visibility="{Binding BigEntryInput.AllManifests,
|
||||||
|
Converter={StaticResource NotNullToVisibilityConverter}}"
|
||||||
>
|
>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition />
|
<ColumnDefinition />
|
||||||
@ -116,98 +120,162 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
DockPanel.Dock="Top"
|
DockPanel.Dock="Top"
|
||||||
Height="50"
|
Height="25"
|
||||||
Margin="0,10"
|
Margin="0,5,0,0"
|
||||||
>
|
>
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition />
|
|
||||||
<RowDefinition />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Text="{Binding StatusText}"
|
Text="{Binding StatusText}"
|
||||||
Grid.Row="0"
|
Margin="5,0,0,0"
|
||||||
Margin="10,0"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
/>
|
|
||||||
<TextBlock
|
|
||||||
Text="{Binding Entries.Count, StringFormat=目前加载了{0}个素材}"
|
|
||||||
Grid.Row="1"
|
|
||||||
Margin="10,5,0,5"
|
|
||||||
Width="160"
|
|
||||||
HorizontalAlignment="Left"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Visibility="{Binding Entries.Count, Converter={StaticResource NonZeroToVisibilityConverter}}"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<CheckBox
|
|
||||||
Content="只显示 GameObject"
|
|
||||||
IsChecked="True"
|
|
||||||
Grid.Row="1"
|
|
||||||
Margin="0,5,242,5"
|
|
||||||
Width="155"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
VerticalAlignment="Center"
|
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
|
<Button
|
||||||
Content="加载 csf/mod.str"
|
Content="{Binding LoadCsfText}"
|
||||||
Grid.Row="1"
|
Command="{Binding LoadCsf}"
|
||||||
Width="140"
|
CommandParameter="{Binding ElementName=Self}"
|
||||||
Margin="0,0,97,0"
|
Width="92"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Left"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
Content="取消加载"
|
Content="取消加载"
|
||||||
Grid.Row="1"
|
Command="{Binding CancelXml}"
|
||||||
Width="92"
|
Width="92"
|
||||||
HorizontalAlignment="Right"
|
HorizontalAlignment="Right"
|
||||||
|
Visibility="{Binding IsLoadingXml,
|
||||||
|
Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<CheckBox
|
||||||
|
DockPanel.Dock="Right"
|
||||||
|
Content="只显示 GameObject"
|
||||||
|
IsChecked="{Binding GameObjectOnly}"
|
||||||
|
Command="{Binding FilterDisplayEntries}"
|
||||||
|
Width="155"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
/>
|
||||||
|
<TextBlock
|
||||||
|
Text="{Binding DisplayEntries.Count, StringFormat=列表里显示了{0}个素材}"
|
||||||
|
Margin="5,0,0,0"
|
||||||
|
Width="160"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Visibility="{Binding DisplayEntries.Count,
|
||||||
|
Converter={StaticResource NonZeroToVisibilityConverter}}"
|
||||||
|
/>
|
||||||
|
</DockPanel>
|
||||||
<Grid
|
<Grid
|
||||||
DockPanel.Dock="Top"
|
DockPanel.Dock="Top"
|
||||||
Height="25"
|
Height="25"
|
||||||
>
|
>
|
||||||
<TextBox></TextBox>
|
<TextBox
|
||||||
|
Text="{Binding Filter, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Text="过滤Asset ID(可选)"
|
Text="可以按照素材名称或 Instance ID 来查找素材,加载 CSF 之后还可以按照单位名称来查找"
|
||||||
|
Foreground="#B0B0B0"
|
||||||
Padding="5,0"
|
Padding="5,0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
|
Visibility="{Binding Filter,
|
||||||
|
Converter={StaticResource NullToVisibilityConverter}}"
|
||||||
|
IsHitTestVisible="False"
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
<FrameworkElement x:Name="ReferenceProvider" Visibility="Collapsed"/>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
ItemsSource="{Binding Entries}"
|
x:Name="DataGrid"
|
||||||
|
ItemsSource="{Binding DisplayEntries}"
|
||||||
|
IsReadOnly="True"
|
||||||
|
AutoGenerateColumns="False"
|
||||||
ScrollViewer.CanContentScroll="True"
|
ScrollViewer.CanContentScroll="True"
|
||||||
VerticalScrollBarVisibility="Auto"
|
VerticalScrollBarVisibility="Auto"
|
||||||
EnableRowVirtualization="True"
|
EnableRowVirtualization="True"
|
||||||
VirtualizingPanel.VirtualizationMode="Recycling"
|
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>
|
</DockPanel>
|
||||||
<StackPanel
|
<ScrollViewer
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Margin="0,10,0,0"
|
Margin="0,10,0,0"
|
||||||
ScrollViewer.CanContentScroll="True"
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
VerticalScrollBarVisibility="Auto"
|
||||||
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
ScrollChanged="OnButtomScrollViewerScrollChanged"
|
||||||
>
|
>
|
||||||
|
<StackPanel>
|
||||||
<TextBlock TextWrapping="Wrap">
|
<TextBlock TextWrapping="Wrap">
|
||||||
本工具基于 <l:ShellLink NavigateUri="https://github.com/Qibbi">Qibbi</l:ShellLink> 提供的 TechnologyAssembler 制作<LineBreak />
|
本工具基于 <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">Bradley Grainger</l:ShellLink> 的
|
||||||
<l:ShellLink NavigateUri="https://github.com/bgrainger/IndexRange">IndexRange</l:ShellLink>
|
<l:ShellLink NavigateUri="https://github.com/bgrainger/IndexRange">IndexRange</l:ShellLink>
|
||||||
从而能在 .NET Standard 2.0 上使用 C# 8.0 的末尾索引操作符<LineBreak />
|
从而能在 .NET Standard 2.0 上使用 C# 8.0 的末尾索引操作符<LineBreak />
|
||||||
|
此外还使用了 <l:ShellLink NavigateUri="http://mvpxml.codeplex.com/">Mvp.Xml</l:ShellLink> 来解析 XML 文件<LineBreak />
|
||||||
<LineBreak />
|
<LineBreak />
|
||||||
假如你对本工具有任何疑问或者建议的话,可以来到<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
|
||||||
|
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
|
<StackPanel
|
||||||
|
FlowDirection="RightToLeft"
|
||||||
|
Orientation="Horizontal"
|
||||||
Height="25"
|
Height="25"
|
||||||
HorizontalAlignment="Right"
|
Visibility="{Binding TraceText,
|
||||||
|
Converter={StaticResource NotNullToVisibilityConverter}}"
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
Content="清除输出"
|
Content="清除输出"
|
||||||
|
Command="{Binding ClearTraceText}"
|
||||||
Width="92"
|
Width="92"
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
/>
|
/>
|
||||||
|
<TextBlock
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Margin="10,0"
|
||||||
|
Visibility="{Binding SuggestClearFilter, Converter={StaticResource BooleanToVisibilityConverter}}"
|
||||||
|
>
|
||||||
|
假如觉得有点卡的话,可以试试点击右边的这个按钮
|
||||||
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
@ -1,17 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Data;
|
using System.Windows.Threading;
|
||||||
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;
|
|
||||||
|
|
||||||
namespace HashCalculator.GUI
|
namespace HashCalculator.GUI
|
||||||
{
|
{
|
||||||
@ -26,11 +16,15 @@ namespace HashCalculator.GUI
|
|||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
Translator.ProviderChanged += (s, e) => Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
ViewModel.NotifyCsfChange();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnButtomScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e)
|
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
|
// User scroll event : set or unset auto-scroll mode
|
||||||
if (e.ExtentHeightChange == 0)
|
if (e.ExtentHeightChange == 0)
|
||||||
{ // Content unchanged : user scroll event
|
{ // Content unchanged : user scroll event
|
||||||
@ -53,5 +47,10 @@ namespace HashCalculator.GUI
|
|||||||
scrollViewer.ScrollToVerticalOffset(scrollViewer.ExtentHeight);
|
scrollViewer.ScrollToVerticalOffset(scrollViewer.ExtentHeight);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnInitialized(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
ViewModel.StartTracerListener(s => Dispatcher.BeginInvoke(s));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
68
ModXml.cs
68
ModXml.cs
@ -4,7 +4,9 @@ using System.Collections.Concurrent;
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Threading.Tasks.Dataflow;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
@ -28,17 +30,21 @@ 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 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<string, byte> _processed = new ConcurrentDictionary<string, byte>();
|
||||||
private readonly ConcurrentDictionary<AssetEntry, byte> _assets = new ConcurrentDictionary<AssetEntry, byte>();
|
private readonly ConcurrentDictionary<AssetEntry, string> _assets = new ConcurrentDictionary<AssetEntry, string>();
|
||||||
private readonly ConcurrentQueue<string> _errors = new ConcurrentQueue<string>();
|
|
||||||
private readonly ModUriResolver _uriResolver;
|
private readonly ModUriResolver _uriResolver;
|
||||||
private readonly string _xmlFullPath;
|
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));
|
BaseDirectory = new DirectoryInfo(FindBaseDirectory(xmlPath));
|
||||||
var allMods = baseDirectory.Parent;
|
var allMods = BaseDirectory.Parent;
|
||||||
if (!allMods.Name.Equals("Mods", StringComparison.OrdinalIgnoreCase))
|
if (!allMods.Name.Equals("Mods", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
throw new DirectoryNotFoundException();
|
throw new DirectoryNotFoundException();
|
||||||
@ -55,7 +61,7 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
sdkRootPath,
|
sdkRootPath,
|
||||||
allModsParent.FullName,
|
allModsParent.FullName,
|
||||||
Path.Combine(baseDirectory.FullName, name),
|
Path.Combine(BaseDirectory.FullName, name),
|
||||||
Path.Combine(sdkRootPath, "Mods"),
|
Path.Combine(sdkRootPath, "Mods"),
|
||||||
allMods.FullName,
|
allMods.FullName,
|
||||||
Path.Combine(sdkRootPath, name.Equals("Data", StringComparison.OrdinalIgnoreCase) ? "SageXml" : name)
|
Path.Combine(sdkRootPath, name.Equals("Data", StringComparison.OrdinalIgnoreCase) ? "SageXml" : name)
|
||||||
@ -69,31 +75,54 @@ namespace HashCalculator.GUI
|
|||||||
});
|
});
|
||||||
|
|
||||||
_xmlFullPath = Path.GetFullPath(xmlPath);
|
_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();
|
throw new InvalidOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.Run(() =>
|
var task = Task.Run(() =>
|
||||||
{
|
{
|
||||||
var includes = ProcessDocumentInternal(_xmlFullPath).AsParallel();
|
try
|
||||||
|
{
|
||||||
|
var includes = ProcessDocumentInternal(_xmlFullPath);
|
||||||
while (includes.Any())
|
while (includes.Any())
|
||||||
{
|
{
|
||||||
includes = from include in includes
|
var result = from include in includes.AsParallel()
|
||||||
from newInclude in ProcessDocumentInternal(include)
|
from newInclude in ProcessDocumentInternal(include)
|
||||||
select newInclude;
|
select newInclude;
|
||||||
|
includes = result.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
if (!_token.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_entries.Complete();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
while(await _entries.OutputAvailableAsync().ConfigureAwait(false))
|
||||||
|
{
|
||||||
|
yield return _entries.Receive();
|
||||||
}
|
}
|
||||||
|
|
||||||
return _assets.Keys;
|
await task.ConfigureAwait(false);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private string[] ProcessDocumentInternal(string fullPath)
|
private string[] ProcessDocumentInternal(string fullPath)
|
||||||
{
|
{
|
||||||
|
_token.ThrowIfCancellationRequested();
|
||||||
if (!_processed.TryAdd(fullPath.ToUpperInvariant(), default))
|
if (!_processed.TryAdd(fullPath.ToUpperInvariant(), default))
|
||||||
{
|
{
|
||||||
return Array.Empty<string>();
|
return Array.Empty<string>();
|
||||||
@ -110,9 +139,14 @@ namespace HashCalculator.GUI
|
|||||||
select new AssetEntry(element);
|
select new AssetEntry(element);
|
||||||
foreach (var item in items)
|
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)
|
catch (FileNotFoundException error)
|
||||||
{
|
{
|
||||||
_errors.Enqueue(error.Message);
|
TracerListener.WriteLine($"[ModXml]: {error}");
|
||||||
return null;
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
254
ViewModel.cs
254
ViewModel.cs
@ -1,15 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.ObjectModel;
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Collections.Specialized;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text;
|
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
|
||||||
using Microsoft.Win32;
|
using Microsoft.Win32;
|
||||||
using TechnologyAssembler.Core.IO;
|
using TechnologyAssembler.Core.IO;
|
||||||
|
|
||||||
@ -48,16 +46,38 @@ namespace HashCalculator.GUI
|
|||||||
internal class ViewModel : NotifyPropertyChanged
|
internal class ViewModel : NotifyPropertyChanged
|
||||||
{
|
{
|
||||||
private static readonly Random _random = new Random();
|
private static readonly Random _random = new Random();
|
||||||
|
public Action<Action<Action>> OnStartTraceListener { get; }
|
||||||
|
|
||||||
public MainInputViewModel MainInput { get; }
|
public MainInputViewModel MainInput { get; }
|
||||||
public BigInputViewModel BigEntryInput { get; }
|
public BigInputViewModel BigEntryInput { get; }
|
||||||
|
|
||||||
private ObservableCollection<DisplayAssetEntry> _entries = new ObservableCollection<DisplayAssetEntry>();
|
private string? _filter;
|
||||||
public ObservableCollection<DisplayAssetEntry> Entries
|
public string? Filter
|
||||||
{
|
{
|
||||||
get => _entries;
|
get => _filter;
|
||||||
set => SetField(ref _entries, value);
|
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("不知道该显示些什么呢……");
|
private string _statusText = SuggestionString("不知道该显示些什么呢……");
|
||||||
public string StatusText
|
public string StatusText
|
||||||
@ -66,18 +86,92 @@ namespace HashCalculator.GUI
|
|||||||
set => SetField(ref _statusText, value);
|
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;
|
private string _traceText = string.Empty;
|
||||||
public string TraceText
|
public string TraceText
|
||||||
{
|
{
|
||||||
get => _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()
|
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 () =>
|
||||||
|
{
|
||||||
|
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)
|
public static string SuggestionString(string original)
|
||||||
@ -92,8 +186,74 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
return "本来以为两小时就能写完这个小工具,没想到写了两个星期,开始怀疑自己的智商orz";
|
return "本来以为两小时就能写完这个小工具,没想到写了两个星期,开始怀疑自己的智商orz";
|
||||||
}
|
}
|
||||||
|
if (generated.IndexOf('7') < 5)
|
||||||
|
{
|
||||||
|
return "小提示:在下方的素材列表里,选择一行或多行之后,直接按 Ctrl+C 就可以复制内容~";
|
||||||
|
}
|
||||||
|
if (generated.IndexOf('2') == 3)
|
||||||
|
{
|
||||||
|
return "温馨提示:请多留意一下自己的重工,不要让它卖自己";
|
||||||
|
}
|
||||||
return original;
|
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
|
internal class InputBarViewModel : NotifyPropertyChanged
|
||||||
@ -256,11 +416,14 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
base.Text = value;
|
base.Text = value;
|
||||||
if (value != SelectedItem?.ToString())
|
if (value != SelectedItem?.ToString())
|
||||||
|
{
|
||||||
|
if(AllManifests != null)
|
||||||
{
|
{
|
||||||
UpdateList(value);
|
UpdateList(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private InputEntry? _lastProcessedManifest;
|
private InputEntry? _lastProcessedManifest;
|
||||||
public InputEntry? LastProcessedManifest
|
public InputEntry? LastProcessedManifest
|
||||||
@ -277,6 +440,7 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
SetField(ref _allManifests, value);
|
SetField(ref _allManifests, value);
|
||||||
Items = value;
|
Items = value;
|
||||||
|
SelectedItem = null;
|
||||||
LastProcessedManifest = null;
|
LastProcessedManifest = null;
|
||||||
Text = null;
|
Text = null;
|
||||||
}
|
}
|
||||||
@ -300,7 +464,7 @@ namespace HashCalculator.GUI
|
|||||||
}
|
}
|
||||||
catch (Exception exception)
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
CopyableBox.ShowDialog(_ =>
|
CopyableBox.ShowDialog("SAGE FastHash 计算器的错误" , _ =>
|
||||||
{
|
{
|
||||||
return Task.FromResult($"在加载 manifest 时发生错误:{exception}");
|
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