Skip to content

Instantly share code, notes, and snippets.

@milnak
Created December 2, 2022 08:57
Show Gist options
  • Save milnak/a6e522c81d9aa2da3b60ae83e49208e4 to your computer and use it in GitHub Desktop.
Save milnak/a6e522c81d9aa2da3b60ae83e49208e4 to your computer and use it in GitHub Desktop.
SFV tool: Adaptation of SFVCON to PowerShell #crc #sfv
# 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