2014-11-12 00:04:08 +00:00
using System ;
using System.Collections.Generic ;
2013-10-25 00:59:34 +00:00
using System.IO ;
2014-01-08 03:53:53 +00:00
using System.Linq ;
2013-10-25 00:59:34 +00:00
2014-07-03 19:20:34 +00:00
using BizHawk.Common.BufferExtensions ;
2013-11-04 01:06:36 +00:00
using BizHawk.Emulation.Common ;
2013-11-04 00:36:15 +00:00
2013-10-25 00:59:34 +00:00
namespace BizHawk.Client.Common
{
public class FirmwareManager
{
2014-01-08 03:53:53 +00:00
// represents a file found on disk in the user's firmware directory matching a file in our database
public class RealFirmwareFile
2013-10-25 00:59:34 +00:00
{
2014-01-08 03:53:53 +00:00
public FileInfo FileInfo { get ; set ; }
public string Hash { get ; set ; }
2013-10-25 00:59:34 +00:00
}
public class ResolutionInfo
{
2014-01-08 03:53:53 +00:00
public bool UserSpecified { get ; set ; }
public bool Missing { get ; set ; }
public bool KnownMismatching { get ; set ; }
public FirmwareDatabase . FirmwareFile KnownFirmwareFile { get ; set ; }
public string FilePath { get ; set ; }
public string Hash { get ; set ; }
2014-11-13 00:09:16 +00:00
public long Size { get ; set ; }
2013-10-25 00:59:34 +00:00
}
2014-01-08 03:53:53 +00:00
private readonly Dictionary < FirmwareDatabase . FirmwareRecord , ResolutionInfo > _resolutionDictionary = new Dictionary < FirmwareDatabase . FirmwareRecord , ResolutionInfo > ( ) ;
2013-10-25 00:59:34 +00:00
public ResolutionInfo Resolve ( string sysId , string firmwareId )
{
return Resolve ( FirmwareDatabase . LookupFirmwareRecord ( sysId , firmwareId ) ) ;
}
2014-11-12 00:04:08 +00:00
public ResolutionInfo Resolve ( FirmwareDatabase . FirmwareRecord record , bool forbidScan = false )
2013-10-25 00:59:34 +00:00
{
2014-11-12 00:04:08 +00:00
//purpose of forbidScan: sometimes this is called from a loop in Scan(). we dont want to repeatedly DoScanAndResolve in that case, its already been done.
2013-10-25 00:59:34 +00:00
bool first = true ;
RETRY :
2013-10-27 17:47:54 +00:00
ResolutionInfo resolved ;
2014-01-08 03:53:53 +00:00
_resolutionDictionary . TryGetValue ( record , out resolved ) ;
2013-10-25 00:59:34 +00:00
2014-01-08 03:53:53 +00:00
// couldnt find it! do a scan and resolve to try harder
2014-08-03 22:38:27 +00:00
// NOTE: this could result in bad performance in some cases if the scanning happens repeatedly..
2013-10-25 00:59:34 +00:00
if ( resolved = = null & & first )
{
2014-11-12 00:04:08 +00:00
if ( ! forbidScan ) DoScanAndResolve ( ) ;
2013-10-25 00:59:34 +00:00
first = false ;
goto RETRY ;
}
return resolved ;
}
2014-01-08 03:53:53 +00:00
// Requests the spcified firmware. tries really hard to scan and resolve as necessary
2013-10-25 00:59:34 +00:00
public string Request ( string sysId , string firmwareId )
{
var resolved = Resolve ( sysId , firmwareId ) ;
if ( resolved = = null ) return null ;
return resolved . FilePath ;
}
2014-11-12 00:04:08 +00:00
public class RealFirmwareReader : IDisposable
2013-10-25 00:59:34 +00:00
{
2014-11-12 00:04:08 +00:00
System . Security . Cryptography . SHA1 sha1 = System . Security . Cryptography . SHA1 . Create ( ) ;
public void Dispose ( )
{
sha1 . Dispose ( ) ;
sha1 = null ;
}
2013-10-25 00:59:34 +00:00
public RealFirmwareFile Read ( FileInfo fi )
{
2014-01-08 03:53:53 +00:00
var rff = new RealFirmwareFile { FileInfo = fi } ;
2013-10-25 00:59:34 +00:00
long len = fi . Length ;
2014-01-08 03:53:53 +00:00
using ( var fs = fi . OpenRead ( ) )
{
2014-11-12 01:27:11 +00:00
sha1 . ComputeHash ( fs ) ;
2014-01-08 03:53:53 +00:00
}
2014-11-12 00:04:08 +00:00
rff . Hash = sha1 . Hash . BytesToHexString ( ) ;
2014-01-08 03:53:53 +00:00
dict [ rff . Hash ] = rff ;
_files . Add ( rff ) ;
2013-10-25 00:59:34 +00:00
return rff ;
}
2014-01-08 03:53:53 +00:00
2013-10-27 17:47:54 +00:00
public readonly Dictionary < string , RealFirmwareFile > dict = new Dictionary < string , RealFirmwareFile > ( ) ;
2014-01-08 03:53:53 +00:00
private readonly List < RealFirmwareFile > _files = new List < RealFirmwareFile > ( ) ;
2013-10-25 00:59:34 +00:00
}
public void DoScanAndResolve ( )
{
2014-11-13 00:09:16 +00:00
//build a list of file sizes. Only those will be checked during scanning
HashSet < long > sizes = new HashSet < long > ( ) ;
foreach ( var ff in FirmwareDatabase . FirmwareFiles )
sizes . Add ( ff . size ) ;
2014-11-12 00:04:08 +00:00
using ( var reader = new RealFirmwareReader ( ) )
2013-10-25 00:59:34 +00:00
{
2014-11-12 00:04:08 +00:00
// build a list of files under the global firmwares path, and build a hash for each of them while we're at it
var todo = new Queue < DirectoryInfo > ( ) ;
todo . Enqueue ( new DirectoryInfo ( PathManager . MakeAbsolutePath ( Global . Config . PathEntries . FirmwaresPathFragment , null ) ) ) ;
while ( todo . Count ! = 0 )
{
var di = todo . Dequeue ( ) ;
2013-10-25 00:59:34 +00:00
2014-11-12 00:04:08 +00:00
if ( ! di . Exists )
continue ;
2014-02-05 23:53:41 +00:00
2014-11-12 00:04:08 +00:00
// we're going to allow recursing into subdirectories, now. its been verified to work OK
foreach ( var disub in di . GetDirectories ( ) )
{
todo . Enqueue ( disub ) ;
}
2013-10-25 00:59:34 +00:00
2014-11-12 00:04:08 +00:00
foreach ( var fi in di . GetFiles ( ) )
{
2014-11-13 00:09:16 +00:00
if ( sizes . Contains ( fi . Length ) )
reader . Read ( fi ) ;
2014-11-12 00:04:08 +00:00
}
2013-10-25 00:59:34 +00:00
}
2014-11-12 00:04:08 +00:00
// now, for each firmware record, try to resolve it
foreach ( var fr in FirmwareDatabase . FirmwareRecords )
2013-10-25 00:59:34 +00:00
{
2014-11-12 00:04:08 +00:00
// clear previous resolution results
_resolutionDictionary . Remove ( fr ) ;
// get all options for this firmware (in order)
var fr1 = fr ;
var options =
from fo in FirmwareDatabase . FirmwareOptions
where fo . systemId = = fr1 . systemId & & fo . firmwareId = = fr1 . firmwareId & & fo . IsAcceptableOrIdeal
select fo ;
// try each option
foreach ( var fo in options )
2013-10-25 00:59:34 +00:00
{
2014-11-12 00:04:08 +00:00
var hash = fo . hash ;
// did we find this firmware?
if ( reader . dict . ContainsKey ( hash ) )
{
// rad! then we can use it
var ri = new ResolutionInfo
{
FilePath = reader . dict [ hash ] . FileInfo . FullName ,
KnownFirmwareFile = FirmwareDatabase . FirmwareFilesByHash [ hash ] ,
2014-11-13 00:09:16 +00:00
Hash = hash ,
Size = fo . size
2014-11-12 00:04:08 +00:00
} ;
_resolutionDictionary [ fr ] = ri ;
goto DONE_FIRMWARE ;
}
2013-10-25 00:59:34 +00:00
}
2014-11-12 00:04:08 +00:00
DONE_FIRMWARE : ;
2013-10-25 00:59:34 +00:00
2014-11-12 00:04:08 +00:00
}
2013-10-25 00:59:34 +00:00
2014-11-12 00:04:08 +00:00
// apply user overrides
foreach ( var fr in FirmwareDatabase . FirmwareRecords )
2013-10-25 00:59:34 +00:00
{
2014-11-12 00:04:08 +00:00
string userSpec ;
2013-10-25 00:59:34 +00:00
2014-11-12 00:04:08 +00:00
// do we have a user specification for this firmware record?
if ( Global . Config . FirmwareUserSpecifications . TryGetValue ( fr . ConfigKey , out userSpec ) )
2013-10-25 00:59:34 +00:00
{
2014-11-12 00:04:08 +00:00
// flag it as user specified
ResolutionInfo ri ;
if ( ! _resolutionDictionary . TryGetValue ( fr , out ri ) )
{
ri = new ResolutionInfo ( ) ;
_resolutionDictionary [ fr ] = ri ;
}
ri . UserSpecified = true ;
ri . KnownFirmwareFile = null ;
ri . FilePath = userSpec ;
ri . Hash = null ;
// check whether it exists
var fi = new FileInfo ( userSpec ) ;
if ( ! fi . Exists )
{
ri . Missing = true ;
continue ;
}
2013-10-25 00:59:34 +00:00
2014-11-12 00:04:08 +00:00
// compute its hash
var rff = reader . Read ( fi ) ;
2014-11-13 00:09:16 +00:00
ri . Size = fi . Length ;
2014-11-12 00:04:08 +00:00
ri . Hash = rff . Hash ;
2014-01-08 03:53:53 +00:00
2014-11-12 00:04:08 +00:00
// check whether it was a known file anyway, and go ahead and bind to the known file, as a perk (the firmwares config doesnt really use this information right now)
FirmwareDatabase . FirmwareFile ff ;
if ( FirmwareDatabase . FirmwareFilesByHash . TryGetValue ( rff . Hash , out ff ) )
2014-01-08 03:53:53 +00:00
{
2014-11-12 00:04:08 +00:00
ri . KnownFirmwareFile = ff ;
// if the known firmware file is for a different firmware, flag it so we can show a warning
var option =
( from fo in FirmwareDatabase . FirmwareOptions
where fo . hash = = rff . Hash & & fo . ConfigKey ! = fr . ConfigKey
select fr ) . FirstOrDefault ( ) ;
if ( option ! = null )
{
ri . KnownMismatching = true ;
}
2014-01-08 03:53:53 +00:00
}
2013-10-25 00:59:34 +00:00
}
2014-11-12 00:04:08 +00:00
} //foreach(firmware record)
} //using(new RealFirmwareReader())
} //DoScanAndResolve()
} //class FirmwareManager
} //namespace