Created
December 2, 2022 08:57
-
-
Save milnak/a6e522c81d9aa2da3b60ae83e49208e4 to your computer and use it in GitHub Desktop.
SFV tool: Adaptation of SFVCON to PowerShell #crc #sfv
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # TODO: | |
| # sfv.exe takes in a file that can include a path. | |
| # | |
| # sfv.exe supports -rep, -noperc | |
| # | |
| # sfv seems to have a filename truncation routine where it puts ellipses. Not sure what the logic is. | |
| # should be: | |
| # │ [ 4/ 23] Info/05 - Roxy Music - While My Hear...ng.dsf_report.png MISS | |
| # │ [ 5/ 23] Info/07 - Roxy Music - Take A Chance W...dsf_report.png MISS | |
| # not: | |
| # │ [ 4/ 23] Info/05 - Roxy Music - While My Heart Is Still Beating.ds OK | |
| # │ [ 5/ 23] Info/07 - Roxy Music - Take A Chance With Me.dsf_report.p OK | |
| param([string]$File = '*.sfv') | |
| # Bit order of polynomial representation is reversed. | |
| # 0xEDB88320 means reversed bit order representation of the following polynomial | |
| # x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + x^5 + x^4 + x^2 + x + 1 | |
| Add-Type -TypeDefinition @" | |
| // Copyright (c) Damien Guard. All rights reserved. | |
| // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Security.Cryptography; | |
| /// <summary> | |
| /// Implements a 32-bit CRC hash algorithm compatible with Zip etc. | |
| /// </summary> | |
| /// <remarks> | |
| /// Crc32 should only be used for backward compatibility with older file formats | |
| /// and algorithms. It is not secure enough for new applications. | |
| /// If you need to call multiple times for the same data either use the HashAlgorithm | |
| /// interface or remember that the result of one Compute call needs to be ~ (XOR) before | |
| /// being passed in as the seed for the next Compute call. | |
| /// </remarks> | |
| public sealed class Crc32 : HashAlgorithm | |
| { | |
| public const UInt32 DefaultPolynomial = 0xedb88320u; | |
| public const UInt32 DefaultSeed = 0xffffffffu; | |
| static UInt32[] defaultTable; | |
| readonly UInt32 seed; | |
| readonly UInt32[] table; | |
| UInt32 hash; | |
| public Crc32() | |
| : this(DefaultPolynomial, DefaultSeed) | |
| { | |
| } | |
| public Crc32(UInt32 polynomial, UInt32 seed) | |
| { | |
| table = InitializeTable(polynomial); | |
| this.seed = hash = seed; | |
| } | |
| public override void Initialize() | |
| { | |
| hash = seed; | |
| } | |
| protected override void HashCore(byte[] array, int ibStart, int cbSize) | |
| { | |
| hash = CalculateHash(table, hash, array, ibStart, cbSize); | |
| } | |
| protected override byte[] HashFinal() | |
| { | |
| var hashBuffer = UInt32ToBigEndianBytes(~hash); | |
| HashValue = hashBuffer; | |
| return hashBuffer; | |
| } | |
| public override int HashSize { get { return 32; } } | |
| public static UInt32 Compute(byte[] buffer) | |
| { | |
| return Compute(DefaultSeed, buffer); | |
| } | |
| public static UInt32 Compute(UInt32 seed, byte[] buffer) | |
| { | |
| return Compute(DefaultPolynomial, seed, buffer); | |
| } | |
| public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer) | |
| { | |
| return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length); | |
| } | |
| static UInt32[] InitializeTable(UInt32 polynomial) | |
| { | |
| if (polynomial == DefaultPolynomial && defaultTable != null) | |
| return defaultTable; | |
| var createTable = new UInt32[256]; | |
| for (var i = 0; i < 256; i++) | |
| { | |
| var entry = (UInt32)i; | |
| for (var j = 0; j < 8; j++) | |
| if ((entry & 1) == 1) | |
| entry = (entry >> 1) ^ polynomial; | |
| else | |
| entry = entry >> 1; | |
| createTable[i] = entry; | |
| } | |
| if (polynomial == DefaultPolynomial) | |
| defaultTable = createTable; | |
| return createTable; | |
| } | |
| static UInt32 CalculateHash(UInt32[] table, UInt32 seed, IList<byte> buffer, int start, int size) | |
| { | |
| var hash = seed; | |
| for (var i = start; i < start + size; i++) | |
| hash = (hash >> 8) ^ table[buffer[i] ^ hash & 0xff]; | |
| return hash; | |
| } | |
| static byte[] UInt32ToBigEndianBytes(UInt32 uint32) | |
| { | |
| var result = BitConverter.GetBytes(uint32); | |
| if (BitConverter.IsLittleEndian) | |
| Array.Reverse(result); | |
| return result; | |
| } | |
| } | |
| "@ -PassThru | Out-Null | |
| Function Get-FileHashCRC32 { | |
| Param ( | |
| [Parameter(Mandatory = $true)][String]$Path | |
| ) | |
| $ErrorActionPreference = "Stop" | |
| $crc32 = New-Object Crc32 | |
| $stream = New-Object IO.FileStream($Path, [System.IO.FileMode]::Open) | |
| $hash = [String]::Empty | |
| foreach ($byte in $crc32.ComputeHash($stream)) { | |
| $hash += $byte.toString('x2').toUpper() | |
| } | |
| $stream.Close() | |
| $hash | |
| } | |
| @' | |
| SFV/CON v1.6 (Win9x/NT/2000) | |
| (C) 2001 seaw0lf (www.amok.am) | |
| [+] Looking for SFV files... | |
| '@ | |
| $sfvs = Get-ChildItem -Filter $File -File -ErrorAction SilentlyContinue | |
| if ($sfvs.Count -eq 0) { | |
| @' | |
| │ | |
| └─ No files to check | |
| '@ | |
| return | |
| } | |
| @' | |
| │ {0} file(s) found. | |
| │ | |
| '@ -f $sfvs.Count | |
| $missing = 0 | |
| $errors = 0 | |
| $sfvs | ForEach-Object { | |
| @' | |
| [=] {0} | |
| │ | |
| '@ -f $_.Name | |
| $folder = Split-Path -Parent $_.FullName | |
| # Need to replace [ with `[ | |
| $files = Get-Content -Path ($_.FullName -replace '\[', '``[') ` | Select-String -Pattern '^([^;].*) ([A-F0-9]{8})$' ` | |
| $i = 1 | |
| $files | ForEach-Object { | |
| $sfv_file = $_.Matches.Groups[1].value | |
| $crc = $_.Matches.Groups[2].value | |
| if (-not (Test-Path -Path $sfv_file -PathType Leaf)) { | |
| $missing++ | |
| $result = 'MISS' | |
| } | |
| else { | |
| if ((Get-FileHashCRC32 (Join-Path $folder $sfv_file)) -eq $crc) { | |
| $result = ' OK ' | |
| } | |
| else { | |
| $errors++ | |
| $result = 'ERROR' | |
| } | |
| } | |
| $file_format = ('{0} {1}' -f $file, ('.' * 57)).Substring(0, 57) | |
| ' │ [{0,3}/{1,3}] {2}{3}' -f $i, $files.Count, $file_format, $result | |
| $i++ | |
| } | |
| ' │' | |
| } | |
| ' └─ Completed.' | |
| if (!$missing -and !$errors) { | |
| ' All OK.' | |
| } | |
| else { | |
| if ($missing) { | |
| ' {0} file{1} missing.' -f $missing, @('', 's')[$missing -ne 1] | |
| } | |
| if ($errors) { | |
| ' {0} file{1} error.' -f $errors, @('', 's')[$errors -ne 1] | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment