From ee70aefd3ac43daa8079e8437d8e2d4f2c6d3042 Mon Sep 17 00:00:00 2001 From: zeromus Date: Sun, 27 Oct 2013 21:29:18 +0000 Subject: [PATCH] lay some groundwork for decoupling HawkFile from 7z. not actually reorganized yet, but we can let it gestate for a while and it will be simple to move HawkFile to Bizhawk.Common when someone needs it --- BizHawk.Client.Common/HawkFile.cs | 247 +++++++++++++++++++------- BizHawk.MultiClient/ArchiveChooser.cs | 17 +- 2 files changed, 188 insertions(+), 76 deletions(-) diff --git a/BizHawk.Client.Common/HawkFile.cs b/BizHawk.Client.Common/HawkFile.cs index 67b4473d0b..3737cb2c30 100644 --- a/BizHawk.Client.Common/HawkFile.cs +++ b/BizHawk.Client.Common/HawkFile.cs @@ -3,14 +3,121 @@ using System.Collections.Generic; using System.IO; using System.Linq; +//the HawkFile class is excessively engineered with the IHawkFileArchiveHandler to decouple the archive handling from the basic file handling. +//This is so we could drop in an unamanged dearchiver library optionally later as a performance optimization without ruining the portability of the code. +//Also, we want to be able to use HawkFiles in BizHawk.Common withuot bringing in a large 7-zip dependency + namespace BizHawk.Client.Common { //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 interface IHawkFileArchiveHandler : IDisposable + { + //todo - could this receive a hawkfile itself? possibly handy, in very clever scenarios of mounting fake files + bool CheckSignature(string fileName, out int offset, out bool isExecutable); + + List Scan(); + + IHawkFileArchiveHandler Construct(string path); + + void ExtractFile(int index, Stream stream); + } + + public class HawkFileArchiveItem + { + /// + /// member name + /// + public string name; + + /// + /// size of member file + /// + public long size; + + /// + /// the index of this archive item + /// + public int index; + + /// + /// the index WITHIN THE ARCHIVE (for internal tracking by a IHawkFileArchiveHandler) of the member + /// + public int archiveIndex; + } + + /// + /// Implementation of IHawkFileArchiveHandler using SevenZipSharp pure managed code + /// + class SevenZipSharpArchiveHandler : IHawkFileArchiveHandler + { + public void Dispose() + { + if(extractor != null) + { + extractor.Dispose(); + extractor = null; + } + } + + public bool CheckSignature(string fileName, out int offset, out bool isExecutable) + { + SevenZip.FileChecker.ThrowExceptions = false; + return SevenZip.FileChecker.CheckSignature(fileName, out offset, out isExecutable) != SevenZip.InArchiveFormat.None; + } + + public IHawkFileArchiveHandler Construct(string path) + { + SevenZipSharpArchiveHandler ret = new SevenZipSharpArchiveHandler(); + ret.Open(path); + return ret; + } + + void Open(string path) + { + extractor = new SevenZip.SevenZipExtractor(path); + } + + SevenZip.SevenZipExtractor extractor; + + public List Scan() + { + List ret = new List(); + for (int i = 0; i < extractor.ArchiveFileData.Count; i++) + { + var afd = extractor.ArchiveFileData[i]; + if (afd.IsDirectory) continue; + var ai = new HawkFileArchiveItem {name = HawkFile.Util_FixArchiveFilename(afd.FileName), size = (long) afd.Size, archiveIndex = i, index = ret.Count}; + ret.Add(ai); + } + + return ret; + } + + public void ExtractFile(int index, Stream stream) + { + extractor.ExtractFile(index, stream); + } + } + + /// + /// HawkFile allows a variety of objects (actual files, archive members) to be treated as normal filesystem objects to be opened, closed, and read. + /// It can understand paths in 'canonical' format which includes /path/to/archive.zip|member.rom as well as /path/to/file.rom + /// When opening an archive, it won't always be clear automatically which member should actually be used. + /// Therefore there is a concept of 'binding' where a HawkFile attaches itself to an archive member which is the file that it will actually be using. + /// public sealed class HawkFile : IDisposable { + /// + /// Set this with an instance which can construct archive handlers as necessary for archive handling. + /// + public static IHawkFileArchiveHandler ArchiveHandlerFactory = new SevenZipSharpArchiveHandler(); + + /// + /// Utility: Uses full HawkFile processing to determine whether a file exists at the provided path + /// public static bool ExistsAt(string path) { using (var file = new HawkFile(path)) @@ -19,6 +126,9 @@ namespace BizHawk.Client.Common } } + /// + /// Utility: attempts to read all the content from the provided path. + /// public static byte[] ReadAllBytes(string path) { using (var file = new HawkFile(path)) @@ -92,14 +202,14 @@ namespace BizHawk.Client.Common } - public class ArchiveItem - { - public string name; - public long size; - public int index; - } + //public class ArchiveItem + //{ + // public string name; + // public long size; + // public int index; + //} - public IEnumerable ArchiveItems + public IList ArchiveItems { get { @@ -119,8 +229,8 @@ namespace BizHawk.Client.Common string rootPath; string memberPath; Stream rootStream, boundStream; - SevenZip.SevenZipExtractor extractor; - List archiveItems; + IHawkFileArchiveHandler extractor; + List archiveItems; public HawkFile() { @@ -165,12 +275,16 @@ namespace BizHawk.Client.Common else { autobind = autobind.ToUpperInvariant(); - for (int i = 0; i < extractor.ArchiveFileData.Count; i++) + if (extractor != null) { - if (FixArchiveFilename(extractor.ArchiveFileNames[i]).ToUpperInvariant() == autobind) + var scanResults = extractor.Scan(); + for (int i = 0; i < scanResults.Count; i++) { - BindArchiveMember(i); - return; + if (scanResults[i].name.ToUpperInvariant() == autobind) + { + BindArchiveMember(i); + return; + } } } @@ -178,54 +292,27 @@ namespace BizHawk.Client.Common } } + /// + /// Makes a new HawkFile based on the provided path. + /// public HawkFile(string path) { Open(path); } - /// - /// is the supplied path a canonical name including an archive? - /// - bool IsCanonicalArchivePath(string path) - { - return (path.IndexOf('|') != -1); - } - - /// - /// converts a canonical name to a bound name (the bound part, whether or not it is an archive) - /// - string GetBoundNameFromCanonical(string canonical) - { - string[] parts = canonical.Split('|'); - return parts[parts.Length - 1]; - } - - /// - /// makes a canonical name from two parts - /// - string MakeCanonicalName(string root, string member) - { - if (member == null) return root; - else return string.Format("{0}|{1}", root, member); - } - - string FixArchiveFilename(string fn) - { - return fn.Replace('\\', '/'); - } /// /// binds the specified ArchiveItem which you should have gotten by interrogating an archive hawkfile /// - public HawkFile BindArchiveMember(ArchiveItem item) + public HawkFile BindArchiveMember(HawkFileArchiveItem item) { - return BindArchiveMember(item.index); + return BindArchiveMember(item.archiveIndex); } /// /// finds an ArchiveItem with the specified name (path) within the archive; returns null if it doesnt exist /// - public ArchiveItem FindArchiveMember(string name) + public HawkFileArchiveItem FindArchiveMember(string name) { return ArchiveItems.FirstOrDefault(ai => ai.name == name); } @@ -243,15 +330,16 @@ namespace BizHawk.Client.Common /// /// binds the selected archive index /// - public HawkFile BindArchiveMember(int archiveIndex) + public HawkFile BindArchiveMember(int index) { if (!rootExists) return this; if (boundStream != null) throw new InvalidOperationException("stream already bound!"); boundStream = new MemoryStream(); + int archiveIndex = archiveItems[index].archiveIndex; extractor.ExtractFile(archiveIndex, boundStream); boundStream.Position = 0; - memberPath = FixArchiveFilename(extractor.ArchiveFileNames[archiveIndex]); //TODO - maybe go through our own list of names? maybe not, its indexes dont match.. + memberPath = archiveItems[index].name; //TODO - maybe go through our own list of names? maybe not, its indexes dont match.. Console.WriteLine("HawkFile bound " + CanonicalFullPath); BoundIndex = archiveIndex; return this; @@ -320,11 +408,10 @@ namespace BizHawk.Client.Common } var candidates = new List(); - for (int i = 0; i < extractor.ArchiveFileData.Count; i++) + for (int i = 0; i < archiveItems.Count; i++) { - var e = extractor.ArchiveFileData[i]; - if (e.IsDirectory) continue; - var extension = Path.GetExtension(e.FileName).ToUpperInvariant(); + var e = archiveItems[i]; + var extension = Path.GetExtension(e.name).ToUpperInvariant(); extension = extension.TrimStart('.'); if (extensions.Length == 0 || extension.In(extensions)) { @@ -341,32 +428,27 @@ namespace BizHawk.Client.Common return this; } - void ScanArchive() { - archiveItems = new List(); - for (int i = 0; i < extractor.ArchiveFileData.Count; i++) - { - var afd = extractor.ArchiveFileData[i]; - if (afd.IsDirectory) continue; - var ai = new ArchiveItem {name = FixArchiveFilename(afd.FileName), size = (long) afd.Size, index = i}; - archiveItems.Add(ai); - } + archiveItems = extractor.Scan(); } private void AnalyzeArchive(string path) { - SevenZip.FileChecker.ThrowExceptions = false; + //no archive handler == no analysis + if (ArchiveHandlerFactory == null) + return; + int offset; bool isExecutable; if (NonArchiveExtensions.Any(ext => Path.GetExtension(path).Substring(1).ToLower() == ext.ToLower())) { return; } - - if (SevenZip.FileChecker.CheckSignature(path, out offset, out isExecutable) != SevenZip.InArchiveFormat.None) + + if (ArchiveHandlerFactory.CheckSignature(path, out offset, out isExecutable)) { - extractor = new SevenZip.SevenZipExtractor(path); + extractor = ArchiveHandlerFactory.Construct(path); try { ScanArchive(); @@ -390,5 +472,40 @@ namespace BizHawk.Client.Common extractor = null; rootStream = null; } + + /// + /// is the supplied path a canonical name including an archive? + /// + static bool IsCanonicalArchivePath(string path) + { + return (path.IndexOf('|') != -1); + } + + /// + /// Repairs paths from an archive which contain offensive characters + /// + public static string Util_FixArchiveFilename(string fn) + { + return fn.Replace('\\', '/'); + } + + /// + /// converts a canonical name to a bound name (the bound part, whether or not it is an archive) + /// + static string GetBoundNameFromCanonical(string canonical) + { + string[] parts = canonical.Split('|'); + return parts[parts.Length - 1]; + } + + /// + /// makes a canonical name from two parts + /// + string MakeCanonicalName(string root, string member) + { + if (member == null) return root; + else return string.Format("{0}|{1}", root, member); + } + } } diff --git a/BizHawk.MultiClient/ArchiveChooser.cs b/BizHawk.MultiClient/ArchiveChooser.cs index 232c49b823..8c61029bd9 100644 --- a/BizHawk.MultiClient/ArchiveChooser.cs +++ b/BizHawk.MultiClient/ArchiveChooser.cs @@ -13,9 +13,11 @@ namespace BizHawk.MultiClient public ArchiveChooser(HawkFile hawkfile) { InitializeComponent(); - foreach (var item in hawkfile.ArchiveItems) + var items = hawkfile.ArchiveItems; + for(int i=0;i