Created
November 9, 2025 05:04
-
-
Save brianonn/8546f6d1aa286fe2229c2eff72507d57 to your computer and use it in GitHub Desktop.
SFV verifier
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
| #!/usr/bin/env python3 | |
| """ | |
| SFV (Simple File Verification) file validator. | |
| Reads an SFV file and verifies the CRC32 checksum of each listed file. | |
| """ | |
| import sys | |
| import os | |
| import zlib | |
| def calculate_crc32(filepath): | |
| """ | |
| Calculate the CRC32 checksum for a file. | |
| Returns the checksum as an 8-character uppercase hex string. | |
| """ | |
| crc = 0 | |
| try: | |
| with open(filepath, 'rb') as f: | |
| # Read file in chunks to handle large files efficiently | |
| while chunk := f.read(65536): # 64KB chunks | |
| crc = zlib.crc32(chunk, crc) | |
| # Convert to unsigned 32-bit value and format as 8-digit hex | |
| return format(crc & 0xFFFFFFFF, '08X') | |
| except IOError as e: | |
| raise IOError(f"Error reading file '{filepath}': {e}") | |
| def parse_sfv_line(line): | |
| """ | |
| Parse a line from an SFV file. | |
| Returns (filename, expected_crc) tuple or None if it's a comment/empty line. | |
| """ | |
| # Strip whitespace | |
| line = line.strip() | |
| # Skip empty lines and comments (lines starting with semicolon) | |
| if not line or line.startswith(';'): | |
| return None | |
| # Split on whitespace - last token should be the CRC32 | |
| parts = line.rsplit(None, 1) | |
| if len(parts) != 2: | |
| return None | |
| filename, crc = parts | |
| # CRC should be 8 hex characters | |
| if len(crc) == 8 and all(c in '0123456789ABCDEFabcdef' for c in crc): | |
| return filename, crc.upper() | |
| return None | |
| def verify_sfv_file(sfv_filepath): | |
| """ | |
| Read and verify all files listed in the SFV file. | |
| Returns tuple of (total_files, passed_files, failed_files). | |
| """ | |
| if not os.path.exists(sfv_filepath): | |
| print(f"ERROR: SFV file '{sfv_filepath}' not found") | |
| return 0, 0, 0 | |
| # Get the directory containing the SFV file to resolve relative paths | |
| sfv_dir = os.path.dirname(os.path.abspath(sfv_filepath)) | |
| total = 0 | |
| passed = 0 | |
| failed = 0 | |
| try: | |
| with open(sfv_filepath, 'r', encoding='utf-8', errors='replace') as sfv: | |
| for line_num, line in enumerate(sfv, 1): | |
| result = parse_sfv_line(line) | |
| if result is None: | |
| continue | |
| filename, expected_crc = result | |
| total += 1 | |
| # Resolve file path relative to SFV file location | |
| filepath = os.path.join(sfv_dir, filename) | |
| # Check if file exists | |
| if not os.path.exists(filepath): | |
| print(f"FAIL: File not found: {filename}") | |
| failed += 1 | |
| continue | |
| # Check if it's a directory | |
| if os.path.isdir(filepath): | |
| print(f"FAIL: Path is a directory, not a file: {filename}") | |
| failed += 1 | |
| continue | |
| # Calculate CRC32 | |
| try: | |
| calculated_crc = calculate_crc32(filepath) | |
| if calculated_crc == expected_crc: | |
| print(f"OK: {filename}") | |
| passed += 1 | |
| else: | |
| print(f"FAIL: {filename}") | |
| print(f" Expected: {expected_crc}, Got: {calculated_crc}") | |
| failed += 1 | |
| except IOError as e: | |
| print(f"FAIL: {filename}") | |
| print(f" {e}") | |
| failed += 1 | |
| except IOError as e: | |
| print(f"ERROR: Cannot read SFV file '{sfv_filepath}': {e}") | |
| return 0, 0, 0 | |
| return total, passed, failed | |
| def main(): | |
| if len(sys.argv) != 2: | |
| print("Usage: python sfv_validator.py <sfv_file>") | |
| print("Example: python sfv_validator.py files.sfv") | |
| sys.exit(1) | |
| sfv_file = sys.argv[1] | |
| print(f"Verifying files listed in: {sfv_file}") | |
| print("-" * 60) | |
| total, passed, failed = verify_sfv_file(sfv_file) | |
| print("-" * 60) | |
| if total == 0: | |
| print("No files found to verify in SFV file") | |
| else: | |
| print(f"Total: {total} files") | |
| print(f"Passed: {passed} files") | |
| print(f"Failed: {failed} files") | |
| if failed == 0: | |
| print("\nAll files verified successfully!") | |
| sys.exit(0) | |
| else: | |
| print(f"\nVerification failed for {failed} file(s)") | |
| sys.exit(1) | |
| if __name__ == "__main__": | |
| main() |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Description
Verify the CRCs listed in SFV files against their file contents
SFV files have the format:
i.e.
Usage