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