228 lines
9.4 KiB
C#
228 lines
9.4 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|