using System.Security.Cryptography; namespace AcDream.Launcher.Core.Integrity; /// /// Streaming SHA-256 for pak/download verification, consumed by the /// install engine (LA9) and the updater (LA10). Kept minimal in this /// slice: hash a file and compare its hex digest. /// public static class FileIntegrity { /// /// Computes the lower-case hex SHA-256 digest of a file, streaming it /// from disk rather than loading it fully into memory (relevant for /// the ~30 GB pak file LA9 verifies). /// public static string ComputeSha256Hex(string filePath) { ArgumentException.ThrowIfNullOrWhiteSpace(filePath); using FileStream stream = new( filePath, FileMode.Open, FileAccess.Read, FileShare.Read); byte[] hash = SHA256.HashData(stream); return Convert.ToHexStringLower(hash); } /// /// Asynchronous, cancellable counterpart used while verifying a multi- /// gigabyte prepared package. The file stays streamed and no buffer is /// retained after the hash completes. /// public static async Task ComputeSha256HexAsync( string filePath, CancellationToken cancellationToken = default) { ArgumentException.ThrowIfNullOrWhiteSpace(filePath); await using FileStream stream = new( filePath, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 1024 * 1024, options: FileOptions.Asynchronous | FileOptions.SequentialScan); byte[] hash = await SHA256.HashDataAsync(stream, cancellationToken) .ConfigureAwait(false); return Convert.ToHexStringLower(hash); } /// Case-insensitive hex comparison — callers may receive an /// expected digest in either case from a manifest or a hand-typed /// fixture. public static bool Matches(string actualHex, string expectedHex) { ArgumentNullException.ThrowIfNull(actualHex); ArgumentNullException.ThrowIfNull(expectedHex); return string.Equals(actualHex, expectedHex, StringComparison.OrdinalIgnoreCase); } /// Computes and compares in one call. public static bool Verify(string filePath, string expectedHex) => Matches(ComputeSha256Hex(filePath), expectedHex); }