static void VerifyArgs(string[] args) { string currentArg; int argcounter = 1; bool fullVerify = true; while ((currentArg = args[argcounter++]).StartsWith("-")) { currentArg = currentArg.ToLower(); if (currentArg == "-fullverify") { fullVerify = args[argcounter++].ToLower() == "on"; } else { HaltAndCatchFire($"Unknown command: \"{currentArg}\"", 1); return; } } string filename = args[--argcounter]; if (!File.Exists(filename)) { HaltAndCatchFire($"Input file does not exist: {filename}", 1); } ExtendedArchive arc; try { arc = new ExtendedArchive(filename); } catch (PpexException ex) { if (ex.ErrorCode == PpexException.PpexErrorCode.FileNotPPXArchive || ex.ErrorCode == PpexException.PpexErrorCode.IncorrectVersionNumber) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(ex.Message); Console.ResetColor(); return; } else { throw; } } Console.CursorVisible = false; Action <int, string> UpdateProgress = (i, x) => { Console.SetCursorPosition(0, Console.CursorTop); string message = $"Verification {i}% complete [{x}]"; message = message.PadRight(Console.BufferWidth - 1); Console.Write(message); }; var orderedChunks = arc.Chunks.OrderBy(x => x.Offset).ToArray(); Console.WriteLine("Archive name: " + arc.Title); Console.WriteLine("File count: " + arc.Files.Count); long currentOffset = 0; long totalOffset = orderedChunks.Sum(x => (long)x.CompressedLength); HashSet <(Md5Hash, ulong, ulong)> verifiedHashes = new HashSet <(Md5Hash, ulong, ulong)>(); for (int i = 0; i < orderedChunks.Length; i++) { ExtendedArchiveChunk chunk = orderedChunks[i]; using var compressedBuffer = MemoryPool <byte> .Shared.Rent((int) chunk.CompressedLength); var compressedMemory = compressedBuffer.Memory.Slice(0, (int)chunk.CompressedLength); using (var rawStream = chunk.GetRawStream()) { rawStream.Read(compressedMemory.Span); } if (chunk.CRC32 != CRC32.Compute(compressedMemory.Span)) { HaltAndCatchFire($"Chunk hash mismatch; id: {chunk.ID}, offset: {chunk.Offset}", 8); } if (fullVerify) { using var uncompressedBuffer = MemoryPool <byte> .Shared.Rent((int) chunk.UncompressedLength); var uncompressedMemory = uncompressedBuffer.Memory.Slice(0, (int)chunk.UncompressedLength); chunk.CopyToMemory(uncompressedMemory); foreach (var file in chunk.Files) { var expectedHash = (file.RawSource.Md5, file.RawSource.Offset, file.RawSource.Size); if (verifiedHashes.Contains(expectedHash)) { continue; } var actualMd5 = Utility.GetMd5(uncompressedMemory.Span.Slice((int)file.RawSource.Offset, (int)file.RawSource.Size)); if ((Md5Hash)file.RawSource.Md5 != (Md5Hash)actualMd5) { HaltAndCatchFire($"File md5 mismatch; chunk id: {chunk.ID}, archive: {file.ArchiveName}, file: {file.Name}", 9); } verifiedHashes.Add((actualMd5, file.RawSource.Offset, file.RawSource.Size)); } verifiedHashes.Clear(); } currentOffset += (long)chunk.CompressedLength; //int percentage = (int)((float)(100 * i) / (float)orderedChunks.Length); int percentage = (int)Math.Round((float)(100 * currentOffset) / (float)totalOffset); UpdateProgress(percentage, $"{i + 1}/{orderedChunks.Length} : {ConvertToReadable(currentOffset)}/{ConvertToReadable(totalOffset)}"); } Console.WriteLine(); Console.WriteLine($"\"{arc.Title}\" successfully verified."); Console.CursorVisible = true; }