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>
private string extension; public bool RootExists { get { return rootExists; } }
public string Extension { get { return extension; } }
public string Directory { get { return Path.GetDirectoryName(rawFileName); } } /// <summary>
/// gets the directory containing the root
/// </summary>
public string Directory { get { return Path.GetDirectoryName(rootPath); } }
private string rawFileName; /// <summary>
private string name; /// returns a stream for the currently bound file
public string Name { get { return name; } } /// </summary>
public string FullName { get { return name + "." + extension; } } public Stream GetStream()
{
if (boundStream == null)
throw new InvalidOperationException("HawkFil: Can't call GetStream() before youve successfully bound something!");
return boundStream;
}
private IDisposable thingToDispose; /// <summary>
private Stream zippedStream; /// indicates whether this instance is bound
/// </summary>
public bool IsBound { get { return boundStream != null; } }
public HawkFile(string path) : this(path,"SMS","PCE","SGX","GG","SG","BIN","SMD","GB","IPS") {} /// <summary>
/// returns the complete canonical name ("archive|member") of the bound file
public HawkFile(string path, params string[] recognizedExtensions) /// </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)
{ {
var file = new FileInfo(path); string autobind = null;
if (IsCanonicalArchivePath(path))
{
string[] parts = path.Split('|');
path = parts[0];
autobind = parts[1];
}
exists = file.Exists; var fi = new FileInfo(path);
if (file.Exists == false)
rootExists = fi.Exists;
if (fi.Exists == false)
return; return;
if (file.Extension.ToLower().In(".zip",".rar",".7z")) rootPath = path;
{
LoadZipFile(path, recognizedExtensions);
return;
}
zipped = false; AnalyzeArchive(path);
extension = file.Extension.Substring(1).ToUpperInvariant(); if (extractor == null)
rawFileName = path; {
name = Path.GetFileNameWithoutExtension(path); 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;
}
}
}
} }
private void LoadZipFile(string path, string[] recognizedExtensions) /// <summary>
{ /// is the supplied path a canonical name including an archive?
zipped = true; /// </summary>
rawFileName = path; bool IsCanonicalArchivePath(string path)
{
return (path.IndexOf('|') != -1);
}
using (var extractor = new SevenZip.SevenZipExtractor(path)) /// <summary>
{ /// converts a canonical name to a bound name (the bound part, whether or not it is an archive)
thingToDispose = extractor; /// </summary>
foreach (var e in extractor.ArchiveFileData) string GetBoundNameFromCanonical(string canonical)
{ {
extension = Path.GetExtension(e.FileName).Substring(1).ToUpperInvariant(); string[] parts = canonical.Split('|');
return parts[parts.Length - 1];
}
if (extension.In(recognizedExtensions)) /// <summary>
{ /// makes a canonical name from two parts
// we found our match. /// </summary>
name = Path.GetFileNameWithoutExtension(e.FileName); string MakeCanonicalName(string root, string member)
zippedStream = new MemoryStream(); {
//e.Extract(zippedStream); if (member == null) return root;
extractor.ExtractFile(e.Index,zippedStream); else return string.Format("{0}|{1}", root, member);
thingToDispose = zippedStream; }
return;
} void BindArchiveMember(int index)
} {
exists = false; boundStream = new MemoryStream();
} extractor.ExtractFile(index, boundStream);
} boundStream.Position = 0;
memberPath = extractor.ArchiveFileNames[index];
public Stream GetStream() 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)
{ {
if (zipped == false) try
{ {
var stream = new FileStream(rawFileName, FileMode.Open, FileAccess.Read); SevenZip.FileChecker.ThrowExceptions = false;
thingToDispose = stream; int offset;
return stream; bool isExecutable;
} if (SevenZip.FileChecker.CheckSignature(path, out offset, out isExecutable) != SevenZip.InArchiveFormat.None)
{
return zippedStream; 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,93 +391,95 @@ 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;
CloseGame();
var game = new RomGame(path);
Global.Game = game;
switch (game.System)
{ {
case "SG": if (!file.RootExists) return false;
case "SMS":
Global.Emulator = new SMS();
Global.Emulator.Controller = Global.SMSControls;
if (Global.Config.SmsEnableFM) game.AddOptions("UseFM");
if (Global.Config.SmsAllowOverlock) game.AddOptions("AllowOverclock");
if (Global.Config.SmsForceStereoSeparation) game.AddOptions("ForceStereo");
break;
case "GG":
Global.Emulator = new SMS { IsGameGear = true };
Global.Emulator.Controller = Global.SMSControls;
if (Global.Config.SmsAllowOverlock) game.AddOptions("AllowOverclock");
break;
case "PCE":
Global.Emulator = new PCEngine(NecSystemType.TurboGrafx);
Global.Emulator.Controller = Global.PCEControls;
break;
case "SGX":
Global.Emulator = new PCEngine(NecSystemType.SuperGrafx);
Global.Emulator.Controller = Global.PCEControls;
break;
case "GEN":
Global.Emulator = new Genesis(false);//TODO
Global.Emulator.Controller = Global.GenControls;
break;
case "TI83":
Global.Emulator = new TI83();
Global.Emulator.Controller = Global.TI83Controls;
break;
case "NES":
Global.Emulator = new NES();
Global.Emulator.Controller = Global.NESControls;
break;
case "GB":
Global.Emulator = new Gameboy();
break;
}
if (Global.Emulator is NullEmulator) CloseGame();
{
throw new Exception();
}
HandlePlatformMenus(Global.Game.System);
Global.Emulator.LoadGame(game);
Text = DisplayNameForSystem(game.System) + " - " + game.Name;
ResetRewindBuffer();
Global.Config.RecentRoms.Add(file.FullName);
if (File.Exists(game.SaveRamPath))
LoadSaveRam();
if (game.System == "GB") var game = new RomGame(file);
{ Global.Game = game;
new BizHawk.Emulation.Consoles.Gameboy.Debugger(Global.Emulator as Gameboy).Show();
}
if (InputLog.GetMovieMode() == MOVIEMODE.RECORD)
InputLog.StartNewRecording(); //TODO: Uncomment and check for a user movie selected?
else if (InputLog.GetMovieMode() == MOVIEMODE.PLAY)
{
InputLog.LoadMovie(); //TODO: Debug
InputLog.StartPlayback(); //TODO: Debug
}
//setup the throttle based on platform's specifications switch (game.System)
//(one day later for some systems we will need to modify it at runtime as the display mode changes) {
{ case "SG":
object o = Global.Emulator.Query(EmulatorQuery.VsyncRate); case "SMS":
if (o is double) Global.Emulator = new SMS();
throttle.SetCoreFps((double)o); Global.Emulator.Controller = Global.SMSControls;
else throttle.SetCoreFps(60); if (Global.Config.SmsEnableFM) game.AddOptions("UseFM");
SetSpeedPercent(Global.Config.SpeedPercent); if (Global.Config.SmsAllowOverlock) game.AddOptions("AllowOverclock");
if (Global.Config.SmsForceStereoSeparation) game.AddOptions("ForceStereo");
break;
case "GG":
Global.Emulator = new SMS { IsGameGear = true };
Global.Emulator.Controller = Global.SMSControls;
if (Global.Config.SmsAllowOverlock) game.AddOptions("AllowOverclock");
break;
case "PCE":
Global.Emulator = new PCEngine(NecSystemType.TurboGrafx);
Global.Emulator.Controller = Global.PCEControls;
break;
case "SGX":
Global.Emulator = new PCEngine(NecSystemType.SuperGrafx);
Global.Emulator.Controller = Global.PCEControls;
break;
case "GEN":
Global.Emulator = new Genesis(false);//TODO
Global.Emulator.Controller = Global.GenControls;
break;
case "TI83":
Global.Emulator = new TI83();
Global.Emulator.Controller = Global.TI83Controls;
break;
case "NES":
Global.Emulator = new NES();
Global.Emulator.Controller = Global.NESControls;
break;
case "GB":
Global.Emulator = new Gameboy();
break;
}
if (Global.Emulator is NullEmulator)
{
throw new Exception();
}
HandlePlatformMenus(Global.Game.System);
Global.Emulator.LoadGame(game);
Text = DisplayNameForSystem(game.System) + " - " + game.Name;
ResetRewindBuffer();
Global.Config.RecentRoms.Add(file.CanonicalName);
if (File.Exists(game.SaveRamPath))
LoadSaveRam();
if (game.System == "GB")
{
new BizHawk.Emulation.Consoles.Gameboy.Debugger(Global.Emulator as Gameboy).Show();
}
if (InputLog.GetMovieMode() == MOVIEMODE.RECORD)
InputLog.StartNewRecording(); //TODO: Uncomment and check for a user movie selected?
else if (InputLog.GetMovieMode() == MOVIEMODE.PLAY)
{
InputLog.LoadMovie(); //TODO: Debug
InputLog.StartPlayback(); //TODO: Debug
}
//setup the throttle based on platform's specifications
//(one day later for some systems we will need to modify it at runtime as the display mode changes)
{
object o = Global.Emulator.Query(EmulatorQuery.VsyncRate);
if (o is double)
throttle.SetCoreFps((double)o);
else throttle.SetCoreFps(60);
SetSpeedPercent(Global.Config.SpeedPercent);
}
RamSearch1.Restart();
HexEditor1.Restart();
CurrentlyOpenRom = path;
return true;
} }
RamSearch1.Restart();
HexEditor1.Restart();
CurrentlyOpenRom = path;
return true;
} }
private void LoadSaveRam() private void LoadSaveRam()

View File

@ -14,43 +14,44 @@ 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.");
var stream = file.GetStream(); var stream = file.GetStream();
FileData = Util.ReadAllBytes(stream); FileData = Util.ReadAllBytes(stream);
int header = (int)(stream.Length % BankSize); int header = (int)(stream.Length % BankSize);
stream.Position = header; stream.Position = header;
int length = (int)stream.Length - header; int length = (int)stream.Length - header;
RomData = new byte[length]; RomData = new byte[length];
stream.Read(RomData, 0, length); stream.Read(RomData, 0, length);
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());
}
}
} }
public void AddOptions(params string[] options) public void AddOptions(params string[] options)

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)
{ {
throw new ArgumentException("The stream must be readable."); if (ThrowExceptions)
throw new ArgumentException("The stream must be readable.");
else return InArchiveFormat.None;
} }
if (stream.Length < SIGNATURE_SIZE) if (stream.Length < SIGNATURE_SIZE)
{ {
throw new ArgumentException("The stream is invalid."); if (ThrowExceptions)
throw new ArgumentException("The stream is invalid.");
else return InArchiveFormat.None;
} }
#region Get file signature #region Get file signature
@ -207,8 +215,10 @@ namespace SevenZip
} }
} }
#endregion #endregion
throw new ArgumentException("The stream is invalid or no corresponding signature was found."); if (ThrowExceptions)
throw new ArgumentException("The stream is invalid or no corresponding signature was found.");
else return InArchiveFormat.None;
} }
/// <summary> /// <summary>
@ -225,14 +235,16 @@ 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; }
isExecutable = false;
return Formats.FormatByFileName(fileName, true); offset = 0;
} isExecutable = false;
return Formats.FormatByFileName(fileName, true);
} }
} }
} }

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
@ -514,13 +518,15 @@ namespace SevenZip
{ {
if (String.IsNullOrEmpty(fileName) && reportErrors) if (String.IsNullOrEmpty(fileName) && reportErrors)
{ {
throw new ArgumentException("File name is null or empty string!"); throw new ArgumentException("File name is null or empty string!");
} }
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];
} }