ui, and controller, ecc, ecc
This commit is contained in:
parent
1d706b1adc
commit
97067a68a5
162
App.xaml
162
App.xaml
@ -1,9 +1,167 @@
|
|||||||
<Application x:Class="HashCalculator.GUI.App"
|
<Application x:Class="HashCalculator.GUI.App"
|
||||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:local="clr-namespace:HashCalculator.GUI"
|
xmlns:l="clr-namespace:HashCalculator.GUI"
|
||||||
|
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
|
||||||
StartupUri="MainWindow.xaml">
|
StartupUri="MainWindow.xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
|
<c:ValueConverterAggregate x:Key="ValidInputEntryToVisibilityConverter">
|
||||||
|
<c:ValidInputEntryTypeToBooleanConverter />
|
||||||
|
<BooleanToVisibilityConverter />
|
||||||
|
</c:ValueConverterAggregate>
|
||||||
|
<c:ValueConverterAggregate x:Key="InvalidInputEntryToVisibilityConverter">
|
||||||
|
<c:ValidInputEntryTypeToBooleanConverter />
|
||||||
|
<c:BooleanInvertConverter />
|
||||||
|
<BooleanToVisibilityConverter />
|
||||||
|
</c:ValueConverterAggregate>
|
||||||
|
<c:ValueConverterAggregate x:Key="NullToVisibilityConverter">
|
||||||
|
<c:NullToBooleanConverter />
|
||||||
|
<c:BooleanInvertConverter />
|
||||||
|
<BooleanToVisibilityConverter />
|
||||||
|
</c:ValueConverterAggregate>
|
||||||
|
<c:ValueConverterAggregate x:Key="NotNullToVisibilityConverter">
|
||||||
|
<c:NullToBooleanConverter />
|
||||||
|
<BooleanToVisibilityConverter />
|
||||||
|
</c:ValueConverterAggregate>
|
||||||
|
<c:ValueConverterAggregate x:Key="NonZeroToVisibilityConverter">
|
||||||
|
<c:IsZeroToBooleanConverter />
|
||||||
|
<BooleanToVisibilityConverter />
|
||||||
|
</c:ValueConverterAggregate>
|
||||||
|
<c:MultiValueEqualityConverter x:Key="MultiValueEqualityConverter" />
|
||||||
|
<Style x:Key="CommonStyle" TargetType="Control">
|
||||||
|
<Setter
|
||||||
|
Property="Foreground"
|
||||||
|
Value="LightGray"
|
||||||
|
/>
|
||||||
|
<Setter
|
||||||
|
Property="Background"
|
||||||
|
Value="#202020"
|
||||||
|
/>
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="l:MainWindow"
|
||||||
|
/>
|
||||||
|
<Style
|
||||||
|
TargetType="l:ShellLink"
|
||||||
|
>
|
||||||
|
<Setter
|
||||||
|
Property="Foreground"
|
||||||
|
Value="#30B0FF"
|
||||||
|
/>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Foreground" Value="Red" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type TextBox}"
|
||||||
|
>
|
||||||
|
<Setter Property="Background" Value="#00000000"/>
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type ComboBox}"
|
||||||
|
>
|
||||||
|
<Setter Property="Background" Value="#00000000"/>
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type Button}"
|
||||||
|
x:Key="ButtonStyle"
|
||||||
|
>
|
||||||
|
<Setter Property="Background" Value="#20808080"/>
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type Button}">
|
||||||
|
<Border
|
||||||
|
x:Name="ButtonBorderTemplate"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
SnapsToDevicePixels="True"
|
||||||
|
>
|
||||||
|
<ContentPresenter
|
||||||
|
x:Name="ButtonContentPresenterTemplate"
|
||||||
|
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||||
|
Content="{TemplateBinding Content}"
|
||||||
|
ContentStringFormat="{TemplateBinding ContentStringFormat}"
|
||||||
|
Focusable="False"
|
||||||
|
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||||
|
Margin="{TemplateBinding Padding}"
|
||||||
|
RecognizesAccessKey="True"
|
||||||
|
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
|
||||||
|
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||||
|
/>
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="Button.IsDefaulted" Value="True">
|
||||||
|
<Setter Property="BorderBrush" TargetName="ButtonBorderTemplate" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" TargetName="ButtonBorderTemplate" Value="#80BEE6FD"/>
|
||||||
|
<Setter Property="BorderBrush" TargetName="ButtonBorderTemplate" Value="#FF3C7FB1"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsPressed" Value="True">
|
||||||
|
<Setter Property="Background" TargetName="ButtonBorderTemplate" Value="#80C4E5F6"/>
|
||||||
|
<Setter Property="BorderBrush" TargetName="ButtonBorderTemplate" Value="#FF2C628B"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="ToggleButton.IsChecked" Value="True">
|
||||||
|
<Setter Property="Background" TargetName="ButtonBorderTemplate" Value="#FFBCDDEE"/>
|
||||||
|
<Setter Property="BorderBrush" TargetName="ButtonBorderTemplate" Value="#FF245A83"/>
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Background" TargetName="ButtonBorderTemplate" Value="#00000000"/>
|
||||||
|
<Setter Property="BorderBrush" TargetName="ButtonBorderTemplate" Value="#FFADB2B5"/>
|
||||||
|
<Setter Property="Foreground" Value="#808080"/>
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource ButtonStyle}"
|
||||||
|
TargetType="Button"
|
||||||
|
/>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="CheckBox"
|
||||||
|
/>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type DataGrid}"
|
||||||
|
/>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type DataGridColumnHeader}"
|
||||||
|
>
|
||||||
|
<Setter Property="Padding" Value="10,5" />
|
||||||
|
<Setter Property="SnapsToDevicePixels" Value="True" />
|
||||||
|
<Setter Property="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="Background" Value="#343434"/>
|
||||||
|
<Setter Property="Foreground" Value="White"/>
|
||||||
|
<Setter Property="BorderThickness" Value="0, 0, 1, 0"/>
|
||||||
|
<Setter Property="BorderBrush" Value="Gray" />
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type DataGridRowHeader}"
|
||||||
|
/>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type DataGridRow}"
|
||||||
|
/>
|
||||||
|
<Style TargetType="{x:Type Button}" x:Key="{ComponentResourceKey ResourceId=DataGridSelectAllButtonStyle, TypeInTargetAssembly={x:Type DataGrid}}">
|
||||||
|
<Setter Property="Background" Value="Black" />
|
||||||
|
</Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource CommonStyle}"
|
||||||
|
TargetType="{x:Type ListBox}"
|
||||||
|
>
|
||||||
|
<Setter Property="Background" Value="#181818"/>
|
||||||
|
</Style>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
|
@ -5,6 +5,7 @@ using System.Data;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using TechnologyAssembler;
|
||||||
|
|
||||||
namespace HashCalculator.GUI
|
namespace HashCalculator.GUI
|
||||||
{
|
{
|
||||||
@ -13,5 +14,10 @@ namespace HashCalculator.GUI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
|
public App()
|
||||||
|
{
|
||||||
|
var core = new TechnologyAssemblerCoreModule();
|
||||||
|
core.Initialize();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using TechnologyAssembler.Core.Assets;
|
||||||
|
|
||||||
namespace HashCalculator.GUI
|
namespace HashCalculator.GUI
|
||||||
{
|
{
|
||||||
@ -10,10 +12,10 @@ namespace HashCalculator.GUI
|
|||||||
public string Type { get; }
|
public string Type { get; }
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public IEnumerable<string> DisplayLabels { get; }
|
public IEnumerable<string> DisplayLabels { get; }
|
||||||
public uint Hash => SageHash.CalculateLowercaseHash(Name);
|
public uint InstanceId => SageHash.CalculateLowercaseHash(Name);
|
||||||
|
|
||||||
public string IdString => $"{Type}:{Name}";
|
public string NameString => $"{Type}:{Name}";
|
||||||
public string HashString => $"{Hash:0X} ({Hash})";
|
public string InstanceIdString => $"{InstanceId:X8} ({InstanceId})";
|
||||||
|
|
||||||
public AssetEntry(XElement element)
|
public AssetEntry(XElement element)
|
||||||
{
|
{
|
||||||
@ -38,6 +40,21 @@ namespace HashCalculator.GUI
|
|||||||
DisplayLabels = labels.Concat(transformLabels).ToArray();
|
DisplayLabels = labels.Concat(transformLabels).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AssetEntry(Asset asset)
|
||||||
|
{
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
throw new InvalidDataException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool Equals(AssetEntry entry)
|
public bool Equals(AssetEntry entry)
|
||||||
{
|
{
|
||||||
return this == entry;
|
return this == entry;
|
||||||
@ -73,7 +90,7 @@ namespace HashCalculator.GUI
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.Type == b.Type && a.Hash == b.Hash;
|
return a.Type == b.Type && a.InstanceId == b.InstanceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool operator !=(AssetEntry a, AssetEntry b)
|
public static bool operator !=(AssetEntry a, AssetEntry b)
|
||||||
@ -84,7 +101,34 @@ namespace HashCalculator.GUI
|
|||||||
// override object.GetHashCode
|
// override object.GetHashCode
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
{
|
{
|
||||||
return HashCode.Combine(Type, Hash);
|
return HashCode.Combine(Type, InstanceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DisplayAssetEntry : IComparable<DisplayAssetEntry>
|
||||||
|
{
|
||||||
|
public string Name { get; }
|
||||||
|
public string InstanceId { get; }
|
||||||
|
|
||||||
|
public DisplayAssetEntry(AssetEntry entry)
|
||||||
|
{
|
||||||
|
Name = entry.NameString;
|
||||||
|
InstanceId = entry.InstanceIdString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(DisplayAssetEntry other)
|
||||||
|
{
|
||||||
|
return string.CompareOrdinal(Name, other.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class LocalizedDisplayAssetEntry : DisplayAssetEntry
|
||||||
|
{
|
||||||
|
public string LocalizedNames { get; }
|
||||||
|
|
||||||
|
public LocalizedDisplayAssetEntry(AssetEntry entry) : base(entry)
|
||||||
|
{
|
||||||
|
LocalizedNames = entry.DisplayLabels.Aggregate((x, y) => $"{x} {y}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
227
Controller.cs
Normal file
227
Controller.cs
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using TechnologyAssembler.Core.IO;
|
||||||
|
using TechnologyAssembler.Core.Assets;
|
||||||
|
|
||||||
|
namespace HashCalculator.GUI
|
||||||
|
{
|
||||||
|
[SuppressMessage("Design", "CA1001:具有可释放字段的类型应该是可释放的", Justification = "<挂起>")]
|
||||||
|
internal class Controller
|
||||||
|
{
|
||||||
|
public const string SelectedFileSystemRootPath = "/selected";
|
||||||
|
public const string ManifestExtension = ".manifest";
|
||||||
|
|
||||||
|
private ViewModel ViewModel { get; }
|
||||||
|
private BigFileSystemProvider? _currentBig = null;
|
||||||
|
|
||||||
|
public Controller(ViewModel viewModel)
|
||||||
|
{
|
||||||
|
ViewModel = viewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
[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
|
||||||
|
{
|
||||||
|
switch (selected.Type)
|
||||||
|
{
|
||||||
|
case InputEntryType.BinaryFile:
|
||||||
|
ViewModel.StatusText = "请留意一下弹出的窗口(";
|
||||||
|
CopyableBox.ShowDialog(token => CalculateBinaryHash(selected.Value, token));
|
||||||
|
ViewModel.StatusText = ViewModel.SuggestionString(string.Empty);
|
||||||
|
return;
|
||||||
|
case InputEntryType.BigFile:
|
||||||
|
await LoadBig(selected.Value).ConfigureAwait(true);
|
||||||
|
return;
|
||||||
|
case InputEntryType.ManifestFile:
|
||||||
|
await LoadManifestFromFile(selected.Value).ConfigureAwait(true);
|
||||||
|
return;
|
||||||
|
case InputEntryType.XmlFile:
|
||||||
|
throw new NotImplementedException();
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception error)
|
||||||
|
{
|
||||||
|
ViewModel.StatusText = "失败…";
|
||||||
|
CopyableBox.ShowDialog(_ =>
|
||||||
|
{
|
||||||
|
return Task.FromResult($"在尝试加载 {selected.Type} `{selected.Value}` 时发生错误:\r\n{error}");
|
||||||
|
});
|
||||||
|
ViewModel.StatusText = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
||||||
|
private static Task<string> CalculateBinaryHash(string filePath, CancellationToken cancel)
|
||||||
|
{
|
||||||
|
return Task.Run(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var file = File.OpenRead(filePath);
|
||||||
|
var array = new byte[file.Length];
|
||||||
|
cancel.ThrowIfCancellationRequested();
|
||||||
|
var totalRead = 0;
|
||||||
|
const int ReadSize = 32 * 1024 * 1024;
|
||||||
|
while (totalRead < array.Length)
|
||||||
|
{
|
||||||
|
totalRead += file.Read(array, totalRead, Math.Min(array.Length - totalRead, ReadSize));
|
||||||
|
cancel.ThrowIfCancellationRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
var hashes = new Func<byte[], uint>[]
|
||||||
|
{
|
||||||
|
SageHash.CalculateBinaryHash,
|
||||||
|
SageHash.CalculateLauncherBinaryHash
|
||||||
|
}.AsParallel().Select(fn => fn(array)).ToArray();
|
||||||
|
|
||||||
|
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]})";
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
|
{
|
||||||
|
return exception.ToString();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadBig(string path)
|
||||||
|
{
|
||||||
|
ViewModel.StatusText = "正在尝试加载 big 文件…";
|
||||||
|
var bigViewModel = ViewModel.BigEntryInput;
|
||||||
|
bigViewModel.Text = string.Empty;
|
||||||
|
bigViewModel.AllManifests = await Task.Run(() =>
|
||||||
|
{
|
||||||
|
using var big = new BigFile(path);
|
||||||
|
_currentBig = new BigFileSystemProvider(SelectedFileSystemRootPath, big, null);
|
||||||
|
return FindManifests();
|
||||||
|
}).ConfigureAwait(true);
|
||||||
|
|
||||||
|
var first = bigViewModel.AllManifests.FirstOrDefault();
|
||||||
|
if (first != null)
|
||||||
|
{
|
||||||
|
var name = VirtualFileSystem.GetFileNameWithoutExtension(first.Value);
|
||||||
|
if (name.StartsWith("mod", StringComparison.OrdinalIgnoreCase)
|
||||||
|
|| name.StartsWith("mapmetadata", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
bigViewModel.SelectedItem = first;
|
||||||
|
await bigViewModel.SelectCommand.ExecuteTask(ViewModel).ConfigureAwait(true);
|
||||||
|
ViewModel.StatusText = "big 已加载完毕,并自动选了一个看起来比较合适的 manifest 文件(";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ViewModel.StatusText = "big 文件已加载完毕,请选择 big 文件里的 manifest 文件";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadManifestFromFile(string path)
|
||||||
|
{
|
||||||
|
using var provider = new FileSystemProvider(SelectedFileSystemRootPath, null);
|
||||||
|
await ProcessManifest(path).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessManifest(string path)
|
||||||
|
{
|
||||||
|
ViewModel.Entries.Clear();
|
||||||
|
ViewModel.TraceText = string.Empty;
|
||||||
|
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);
|
||||||
|
var assets = from asset in manifest.Assets.Values
|
||||||
|
select new DisplayAssetEntry(new AssetEntry(asset));
|
||||||
|
return assets.ToArray();
|
||||||
|
}).ConfigureAwait(true);
|
||||||
|
await AddEntries(entries).ConfigureAwait(true);
|
||||||
|
ViewModel.StatusText = $"总共加载了{ViewModel.Entries.Count}个素材";
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task AddEntries(IEnumerable<DisplayAssetEntry> entries)
|
||||||
|
{
|
||||||
|
var target = ViewModel.Entries;
|
||||||
|
|
||||||
|
int BinarySearch(DisplayAssetEntry entry, int? hint = null)
|
||||||
|
{
|
||||||
|
var begin = hint.GetValueOrDefault(0);
|
||||||
|
var end = target.Count;
|
||||||
|
while (begin < end)
|
||||||
|
{
|
||||||
|
var middle = begin + (end - begin) / 2;
|
||||||
|
var result = entry.CompareTo(target[middle]);
|
||||||
|
if (result == 0)
|
||||||
|
{
|
||||||
|
return middle;
|
||||||
|
}
|
||||||
|
else if (result < 0)
|
||||||
|
{
|
||||||
|
end = middle;
|
||||||
|
}
|
||||||
|
else if (result > 0)
|
||||||
|
{
|
||||||
|
begin = middle + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return end;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ordered = await Task.Run(() =>
|
||||||
|
{
|
||||||
|
var 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<InputEntry> FindManifests()
|
||||||
|
{
|
||||||
|
var manifests = VirtualFileSystem.ListFiles(SelectedFileSystemRootPath, "*.manifest", VirtualSearchOptionType.AllDirectories);
|
||||||
|
var modManifests = from manifest in manifests
|
||||||
|
where FileNameStartsWith(manifest, "mod")
|
||||||
|
select manifest;
|
||||||
|
var globalDataManifests = from manifest in manifests
|
||||||
|
where FileNameStartsWith(manifest, "mapmetadata")
|
||||||
|
select manifest;
|
||||||
|
var firstManifests = modManifests.Concat(globalDataManifests);
|
||||||
|
var otherManifests = from manifest in manifests
|
||||||
|
where !firstManifests.Contains(manifest)
|
||||||
|
select manifest;
|
||||||
|
|
||||||
|
return from manifest in firstManifests.Concat(otherManifests)
|
||||||
|
select new InputEntry(InputEntryType.ManifestFile, manifest, manifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool FileNameStartsWith(string path, string what)
|
||||||
|
{
|
||||||
|
return VirtualFileSystem.GetFileName(path).StartsWith(what, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
|
||||||
namespace HashCalculator.GUI.Converters
|
namespace HashCalculator.GUI.Converters
|
||||||
{
|
{
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
||||||
[ValueConversion(typeof(bool), typeof(bool))]
|
[ValueConversion(typeof(bool), typeof(bool))]
|
||||||
internal class BooleanInvertConverter : IValueConverter
|
internal class BooleanInvertConverter : IValueConverter
|
||||||
{
|
{
|
||||||
|
20
Converters/IsZeroToBooleanConverter.cs
Normal file
20
Converters/IsZeroToBooleanConverter.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace HashCalculator.GUI.Converters
|
||||||
|
{
|
||||||
|
[ValueConversion(typeof(int), typeof(bool))]
|
||||||
|
public class IsZeroToBooleanConverter : IValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return (int)value == 0 ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
22
Converters/MultiValueEqualityConverter.cs
Normal file
22
Converters/MultiValueEqualityConverter.cs
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Data;
|
||||||
|
|
||||||
|
namespace HashCalculator.GUI.Converters
|
||||||
|
{
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
||||||
|
internal class MultiValueEqualityConverter : IMultiValueConverter
|
||||||
|
{
|
||||||
|
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
return values.All(x => Equals(x, values.First()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
|
||||||
|
{
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
|
||||||
namespace HashCalculator.GUI.Converters
|
namespace HashCalculator.GUI.Converters
|
||||||
{
|
{
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
||||||
[ValueConversion(typeof(InputEntry), typeof(bool))]
|
[ValueConversion(typeof(InputEntry), typeof(bool))]
|
||||||
internal class ValidInputEntryTypeToBooleanConverter : IValueConverter
|
internal class ValidInputEntryTypeToBooleanConverter : IValueConverter
|
||||||
{
|
{
|
||||||
|
@ -1,11 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
|
|
||||||
namespace HashCalculator.GUI.Converters
|
namespace HashCalculator.GUI.Converters
|
||||||
{
|
{
|
||||||
|
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
||||||
internal class ValueConverterAggregate : List<IValueConverter>, IValueConverter
|
internal class ValueConverterAggregate : List<IValueConverter>, IValueConverter
|
||||||
{
|
{
|
||||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||||
|
@ -128,32 +128,22 @@ namespace HashCalculator.GUI
|
|||||||
|
|
||||||
private async Task InitializationTask(Func<CancellationToken, Task<string>> action, Dispatcher dispatcher)
|
private async Task InitializationTask(Func<CancellationToken, Task<string>> action, Dispatcher dispatcher)
|
||||||
{
|
{
|
||||||
var utcBegin = DateTimeOffset.UtcNow;
|
|
||||||
var timer = new DispatcherTimer(TimeSpan.FromMilliseconds(200), DispatcherPriority.Normal, (s, e) =>
|
|
||||||
{
|
|
||||||
var timeElapsed = DateTimeOffset.UtcNow - utcBegin;
|
|
||||||
if (((DispatcherTimer)s).IsEnabled && timeElapsed.TotalSeconds > 1)
|
|
||||||
{
|
|
||||||
Text = Text = $"{InitialMessage}\r\n目前耗时{timeElapsed},稍微再等一下吧233";
|
|
||||||
}
|
|
||||||
}, dispatcher);
|
|
||||||
timer.Start();
|
|
||||||
|
|
||||||
var token = _cancellationTokenSource.Token;
|
var token = _cancellationTokenSource.Token;
|
||||||
async Task<string> Action()
|
async Task<string> Action()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
using var timer = new DisposableDispatcherTimer(timer =>
|
||||||
|
{
|
||||||
|
Text = $"{InitialMessage}\r\n目前耗时{timer.TimeSinceCreation},稍微再等一下吧233";
|
||||||
|
}, dispatcher, TimeSpan.FromMilliseconds(200), TimeSpan.FromSeconds(1));
|
||||||
|
|
||||||
return await action(token).ConfigureAwait(false);
|
return await action(token).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
return "操作已被取消";
|
return "操作已被取消";
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
timer.Stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Text = await dispatcher.Invoke(Action).ConfigureAwait(true);
|
Text = await dispatcher.Invoke(Action).ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
|
38
DisposableDispatcherTimer.cs
Normal file
38
DisposableDispatcherTimer.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Threading;
|
||||||
|
|
||||||
|
namespace HashCalculator.GUI
|
||||||
|
{
|
||||||
|
internal class DisposableDispatcherTimer : IDisposable
|
||||||
|
{
|
||||||
|
public DispatcherTimer Timer { get; }
|
||||||
|
public DateTimeOffset CreationTime { get; } = DateTimeOffset.UtcNow;
|
||||||
|
public TimeSpan TimeSinceCreation => DateTimeOffset.UtcNow - CreationTime;
|
||||||
|
|
||||||
|
public DisposableDispatcherTimer(Action<DisposableDispatcherTimer> action, Dispatcher dispatcher, TimeSpan interval) :
|
||||||
|
this(action, dispatcher, interval, TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public DisposableDispatcherTimer(Action<DisposableDispatcherTimer> action, Dispatcher dispatcher, TimeSpan interval, TimeSpan wait)
|
||||||
|
{
|
||||||
|
Timer = new DispatcherTimer(interval, DispatcherPriority.Normal, (s, e) =>
|
||||||
|
{
|
||||||
|
if (Timer.IsEnabled && (TimeSinceCreation > wait))
|
||||||
|
{
|
||||||
|
action(this);
|
||||||
|
}
|
||||||
|
}, dispatcher);
|
||||||
|
Timer.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Timer.Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -44,14 +44,15 @@ namespace HashCalculator.GUI
|
|||||||
|
|
||||||
var currentFiles = new List<InputEntry>();
|
var currentFiles = new List<InputEntry>();
|
||||||
string? fileName;
|
string? fileName;
|
||||||
string? currentFullPath;
|
string? currentFullPath = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
currentFullPath = Path.GetFullPath(path);
|
var currentFile = new FileInfo(path);
|
||||||
fileName = Path.GetFileName(path);
|
fileName = currentFile.Name;
|
||||||
if (File.Exists(currentFullPath))
|
if (currentFile.Exists)
|
||||||
{
|
{
|
||||||
var type = CheckExtension(currentFullPath);
|
currentFullPath = currentFile.FullName;
|
||||||
|
var type = CheckExtension(currentFile);
|
||||||
if (type is InputEntryType entryType)
|
if (type is InputEntryType entryType)
|
||||||
{
|
{
|
||||||
currentFiles.Add(new InputEntry(entryType, path, currentFullPath));
|
currentFiles.Add(new InputEntry(entryType, path, currentFullPath));
|
||||||
@ -77,7 +78,7 @@ namespace HashCalculator.GUI
|
|||||||
select file;
|
select file;
|
||||||
|
|
||||||
var supportedFiles = from file in otherFiles
|
var supportedFiles = from file in otherFiles
|
||||||
let type = CheckExtension(file.Extension)
|
let type = CheckExtension(file)
|
||||||
where type.HasValue
|
where type.HasValue
|
||||||
select new InputEntry(type.Value, _search.GetInputStyleName(file), file.FullName);
|
select new InputEntry(type.Value, _search.GetInputStyleName(file), file.FullName);
|
||||||
|
|
||||||
@ -91,9 +92,9 @@ namespace HashCalculator.GUI
|
|||||||
return currentFiles.Concat(alternatives);
|
return currentFiles.Concat(alternatives);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InputEntryType? CheckExtension(string path)
|
private static InputEntryType? CheckExtension(FileInfo info)
|
||||||
{
|
{
|
||||||
if (Mapping.TryGetValue(path, out var type))
|
if (Mapping.TryGetValue(info.Extension, out var type))
|
||||||
{
|
{
|
||||||
return type;
|
return type;
|
||||||
}
|
}
|
||||||
|
@ -20,11 +20,11 @@
|
|||||||
<PackageReference Include="Mvp.Xml" Version="2.3.0" />
|
<PackageReference Include="Mvp.Xml" Version="2.3.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\TechnologyAssembler.Core\TechnologyAssembler.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Reference Include="System.Windows.Presentation" />
|
<Reference Include="System.Windows.Presentation" />
|
||||||
<Reference Include="TechnologyAssembler.Core">
|
|
||||||
<HintPath>TechnologyAssembler.Core.dll</HintPath>
|
|
||||||
<Private>true</Private>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
@ -8,7 +8,7 @@
|
|||||||
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
|
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
x:ClassModifier="internal"
|
x:ClassModifier="internal"
|
||||||
x:Name="_this"
|
x:Name="Self"
|
||||||
d:DesignHeight="100" d:DesignWidth="800"
|
d:DesignHeight="100" d:DesignWidth="800"
|
||||||
>
|
>
|
||||||
<UserControl.Resources>
|
<UserControl.Resources>
|
||||||
@ -69,12 +69,17 @@
|
|||||||
<Grid>
|
<Grid>
|
||||||
<!--TextChanged="OnTextChanged"-->
|
<!--TextChanged="OnTextChanged"-->
|
||||||
<Grid>
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition/>
|
||||||
|
<ColumnDefinition Width="24"/>
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="TextBox"
|
x:Name="TextBox"
|
||||||
Text="{Binding Path=Text,
|
Text="{Binding Path=Text,
|
||||||
RelativeSource={RelativeSource AncestorType=local:InputBar},
|
RelativeSource={RelativeSource AncestorType=local:InputBar},
|
||||||
UpdateSourceTrigger=PropertyChanged}"
|
UpdateSourceTrigger=PropertyChanged}"
|
||||||
PreviewKeyDown="OnPreviewKeyDown"
|
PreviewKeyDown="OnPreviewKeyDown"
|
||||||
|
Grid.Column="0"
|
||||||
Padding="2, 0"
|
Padding="2, 0"
|
||||||
VerticalContentAlignment="Center"
|
VerticalContentAlignment="Center"
|
||||||
LostFocus="OnLostFocus"
|
LostFocus="OnLostFocus"
|
||||||
@ -84,9 +89,20 @@
|
|||||||
RelativeSource={RelativeSource AncestorType=local:InputBar}}"
|
RelativeSource={RelativeSource AncestorType=local:InputBar}}"
|
||||||
IsHitTestVisible="False"
|
IsHitTestVisible="False"
|
||||||
Visibility="{Binding ElementName=TextBox, Path=Text, Converter={StaticResource NullToVisibilityConverter}}"
|
Visibility="{Binding ElementName=TextBox, Path=Text, Converter={StaticResource NullToVisibilityConverter}}"
|
||||||
|
Grid.Column="0"
|
||||||
Padding="5, 0"
|
Padding="5, 0"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
Click="OnToggleDropDownButtonClick"
|
||||||
|
Grid.Column="1"
|
||||||
|
Background="Transparent"
|
||||||
|
>
|
||||||
|
<Path
|
||||||
|
Fill="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
|
||||||
|
Data="M 0 2 L 1 0 L 6 4 L 11 0 L 12 2 L 6 7 Z"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Popup
|
<Popup
|
||||||
x:Name="DropDown"
|
x:Name="DropDown"
|
||||||
@ -101,7 +117,6 @@
|
|||||||
ItemsSource="{Binding Path=Collection,
|
ItemsSource="{Binding Path=Collection,
|
||||||
RelativeSource={RelativeSource AncestorType=local:InputBar}}"
|
RelativeSource={RelativeSource AncestorType=local:InputBar}}"
|
||||||
SelectionMode="Single"
|
SelectionMode="Single"
|
||||||
SelectionChanged="OnSelectionChanged"
|
|
||||||
SelectedItem="{Binding Path=SelectedItem,
|
SelectedItem="{Binding Path=SelectedItem,
|
||||||
RelativeSource={RelativeSource AncestorType=local:InputBar}}"
|
RelativeSource={RelativeSource AncestorType=local:InputBar}}"
|
||||||
SelectedIndex="{Binding Path=SelectedIndex,
|
SelectedIndex="{Binding Path=SelectedIndex,
|
||||||
|
@ -7,13 +7,9 @@ using System.Threading.Tasks;
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
using System.Windows.Controls;
|
using System.Windows.Controls;
|
||||||
using System.Windows.Data;
|
using System.Windows.Data;
|
||||||
using System.Windows.Documents;
|
|
||||||
using System.Windows.Input;
|
using System.Windows.Input;
|
||||||
using System.Windows.Media;
|
using System.Windows.Media;
|
||||||
using System.Windows.Media.Imaging;
|
using System.Globalization;
|
||||||
using System.Windows.Navigation;
|
|
||||||
using System.Windows.Shapes;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace HashCalculator.GUI
|
namespace HashCalculator.GUI
|
||||||
{
|
{
|
||||||
@ -74,7 +70,12 @@ namespace HashCalculator.GUI
|
|||||||
DependencyProperty.Register(nameof(SelectedItem), typeof(InputEntry), typeof(InputBar), new FrameworkPropertyMetadata
|
DependencyProperty.Register(nameof(SelectedItem), typeof(InputEntry), typeof(InputBar), new FrameworkPropertyMetadata
|
||||||
{
|
{
|
||||||
BindsTwoWayByDefault = true,
|
BindsTwoWayByDefault = true,
|
||||||
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged
|
DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
|
||||||
|
PropertyChangedCallback = (d, e) =>
|
||||||
|
{
|
||||||
|
var self = (InputBar)d;
|
||||||
|
self.OnSelectionChanged();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
public InputEntry? SelectedItem
|
public InputEntry? SelectedItem
|
||||||
{
|
{
|
||||||
@ -127,7 +128,7 @@ namespace HashCalculator.GUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
|
private void OnSelectionChanged()
|
||||||
{
|
{
|
||||||
if (SelectedItem != null)
|
if (SelectedItem != null)
|
||||||
{
|
{
|
||||||
@ -151,5 +152,14 @@ namespace HashCalculator.GUI
|
|||||||
return Math.Min(Math.Max(rawIndex, -1), Collection.Count() - 1);
|
return Math.Min(Math.Max(rawIndex, -1), Collection.Count() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnToggleDropDownButtonClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
TextBox.Focus();
|
||||||
|
TextBox.Select(Text?.Length ?? 0, 0);
|
||||||
|
if (!DropDown.IsOpen)
|
||||||
|
{
|
||||||
|
DropDown.IsOpen = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
239
MainWindow.xaml
239
MainWindow.xaml
@ -8,106 +8,18 @@
|
|||||||
xmlns:l="clr-namespace:HashCalculator.GUI"
|
xmlns:l="clr-namespace:HashCalculator.GUI"
|
||||||
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
|
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
|
||||||
mc:Ignorable="d"
|
mc:Ignorable="d"
|
||||||
Title="Sage FastHash 哈希计算器" Height="600" Width="600"
|
Title="SAGE FastHash 哈希计算器"
|
||||||
Background="#202020"
|
Height="600"
|
||||||
Foreground="LightGray"
|
Width="600"
|
||||||
|
Style="{StaticResource CommonStyle}"
|
||||||
>
|
>
|
||||||
<Window.Resources>
|
|
||||||
<c:ValueConverterAggregate x:Key="ValidInputEntryToVisibilityConverter">
|
|
||||||
<c:ValidInputEntryTypeToBooleanConverter />
|
|
||||||
<BooleanToVisibilityConverter />
|
|
||||||
</c:ValueConverterAggregate>
|
|
||||||
<c:ValueConverterAggregate x:Key="InvalidInputEntryToVisibilityConverter">
|
|
||||||
<c:ValidInputEntryTypeToBooleanConverter />
|
|
||||||
<c:BooleanInvertConverter />
|
|
||||||
<BooleanToVisibilityConverter />
|
|
||||||
</c:ValueConverterAggregate>
|
|
||||||
<Style x:Key="CommonStyle" TargetType="Control">
|
|
||||||
<Setter
|
|
||||||
Property="Foreground"
|
|
||||||
Value="{Binding Path=(TextElement.Foreground),
|
|
||||||
RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}}"
|
|
||||||
/>
|
|
||||||
<Setter
|
|
||||||
Property="Background"
|
|
||||||
Value="{Binding Path=(TextElement.Background),
|
|
||||||
RelativeSource={RelativeSource AncestorType={x:Type FrameworkElement}}}"
|
|
||||||
/>
|
|
||||||
</Style>
|
|
||||||
<Style
|
|
||||||
BasedOn="{StaticResource CommonStyle}"
|
|
||||||
TargetType="{x:Type TextBox}"
|
|
||||||
>
|
|
||||||
<Setter Property="Background" Value="#00000000"/>
|
|
||||||
</Style>
|
|
||||||
<Style
|
|
||||||
BasedOn="{StaticResource CommonStyle}"
|
|
||||||
TargetType="{x:Type Button}"
|
|
||||||
>
|
|
||||||
<Setter Property="Background" Value="#20808080"/>
|
|
||||||
<Setter Property="Template">
|
|
||||||
<Setter.Value>
|
|
||||||
<ControlTemplate TargetType="{x:Type Button}">
|
|
||||||
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
|
|
||||||
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
|
|
||||||
</Border>
|
|
||||||
<ControlTemplate.Triggers>
|
|
||||||
<Trigger Property="Button.IsDefaulted" Value="True">
|
|
||||||
<Setter Property="BorderBrush" TargetName="border" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsMouseOver" Value="True">
|
|
||||||
<Setter Property="Background" TargetName="border" Value="#80BEE6FD"/>
|
|
||||||
<Setter Property="BorderBrush" TargetName="border" Value="#FF3C7FB1"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsPressed" Value="True">
|
|
||||||
<Setter Property="Background" TargetName="border" Value="#80C4E5F6"/>
|
|
||||||
<Setter Property="BorderBrush" TargetName="border" Value="#FF2C628B"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="ToggleButton.IsChecked" Value="True">
|
|
||||||
<Setter Property="Background" TargetName="border" Value="#FFBCDDEE"/>
|
|
||||||
<Setter Property="BorderBrush" TargetName="border" Value="#FF245A83"/>
|
|
||||||
</Trigger>
|
|
||||||
<Trigger Property="IsEnabled" Value="False">
|
|
||||||
<Setter Property="Background" TargetName="border" Value="#00000000"/>
|
|
||||||
<Setter Property="BorderBrush" TargetName="border" Value="#FFADB2B5"/>
|
|
||||||
<Setter Property="Foreground" Value="#808080"/>
|
|
||||||
</Trigger>
|
|
||||||
</ControlTemplate.Triggers>
|
|
||||||
</ControlTemplate>
|
|
||||||
</Setter.Value>
|
|
||||||
</Setter>
|
|
||||||
</Style>
|
|
||||||
<Style
|
|
||||||
BasedOn="{StaticResource CommonStyle}"
|
|
||||||
TargetType="{x:Type DataGrid}"
|
|
||||||
/>
|
|
||||||
<Style
|
|
||||||
BasedOn="{StaticResource CommonStyle}"
|
|
||||||
TargetType="{x:Type DataGridColumnHeader}"
|
|
||||||
>
|
|
||||||
<Setter Property="Padding" Value="10,5" />
|
|
||||||
<Setter Property="MinWidth" Value="0" />
|
|
||||||
<Setter Property="SnapsToDevicePixels" Value="True" />
|
|
||||||
<Setter Property="Cursor" Value="Hand" />
|
|
||||||
<Setter Property="Background" Value="#343434"/>
|
|
||||||
<Setter Property="Foreground" Value="White"/>
|
|
||||||
<Setter Property="BorderThickness" Value="0, 0, 1, 0"/>
|
|
||||||
<Setter Property="BorderBrush" Value="Gray" />
|
|
||||||
</Style>
|
|
||||||
<Style
|
|
||||||
BasedOn="{StaticResource CommonStyle}"
|
|
||||||
TargetType="{x:Type ListBox}"
|
|
||||||
>
|
|
||||||
<Setter Property="Background" Value="#181818"/>
|
|
||||||
</Style>
|
|
||||||
</Window.Resources>
|
|
||||||
<Window.DataContext>
|
<Window.DataContext>
|
||||||
<l:ViewModel />
|
<l:ViewModel />
|
||||||
</Window.DataContext>
|
</Window.DataContext>
|
||||||
<Grid Margin="10,20,10,10">
|
<Grid Margin="10,20,10,10">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition/>
|
<RowDefinition/>
|
||||||
<RowDefinition Height="130" />
|
<RowDefinition Height="150" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
<DockPanel Grid.Row="0">
|
<DockPanel Grid.Row="0">
|
||||||
<Grid
|
<Grid
|
||||||
@ -150,7 +62,7 @@
|
|||||||
DockPanel.Dock="Top"
|
DockPanel.Dock="Top"
|
||||||
Height="25"
|
Height="25"
|
||||||
Margin="0,10,0,0"
|
Margin="0,10,0,0"
|
||||||
Visibility="Visible"
|
Visibility="{Binding BigEntryInput.AllManifests, Converter={StaticResource NotNullToVisibilityConverter}}"
|
||||||
>
|
>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition />
|
<ColumnDefinition />
|
||||||
@ -158,71 +70,126 @@
|
|||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
<l:InputBar
|
<l:InputBar
|
||||||
HintText="输入 big 文件里包含的 manifest 文件的路径"
|
HintText="输入 big 文件里包含的 manifest 文件的路径"
|
||||||
Collection="{Binding Items}"
|
Text="{Binding BigEntryInput.Text}"
|
||||||
SelectedItem="{Binding SelectedItem}"
|
Collection="{Binding BigEntryInput.Items}"
|
||||||
|
SelectedItem="{Binding BigEntryInput.SelectedItem}"
|
||||||
|
SelectedIndex="{Binding BigEntryInput.SelectedIndex}"
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="0,0,10,0"
|
Margin="0,0,10,0"
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
Content="确认"
|
Content="确认"
|
||||||
|
Command="{Binding BigEntryInput.SelectCommand}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="0"
|
Margin="0"
|
||||||
Foreground="ForestGreen"
|
>
|
||||||
BorderBrush="ForestGreen" Template="{DynamicResource ButtonBaseControlTemplate1}"
|
<Button.Style>
|
||||||
|
<Style
|
||||||
|
BasedOn="{StaticResource ButtonStyle}"
|
||||||
|
TargetType="Button"
|
||||||
|
>
|
||||||
|
<Style.Triggers>
|
||||||
|
<MultiDataTrigger>
|
||||||
|
<MultiDataTrigger.Conditions>
|
||||||
|
<Condition Value="False">
|
||||||
|
<Condition.Binding>
|
||||||
|
<MultiBinding Converter="{StaticResource MultiValueEqualityConverter}">
|
||||||
|
<Binding Path="BigEntryInput.LastProcessedManifest"></Binding>
|
||||||
|
<Binding Path="BigEntryInput.SelectedItem"></Binding>
|
||||||
|
</MultiBinding>
|
||||||
|
</Condition.Binding>
|
||||||
|
</Condition>
|
||||||
|
<Condition
|
||||||
|
Binding="{Binding RelativeSource={RelativeSource Mode=Self},
|
||||||
|
Path=IsEnabled}"
|
||||||
|
Value="True"
|
||||||
/>
|
/>
|
||||||
|
</MultiDataTrigger.Conditions>
|
||||||
|
<Setter Property="Foreground" Value="#20FF30" />
|
||||||
|
<Setter Property="BorderBrush" Value="#20FF30" />
|
||||||
|
</MultiDataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
</Button>
|
||||||
</Grid>
|
</Grid>
|
||||||
<DockPanel
|
<Grid
|
||||||
DockPanel.Dock="Top"
|
DockPanel.Dock="Top"
|
||||||
Height="25"
|
Height="50"
|
||||||
Margin="0,10"
|
Margin="0,10"
|
||||||
>
|
>
|
||||||
<Button
|
<Grid.RowDefinitions>
|
||||||
DockPanel.Dock="Right"
|
<RowDefinition />
|
||||||
Content="取消加载"
|
<RowDefinition />
|
||||||
Grid.Column="1"
|
</Grid.RowDefinitions>
|
||||||
Width="92"
|
<TextBlock
|
||||||
/>
|
Text="{Binding StatusText}"
|
||||||
<Button
|
Grid.Row="0"
|
||||||
DockPanel.Dock="Right"
|
|
||||||
Content="加载 csf / mod.str"
|
|
||||||
Grid.Column="1"
|
|
||||||
Width="140"
|
|
||||||
Margin="10,0"
|
Margin="10,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
/>
|
/>
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Text="正在加载……"
|
Text="{Binding Entries.Count, StringFormat=目前加载了{0}个素材}"
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="10,5,0,5"
|
||||||
|
Width="160"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
Grid.Column="0"
|
Visibility="{Binding Entries.Count, Converter={StaticResource NonZeroToVisibilityConverter}}"
|
||||||
/>
|
/>
|
||||||
</DockPanel>
|
|
||||||
<l:InputBar
|
<CheckBox
|
||||||
HintText="过滤Asset ID(可选)"
|
Content="只显示 GameObject"
|
||||||
Collection="{Binding Items}"
|
IsChecked="True"
|
||||||
SelectedItem="{Binding SelectedItem}"
|
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>
|
||||||
|
|
||||||
|
<Grid
|
||||||
DockPanel.Dock="Top"
|
DockPanel.Dock="Top"
|
||||||
Height="25"
|
Height="25"
|
||||||
|
>
|
||||||
|
<TextBox></TextBox>
|
||||||
|
<TextBlock
|
||||||
|
Text="过滤Asset ID(可选)"
|
||||||
|
Padding="5,0"
|
||||||
|
VerticalAlignment="Center"
|
||||||
/>
|
/>
|
||||||
|
</Grid>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
x:Name="DataGrid"
|
ItemsSource="{Binding Entries}"
|
||||||
AutoGenerateColumns="False"
|
|
||||||
ScrollViewer.CanContentScroll="True"
|
ScrollViewer.CanContentScroll="True"
|
||||||
VerticalScrollBarVisibility="Auto"
|
VerticalScrollBarVisibility="Auto"
|
||||||
EnableRowVirtualization="True"
|
EnableRowVirtualization="True"
|
||||||
VirtualizingPanel.VirtualizationMode="Recycling"
|
VirtualizingPanel.VirtualizationMode="Recycling"
|
||||||
DockPanel.Dock="Top"
|
/>
|
||||||
>
|
|
||||||
<DataGrid.Columns>
|
|
||||||
<DataGridTextColumn Header="Asset ID" Width="200" Binding="{Binding AssetId}"/>
|
|
||||||
<DataGridTextColumn Header="哈希" Width="*" Binding="{Binding Hash}"/>
|
|
||||||
</DataGrid.Columns>
|
|
||||||
</DataGrid>
|
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
<TextBlock
|
<StackPanel
|
||||||
x:Name="textBlock"
|
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
Margin="0,10,0,0"
|
Margin="0,10,0,0"
|
||||||
TextWrapping="Wrap"
|
ScrollViewer.CanContentScroll="True"
|
||||||
|
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
|
||||||
|
ScrollViewer.VerticalScrollBarVisibility="Auto"
|
||||||
>
|
>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
本工具基于 <l:ShellLink NavigateUri="https://github.com/Qibbi">Qibbi</l:ShellLink> 提供的 TechnologyAssembler 制作<LineBreak />
|
本工具基于 <l:ShellLink NavigateUri="https://github.com/Qibbi">Qibbi</l:ShellLink> 提供的 TechnologyAssembler 制作<LineBreak />
|
||||||
此外使用了 <l:ShellLink NavigateUri="https://github.com/bgrainger">Bradley Grainger</l:ShellLink> 的
|
此外使用了 <l:ShellLink NavigateUri="https://github.com/bgrainger">Bradley Grainger</l:ShellLink> 的
|
||||||
<l:ShellLink NavigateUri="https://github.com/bgrainger/IndexRange">IndexRange</l:ShellLink>
|
<l:ShellLink NavigateUri="https://github.com/bgrainger/IndexRange">IndexRange</l:ShellLink>
|
||||||
@ -230,5 +197,17 @@
|
|||||||
<LineBreak />
|
<LineBreak />
|
||||||
假如你对本工具有任何疑问或者建议的话,可以来到<l:ShellLink NavigateUri="https://tieba.baidu.com/ra3">红警3吧</l:ShellLink>发帖寻找岚依(
|
假如你对本工具有任何疑问或者建议的话,可以来到<l:ShellLink NavigateUri="https://tieba.baidu.com/ra3">红警3吧</l:ShellLink>发帖寻找岚依(
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
|
<TextBlock />
|
||||||
|
<StackPanel
|
||||||
|
Height="25"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
Content="清除输出"
|
||||||
|
Width="92"
|
||||||
|
Grid.Row="1"
|
||||||
|
/>
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Window>
|
</Window>
|
||||||
|
@ -21,65 +21,37 @@ namespace HashCalculator.GUI
|
|||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
internal ViewModel ViewModel => (ViewModel)DataContext;
|
internal ViewModel ViewModel => (ViewModel)DataContext;
|
||||||
|
private bool _autoscroll = true;
|
||||||
|
|
||||||
public MainWindow()
|
public MainWindow()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnMainInputTextChanged(object sender, TextChangedEventArgs e)
|
private void OnButtomScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e)
|
||||||
{
|
{
|
||||||
CopyableBox.ShowDialog(async token =>
|
var scrollViewer = (ScrollViewer)e.Source;
|
||||||
{
|
// User scroll event : set or unset auto-scroll mode
|
||||||
var init = DateTimeOffset.UtcNow;
|
if (e.ExtentHeightChange == 0)
|
||||||
var i = 0;
|
{ // Content unchanged : user scroll event
|
||||||
while (i < 100)
|
if (scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight)
|
||||||
{
|
{ // Scroll bar is in bottom
|
||||||
token.ThrowIfCancellationRequested();
|
// Set auto-scroll mode
|
||||||
await Task.Delay(100).ConfigureAwait(false);
|
_autoscroll = true;
|
||||||
++i;
|
|
||||||
}
|
}
|
||||||
MessageBox.Show("Completed!");
|
else
|
||||||
return $"Completed after exactly {DateTimeOffset.UtcNow - init}";
|
{ // Scroll bar isn't in bottom
|
||||||
});
|
// Unset auto-scroll mode
|
||||||
/*var mainInput = _viewModel.MainInput;
|
_autoscroll = false;
|
||||||
if (sender is InputBar)
|
|
||||||
{
|
|
||||||
var inputValue = mainInput.Text;
|
|
||||||
if(inputValue != null)
|
|
||||||
{
|
|
||||||
mainInput.Items = _suggestions.ProvideFileSystemSuggestions(inputValue)
|
|
||||||
.Prepend(new InputEntry(InputEntryType.Text, inputValue, inputValue));
|
|
||||||
}
|
}
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnBigInputTextChanged(object sender, TextChangedEventArgs e)
|
// Content scroll event : auto-scroll eventually
|
||||||
{
|
if (_autoscroll && e.ExtentHeightChange != 0)
|
||||||
/*var mainInput = _viewModel.MainInput;
|
{ // Content changed and auto-scroll mode set
|
||||||
if (sender is InputBar)
|
// Autoscroll
|
||||||
{
|
scrollViewer.ScrollToVerticalOffset(scrollViewer.ExtentHeight);
|
||||||
var inputValue = mainInput.Text;
|
}
|
||||||
if (inputValue != null)
|
|
||||||
{
|
|
||||||
mainInput.Items = _suggestions.ProvideFileSystemSuggestions(inputValue)
|
|
||||||
.Prepend(new InputEntry(InputEntryType.Text, inputValue, inputValue));
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnAssetInputTextChanged(object sender, TextChangedEventArgs e)
|
|
||||||
{
|
|
||||||
/*var mainInput = _viewModel.MainInput;
|
|
||||||
if (sender is InputBar)
|
|
||||||
{
|
|
||||||
var inputValue = mainInput.Text;
|
|
||||||
if (inputValue != null)
|
|
||||||
{
|
|
||||||
mainInput.Items = _suggestions.ProvideFileSystemSuggestions(inputValue)
|
|
||||||
.Prepend(new InputEntry(InputEntryType.Text, inputValue, inputValue));
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using TechnologyAssembler.Core.Assets;
|
|
||||||
|
|
||||||
namespace HashCalculator.GUI
|
|
||||||
{
|
|
||||||
public class ModManifest
|
|
||||||
{
|
|
||||||
IReadOnlyCollection<AssetEntry> Entries;
|
|
||||||
|
|
||||||
public ModManifest()
|
|
||||||
{
|
|
||||||
var x = Manifest.Load("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,6 +18,15 @@ namespace HashCalculator.GUI
|
|||||||
return FastHash.GetHashCode(content);
|
return FastHash.GetHashCode(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static uint CalculateLauncherBinaryHash(byte[] content)
|
||||||
|
{
|
||||||
|
if (content == null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException($"{nameof(content)} is null");
|
||||||
|
}
|
||||||
|
return FastHash.GetHashCodeLauncher(0, content);
|
||||||
|
}
|
||||||
|
|
||||||
public static uint CalculateBinaryHash(string content)
|
public static uint CalculateBinaryHash(string content)
|
||||||
{
|
{
|
||||||
if (content == null)
|
if (content == null)
|
||||||
|
262
ViewModel.cs
262
ViewModel.cs
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
@ -46,74 +47,52 @@ namespace HashCalculator.GUI
|
|||||||
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
[SuppressMessage("Microsoft.Performance", "CA1812")]
|
||||||
internal class ViewModel : NotifyPropertyChanged
|
internal class ViewModel : NotifyPropertyChanged
|
||||||
{
|
{
|
||||||
public MainInputViewModel MainInput { get; } = new MainInputViewModel();
|
private static readonly Random _random = new Random();
|
||||||
public BigInputViewModel BigEntryInput { get; } = new BigInputViewModel();
|
|
||||||
public InputBarViewModel AssetIdInput { get; } = new InputBarViewModel();
|
|
||||||
|
|
||||||
private Visibility _bigInputVisibility = Visibility.Collapsed;
|
public MainInputViewModel MainInput { get; }
|
||||||
public Visibility BigInputVisibility
|
public BigInputViewModel BigEntryInput { get; }
|
||||||
|
|
||||||
|
private ObservableCollection<DisplayAssetEntry> _entries = new ObservableCollection<DisplayAssetEntry>();
|
||||||
|
public ObservableCollection<DisplayAssetEntry> Entries
|
||||||
{
|
{
|
||||||
get => _bigInputVisibility;
|
get => _entries;
|
||||||
set => SetField(ref _bigInputVisibility, value);
|
set => SetField(ref _entries, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InputEntry? _mainInputResult; // if not null
|
private string _statusText = SuggestionString("不知道该显示些什么呢……");
|
||||||
private string? _bigManifest;
|
public string StatusText
|
||||||
|
{
|
||||||
|
get => _statusText;
|
||||||
|
set => SetField(ref _statusText, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _traceText = string.Empty;
|
||||||
|
public string TraceText
|
||||||
|
{
|
||||||
|
get => _traceText;
|
||||||
|
set => SetField(ref _traceText, value);
|
||||||
|
}
|
||||||
|
|
||||||
public ViewModel()
|
public ViewModel()
|
||||||
{
|
{
|
||||||
|
var controller = new Controller(this);
|
||||||
|
MainInput = new MainInputViewModel(controller);
|
||||||
|
BigEntryInput = new BigInputViewModel(controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
public static string SuggestionString(string original)
|
||||||
public async Task OnMainInputDecided(InputEntry selected)
|
|
||||||
{
|
{
|
||||||
BigInputVisibility = Visibility.Collapsed;
|
var generated = $"{_random.NextDouble()}";
|
||||||
switch (selected.Type)
|
System.Diagnostics.Debug.WriteLine(generated);
|
||||||
|
if (generated.Contains("38") || generated.Contains("16"))
|
||||||
{
|
{
|
||||||
case InputEntryType.BinaryFile:
|
return "你们都是喂鱼的马甲!(";
|
||||||
CopyableBox.ShowDialog(async cancel =>
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var hash = await Task.Run(() =>
|
|
||||||
{
|
|
||||||
return SageHash.CalculateBinaryHash(File.ReadAllBytes(selected.Value));
|
|
||||||
}).ConfigureAwait(false);
|
|
||||||
return $"使用Sage Hash计算出的哈希值:{hash:X8} (十进制 {hash})\r\n"
|
|
||||||
+ "注意这是以大小写敏感模式计算出的哈希值,与素材ID(大小写不敏感)的哈希其实并不完全一样";
|
|
||||||
}
|
}
|
||||||
catch (Exception exception)
|
if (generated.IndexOf('2') == 2)
|
||||||
{
|
{
|
||||||
return exception.ToString();
|
return "本来以为两小时就能写完这个小工具,没想到写了两个星期,开始怀疑自己的智商orz";
|
||||||
}
|
}
|
||||||
});
|
return original;
|
||||||
break;
|
|
||||||
case InputEntryType.BigFile:
|
|
||||||
BigInputVisibility = Visibility.Visible;
|
|
||||||
throw new NotImplementedException();
|
|
||||||
case InputEntryType.ManifestFile:
|
|
||||||
await ProcessManifest(selected).ConfigureAwait(true);
|
|
||||||
break;
|
|
||||||
case InputEntryType.XmlFile:
|
|
||||||
throw new NotImplementedException();
|
|
||||||
default:
|
|
||||||
throw new NotSupportedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ProcessManifest(InputEntry entry)
|
|
||||||
{
|
|
||||||
var content = await Task.Run(() =>
|
|
||||||
{
|
|
||||||
using var stream = File.OpenRead(entry.Value);
|
|
||||||
return new ManifestContent(entry.Text, stream);
|
|
||||||
}).ConfigureAwait(true);
|
|
||||||
await ProcessManifest(content).ConfigureAwait(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ProcessManifest(ManifestContent content)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +112,13 @@ namespace HashCalculator.GUI
|
|||||||
set => SetField(ref _selectedItem, value);
|
set => SetField(ref _selectedItem, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private int _selectedIndex;
|
||||||
|
public virtual int SelectedIndex
|
||||||
|
{
|
||||||
|
get => _selectedIndex;
|
||||||
|
set => SetField(ref _selectedIndex, value);
|
||||||
|
}
|
||||||
|
|
||||||
private string? _text;
|
private string? _text;
|
||||||
public virtual string? Text
|
public virtual string? Text
|
||||||
{
|
{
|
||||||
@ -145,10 +131,9 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
private readonly FileSystemSuggestions _suggestions = new FileSystemSuggestions();
|
private readonly FileSystemSuggestions _suggestions = new FileSystemSuggestions();
|
||||||
|
|
||||||
private int _selectedIndex;
|
public override int SelectedIndex
|
||||||
public int SelectedIndex
|
|
||||||
{
|
{
|
||||||
get => _selectedIndex;
|
get => base.SelectedIndex;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (value == 0)
|
if (value == 0)
|
||||||
@ -159,10 +144,10 @@ namespace HashCalculator.GUI
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
value = _selectedIndex < value ? 1 : -1;
|
value = base.SelectedIndex < value ? 1 : -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SetField(ref _selectedIndex, value);
|
base.SelectedIndex = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +167,7 @@ namespace HashCalculator.GUI
|
|||||||
public Command<MainWindow> BrowseCommand { get; }
|
public Command<MainWindow> BrowseCommand { get; }
|
||||||
public Command<ViewModel> SelectCommand { get; }
|
public Command<ViewModel> SelectCommand { get; }
|
||||||
|
|
||||||
public MainInputViewModel()
|
public MainInputViewModel(Controller controller)
|
||||||
{
|
{
|
||||||
Items = Enumerable.Empty<InputEntry>();
|
Items = Enumerable.Empty<InputEntry>();
|
||||||
BrowseCommand = new Command<MainWindow>(window =>
|
BrowseCommand = new Command<MainWindow>(window =>
|
||||||
@ -209,7 +194,7 @@ namespace HashCalculator.GUI
|
|||||||
{
|
{
|
||||||
BrowseCommand.CanExecuteValue = false;
|
BrowseCommand.CanExecuteValue = false;
|
||||||
SelectCommand.CanExecuteValue = false;
|
SelectCommand.CanExecuteValue = false;
|
||||||
await viewModel.OnMainInputDecided(SelectedItem).ConfigureAwait(true);
|
await controller.OnMainInputDecided(SelectedItem).ConfigureAwait(true);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -246,102 +231,105 @@ namespace HashCalculator.GUI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("Design", "CA1001:具有可释放字段的类型应该是可释放的", Justification = "<挂起>")]
|
internal class BigInputViewModel : InputBarViewModel
|
||||||
internal class BigInputViewModel : NotifyPropertyChanged
|
|
||||||
{
|
{
|
||||||
private CancellationTokenSource? _currentCancellator = null;
|
public override IEnumerable<InputEntry>? Items
|
||||||
|
|
||||||
private IEnumerable<ManifestContent>? _manifests;
|
|
||||||
public IEnumerable<ManifestContent>? Manifests
|
|
||||||
{
|
{
|
||||||
get => _manifests;
|
get => base.Items;
|
||||||
set => SetField(ref _manifests, value);
|
set => base.Items = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ManifestContent? _selectedManifest;
|
public override InputEntry? SelectedItem
|
||||||
public ManifestContent? SelectedManifest
|
|
||||||
{
|
{
|
||||||
get => _selectedManifest;
|
get => base.SelectedItem;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
SetField(ref _selectedManifest, value);
|
base.SelectedItem = value;
|
||||||
SelectCommand.CanExecuteValue = value != null;
|
SelectCommand.CanExecuteValue = value != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override string? Text
|
||||||
|
{
|
||||||
|
get => base.Text;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Text = value;
|
||||||
|
if (value != SelectedItem?.ToString())
|
||||||
|
{
|
||||||
|
UpdateList(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputEntry? _lastProcessedManifest;
|
||||||
|
public InputEntry? LastProcessedManifest
|
||||||
|
{
|
||||||
|
get => _lastProcessedManifest;
|
||||||
|
set => SetField(ref _lastProcessedManifest, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private IEnumerable<InputEntry>? _allManifests;
|
||||||
|
public IEnumerable<InputEntry>? AllManifests
|
||||||
|
{
|
||||||
|
get => _allManifests;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
SetField(ref _allManifests, value);
|
||||||
|
Items = value;
|
||||||
|
LastProcessedManifest = null;
|
||||||
|
Text = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Command<ViewModel> SelectCommand { get; }
|
public Command<ViewModel> SelectCommand { get; }
|
||||||
|
|
||||||
public BigInputViewModel()
|
[SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
|
||||||
|
public BigInputViewModel(Controller controller)
|
||||||
{
|
{
|
||||||
SelectCommand = new Command<ViewModel>(viewModel => viewModel.ProcessManifest(SelectedManifest!));
|
SelectCommand = new Command<ViewModel>(async viewModel =>
|
||||||
}
|
|
||||||
|
|
||||||
public async Task LoadBig(string path)
|
|
||||||
{
|
{
|
||||||
using var cancellator = _currentCancellator = new CancellationTokenSource();
|
var mainInput = viewModel.MainInput;
|
||||||
var saved = Interlocked.Exchange(ref _currentCancellator, cancellator);
|
|
||||||
saved?.Cancel();
|
|
||||||
|
|
||||||
var token = cancellator.Token;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var big = await Task.Run(() => new BigFile(path), token).ConfigureAwait(true);
|
SelectCommand.CanExecuteValue = false;
|
||||||
var manifests = await Task.Run(() =>
|
mainInput.BrowseCommand.CanExecuteValue = false;
|
||||||
|
mainInput.SelectCommand.CanExecuteValue = false;
|
||||||
|
LastProcessedManifest = SelectedItem;
|
||||||
|
await controller.ProcessManifest(SelectedItem!.Value).ConfigureAwait(true);
|
||||||
|
}
|
||||||
|
catch (Exception exception)
|
||||||
{
|
{
|
||||||
var manifests = big.GetFiles(string.Empty, "*.manifest", VirtualSearchOptionType.AllDirectories);
|
CopyableBox.ShowDialog(_ =>
|
||||||
var modManifests = from manifest in manifests
|
{
|
||||||
where FileNameStartsWith(manifest, "mod")
|
return Task.FromResult($"在加载 manifest 时发生错误:{exception}");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
SelectCommand.CanExecuteValue = true;
|
||||||
|
mainInput.BrowseCommand.CanExecuteValue = true;
|
||||||
|
mainInput.SelectCommand.CanExecuteValue = true;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CanExecuteValue = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateList(string? input)
|
||||||
|
{
|
||||||
|
input ??= string.Empty;
|
||||||
|
input = input.Replace(VirtualFileSystem.AltDirectorySeparatorChar, VirtualFileSystem.DirectorySeparatorChar);
|
||||||
|
|
||||||
|
var filtered = from manifest in AllManifests
|
||||||
|
where manifest.Value.IndexOf(input, StringComparison.OrdinalIgnoreCase) != -1
|
||||||
select manifest;
|
select manifest;
|
||||||
var globalDataManifests = from manifest in manifests
|
Items = filtered;
|
||||||
where FileNameStartsWith(manifest, "mapmetadata")
|
if (Items.FirstOrDefault()?.Value.Equals(input, StringComparison.OrdinalIgnoreCase) == true)
|
||||||
select manifest;
|
|
||||||
var firstManifests = modManifests.Concat(globalDataManifests);
|
|
||||||
var otherManifests = from manifest in manifests
|
|
||||||
where !firstManifests.Contains(manifest)
|
|
||||||
select manifest;
|
|
||||||
|
|
||||||
var list = new List<ManifestContent>();
|
|
||||||
foreach (var path in firstManifests.Concat(otherManifests))
|
|
||||||
{
|
{
|
||||||
token.ThrowIfCancellationRequested();
|
SelectedIndex = 0;
|
||||||
using var stream = big.OpenStream(path);
|
}
|
||||||
list.Add(new ManifestContent(path, stream));
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}, token).ConfigureAwait(true);
|
|
||||||
|
|
||||||
if (!token.IsCancellationRequested)
|
|
||||||
{
|
|
||||||
Manifests = manifests;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (OperationCanceledException) { }
|
|
||||||
|
|
||||||
Interlocked.CompareExchange(ref _currentCancellator, null, cancellator);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool FileNameStartsWith(string path, string what)
|
|
||||||
{
|
|
||||||
return Path.GetFileName(path).StartsWith(what, StringComparison.OrdinalIgnoreCase);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class ManifestContent
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public byte[] Data { get; }
|
|
||||||
|
|
||||||
public ManifestContent(string name, Stream stream)
|
|
||||||
{
|
|
||||||
Name = name;
|
|
||||||
using var memoryStream = new MemoryStream();
|
|
||||||
stream.CopyTo(memoryStream);
|
|
||||||
Data = memoryStream.ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
{
|
|
||||||
return Name;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user