2011-01-11 02:55:51 +00:00
using System ;
2011-03-07 02:04:42 +00:00
using System.Collections ;
using System.Collections.Generic ;
2011-01-11 02:55:51 +00:00
using System.IO ;
namespace BizHawk.MultiClient
{
2011-03-07 01:07:49 +00:00
//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)
2011-06-19 23:39:25 +00:00
public class HawkFile : IDisposable
{
2011-06-10 05:31:46 +00:00
public static bool ExistsAt ( string path )
{
using ( var file = new HawkFile ( path ) )
{
return file . Exists ;
}
}
public static byte [ ] ReadAllBytes ( string path )
{
using ( var file = new HawkFile ( path ) )
{
if ( ! file . Exists ) throw new FileNotFoundException ( path ) ;
using ( Stream stream = file . GetStream ( ) )
{
MemoryStream ms = new MemoryStream ( ( int ) stream . Length ) ;
stream . CopyTo ( ms ) ;
return ms . GetBuffer ( ) ;
}
}
}
2011-03-07 01:07:49 +00:00
/// <summary>
/// returns whether a bound file exists. if there is no bound file, it can't exist
/// </summary>
2011-03-07 02:44:30 +00:00
public bool Exists { get { return exists ; } }
2011-03-07 01:07:49 +00:00
/// <summary>
/// gets the directory containing the root
/// </summary>
public string Directory { get { return Path . GetDirectoryName ( rootPath ) ; } }
2011-01-11 02:55:51 +00:00
2011-03-07 01:07:49 +00:00
/// <summary>
/// returns a stream for the currently bound file
/// </summary>
public Stream GetStream ( )
{
if ( boundStream = = null )
2011-03-07 02:44:30 +00:00
throw new InvalidOperationException ( "HawkFile: Can't call GetStream() before youve successfully bound something!" ) ;
2011-03-07 01:07:49 +00:00
return boundStream ;
}
2011-01-11 02:55:51 +00:00
2011-03-07 01:07:49 +00:00
/// <summary>
/// indicates whether this instance is bound
/// </summary>
public bool IsBound { get { return boundStream ! = null ; } }
2011-01-11 02:55:51 +00:00
2011-05-21 19:31:00 +00:00
/// <summary>
/// returns the complete canonical full path ("c:\path\to\archive|member") of the bound file
/// </summary>
2011-06-19 23:39:25 +00:00
public string CanonicalFullPath { get { return MakeCanonicalName ( rootPath , memberPath ) ; } }
2011-05-21 19:31:00 +00:00
2011-03-07 01:07:49 +00:00
/// <summary>
/// returns the complete canonical name ("archive|member") of the bound file
/// </summary>
2011-05-21 19:31:00 +00:00
public string CanonicalName { get { return MakeCanonicalName ( Path . GetFileName ( rootPath ) , memberPath ) ; } }
2011-01-11 02:55:51 +00:00
2011-03-07 01:07:49 +00:00
/// <summary>
/// returns the virtual name of the bound file (disregarding the archive)
/// </summary>
2011-06-19 23:39:25 +00:00
public string Name { get { return GetBoundNameFromCanonical ( MakeCanonicalName ( rootPath , memberPath ) ) ; } }
2011-01-11 02:55:51 +00:00
2011-03-07 01:07:49 +00:00
/// <summary>
/// returns the extension of Name
/// </summary>
2011-07-10 04:54:18 +00:00
public string Extension { get { return Path . GetExtension ( Name ) . ToUpper ( ) ; } }
2011-03-07 01:07:49 +00:00
2011-03-07 02:04:42 +00:00
/// <summary>
/// Indicates whether this file is an archive
/// </summary>
public bool IsArchive { get { return extractor ! = null ; } }
public class ArchiveItem
{
public string name ;
public long size ;
public int index ;
}
public IEnumerable < ArchiveItem > ArchiveItems
{
get
{
if ( ! IsArchive ) throw new InvalidOperationException ( "Cant get archive items from non-archive" ) ;
return archiveItems ;
}
}
2011-06-27 05:31:46 +00:00
/// <summary>
/// these extensions won't even be tried as archives (removes spurious archive detects since some of the signatures are pretty damn weak)
/// </summary>
public string [ ] NonArchiveExtensions = new string [ ] { } ;
2011-03-07 01:07:49 +00:00
//---
2011-03-07 02:44:30 +00:00
bool exists ;
2011-03-07 01:07:49 +00:00
bool rootExists ;
string rootPath ;
string memberPath ;
Stream rootStream , boundStream ;
SevenZip . SevenZipExtractor extractor ;
2011-03-07 02:04:42 +00:00
List < ArchiveItem > archiveItems ;
2011-03-07 01:07:49 +00:00
2011-06-27 05:31:46 +00:00
public HawkFile ( )
{
}
public void Open ( string path )
2011-06-19 23:39:25 +00:00
{
2011-06-27 05:31:46 +00:00
if ( rootPath ! = null ) throw new InvalidOperationException ( "Don't reopen a HawkFile." ) ;
2011-03-07 01:07:49 +00:00
string autobind = null ;
2011-06-10 05:31:46 +00:00
bool isArchivePath = IsCanonicalArchivePath ( path ) ;
if ( isArchivePath )
2011-03-07 01:07:49 +00:00
{
string [ ] parts = path . Split ( '|' ) ;
path = parts [ 0 ] ;
autobind = parts [ 1 ] ;
}
2011-01-11 02:55:51 +00:00
2011-06-19 23:39:25 +00:00
var fi = new FileInfo ( path ) ;
2011-01-11 02:55:51 +00:00
2011-03-07 01:07:49 +00:00
rootExists = fi . Exists ;
if ( fi . Exists = = false )
2011-06-19 23:39:25 +00:00
return ;
2011-01-11 02:55:51 +00:00
2011-03-07 01:07:49 +00:00
rootPath = path ;
2011-03-07 02:44:30 +00:00
exists = true ;
2011-01-11 02:55:51 +00:00
2011-03-07 01:07:49 +00:00
AnalyzeArchive ( path ) ;
if ( extractor = = null )
{
rootStream = new FileStream ( path , FileMode . Open , FileAccess . Read , FileShare . Read ) ;
2011-03-07 02:04:42 +00:00
//we could autobind here, but i dont want to
//bind it later with the desired extensions.
2011-03-07 01:07:49 +00:00
}
2011-06-10 05:31:46 +00:00
if ( autobind = = null )
{
//non-archive files can be automatically bound this way
if ( ! isArchivePath )
BindRoot ( ) ;
}
else
2011-03-07 01:07:49 +00:00
{
autobind = autobind . ToUpperInvariant ( ) ;
for ( int i = 0 ; i < extractor . ArchiveFileData . Count ; i + + )
{
2011-03-07 02:04:42 +00:00
if ( FixArchiveFilename ( extractor . ArchiveFileNames [ i ] ) . ToUpperInvariant ( ) = = autobind )
2011-03-07 01:07:49 +00:00
{
BindArchiveMember ( i ) ;
return ;
}
}
2011-03-07 02:44:30 +00:00
exists = false ;
2011-03-07 01:07:49 +00:00
}
2011-06-19 23:39:25 +00:00
}
2011-03-07 01:07:49 +00:00
2011-06-27 05:31:46 +00:00
public HawkFile ( string path )
{
Open ( path ) ;
}
2011-03-07 01:07:49 +00:00
/// <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 ) ;
}
2011-03-07 02:04:42 +00:00
string FixArchiveFilename ( string fn )
{
return fn . Replace ( '\\' , '/' ) ;
}
/// <summary>
/// binds the selected archive index
/// </summary>
public HawkFile BindArchiveMember ( int archiveIndex )
2011-03-07 01:07:49 +00:00
{
2011-03-07 02:04:42 +00:00
if ( ! rootExists ) return this ;
if ( boundStream ! = null ) throw new InvalidOperationException ( "stream already bound!" ) ;
2011-03-07 01:07:49 +00:00
boundStream = new MemoryStream ( ) ;
2011-03-07 02:04:42 +00:00
extractor . ExtractFile ( archiveIndex , boundStream ) ;
2011-03-07 01:07:49 +00:00
boundStream . Position = 0 ;
2011-03-07 02:04:42 +00:00
memberPath = FixArchiveFilename ( extractor . ArchiveFileNames [ archiveIndex ] ) ; //TODO - maybe go through our own list of names? maybe not, its indexes dont match..
2011-05-21 19:31:00 +00:00
Console . WriteLine ( "bound " + CanonicalFullPath ) ;
2011-06-19 23:39:25 +00:00
2011-03-07 02:04:42 +00:00
return this ;
2011-03-07 01:07:49 +00:00
}
/// <summary>
/// Removes any existing binding
/// </summary>
public void Unbind ( )
{
if ( boundStream ! = null & & boundStream ! = rootStream ) boundStream . Close ( ) ;
boundStream = null ;
memberPath = null ;
}
2011-03-07 02:04:42 +00:00
/// <summary>
2011-06-10 05:31:46 +00:00
/// causes the root to be bound (in the case of non-archive files)
2011-03-07 02:04:42 +00:00
/// </summary>
2011-03-07 01:07:49 +00:00
void BindRoot ( )
{
boundStream = rootStream ;
2011-05-21 19:31:00 +00:00
Console . WriteLine ( "bound " + CanonicalFullPath ) ;
2011-03-07 01:07:49 +00:00
}
/// <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>
2011-03-07 02:04:42 +00:00
/// binds one of the supplied extensions if there is only one match in the archive
/// </summary>
public HawkFile BindSoleItemOf ( params string [ ] extensions )
{
return BindByExtensionCore ( false , extensions ) ;
}
2011-06-19 23:39:25 +00:00
/// <summary>
2011-03-07 02:04:42 +00:00
/// Binds the first item in the archive (or the file itself) if the extension matches one of the supplied templates.
/// You probably should not use this. use BindSoleItemOf or the archive chooser instead
2011-03-07 01:07:49 +00:00
/// </summary>
public HawkFile BindFirstOf ( params string [ ] extensions )
2011-03-07 02:04:42 +00:00
{
return BindByExtensionCore ( true , extensions ) ;
}
HawkFile BindByExtensionCore ( bool first , params string [ ] extensions )
2011-03-07 01:07:49 +00:00
{
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 ( ) ;
2011-03-07 02:04:42 +00:00
if ( extensions . Length = = 0 | | extension . In ( extensions ) )
2011-03-07 01:07:49 +00:00
{
BindRoot ( ) ;
}
return this ;
}
2011-03-07 02:04:42 +00:00
var candidates = new List < int > ( ) ;
for ( int i = 0 ; i < extractor . ArchiveFileData . Count ; i + + )
2011-03-07 01:07:49 +00:00
{
var e = extractor . ArchiveFileData [ i ] ;
2011-03-07 02:04:42 +00:00
if ( e . IsDirectory ) continue ;
var extension = Path . GetExtension ( e . FileName ) . ToUpperInvariant ( ) ;
extension = extension . TrimStart ( '.' ) ;
2011-03-07 01:07:49 +00:00
if ( extensions . Length = = 0 | | extension . In ( extensions ) )
{
2011-03-07 02:04:42 +00:00
if ( first )
{
BindArchiveMember ( i ) ;
return this ;
}
candidates . Add ( i ) ;
2011-03-07 01:07:49 +00:00
}
}
2011-03-07 02:04:42 +00:00
if ( candidates . Count = = 1 )
BindArchiveMember ( candidates [ 0 ] ) ;
2011-03-07 01:07:49 +00:00
return this ;
}
2011-03-07 02:04:42 +00:00
void ScanArchive ( )
{
archiveItems = new List < ArchiveItem > ( ) ;
for ( int i = 0 ; i < extractor . ArchiveFileData . Count ; i + + )
2011-03-07 01:07:49 +00:00
{
2011-03-07 02:04:42 +00:00
var afd = extractor . ArchiveFileData [ i ] ;
2011-03-07 02:09:18 +00:00
if ( afd . IsDirectory ) continue ;
2011-03-07 02:04:42 +00:00
var ai = new ArchiveItem ( ) ;
ai . name = FixArchiveFilename ( afd . FileName ) ;
ai . size = ( long ) afd . Size ; //ulong. obnoxious.
ai . index = i ;
archiveItems . Add ( ai ) ;
2011-03-07 01:07:49 +00:00
}
2011-03-07 02:04:42 +00:00
}
private void AnalyzeArchive ( string path )
2011-06-19 23:39:25 +00:00
{
2011-03-07 02:04:42 +00:00
SevenZip . FileChecker . ThrowExceptions = false ;
int offset ;
bool isExecutable ;
2011-06-27 05:31:46 +00:00
foreach ( string ext in NonArchiveExtensions )
if ( Path . GetExtension ( path ) . Substring ( 1 ) . ToLower ( ) = = ext . ToLower ( ) )
return ;
2011-03-07 02:04:42 +00:00
if ( SevenZip . FileChecker . CheckSignature ( path , out offset , out isExecutable ) ! = SevenZip . InArchiveFormat . None )
2011-03-07 01:07:49 +00:00
{
2011-03-07 02:04:42 +00:00
extractor = new SevenZip . SevenZipExtractor ( path ) ;
2011-03-07 02:53:18 +00:00
try
{
ScanArchive ( ) ;
}
catch
{
2011-03-07 02:54:38 +00:00
extractor . Dispose ( ) ;
2011-03-07 02:53:18 +00:00
extractor = null ;
archiveItems = null ;
}
2011-03-07 01:07:49 +00:00
}
2011-06-19 23:39:25 +00:00
}
2011-01-11 02:55:51 +00:00
2011-06-19 23:39:25 +00:00
public void Dispose ( )
{
2011-03-07 01:07:49 +00:00
Unbind ( ) ;
2011-06-19 23:39:25 +00:00
2011-03-07 01:07:49 +00:00
if ( extractor ! = null ) extractor . Dispose ( ) ;
if ( rootStream ! = null ) rootStream . Dispose ( ) ;
extractor = null ;
rootStream = null ;
}
2011-06-19 23:39:25 +00:00
}
2011-06-27 05:31:46 +00:00
}