/******************************************************
* ROMVault2 is written by Gordon J. *
* Contact gordon@romvault.com *
* Copyright 2014 *
******************************************************/
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Security.Cryptography;
using ROMVault2.RvDB;
using ROMVault2.SupportedFiles;
using ROMVault2.SupportedFiles.SevenZip;
using ROMVault2.SupportedFiles.Zip;
using ROMVault2.SupportedFiles.Zip.ZLib;
using ROMVault2.Utils;
namespace ROMVault2
{
public enum ReturnCode { Good, RescanNeeded, FindFixes, LogicError, FileSystemError, SourceCRCCheckSumError, SourceCheckSumError, DestinationCheckSumError, StartOver }
public static class FixFileCopy
{
private const int BufferSize = 4096 * 128;
private static byte[] _buffer;
// This Function returns:
// Good : Everything Worked Correctly
// RescanNeeded : Something unexpectidly changed in the files, so Stop prompt user to rescan.
// LogicError : This Should never happen and is a logic problem in the code
// FileSystemError : Something is wrong with the files
///
/// Performs the RomVault File Copy, with the source and destination being files or zipped files
///
/// This is the file being copied, it may be a zipped file or a regular file system file
/// This is the zip file that is being writen to, if it is null a new zip file will be made if we are coping to a zip
/// This is the name of the .zip file to be made that will be created in zipFileOut
/// This is the actual output filename
/// if true then we will do a raw copy, this is so that we can copy corrupt zips
/// This is the returned error message if this copy fails
/// If we are SHA1/MD5 checking the source file for the first time, and it is different from what we expected the correct values for this file are returned in foundFile
/// ReturnCode.Good is the valid return code otherwire we have an error
public static ReturnCode CopyFile(RvFile fileIn, ref ICompress zipFileOut, string zipFilenameOut, RvFile fileOut, bool forceRaw, out string error, out RvFile foundFile)
{
foundFile = null;
error = "";
if (_buffer == null)
_buffer = new byte[BufferSize];
bool rawCopy = RawCopy(fileIn, fileOut, forceRaw);
ulong streamSize = 0;
ushort compressionMethod = 8;
bool sourceTrrntzip = false;
ICompress zipFileIn = null;
Stream readStream = null;
ReturnCode retC;
bool isZeroLengthFile = DBHelper.IsZeroLengthFile(fileOut);
if (!isZeroLengthFile)
{
retC = CheckInputAndOutputFile(fileIn, fileOut, out error);
if (retC != ReturnCode.Good)
return retC;
retC = OpenInputStream(fileIn, rawCopy, out zipFileIn, out sourceTrrntzip, out readStream, out streamSize, out compressionMethod, out error);
if (retC != ReturnCode.Good)
return retC;
}
else
{
sourceTrrntzip = true;
}
if (!rawCopy && (Settings.FixLevel == eFixLevel.TrrntZipLevel1 || Settings.FixLevel == eFixLevel.TrrntZipLevel2 || Settings.FixLevel == eFixLevel.TrrntZipLevel3))
compressionMethod = 8;
#region Find and Check/Open Output Files
Stream writeStream;
retC = OpenOutputStream(fileOut, fileIn, ref zipFileOut, zipFilenameOut, compressionMethod, rawCopy, sourceTrrntzip, out writeStream, out error);
if (retC != ReturnCode.Good)
return retC;
#endregion
byte[] bCRC;
byte[] bMD5 = null;
byte[] bSHA1 = null;
if (!isZeroLengthFile)
{
#region Do Data Tranfer
CRC32Hash crc32 = null;
MD5 md5 = null;
SHA1 sha1 = null;
if (!rawCopy)
{
crc32 = new CRC32Hash();
md5 = MD5.Create();
sha1 = SHA1.Create();
}
ulong sizetogo = streamSize;
while (sizetogo > 0)
{
int sizenow = sizetogo > BufferSize ? BufferSize : (int)sizetogo;
try
{
readStream.Read(_buffer, 0, sizenow);
}
catch (ZlibException)
{
if (fileIn.FileType == FileType.ZipFile && zipFileIn != null)
{
ZipReturn zr = zipFileIn.ZipFileCloseReadStream();
if (zr != ZipReturn.ZipGood)
{
error = "Error Closing " + zr + " Stream :" + zipFileIn.Filename(fileIn.ReportIndex);
return ReturnCode.FileSystemError;
}
zipFileIn.ZipFileClose();
}
else
{
readStream.Close();
}
if (fileOut.FileType == FileType.ZipFile)
{
ZipReturn zr = zipFileOut.ZipFileCloseWriteStream(new byte[] { 0, 0, 0, 0 });
if (zr != ZipReturn.ZipGood)
{
error = "Error Closing Stream " + zr;
return ReturnCode.FileSystemError;
}
zipFileOut.ZipFileRollBack();
}
else
{
writeStream.Flush();
writeStream.Close();
IO.File.Delete(zipFilenameOut);
}
error = "Error in Data Stream";
return ReturnCode.SourceCRCCheckSumError;
}
catch (Exception e)
{
error = "Error reading Source File " + e.Message;
return ReturnCode.FileSystemError;
}
if (!rawCopy)
{
crc32.TransformBlock(_buffer, 0, sizenow, null, 0);
md5.TransformBlock(_buffer, 0, sizenow, null, 0);
sha1.TransformBlock(_buffer, 0, sizenow, null, 0);
}
try
{
writeStream.Write(_buffer, 0, sizenow);
}
catch (Exception e)
{
Debug.WriteLine(e.Message);
error = "Error writing out file. " + Environment.NewLine + e.Message;
return ReturnCode.FileSystemError;
}
sizetogo = sizetogo - (ulong)sizenow;
}
writeStream.Flush();
#endregion
#region Collect Checksums
// if we did a full copy then we just calculated all the checksums while doing the copy
if (!rawCopy)
{
crc32.TransformFinalBlock(_buffer, 0, 0);
md5.TransformFinalBlock(_buffer, 0, 0);
sha1.TransformFinalBlock(_buffer, 0, 0);
bCRC = crc32.Hash;
bMD5 = md5.Hash;
bSHA1 = sha1.Hash;
}
// if we raw copied and the source file has been FileChecked then we can trust the checksums in the source file
else
{
bCRC = ArrByte.Copy(fileIn.CRC);
if (fileIn.FileStatusIs(FileStatus.MD5Verified)) bMD5 = ArrByte.Copy(fileIn.MD5);
if (fileIn.FileStatusIs(FileStatus.SHA1Verified)) bSHA1 = ArrByte.Copy(fileIn.SHA1);
}
#endregion
#region close the ReadStream
if ((fileIn.FileType == FileType.ZipFile || fileIn.FileType == FileType.SevenZipFile) && zipFileIn != null)
{
ZipReturn zr = zipFileIn.ZipFileCloseReadStream();
if (zr != ZipReturn.ZipGood)
{
error = "Error Closing " + zr + " Stream :" + zipFileIn.Filename(fileIn.ReportIndex);
return ReturnCode.FileSystemError;
}
zipFileIn.ZipFileClose();
}
else
{
readStream.Close();
//if (IO.File.Exists(tmpFilename))
// IO.File.Delete(tmpFilename);
}
#endregion
}
else
{
// Zero Length File (Directory in a Zip)
if (fileOut.FileType == FileType.ZipFile)
{
zipFileOut.ZipFileAddDirectory();
}
bCRC = VarFix.CleanMD5SHA1("00000000", 8);
bMD5 = VarFix.CleanMD5SHA1("d41d8cd98f00b204e9800998ecf8427e", 32);
bSHA1 = VarFix.CleanMD5SHA1("da39a3ee5e6b4b0d3255bfef95601890afd80709", 40);
}
#region close the WriteStream
if (fileOut.FileType == FileType.ZipFile || fileOut.FileType == FileType.SevenZipFile)
{
ZipReturn zr = zipFileOut.ZipFileCloseWriteStream(bCRC);
if (zr != ZipReturn.ZipGood)
{
error = "Error Closing Stream " + zr;
return ReturnCode.FileSystemError;
}
fileOut.ZipFileIndex = zipFileOut.LocalFilesCount() - 1;
fileOut.ZipFileHeaderPosition = zipFileOut.LocalHeader(fileOut.ZipFileIndex);
}
else
{
writeStream.Flush();
writeStream.Close();
IO.FileInfo fi = new IO.FileInfo(zipFilenameOut);
fileOut.TimeStamp = fi.LastWriteTime;
}
#endregion
if (!isZeroLengthFile)
{
if (!rawCopy)
{
if (!ArrByte.bCompare(bCRC, fileIn.CRC))
{
fileIn.GotStatus = GotStatus.Corrupt;
error = "Source CRC does not match Source Data stream, corrupt Zip";
if (fileOut.FileType == FileType.ZipFile)
zipFileOut.ZipFileRollBack();
else
IO.File.Delete(zipFilenameOut);
return ReturnCode.SourceCRCCheckSumError;
}
fileIn.FileStatusSet(FileStatus.CRCVerified | FileStatus.SizeVerified);
bool sourceFailed = false;
// check to see if we have a MD5 from the DAT file
if (fileIn.FileStatusIs(FileStatus.MD5FromDAT))
{
if (fileIn.MD5 == null)
{
error = "Should have an filein MD5 from Dat but not found. Logic Error.";
return ReturnCode.LogicError;
}
if (!ArrByte.bCompare(fileIn.MD5, bMD5))
sourceFailed = true;
else
fileIn.FileStatusSet(FileStatus.MD5Verified);
}
// check to see if we have an MD5 (not from the DAT) so must be from previously scanning this file.
else if (fileIn.MD5 != null)
{
if (!ArrByte.bCompare(fileIn.MD5, bMD5))
{
// if we had an MD5 from a preview scan and it now does not match, something has gone really bad.
error = "The MD5 found does not match a previously scanned MD5, this should not happen, unless something got corrupt.";
return ReturnCode.LogicError;
}
}
else // (FileIn.MD5 == null)
{
fileIn.MD5 = bMD5;
fileIn.FileStatusSet(FileStatus.MD5Verified);
}
// check to see if we have a SHA1 from the DAT file
if (fileIn.FileStatusIs(FileStatus.SHA1FromDAT))
{
if (fileIn.SHA1 == null)
{
error = "Should have an filein SHA1 from Dat but not found. Logic Error.";
return ReturnCode.LogicError;
}
if (!ArrByte.bCompare(fileIn.SHA1, bSHA1))
sourceFailed = true;
else
fileIn.FileStatusSet(FileStatus.SHA1Verified);
}
// check to see if we have an SHA1 (not from the DAT) so must be from previously scanning this file.
else if (fileIn.SHA1 != null)
{
if (!ArrByte.bCompare(fileIn.SHA1, bSHA1))
{
// if we had an SHA1 from a preview scan and it now does not match, something has gone really bad.
error = "The SHA1 found does not match a previously scanned SHA1, this should not happen, unless something got corrupt.";
return ReturnCode.LogicError;
}
}
else // (FileIn.SHA1 == null)
{
fileIn.SHA1 = bSHA1;
fileIn.FileStatusSet(FileStatus.SHA1Verified);
}
if (sourceFailed)
{
if (fileIn.FileType == FileType.ZipFile || fileIn.FileType == FileType.SevenZipFile)
{
RvFile tZFile = new RvFile(FileType.ZipFile);
foundFile = tZFile;
tZFile.ZipFileIndex = fileIn.ZipFileIndex;
tZFile.ZipFileHeaderPosition = fileIn.ZipFileHeaderPosition;
}
else
{
foundFile = new RvFile(fileIn.FileType);
}
foundFile.Name = fileIn.Name;
foundFile.Size = fileIn.Size;
foundFile.CRC = bCRC;
foundFile.MD5 = bMD5;
foundFile.SHA1 = bSHA1;
foundFile.TimeStamp = fileIn.TimeStamp;
foundFile.SetStatus(DatStatus.NotInDat, GotStatus.Got);
foundFile.FileStatusSet(FileStatus.SizeVerified | FileStatus.CRCVerified | FileStatus.MD5Verified | FileStatus.SHA1Verified);
if (fileOut.FileType == FileType.ZipFile)
zipFileOut.ZipFileRollBack();
else
IO.File.Delete(zipFilenameOut);
return ReturnCode.SourceCheckSumError;
}
}
}
if (fileOut.FileType == FileType.ZipFile || fileOut.FileType == FileType.SevenZipFile)
{
fileOut.FileStatusSet(FileStatus.SizeFromHeader | FileStatus.CRCFromHeader);
}
if (fileOut.FileStatusIs(FileStatus.CRCFromDAT) && fileOut.CRC != null && !ArrByte.bCompare(fileOut.CRC, bCRC))
{
//Rollback the file
if (fileOut.FileType == FileType.ZipFile)
zipFileOut.ZipFileRollBack();
else
IO.File.Delete(zipFilenameOut);
return ReturnCode.DestinationCheckSumError;
}
fileOut.CRC = bCRC;
if (!rawCopy || fileIn.FileStatusIs(FileStatus.CRCVerified))
fileOut.FileStatusSet(FileStatus.CRCVerified);
if (bSHA1 != null)
{
if (fileOut.FileStatusIs(FileStatus.SHA1FromDAT) && fileOut.SHA1 != null && !ArrByte.bCompare(fileOut.SHA1, bSHA1))
{
//Rollback the file
if (fileOut.FileType == FileType.ZipFile)
zipFileOut.ZipFileRollBack();
else
IO.File.Delete(zipFilenameOut);
return ReturnCode.DestinationCheckSumError;
}
fileOut.SHA1 = bSHA1;
fileOut.FileStatusSet(FileStatus.SHA1Verified);
}
if (bMD5 != null)
{
if (fileOut.FileStatusIs(FileStatus.MD5FromDAT) && fileOut.MD5 != null && !ArrByte.bCompare(fileOut.MD5, bMD5))
{
//Rollback the file
if (fileOut.FileType == FileType.ZipFile)
zipFileOut.ZipFileRollBack();
else
IO.File.Delete(zipFilenameOut);
return ReturnCode.DestinationCheckSumError;
}
fileOut.MD5 = bMD5;
fileOut.FileStatusSet(FileStatus.MD5Verified);
}
if (fileIn.Size != null)
{
fileOut.Size = fileIn.Size;
fileOut.FileStatusSet(FileStatus.SizeVerified);
}
if (fileIn.GotStatus == GotStatus.Corrupt)
fileOut.GotStatus = GotStatus.Corrupt;
else
fileOut.GotStatus = GotStatus.Got; // Changes RepStatus to Correct
fileOut.FileStatusSet(FileStatus.SizeVerified);
if (fileOut.SHA1CHD == null && fileIn.SHA1CHD != null) fileOut.SHA1CHD = fileIn.SHA1CHD;
if (fileOut.MD5CHD == null && fileIn.MD5CHD != null) fileOut.MD5CHD = fileIn.MD5CHD;
fileOut.CHDVersion = fileIn.CHDVersion;
fileOut.FileStatusSet(FileStatus.SHA1CHDFromHeader | FileStatus.MD5CHDFromHeader | FileStatus.SHA1CHDVerified | FileStatus.MD5CHDVerified, fileIn);
return ReturnCode.Good;
}
//Raw Copy
// Returns True is a raw copy can be used
// Returns False is a full recompression is required
private static bool RawCopy(RvFile fileIn, RvFile fileOut, bool forceRaw)
{
if (fileIn == null || fileOut == null)
return false;
if ((fileIn.FileType != FileType.ZipFile) || (fileOut.FileType != FileType.ZipFile))
return false;
if (fileIn.Parent == null)
return false;
if (forceRaw) return true;
bool trrntzipped = (fileIn.Parent.ZipStatus & ZipStatus.TrrntZip) == ZipStatus.TrrntZip;
bool deepchecked = fileIn.FileStatusIs(FileStatus.SHA1Verified) && fileIn.FileStatusIs(FileStatus.MD5Verified);
switch (Settings.FixLevel)
{
case eFixLevel.TrrntZipLevel1:
return trrntzipped;
case eFixLevel.TrrntZipLevel2:
return trrntzipped && deepchecked;
case eFixLevel.TrrntZipLevel3:
return false;
case eFixLevel.Level1:
return true;
case eFixLevel.Level2:
return deepchecked;
case eFixLevel.Level3:
return false;
}
return false;
}
private static ReturnCode CheckInputAndOutputFile(RvFile fileIn, RvFile fileOut, out string error)
{
if (fileOut.FileStatusIs(FileStatus.SizeFromDAT) && fileOut.Size != null && fileIn.Size != fileOut.Size)
{
error = "Source and destination Size does not match. Logic Error.";
return ReturnCode.LogicError;
}
if (fileOut.FileStatusIs(FileStatus.CRCFromDAT) && fileOut.CRC != null && !ArrByte.bCompare(fileIn.CRC, fileOut.CRC))
{
error = "Source and destination CRC does not match. Logic Error.";
return ReturnCode.LogicError;
}
if (fileOut.FileStatusIs(FileStatus.SHA1FromDAT) && fileIn.FileStatusIs(FileStatus.SHA1Verified))
{
if (fileIn.SHA1 != null && fileOut.SHA1 != null && !ArrByte.bCompare(fileIn.SHA1, fileOut.SHA1))
{
error = "Source and destination SHA1 does not match. Logic Error.";
return ReturnCode.LogicError;
}
}
if (fileOut.FileStatusIs(FileStatus.MD5CHDFromDAT) && fileIn.FileStatusIs(FileStatus.MD5Verified))
{
if (fileIn.MD5 != null && fileOut.MD5 != null && !ArrByte.bCompare(fileIn.MD5, fileOut.MD5))
{
error = "Source and destination SHA1 does not match. Logic Error.";
return ReturnCode.LogicError;
}
}
error = "";
return ReturnCode.Good;
}
private static ReturnCode OpenInputStream(RvFile fileIn, bool rawCopy, out ICompress zipFileIn, out bool sourceTrrntzip, out Stream readStream, out ulong streamSize, out ushort compressionMethod, out string error)
{
zipFileIn = null;
sourceTrrntzip = false;
readStream = null;
streamSize = 0;
compressionMethod = 0;
switch (fileIn.FileType)
{
case FileType.SevenZipFile:
case FileType.ZipFile:
{
RvDir zZipFileIn = fileIn.Parent;
if (zZipFileIn.FileType != DBTypeGet.DirFromFile(fileIn.FileType))
{
error = "SevenZip File Open but Source File is not a SevenZip, Logic Error.";
return ReturnCode.LogicError;
}
string fileNameIn = zZipFileIn.FullName;
ZipReturn zr1;
if (fileIn.FileType == FileType.SevenZipFile)
{
sourceTrrntzip = false;
zipFileIn = new SevenZ();
zr1 = zipFileIn.ZipFileOpen(fileNameIn, zZipFileIn.TimeStamp, true);
}
else
{
sourceTrrntzip = (zZipFileIn.ZipStatus & ZipStatus.TrrntZip) == ZipStatus.TrrntZip;
zipFileIn = new ZipFile();
zr1 = zipFileIn.ZipFileOpen(fileNameIn, zZipFileIn.TimeStamp, fileIn.ZipFileHeaderPosition == null);
}
switch (zr1)
{
case ZipReturn.ZipGood:
break;
case ZipReturn.ZipErrorFileNotFound:
error = "File not found, Rescan required before fixing " + fileIn.Name;
return ReturnCode.FileSystemError;
case ZipReturn.ZipErrorTimeStamp:
error = "File has changed, Rescan required before fixing " + fileIn.Name;
return ReturnCode.FileSystemError;
default:
error = "Error Open Zip" + zr1 + ", with File " + fileIn.DatFullName;
return ReturnCode.FileSystemError;
}
if (fileIn.FileType == FileType.SevenZipFile)
{
SevenZ z = zipFileIn as SevenZ;
z.ZipFileOpenReadStream(fileIn.ZipFileIndex, out readStream, out streamSize);
}
else
{
ZipFile z = zipFileIn as ZipFile;
if (fileIn.ZipFileHeaderPosition != null)
z.ZipFileOpenReadStreamQuick((ulong)fileIn.ZipFileHeaderPosition, rawCopy, out readStream, out streamSize, out compressionMethod);
else
z.ZipFileOpenReadStream(fileIn.ZipFileIndex, rawCopy, out readStream, out streamSize, out compressionMethod);
}
}
break;
default:
{
string fileNameIn = fileIn.FullName;
if (!IO.File.Exists(fileNameIn))
{
error = "Rescan needed, File Changed :" + fileNameIn;
return ReturnCode.RescanNeeded;
}
IO.FileInfo fileInInfo = new IO.FileInfo(fileNameIn);
if (fileInInfo.LastWriteTime != fileIn.TimeStamp)
{
error = "Rescan needed, File Changed :" + fileNameIn;
return ReturnCode.RescanNeeded;
}
int errorCode = IO.FileStream.OpenFileRead(fileNameIn, out readStream);
if (errorCode != 0)
{
error = new Win32Exception(errorCode).Message + ". " + fileNameIn;
return ReturnCode.FileSystemError;
}
if (fileIn.Size == null)
{
error = "Null File Size found in Fixing File :" + fileNameIn;
return ReturnCode.LogicError;
}
streamSize = (ulong)fileIn.Size;
if ((ulong)readStream.Length != streamSize)
{
error = "Rescan needed, File Length Changed :" + fileNameIn;
return ReturnCode.RescanNeeded;
}
}
break;
}
error = "";
return ReturnCode.Good;
}
private static ReturnCode OpenOutputStream(RvFile fileOut, RvFile fileIn, ref ICompress zipFileOut, string zipFilenameOut, ushort compressionMethod, bool rawCopy, bool sourceTrrntzip, out System.IO.Stream writeStream, out string error)
{
writeStream = null;
switch (fileOut.FileType)
{
case FileType.ZipFile:
case FileType.SevenZipFile:
{
// if ZipFileOut == null then we have not open the output zip yet so open it from writing.
if (zipFileOut == null)
{
if (IO.Path.GetFileName(zipFilenameOut) == "__RomVault.tmp")
{
if (IO.File.Exists(zipFilenameOut))
IO.File.Delete(zipFilenameOut);
}
else if (IO.File.Exists(zipFilenameOut))
{
error = "Rescan needed, File Changed :" + zipFilenameOut;
return ReturnCode.RescanNeeded;
}
if (fileOut.FileType == FileType.ZipFile)
zipFileOut = new ZipFile();
else
zipFileOut = new SevenZ();
ZipReturn zrf = zipFileOut.ZipFileCreate(zipFilenameOut);
if (zrf != ZipReturn.ZipGood)
{
error = "Error Opening Write Stream " + zrf;
return ReturnCode.FileSystemError;
}
}
else
{
if (zipFileOut.ZipOpen != ZipOpenType.OpenWrite)
{
error = "Output Zip File is not set to OpenWrite, Logic Error.";
return ReturnCode.LogicError;
}
if (zipFileOut.ZipFilename != (new IO.FileInfo(zipFilenameOut).FullName))
{
error = "Output Zip file has changed name from " + zipFileOut.ZipFilename + " to " + zipFilenameOut + ". Logic Error";
return ReturnCode.LogicError;
}
}
if (fileIn.Size == null)
{
error = "Null File Size found in Fixing File :" + fileIn.FullName;
return ReturnCode.LogicError;
}
ZipReturn zr = zipFileOut.ZipFileOpenWriteStream(rawCopy, sourceTrrntzip, fileOut.Name, (ulong)fileIn.Size, compressionMethod, out writeStream);
if (zr != ZipReturn.ZipGood)
{
error = "Error Opening Write Stream " + zr;
return ReturnCode.FileSystemError;
}
}
break;
default:
{
if (IO.File.Exists(zipFilenameOut) && fileOut.GotStatus != GotStatus.Corrupt)
{
error = "Rescan needed, File Changed :" + zipFilenameOut;
return ReturnCode.RescanNeeded;
}
int errorCode = IO.FileStream.OpenFileWrite(zipFilenameOut, out writeStream);
if (errorCode != 0)
{
error = new Win32Exception(errorCode).Message + ". " + zipFilenameOut;
return ReturnCode.FileSystemError;
}
}
break;
}
error = "";
return ReturnCode.Good;
}
}
}