HashCalculator.GUI/Controller.cs

228 lines
9.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}