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