diff --git a/App.xaml b/App.xaml
index 77bec7c..f4fc817 100644
--- a/App.xaml
+++ b/App.xaml
@@ -1,9 +1,167 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/App.xaml.cs b/App.xaml.cs
index d9ae60e..bc1c6e7 100644
--- a/App.xaml.cs
+++ b/App.xaml.cs
@@ -5,6 +5,7 @@ using System.Data;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
+using TechnologyAssembler;
namespace HashCalculator.GUI
{
@@ -13,5 +14,10 @@ namespace HashCalculator.GUI
///
public partial class App : Application
{
+ public App()
+ {
+ var core = new TechnologyAssemblerCoreModule();
+ core.Initialize();
+ }
}
}
diff --git a/AssetEntry.cs b/AssetEntry.cs
index e504547..3a8bc0c 100644
--- a/AssetEntry.cs
+++ b/AssetEntry.cs
@@ -1,7 +1,9 @@
using System;
using System.Collections.Generic;
+using System.IO;
using System.Linq;
using System.Xml.Linq;
+using TechnologyAssembler.Core.Assets;
namespace HashCalculator.GUI
{
@@ -10,10 +12,10 @@ namespace HashCalculator.GUI
public string Type { get; }
public string Name { get; }
public IEnumerable DisplayLabels { get; }
- public uint Hash => SageHash.CalculateLowercaseHash(Name);
+ public uint InstanceId => SageHash.CalculateLowercaseHash(Name);
- public string IdString => $"{Type}:{Name}";
- public string HashString => $"{Hash:0X} ({Hash})";
+ public string NameString => $"{Type}:{Name}";
+ public string InstanceIdString => $"{InstanceId:X8} ({InstanceId})";
public AssetEntry(XElement element)
{
@@ -38,6 +40,21 @@ namespace HashCalculator.GUI
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();
+ if(InstanceId != asset.InstanceId || SageHash.CalculateBinaryHash(Type) != asset.TypeId)
+ {
+ throw new InvalidDataException();
+ }
+ }
+
public bool Equals(AssetEntry entry)
{
return this == entry;
@@ -73,7 +90,7 @@ namespace HashCalculator.GUI
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)
@@ -84,7 +101,34 @@ namespace HashCalculator.GUI
// override object.GetHashCode
public override int GetHashCode()
{
- return HashCode.Combine(Type, Hash);
+ return HashCode.Combine(Type, InstanceId);
+ }
+ }
+
+ internal class DisplayAssetEntry : IComparable
+ {
+ 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}");
}
}
}
diff --git a/Controller.cs b/Controller.cs
new file mode 100644
index 0000000..8f36351
--- /dev/null
+++ b/Controller.cs
@@ -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 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[]
+ {
+ 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 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 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);
+ }
+ }
+}
diff --git a/Converters/BooleanInvertConverter.cs b/Converters/BooleanInvertConverter.cs
index cf6b4a4..d42545a 100644
--- a/Converters/BooleanInvertConverter.cs
+++ b/Converters/BooleanInvertConverter.cs
@@ -1,9 +1,11 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Windows.Data;
namespace HashCalculator.GUI.Converters
{
+ [SuppressMessage("Microsoft.Performance", "CA1812")]
[ValueConversion(typeof(bool), typeof(bool))]
internal class BooleanInvertConverter : IValueConverter
{
diff --git a/Converters/IsZeroToBooleanConverter.cs b/Converters/IsZeroToBooleanConverter.cs
new file mode 100644
index 0000000..0995376
--- /dev/null
+++ b/Converters/IsZeroToBooleanConverter.cs
@@ -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();
+ }
+ }
+}
diff --git a/Converters/MultiValueEqualityConverter.cs b/Converters/MultiValueEqualityConverter.cs
new file mode 100644
index 0000000..07729c2
--- /dev/null
+++ b/Converters/MultiValueEqualityConverter.cs
@@ -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();
+ }
+ }
+}
diff --git a/Converters/ValidInputEntryTypeToBooleanConverter.cs b/Converters/ValidInputEntryTypeToBooleanConverter.cs
index 2dc5785..2fb8c83 100644
--- a/Converters/ValidInputEntryTypeToBooleanConverter.cs
+++ b/Converters/ValidInputEntryTypeToBooleanConverter.cs
@@ -1,9 +1,11 @@
using System;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Windows.Data;
namespace HashCalculator.GUI.Converters
{
+ [SuppressMessage("Microsoft.Performance", "CA1812")]
[ValueConversion(typeof(InputEntry), typeof(bool))]
internal class ValidInputEntryTypeToBooleanConverter : IValueConverter
{
diff --git a/Converters/ValueConverterAggregate.cs b/Converters/ValueConverterAggregate.cs
index ac1afcf..3729ea9 100644
--- a/Converters/ValueConverterAggregate.cs
+++ b/Converters/ValueConverterAggregate.cs
@@ -1,11 +1,13 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace HashCalculator.GUI.Converters
{
+ [SuppressMessage("Microsoft.Performance", "CA1812")]
internal class ValueConverterAggregate : List, IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
diff --git a/CopyableBox.xaml.cs b/CopyableBox.xaml.cs
index a5c0595..1bc9997 100644
--- a/CopyableBox.xaml.cs
+++ b/CopyableBox.xaml.cs
@@ -128,32 +128,22 @@ namespace HashCalculator.GUI
private async Task InitializationTask(Func> 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;
async Task Action()
{
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);
}
catch (OperationCanceledException)
{
return "操作已被取消";
}
- finally
- {
- timer.Stop();
- }
}
Text = await dispatcher.Invoke(Action).ConfigureAwait(true);
}
diff --git a/DisposableDispatcherTimer.cs b/DisposableDispatcherTimer.cs
new file mode 100644
index 0000000..a666da3
--- /dev/null
+++ b/DisposableDispatcherTimer.cs
@@ -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 action, Dispatcher dispatcher, TimeSpan interval) :
+ this(action, dispatcher, interval, TimeSpan.Zero)
+ {
+ }
+
+ public DisposableDispatcherTimer(Action 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();
+ }
+ }
+}
diff --git a/FileSystemSuggestions.cs b/FileSystemSuggestions.cs
index 5910a65..4801518 100644
--- a/FileSystemSuggestions.cs
+++ b/FileSystemSuggestions.cs
@@ -44,15 +44,16 @@ namespace HashCalculator.GUI
var currentFiles = new List();
string? fileName;
- string? currentFullPath;
+ string? currentFullPath = null;
try
{
- currentFullPath = Path.GetFullPath(path);
- fileName = Path.GetFileName(path);
- if (File.Exists(currentFullPath))
+ var currentFile = new FileInfo(path);
+ fileName = currentFile.Name;
+ if (currentFile.Exists)
{
- var type = CheckExtension(currentFullPath);
- if(type is InputEntryType entryType)
+ currentFullPath = currentFile.FullName;
+ var type = CheckExtension(currentFile);
+ if (type is InputEntryType entryType)
{
currentFiles.Add(new InputEntry(entryType, path, currentFullPath));
}
@@ -77,7 +78,7 @@ namespace HashCalculator.GUI
select file;
var supportedFiles = from file in otherFiles
- let type = CheckExtension(file.Extension)
+ let type = CheckExtension(file)
where type.HasValue
select new InputEntry(type.Value, _search.GetInputStyleName(file), file.FullName);
@@ -91,9 +92,9 @@ namespace HashCalculator.GUI
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;
}
diff --git a/HashCalculator.GUI.csproj b/HashCalculator.GUI.csproj
index 6004613..23e51da 100644
--- a/HashCalculator.GUI.csproj
+++ b/HashCalculator.GUI.csproj
@@ -20,11 +20,11 @@
+
+
+
+
-
- TechnologyAssembler.Core.dll
- true
-
\ No newline at end of file
diff --git a/InputBar.xaml b/InputBar.xaml
index 02027dd..b9697de 100644
--- a/InputBar.xaml
+++ b/InputBar.xaml
@@ -8,7 +8,7 @@
xmlns:c="clr-namespace:HashCalculator.GUI.Converters"
mc:Ignorable="d"
x:ClassModifier="internal"
- x:Name="_this"
+ x:Name="Self"
d:DesignHeight="100" d:DesignWidth="800"
>
@@ -69,12 +69,17 @@
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
+
+ >
+
+
+
+
-
-
-
-
+
+
+
+
+
+ >
+
+
+
-
-
-
-
-
+ />
-
- 本工具基于 Qibbi 提供的 TechnologyAssembler 制作
- 此外使用了 Bradley Grainger 的
- IndexRange
- 从而能在 .NET Standard 2.0 上使用 C# 8.0 的末尾索引操作符
-
- 假如你对本工具有任何疑问或者建议的话,可以来到红警3吧发帖寻找岚依(
-
+
+ 本工具基于 Qibbi 提供的 TechnologyAssembler 制作
+ 此外使用了 Bradley Grainger 的
+ IndexRange
+ 从而能在 .NET Standard 2.0 上使用 C# 8.0 的末尾索引操作符
+
+ 假如你对本工具有任何疑问或者建议的话,可以来到红警3吧发帖寻找岚依(
+
+
+
+
+
+
diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs
index 903b488..7ea4ff0 100644
--- a/MainWindow.xaml.cs
+++ b/MainWindow.xaml.cs
@@ -21,65 +21,37 @@ namespace HashCalculator.GUI
public partial class MainWindow : Window
{
internal ViewModel ViewModel => (ViewModel)DataContext;
+ private bool _autoscroll = true;
public MainWindow()
{
InitializeComponent();
}
- private void OnMainInputTextChanged(object sender, TextChangedEventArgs e)
+ private void OnButtomScrollViewerScrollChanged(object sender, ScrollChangedEventArgs e)
{
- CopyableBox.ShowDialog(async token =>
- {
- var init = DateTimeOffset.UtcNow;
- var i = 0;
- while (i < 100)
- {
- token.ThrowIfCancellationRequested();
- await Task.Delay(100).ConfigureAwait(false);
- ++i;
+ var scrollViewer = (ScrollViewer)e.Source;
+ // User scroll event : set or unset auto-scroll mode
+ if (e.ExtentHeightChange == 0)
+ { // Content unchanged : user scroll event
+ if (scrollViewer.VerticalOffset == scrollViewer.ScrollableHeight)
+ { // Scroll bar is in bottom
+ // Set auto-scroll mode
+ _autoscroll = true;
}
- MessageBox.Show("Completed!");
- return $"Completed after exactly {DateTimeOffset.UtcNow - init}";
- });
- /*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));
+ else
+ { // Scroll bar isn't in bottom
+ // Unset auto-scroll mode
+ _autoscroll = false;
}
- }*/
- }
+ }
- private void OnBigInputTextChanged(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));
- }
- }*/
- }
-
- 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));
- }
- }*/
+ // Content scroll event : auto-scroll eventually
+ if (_autoscroll && e.ExtentHeightChange != 0)
+ { // Content changed and auto-scroll mode set
+ // Autoscroll
+ scrollViewer.ScrollToVerticalOffset(scrollViewer.ExtentHeight);
+ }
}
}
}
diff --git a/ModManifest.cs b/ModManifest.cs
deleted file mode 100644
index 5359939..0000000
--- a/ModManifest.cs
+++ /dev/null
@@ -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 Entries;
-
- public ModManifest()
- {
- var x = Manifest.Load("");
- }
- }
-}
diff --git a/SageHash.cs b/SageHash.cs
index aafe0b5..d4c87ca 100644
--- a/SageHash.cs
+++ b/SageHash.cs
@@ -18,6 +18,15 @@ namespace HashCalculator.GUI
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)
{
if (content == null)
diff --git a/ViewModel.cs b/ViewModel.cs
index 82bbe02..28732af 100644
--- a/ViewModel.cs
+++ b/ViewModel.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
@@ -46,74 +47,52 @@ namespace HashCalculator.GUI
[SuppressMessage("Microsoft.Performance", "CA1812")]
internal class ViewModel : NotifyPropertyChanged
{
- public MainInputViewModel MainInput { get; } = new MainInputViewModel();
- public BigInputViewModel BigEntryInput { get; } = new BigInputViewModel();
- public InputBarViewModel AssetIdInput { get; } = new InputBarViewModel();
+ private static readonly Random _random = new Random();
- private Visibility _bigInputVisibility = Visibility.Collapsed;
- public Visibility BigInputVisibility
+ public MainInputViewModel MainInput { get; }
+ public BigInputViewModel BigEntryInput { get; }
+
+ private ObservableCollection _entries = new ObservableCollection();
+ public ObservableCollection Entries
{
- get => _bigInputVisibility;
- set => SetField(ref _bigInputVisibility, value);
+ get => _entries;
+ set => SetField(ref _entries, value);
}
- private InputEntry? _mainInputResult; // if not null
- private string? _bigManifest;
+ private string _statusText = SuggestionString("不知道该显示些什么呢……");
+ 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()
{
+ var controller = new Controller(this);
+ MainInput = new MainInputViewModel(controller);
+ BigEntryInput = new BigInputViewModel(controller);
}
- [SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
- public async Task OnMainInputDecided(InputEntry selected)
+ public static string SuggestionString(string original)
{
- BigInputVisibility = Visibility.Collapsed;
- switch (selected.Type)
+ var generated = $"{_random.NextDouble()}";
+ System.Diagnostics.Debug.WriteLine(generated);
+ if (generated.Contains("38") || generated.Contains("16"))
{
- case InputEntryType.BinaryFile:
- 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)
- {
- return exception.ToString();
- }
- });
- 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();
+ return "你们都是喂鱼的马甲!(";
}
- }
-
- public async Task ProcessManifest(InputEntry entry)
- {
- var content = await Task.Run(() =>
+ if (generated.IndexOf('2') == 2)
{
- 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();
+ return "本来以为两小时就能写完这个小工具,没想到写了两个星期,开始怀疑自己的智商orz";
+ }
+ return original;
}
}
@@ -133,6 +112,13 @@ namespace HashCalculator.GUI
set => SetField(ref _selectedItem, value);
}
+ private int _selectedIndex;
+ public virtual int SelectedIndex
+ {
+ get => _selectedIndex;
+ set => SetField(ref _selectedIndex, value);
+ }
+
private string? _text;
public virtual string? Text
{
@@ -145,10 +131,9 @@ namespace HashCalculator.GUI
{
private readonly FileSystemSuggestions _suggestions = new FileSystemSuggestions();
- private int _selectedIndex;
- public int SelectedIndex
+ public override int SelectedIndex
{
- get => _selectedIndex;
+ get => base.SelectedIndex;
set
{
if (value == 0)
@@ -159,10 +144,10 @@ namespace HashCalculator.GUI
}
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 BrowseCommand { get; }
public Command SelectCommand { get; }
- public MainInputViewModel()
+ public MainInputViewModel(Controller controller)
{
Items = Enumerable.Empty();
BrowseCommand = new Command(window =>
@@ -200,7 +185,7 @@ namespace HashCalculator.GUI
});
SelectCommand = new Command(async viewModel =>
{
- if(SelectedItem?.IsValid != true)
+ if (SelectedItem?.IsValid != true)
{
return;
}
@@ -209,7 +194,7 @@ namespace HashCalculator.GUI
{
BrowseCommand.CanExecuteValue = false;
SelectCommand.CanExecuteValue = false;
- await viewModel.OnMainInputDecided(SelectedItem).ConfigureAwait(true);
+ await controller.OnMainInputDecided(SelectedItem).ConfigureAwait(true);
}
finally
{
@@ -246,102 +231,105 @@ namespace HashCalculator.GUI
}
}
- [SuppressMessage("Design", "CA1001:具有可释放字段的类型应该是可释放的", Justification = "<挂起>")]
- internal class BigInputViewModel : NotifyPropertyChanged
+ internal class BigInputViewModel : InputBarViewModel
{
- private CancellationTokenSource? _currentCancellator = null;
-
- private IEnumerable? _manifests;
- public IEnumerable? Manifests
+ public override IEnumerable? Items
{
- get => _manifests;
- set => SetField(ref _manifests, value);
+ get => base.Items;
+ set => base.Items = value;
}
- private ManifestContent? _selectedManifest;
- public ManifestContent? SelectedManifest
+ public override InputEntry? SelectedItem
{
- get => _selectedManifest;
+ get => base.SelectedItem;
set
{
- SetField(ref _selectedManifest, value);
+ base.SelectedItem = value;
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? _allManifests;
+ public IEnumerable? AllManifests
+ {
+ get => _allManifests;
+ set
+ {
+ SetField(ref _allManifests, value);
+ Items = value;
+ LastProcessedManifest = null;
+ Text = null;
+ }
+ }
+
public Command SelectCommand { get; }
- public BigInputViewModel()
+ [SuppressMessage("Design", "CA1031:不捕获常规异常类型", Justification = "<挂起>")]
+ public BigInputViewModel(Controller controller)
{
- SelectCommand = new Command(viewModel => viewModel.ProcessManifest(SelectedManifest!));
- }
-
- public async Task LoadBig(string path)
- {
- using var cancellator = _currentCancellator = new CancellationTokenSource();
- var saved = Interlocked.Exchange(ref _currentCancellator, cancellator);
- saved?.Cancel();
-
- var token = cancellator.Token;
- try
+ SelectCommand = new Command(async viewModel =>
{
- using var big = await Task.Run(() => new BigFile(path), token).ConfigureAwait(true);
- var manifests = await Task.Run(() =>
+ var mainInput = viewModel.MainInput;
+ try
{
- var manifests = big.GetFiles(string.Empty, "*.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;
-
- var list = new List();
- foreach (var path in firstManifests.Concat(otherManifests))
- {
- token.ThrowIfCancellationRequested();
- using var stream = big.OpenStream(path);
- list.Add(new ManifestContent(path, stream));
- }
- return list;
- }, token).ConfigureAwait(true);
-
- if (!token.IsCancellationRequested)
- {
- Manifests = manifests;
+ SelectCommand.CanExecuteValue = false;
+ mainInput.BrowseCommand.CanExecuteValue = false;
+ mainInput.SelectCommand.CanExecuteValue = false;
+ LastProcessedManifest = SelectedItem;
+ await controller.ProcessManifest(SelectedItem!.Value).ConfigureAwait(true);
}
+ catch (Exception exception)
+ {
+ CopyableBox.ShowDialog(_ =>
+ {
+ 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;
+ Items = filtered;
+ if (Items.FirstOrDefault()?.Value.Equals(input, StringComparison.OrdinalIgnoreCase) == true)
+ {
+ SelectedIndex = 0;
}
- 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;
}
}
}