BizHawk/BizHawk.Client.Common/7z/FileSignatureChecker.cs

252 lines
9.7 KiB
C#

/* This file is part of SevenZipSharp.
SevenZipSharp is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SevenZipSharp is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with SevenZipSharp. If not, see <http://www.gnu.org/licenses/>.
*/
using System;
using System.IO;
namespace SevenZip
{
#if UNMANAGED
/// <summary>
/// The signature checker class. Original code by Siddharth Uppal, adapted by Markhor.
/// </summary>
/// <remarks>Based on the code at http://blog.somecreativity.com/2008/04/08/how-to-check-if-a-file-is-compressed-in-c/#</remarks>
public static class FileChecker
{
public static bool ThrowExceptions = true;
private const int SIGNATURE_SIZE = 16;
private const int SFX_SCAN_LENGTH = 256 * 1024;
private static bool SpecialDetect(Stream stream, int offset, InArchiveFormat expectedFormat)
{
if (stream.Length > offset + SIGNATURE_SIZE)
{
var signature = new byte[SIGNATURE_SIZE];
int bytesRequired = SIGNATURE_SIZE;
int index = 0;
stream.Seek(offset, SeekOrigin.Begin);
while (bytesRequired > 0)
{
int bytesRead = stream.Read(signature, index, bytesRequired);
bytesRequired -= bytesRead;
index += bytesRead;
}
string actualSignature = BitConverter.ToString(signature);
foreach (string expectedSignature in Formats.InSignatureFormats.Keys)
{
if (Formats.InSignatureFormats[expectedSignature] != expectedFormat)
{
continue;
}
if (actualSignature.StartsWith(expectedSignature, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Gets the InArchiveFormat for a specific extension.
/// </summary>
/// <param name="stream">The stream to identify.</param>
/// <param name="offset">The archive beginning offset.</param>
/// <param name="isExecutable">True if the original format of the stream is PE; otherwise, false.</param>
/// <returns>Corresponding InArchiveFormat.</returns>
public static InArchiveFormat CheckSignature(Stream stream, out int offset, out bool isExecutable)
{
offset = 0;
isExecutable = false;
if (!stream.CanRead)
{
if (ThrowExceptions)
throw new ArgumentException("The stream must be readable.");
else return InArchiveFormat.None;
}
if (stream.Length < SIGNATURE_SIZE)
{
if (ThrowExceptions)
throw new ArgumentException("The stream is invalid.");
else return InArchiveFormat.None;
}
#region Get file signature
var signature = new byte[SIGNATURE_SIZE];
int bytesRequired = SIGNATURE_SIZE;
int index = 0;
stream.Seek(0, SeekOrigin.Begin);
while (bytesRequired > 0)
{
int bytesRead = stream.Read(signature, index, bytesRequired);
bytesRequired -= bytesRead;
index += bytesRead;
}
string actualSignature = BitConverter.ToString(signature);
#endregion
InArchiveFormat suspectedFormat = InArchiveFormat.XZ; // any except PE and Cab
isExecutable = false;
foreach (string expectedSignature in Formats.InSignatureFormats.Keys)
{
if (actualSignature.StartsWith(expectedSignature, StringComparison.OrdinalIgnoreCase) ||
actualSignature.Substring(6).StartsWith(expectedSignature, StringComparison.OrdinalIgnoreCase) &&
Formats.InSignatureFormats[expectedSignature] == InArchiveFormat.Lzh)
{
if (Formats.InSignatureFormats[expectedSignature] == InArchiveFormat.PE)
{
suspectedFormat = InArchiveFormat.PE;
isExecutable = true;
}
else
{
return Formats.InSignatureFormats[expectedSignature];
}
}
}
// Many Microsoft formats
if (actualSignature.StartsWith("D0-CF-11-E0-A1-B1-1A-E1", StringComparison.OrdinalIgnoreCase))
{
suspectedFormat = InArchiveFormat.Cab; // != InArchiveFormat.XZ
}
#region SpecialDetect
try
{
SpecialDetect(stream, 257, InArchiveFormat.Tar);
}
catch (ArgumentException) {}
if (SpecialDetect(stream, 0x8001, InArchiveFormat.Iso))
{
return InArchiveFormat.Iso;
}
if (SpecialDetect(stream, 0x8801, InArchiveFormat.Iso))
{
return InArchiveFormat.Iso;
}
if (SpecialDetect(stream, 0x9001, InArchiveFormat.Iso))
{
return InArchiveFormat.Iso;
}
if (SpecialDetect(stream, 0x9001, InArchiveFormat.Iso))
{
return InArchiveFormat.Iso;
}
if (SpecialDetect(stream, 0x400, InArchiveFormat.Hfs))
{
return InArchiveFormat.Hfs;
}
#region Last resort for tar - can mistake
if (stream.Length >= 1024)
{
stream.Seek(-1024, SeekOrigin.End);
byte[] buf = new byte[1024];
stream.Read(buf, 0, 1024);
bool istar = true;
for (int i = 0; i < 1024; i++)
{
istar = istar && buf[i] == 0;
}
if (istar)
{
return InArchiveFormat.Tar;
}
}
#endregion
#endregion
#region Check if it is an SFX archive or a file with an embedded archive.
if (suspectedFormat != InArchiveFormat.XZ)
{
#region Get first Min(stream.Length, SFX_SCAN_LENGTH) bytes
var scanLength = Math.Min(stream.Length, SFX_SCAN_LENGTH);
signature = new byte[scanLength];
bytesRequired = (int)scanLength;
index = 0;
stream.Seek(0, SeekOrigin.Begin);
while (bytesRequired > 0)
{
int bytesRead = stream.Read(signature, index, bytesRequired);
bytesRequired -= bytesRead;
index += bytesRead;
}
actualSignature = BitConverter.ToString(signature);
#endregion
foreach (var format in new InArchiveFormat[]
{
InArchiveFormat.Zip,
InArchiveFormat.SevenZip,
InArchiveFormat.Rar,
InArchiveFormat.Cab,
InArchiveFormat.Arj
})
{
int pos = actualSignature.IndexOf(Formats.InSignatureFormatsReversed[format]);
if (pos > -1)
{
offset = pos / 3;
return format;
}
}
// Nothing
if (suspectedFormat == InArchiveFormat.PE)
{
return InArchiveFormat.PE;
}
}
#endregion
if (ThrowExceptions)
throw new ArgumentException("The stream is invalid or no corresponding signature was found.");
else return InArchiveFormat.None;
}
/// <summary>
/// Gets the InArchiveFormat for a specific file name.
/// </summary>
/// <param name="fileName">The archive file name.</param>
/// <param name="offset">The archive beginning offset.</param>
/// <param name="isExecutable">True if the original format of the file is PE; otherwise, false.</param>
/// <returns>Corresponding InArchiveFormat.</returns>
/// <exception cref="System.ArgumentException"/>
public static InArchiveFormat CheckSignature(string fileName, out int offset, out bool isExecutable)
{
using (var fs = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
try
{
InArchiveFormat format = CheckSignature(fs, out offset, out isExecutable);
if (format != InArchiveFormat.None) return format;
}
catch (ArgumentException)
{
}
offset = 0;
isExecutable = false;
return Formats.FormatByFileName(fileName, true);
}
}
}
#endif
}