This repository has been archived on 2025-05-24. You can view files and clone it, but cannot push or open issues or pull requests.
Files
RomVault/ROMVault2/SupportedFiles/CHD/CHD.cs
2014-09-23 12:06:15 -07:00

439 lines
18 KiB
C#

/******************************************************
* ROMVault2 is written by Gordon J. *
* Contact gordon@romvault.com *
* Copyright 2014 *
******************************************************/
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using ROMVault2.IO;
using File = System.IO.File;
using Path = System.IO.Path;
namespace ROMVault2.SupportedFiles.CHD
{
public static class CHD
{
private const int MaxHeader = 124;
public static int CheckFile(IO.FileInfo ofile, out byte[] SHA1CHD, out byte[] MD5CHD, out uint? version)
{
SHA1CHD = null;
MD5CHD = null;
version = null;
string ext = IO.Path.GetExtension(ofile.Name).ToLower();
if (ext != ".chd")
return 0;
if (ofile.Length < MaxHeader)
return 0;
Stream s;
int retval = IO.FileStream.OpenFileRead(ofile.FullName, out s);
if (retval != 0)
return retval;
if (s == null) return 1;
CheckFile(s, out SHA1CHD, out MD5CHD, out version);
s.Close();
s.Dispose();
return 0;
}
private static void CheckFile(Stream s, out byte[] SHA1CHD, out byte[] MD5CHD, out uint? version)
{
using (BinaryReader br = new BinaryReader(s))
{
byte[] buff = br.ReadBytes(MaxHeader);
CheckFile(buff, out SHA1CHD, out MD5CHD, out version);
}
}
private static void CheckFile(byte[] buff, out byte[] SHA1CHD, out byte[] MD5CHD, out uint? version)
{
SHA1CHD = null;
MD5CHD = null;
version = null;
UInt32 compression = 0;
byte[] header = new[] { (byte)'M', (byte)'C', (byte)'o', (byte)'m', (byte)'p', (byte)'r', (byte)'H', (byte)'D' };
for (int i = 0; i < header.Length; i++)
{
if (buff[i] != header[i])
return;
}
UInt32 length = ReadUInt32(buff, 8);
version = ReadUInt32(buff, 12);
if (version > 5)
return;
switch (version)
{
case 1:
if (length != 76) return;
// V1 header:
// [ 0] char tag[8]; // 'MComprHD'
// [ 8] UINT32 length; // length of header (including tag and length fields)
// [ 12] UINT32 version; // drive format version
// [ 16] UINT32 flags; // flags (see below)
// [ 20] UINT32 compression; // compression type
compression = ReadUInt32(buff, 20);
if (compression == 0) return;
// [ 24] UINT32 hunksize; // 512-byte sectors per hunk
// [ 28] UINT32 totalhunks; // total # of hunks represented
// [ 32] UINT32 cylinders; // number of cylinders on hard disk
// [ 36] UINT32 heads; // number of heads on hard disk
// [ 40] UINT32 sectors; // number of sectors on hard disk
// [ 44] UINT8 md5[16]; // MD5 checksum of raw data
MD5CHD = ReadBytes(buff, 44, 16);
// [ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file
// [ 76] (V1 header length)
return;
case 2:
if (length != 80) return;
// V2 header:
// [ 0] char tag[8]; // 'MComprHD'
// [ 8] UINT32 length; // length of header (including tag and length fields)
// [ 12] UINT32 version; // drive format version
// [ 16] UINT32 flags; // flags (see below)
// [ 20] UINT32 compression; // compression type
compression = ReadUInt32(buff, 20);
if (compression == 0) return;
// [ 24] UINT32 hunksize; // seclen-byte sectors per hunk
// [ 28] UINT32 totalhunks; // total # of hunks represented
// [ 32] UINT32 cylinders; // number of cylinders on hard disk
// [ 36] UINT32 heads; // number of heads on hard disk
// [ 40] UINT32 sectors; // number of sectors on hard disk
// [ 44] UINT8 md5[16]; // MD5 checksum of raw data
MD5CHD = ReadBytes(buff, 44, 16);
// [ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file
// [ 76] UINT32 seclen; // number of bytes per sector
// [ 80] (V2 header length)
return;
case 3:
if (length != 120) return;
// V3 header:
// [ 0] char tag[8]; // 'MComprHD'
// [ 8] UINT32 length; // length of header (including tag and length fields)
// [ 12] UINT32 version; // drive format version
// [ 16] UINT32 flags; // flags (see below)
// [ 20] UINT32 compression; // compression type
compression = ReadUInt32(buff, 20);
if (compression == 0) return;
// [ 24] UINT32 totalhunks; // total # of hunks represented
// [ 28] UINT64 logicalbytes; // logical size of the data (in bytes)
// [ 36] UINT64 metaoffset; // offset to the first blob of metadata
// [ 44] UINT8 md5[16]; // MD5 checksum of raw data
MD5CHD = ReadBytes(buff, 44, 16);
// [ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file
// [ 76] UINT32 hunkbytes; // number of bytes per hunk
// [ 80] UINT8 sha1[20]; // SHA1 checksum of raw data
SHA1CHD = ReadBytes(buff, 80, 20);
// [100] UINT8 parentsha1[20];// SHA1 checksum of parent file
// [120] (V3 header length)
return;
case 4:
if (length != 108) return;
// V4 header:
// [ 0] char tag[8]; // 'MComprHD'
// [ 8] UINT32 length; // length of header (including tag and length fields)
// [ 12] UINT32 version; // drive format version
// [ 16] UINT32 flags; // flags (see below)
// [ 20] UINT32 compression; // compression type
compression = ReadUInt32(buff, 20);
if (compression == 0) return;
// [ 24] UINT32 totalhunks; // total # of hunks represented
// [ 28] UINT64 logicalbytes; // logical size of the data (in bytes)
// [ 36] UINT64 metaoffset; // offset to the first blob of metadata
// [ 44] UINT32 hunkbytes; // number of bytes per hunk
// [ 48] UINT8 sha1[20]; // combined raw+meta SHA1
SHA1CHD = ReadBytes(buff, 48, 20);
// [ 68] UINT8 parentsha1[20];// combined raw+meta SHA1 of parent
// [ 88] UINT8 rawsha1[20]; // raw data SHA1
// [108] (V4 header length)
return;
case 5:
if (length != 124) return;
// V5 header:
// [ 0] char tag[8]; // 'MComprHD'
// [ 8] UINT32 length; // length of header (including tag and length fields)
// [ 12] UINT32 version; // drive format version
// [ 16] UINT32 compressors[4];// which custom compressors are used?
UInt32 compression0 = ReadUInt32(buff, 16);
UInt32 compression1 = ReadUInt32(buff, 20);
UInt32 compression2 = ReadUInt32(buff, 24);
UInt32 compression3 = ReadUInt32(buff, 28);
if (compression0 == 0 && compression1 == 0 && compression2 == 0 && compression3 == 0) return;
// [ 32] UINT64 logicalbytes; // logical size of the data (in bytes)
// [ 40] UINT64 mapoffset; // offset to the map
// [ 48] UINT64 metaoffset; // offset to the first blob of metadata
// [ 56] UINT32 hunkbytes; // number of bytes per hunk (512k maximum)
// [ 60] UINT32 unitbytes; // number of bytes per unit within each hunk
// [ 64] UINT8 rawsha1[20]; // raw data SHA1
// [ 84] UINT8 sha1[20]; // combined raw+meta SHA1
SHA1CHD = ReadBytes(buff, 84, 20);
// [104] UINT8 parentsha1[20];// combined raw+meta SHA1 of parent
// [124] (V5 header length)
return;
}
}
private static UInt32 ReadUInt32(byte[] b, int p)
{
return
((UInt32)b[p + 0]) << 24 |
((UInt32)b[p + 1]) << 16 |
((UInt32)b[p + 2]) << 8 |
(b[p + 3]);
}
private static byte[] ReadBytes(byte[] b, int p, int length)
{
byte[] ret = new byte[length];
for (int i = 0; i < length; i++)
ret[i] = b[p + i];
return ret;
}
private static string _result;
private static BackgroundWorker _bgw;
private static CHDManCheck _resultType;
public enum CHDManCheck
{
Unset,
Good,
Corrupt,
CHDNotFound,
ChdmanNotFound,
CHDUnknownError,
CHDReturnError
}
public static CHDManCheck ChdmanCheck(string filename, BackgroundWorker bgw, out string result)
{
_bgw = bgw;
_result = "";
_resultType = CHDManCheck.Unset;
string chdPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "chdman.exe");
if (!File.Exists(chdPath))
{
result = "chdman not found";
return CHDManCheck.ChdmanNotFound;
}
if (!IO.File.Exists(filename))
{
result = filename + " Not Found.";
return CHDManCheck.CHDNotFound;
}
string shortName = NameFix.GetShortPath(filename);
using (Process exeProcess = new Process())
{
exeProcess.StartInfo.FileName = chdPath;
ReportError.LogOut("CHD: FileName :" + exeProcess.StartInfo.FileName);
exeProcess.StartInfo.Arguments = "verify -i \"" + shortName + "\"";
ReportError.LogOut("CHD: Arguments :" + exeProcess.StartInfo.Arguments);
// Set UseShellExecute to false for redirection.
exeProcess.StartInfo.UseShellExecute = false;
// Stops the Command window from popping up.
exeProcess.StartInfo.CreateNoWindow = true;
// Redirect the standard output.
// This stream is read asynchronously using an event handler.
exeProcess.StartInfo.RedirectStandardOutput = true;
exeProcess.StartInfo.RedirectStandardError = true;
// Set our event handler to asynchronously read the process output.
exeProcess.OutputDataReceived += CHDOutputHandler;
exeProcess.ErrorDataReceived += CHDErrorHandler;
_outputLineCount = 0;
_errorLines=0;
ReportError.LogOut("CHD: Scanning Starting");
exeProcess.Start();
// Start the asynchronous read of the process output stream.
exeProcess.BeginOutputReadLine();
exeProcess.BeginErrorReadLine();
// Wait for the process finish.
exeProcess.WaitForExit();
ReportError.LogOut("CHD: Scanning Finished");
}
result = _result;
if (_resultType == CHDManCheck.Unset)
_resultType = CHDManCheck.Good;
_bgw.ReportProgress(0, new bgwText3(""));
ReportError.LogOut("CHD: returning result " + _resultType + " " + result);
return _resultType;
}
/*
chdman - MAME Compressed Hunks of Data (CHD) manager 0.147 (Sep 18 2012)
Raw SHA1 verification successful!
Overall SHA1 verification successful!
*/
private static int _outputLineCount;
private static void CHDOutputHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
// Collect the process command output.
if (String.IsNullOrEmpty(outLine.Data)) return;
string sOut = outLine.Data;
//ReportError.LogOut("CHDOutput: " + _outputLineCount + " : " + sOut);
switch (_outputLineCount)
{
case 0:
if (sOut.Length < 53 || sOut.Substring(0, 53) != "chdman - MAME Compressed Hunks of Data (CHD) manager ")
{
_result = "Incorrect startup of CHDMan :" + sOut;
_resultType = CHDManCheck.CHDReturnError;
}
break;
case 1:
if (sOut != "Raw SHA1 verification successful!")
{
_result = "Raw SHA1 check failed :" + sOut;
_resultType = CHDManCheck.CHDReturnError;
}
break;
case 2:
if (sOut != "Overall SHA1 verification successful!")
{
_result = "Overall SHA1 check failed :" + sOut;
_resultType = CHDManCheck.CHDReturnError;
}
break;
default:
_result = "Unexpected output from chdman :" + sOut;
ReportError.SendErrorMessage("CHDOutputHandler returned =" + sOut);
_resultType = CHDManCheck.CHDUnknownError;
break;
}
_outputLineCount++;
}
private static int _errorLines;
private static void CHDErrorHandler(object sendingProcess, DataReceivedEventArgs outLine)
{
// Collect the process command output.
if (String.IsNullOrEmpty(outLine.Data)) return;
string sOut = outLine.Data;
ReportError.LogOut("CHDError: " + sOut);
_bgw.ReportProgress(0, new bgwText3(sOut));
if (_resultType != CHDManCheck.Unset)
{
if(_errorLines>0)
{
_errorLines -= 1;
_result += "\r\n" + sOut;
}
return;
}
if (sOut.Length > 44)
if (sOut.Substring(0, 23) == "Error reading CHD file " && sOut.Substring(sOut.Length - 21, 21) == ": decompression error")
{
_result = sOut;
_resultType = CHDManCheck.Corrupt;
return;
}
if (sOut.Length > 35)
if (sOut.Substring(0, 23) == "Error opening CHD file " && sOut.Substring(sOut.Length - 21, 21) == ": decompression error")
{
_result = sOut;
_resultType = CHDManCheck.Corrupt;
return;
}
if (sOut.Length > 35)
if (sOut.Substring(0, 23) == "Error opening CHD file " && sOut.Substring(sOut.Length - 12, 12) == ": read error")
{
_result = sOut;
_resultType = CHDManCheck.Corrupt;
return;
}
if (sOut.Length > 25)
if (sOut.Substring(0, 25) == "Error: Raw SHA1 in header")
{
_result = sOut;
_errorLines = 1;
_resultType = CHDManCheck.Corrupt;
return;
}
if (sOut.Length == 13)
if (sOut == "Out of memory")
{
_result = sOut;
_resultType = CHDManCheck.Corrupt;
return;
}
// check for Verifying message
if (sOut.Length >= 24)
if (sOut.Substring(0, 11) == "Verifying, " && sOut.Substring(sOut.Length - 13, 13) == " complete... ")
{
//_resultType = CHDManCheck.Good;
return;
}
ReportError.SendErrorMessage("CHDErrorHandler returned =" + sOut);
_result = "Unknown message : " + sOut;
_resultType = CHDManCheck.CHDUnknownError;
}
}
}