re-engineer HawkFile to be aware of archives. its a little more complex to use now (not only do you have to open it, you have to call one of the Bind() methods on it to choose an interior file), but its more powerful.

This commit is contained in:
zeromus 2011-03-07 01:07:49 +00:00
parent 3ec1ed128d
commit 341ee44509
6 changed files with 355 additions and 192 deletions

View File

@ -1,12 +1,11 @@
//http://nesdev.parodius.com/bbs/viewtopic.php?p=4571&sid=db4c7e35316cc5d734606dd02f11dccb //http://nesdev.parodius.com/bbs/viewtopic.php?p=4571&sid=db4c7e35316cc5d734606dd02f11dccb
using System; using System;
using System.Xml;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization; using System.Globalization;
using System.IO; using System.IO;
using System.Collections.Generic; using System.Collections.Generic;
using BizHawk.Emulation.CPUs.M6502;
namespace BizHawk.Emulation.Consoles.Nintendo namespace BizHawk.Emulation.Consoles.Nintendo
{ {

View File

@ -3,92 +3,230 @@ using System.IO;
namespace BizHawk.MultiClient namespace BizHawk.MultiClient
{ {
//todo:
//split into "bind" and "open (the bound thing)"
//scan archive to flatten interior directories down to a path (maintain our own archive item list)
public class HawkFile : IDisposable public class HawkFile : IDisposable
{ {
private bool zipped; /// <summary>
public bool Zipped { get { return zipped; } } /// returns whether a bound file exists. if there is no bound file, it can't exist
/// </summary>
public bool Exists { get { if (!rootExists) return false; return boundStream != null; } }
private bool exists; /// <summary>
public bool Exists { get { return exists; } } /// returns whether the root exists (the actual physical file)
/// </summary>
public bool RootExists { get { return rootExists; } }
private string extension; /// <summary>
public string Extension { get { return extension; } } /// gets the directory containing the root
/// </summary>
public string Directory { get { return Path.GetDirectoryName(rawFileName); } } public string Directory { get { return Path.GetDirectoryName(rootPath); } }
private string rawFileName;
private string name;
public string Name { get { return name; } }
public string FullName { get { return name + "." + extension; } }
private IDisposable thingToDispose;
private Stream zippedStream;
public HawkFile(string path) : this(path,"SMS","PCE","SGX","GG","SG","BIN","SMD","GB","IPS") {}
public HawkFile(string path, params string[] recognizedExtensions)
{
var file = new FileInfo(path);
exists = file.Exists;
if (file.Exists == false)
return;
if (file.Extension.ToLower().In(".zip",".rar",".7z"))
{
LoadZipFile(path, recognizedExtensions);
return;
}
zipped = false;
extension = file.Extension.Substring(1).ToUpperInvariant();
rawFileName = path;
name = Path.GetFileNameWithoutExtension(path);
}
private void LoadZipFile(string path, string[] recognizedExtensions)
{
zipped = true;
rawFileName = path;
using (var extractor = new SevenZip.SevenZipExtractor(path))
{
thingToDispose = extractor;
foreach (var e in extractor.ArchiveFileData)
{
extension = Path.GetExtension(e.FileName).Substring(1).ToUpperInvariant();
if (extension.In(recognizedExtensions))
{
// we found our match.
name = Path.GetFileNameWithoutExtension(e.FileName);
zippedStream = new MemoryStream();
//e.Extract(zippedStream);
extractor.ExtractFile(e.Index,zippedStream);
thingToDispose = zippedStream;
return;
}
}
exists = false;
}
}
/// <summary>
/// returns a stream for the currently bound file
/// </summary>
public Stream GetStream() public Stream GetStream()
{ {
if (zipped == false) if (boundStream == null)
{ throw new InvalidOperationException("HawkFil: Can't call GetStream() before youve successfully bound something!");
var stream = new FileStream(rawFileName, FileMode.Open, FileAccess.Read); return boundStream;
thingToDispose = stream;
return stream;
} }
return zippedStream; /// <summary>
/// indicates whether this instance is bound
/// </summary>
public bool IsBound { get { return boundStream != null; } }
/// <summary>
/// returns the complete canonical name ("archive|member") of the bound file
/// </summary>
public string CanonicalName { get { return MakeCanonicalName(rootPath,memberPath); } }
/// <summary>
/// returns the virtual name of the bound file (disregarding the archive)
/// </summary>
public string Name { get { return GetBoundNameFromCanonical(MakeCanonicalName(rootPath,memberPath)); } }
/// <summary>
/// returns the extension of Name
/// </summary>
public string Extension { get { return Path.GetExtension(Name); } }
//---
bool rootExists;
string rootPath;
string memberPath;
Stream rootStream, boundStream;
SevenZip.SevenZipExtractor extractor;
public static bool PathExists(string path)
{
using (var hf = new HawkFile(path))
return hf.Exists;
}
public HawkFile(string path)
{
string autobind = null;
if (IsCanonicalArchivePath(path))
{
string[] parts = path.Split('|');
path = parts[0];
autobind = parts[1];
}
var fi = new FileInfo(path);
rootExists = fi.Exists;
if (fi.Exists == false)
return;
rootPath = path;
AnalyzeArchive(path);
if (extractor == null)
{
rootStream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
}
if (autobind != null)
{
autobind = autobind.ToUpperInvariant();
for (int i = 0; i < extractor.ArchiveFileData.Count; i++)
{
if (extractor.ArchiveFileNames[i].ToUpperInvariant() == autobind)
{
BindArchiveMember(i);
return;
}
}
}
}
/// <summary>
/// is the supplied path a canonical name including an archive?
/// </summary>
bool IsCanonicalArchivePath(string path)
{
return (path.IndexOf('|') != -1);
}
/// <summary>
/// converts a canonical name to a bound name (the bound part, whether or not it is an archive)
/// </summary>
string GetBoundNameFromCanonical(string canonical)
{
string[] parts = canonical.Split('|');
return parts[parts.Length - 1];
}
/// <summary>
/// makes a canonical name from two parts
/// </summary>
string MakeCanonicalName(string root, string member)
{
if (member == null) return root;
else return string.Format("{0}|{1}", root, member);
}
void BindArchiveMember(int index)
{
boundStream = new MemoryStream();
extractor.ExtractFile(index, boundStream);
boundStream.Position = 0;
memberPath = extractor.ArchiveFileNames[index];
Console.WriteLine("bound " + CanonicalName);
}
/// <summary>
/// Removes any existing binding
/// </summary>
public void Unbind()
{
if (boundStream != null && boundStream != rootStream) boundStream.Close();
boundStream = null;
memberPath = null;
}
void BindRoot()
{
boundStream = rootStream;
Console.WriteLine("bound " + CanonicalName);
}
/// <summary>
/// Binds the first item in the archive (or the file itself). Supposing that there is anything in the archive.
/// </summary>
public HawkFile BindFirst()
{
BindFirstOf();
return this;
}
/// <summary>
/// Binds the first item in the archive (or the file itself) if the extension matches one of the supplied templates
/// </summary>
public HawkFile BindFirstOf(params string[] extensions)
{
if (!rootExists) return this;
if (boundStream != null) throw new InvalidOperationException("stream already bound!");
if (extractor == null)
{
//open uncompressed file
string extension = Path.GetExtension(rootPath).Substring(1).ToUpperInvariant();
if (extensions.Length==0 || extension.In(extensions))
{
BindRoot();
}
return this;
}
for(int i=0;i<extractor.ArchiveFileData.Count;i++)
{
var e = extractor.ArchiveFileData[i];
var extension = Path.GetExtension(e.FileName).Substring(1).ToUpperInvariant();
if (extensions.Length == 0 || extension.In(extensions))
{
BindArchiveMember(i);
return this;
}
}
return this;
}
private void AnalyzeArchive(string path)
{
try
{
SevenZip.FileChecker.ThrowExceptions = false;
int offset;
bool isExecutable;
if (SevenZip.FileChecker.CheckSignature(path, out offset, out isExecutable) != SevenZip.InArchiveFormat.None)
{
extractor = new SevenZip.SevenZipExtractor(path);
//now would be a good time to scan the archive..
}
}
catch
{
//must not be an archive. is there a better way to determine this? the exceptions are as annoying as hell
}
} }
public void Dispose() public void Dispose()
{ {
if (thingToDispose != null) Unbind();
thingToDispose.Dispose();
if (extractor != null) extractor.Dispose();
if (rootStream != null) rootStream.Dispose();
extractor = null;
rootStream = null;
} }
} }
} }

View File

@ -46,6 +46,11 @@ namespace BizHawk.MultiClient
public MainForm(string[] args) public MainForm(string[] args)
{ {
//using (HawkFile NesCartFile = new HawkFile("NesCarts.7z").BindFirst())
//{
// var NesCartXmlBytes = Util.ReadAllBytes(NesCartFile.GetStream());
//}
Global.MainForm = this; Global.MainForm = this;
Global.Config = ConfigService.Load<Config>("config.ini"); Global.Config = ConfigService.Load<Config>("config.ini");
@ -386,12 +391,13 @@ namespace BizHawk.MultiClient
private bool LoadRom(string path) private bool LoadRom(string path)
{ {
var file = new FileInfo(path); using (var file = new HawkFile(path))
if (file.Exists == false) return false; {
if (!file.RootExists) return false;
CloseGame(); CloseGame();
var game = new RomGame(path); var game = new RomGame(file);
Global.Game = game; Global.Game = game;
switch (game.System) switch (game.System)
@ -443,7 +449,7 @@ namespace BizHawk.MultiClient
Global.Emulator.LoadGame(game); Global.Emulator.LoadGame(game);
Text = DisplayNameForSystem(game.System) + " - " + game.Name; Text = DisplayNameForSystem(game.System) + " - " + game.Name;
ResetRewindBuffer(); ResetRewindBuffer();
Global.Config.RecentRoms.Add(file.FullName); Global.Config.RecentRoms.Add(file.CanonicalName);
if (File.Exists(game.SaveRamPath)) if (File.Exists(game.SaveRamPath))
LoadSaveRam(); LoadSaveRam();
@ -474,6 +480,7 @@ namespace BizHawk.MultiClient
CurrentlyOpenRom = path; CurrentlyOpenRom = path;
return true; return true;
} }
}
private void LoadSaveRam() private void LoadSaveRam()
{ {

View File

@ -14,12 +14,12 @@ namespace BizHawk.MultiClient
private List<string> options; private List<string> options;
private const int BankSize = 4096; private const int BankSize = 4096;
public RomGame(string path) : this(path, null){} public RomGame(HawkFile file) : this(file, null){}
public RomGame(string path, string patch) public RomGame(HawkFile file, string patch)
{
using (var file = new HawkFile(path))
{ {
if(!file.IsBound)
file.BindFirstOf("SMS", "PCE", "SGX", "GG", "SG", "BIN", "SMD", "GB", "NES");
if (!file.Exists) if (!file.Exists)
throw new Exception("The file needs to exist, yo."); throw new Exception("The file needs to exist, yo.");
@ -37,18 +37,19 @@ namespace BizHawk.MultiClient
if (file.Extension == "SMD") if (file.Extension == "SMD")
RomData = DeInterleaveSMD(RomData); RomData = DeInterleaveSMD(RomData);
var info = Database.GetGameInfo(RomData, file.FullName); var info = Database.GetGameInfo(RomData, file.Name);
name = info.Name; name = info.Name;
System = info.System; System = info.System;
options = new List<string>(info.GetOptions()); options = new List<string>(info.GetOptions());
CheckForPatchOptions(); CheckForPatchOptions();
}
if (patch != null) if (patch != null)
{ {
using (var stream = new HawkFile(patch).GetStream()) using (var patchFile = new HawkFile(patch))
{ {
IPS.Patch(RomData, stream); patchFile.BindFirstOf("IPS");
if(patchFile.Exists)
IPS.Patch(RomData, patchFile.GetStream());
} }
} }
} }

View File

@ -24,8 +24,10 @@ namespace SevenZip
/// The signature checker class. Original code by Siddharth Uppal, adapted by Markhor. /// The signature checker class. Original code by Siddharth Uppal, adapted by Markhor.
/// </summary> /// </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> /// <remarks>Based on the code at http://blog.somecreativity.com/2008/04/08/how-to-check-if-a-file-is-compressed-in-c/#</remarks>
internal static class FileChecker public static class FileChecker
{ {
public static bool ThrowExceptions = true;
private const int SIGNATURE_SIZE = 16; private const int SIGNATURE_SIZE = 16;
private const int SFX_SCAN_LENGTH = 256 * 1024; private const int SFX_SCAN_LENGTH = 256 * 1024;
@ -69,13 +71,19 @@ namespace SevenZip
public static InArchiveFormat CheckSignature(Stream stream, out int offset, out bool isExecutable) public static InArchiveFormat CheckSignature(Stream stream, out int offset, out bool isExecutable)
{ {
offset = 0; offset = 0;
isExecutable = false;
if (!stream.CanRead) if (!stream.CanRead)
{ {
if (ThrowExceptions)
throw new ArgumentException("The stream must be readable."); throw new ArgumentException("The stream must be readable.");
else return InArchiveFormat.None;
} }
if (stream.Length < SIGNATURE_SIZE) if (stream.Length < SIGNATURE_SIZE)
{ {
if (ThrowExceptions)
throw new ArgumentException("The stream is invalid."); throw new ArgumentException("The stream is invalid.");
else return InArchiveFormat.None;
} }
#region Get file signature #region Get file signature
@ -208,7 +216,9 @@ namespace SevenZip
} }
#endregion #endregion
if (ThrowExceptions)
throw new ArgumentException("The stream is invalid or no corresponding signature was found."); throw new ArgumentException("The stream is invalid or no corresponding signature was found.");
else return InArchiveFormat.None;
} }
/// <summary> /// <summary>
@ -225,16 +235,18 @@ namespace SevenZip
{ {
try try
{ {
return CheckSignature(fs, out offset, out isExecutable); InArchiveFormat format = CheckSignature(fs, out offset, out isExecutable);
if (format != InArchiveFormat.None) return format;
} }
catch (ArgumentException) catch (ArgumentException)
{ {
}
offset = 0; offset = 0;
isExecutable = false; isExecutable = false;
return Formats.FormatByFileName(fileName, true); return Formats.FormatByFileName(fileName, true);
} }
} }
} }
}
#endif #endif
} }

View File

@ -186,7 +186,11 @@ namespace SevenZip
/// Microsoft virtual hard disk file format. /// Microsoft virtual hard disk file format.
/// </summary> /// </summary>
/// <remarks><a href="http://en.wikipedia.org/wiki/VHD_%28file_format%29">Wikipedia information</a></remarks> /// <remarks><a href="http://en.wikipedia.org/wiki/VHD_%28file_format%29">Wikipedia information</a></remarks>
Vhd Vhd,
/// <summary>
/// Not an archive
/// </summary>
None
} }
#if COMPRESS #if COMPRESS
@ -519,8 +523,10 @@ namespace SevenZip
string extension = Path.GetExtension(fileName).Substring(1); string extension = Path.GetExtension(fileName).Substring(1);
if (!InExtensionFormats.ContainsKey(extension) && reportErrors) if (!InExtensionFormats.ContainsKey(extension) && reportErrors)
{ {
throw new ArgumentException("Extension \"" + extension + if (FileChecker.ThrowExceptions)
"\" is not a supported archive file name extension."); throw new ArgumentException("Extension \"" + extension + "\" is not a supported archive file name extension.");
else return InArchiveFormat.None;
} }
return InExtensionFormats[extension]; return InExtensionFormats[extension];
} }