2014-01-08 03:53:53 +00:00
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
2014-01-08 03:53:53 +00:00
// IDEA: put filesizes in DB too. then scans can go real quick by only scanning filesizes that match (and then scanning filesizes that dont match, in case of an emergency)
// this would be adviseable if we end up with a very large firmware file
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 ; }
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 ) ) ;
}
public ResolutionInfo Resolve ( FirmwareDatabase . FirmwareRecord record )
{
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
2013-10-25 00:59:34 +00:00
if ( resolved = = null & & first )
{
DoScanAndResolve ( ) ;
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-01-08 03:53:53 +00:00
public class RealFirmwareReader
2013-10-25 00:59:34 +00:00
{
byte [ ] buffer = new byte [ 0 ] ;
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
if ( len > buffer . Length )
{
buffer = new byte [ len ] ;
}
using ( var fs = fi . OpenRead ( ) )
{
fs . Read ( buffer , 0 , ( int ) len ) ;
}
2014-07-03 19:20:34 +00:00
rff . Hash = buffer . HashSHA1 ( 0 , ( int ) len ) ;
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-01-08 03:53:53 +00:00
var reader = new RealFirmwareReader ( ) ;
2013-10-25 00:59:34 +00:00
2014-01-08 03:53:53 +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
2013-12-13 04:57:14 +00:00
var todo = new Queue < DirectoryInfo > ( ) ;
2013-12-13 05:20:50 +00:00
todo . Enqueue ( new DirectoryInfo ( PathManager . MakeAbsolutePath ( Global . Config . PathEntries . FirmwaresPathFragment , null ) ) ) ;
2013-10-25 00:59:34 +00:00
while ( todo . Count ! = 0 )
{
var di = todo . Dequeue ( ) ;
2014-02-05 23:53:41 +00:00
if ( ! di . Exists )
continue ;
2014-01-08 03:53:53 +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
foreach ( var fi in di . GetFiles ( ) )
{
reader . Read ( fi ) ;
}
}
2014-01-08 03:53:53 +00:00
// now, for each firmware record, try to resolve it
2013-10-25 00:59:34 +00:00
foreach ( var fr in FirmwareDatabase . FirmwareRecords )
{
2014-01-08 03:53:53 +00:00
// clear previous resolution results
_resolutionDictionary . Remove ( fr ) ;
2013-10-25 00:59:34 +00:00
2014-01-08 03:53:53 +00:00
// get all options for this firmware (in order)
var fr1 = fr ;
2013-10-25 00:59:34 +00:00
var options =
from fo in FirmwareDatabase . FirmwareOptions
2014-06-21 06:40:07 +00:00
where fo . systemId = = fr1 . systemId & & fo . firmwareId = = fr1 . firmwareId & & fo . IsAcceptableOrIdeal
2013-10-25 00:59:34 +00:00
select fo ;
2014-01-08 03:53:53 +00:00
// try each option
2013-10-25 00:59:34 +00:00
foreach ( var fo in options )
{
var hash = fo . hash ;
2014-01-08 03:53:53 +00:00
// did we find this firmware?
2013-10-25 00:59:34 +00:00
if ( reader . dict . ContainsKey ( hash ) )
{
2014-01-08 03:53:53 +00:00
// rad! then we can use it
2013-10-27 17:47:54 +00:00
var ri = new ResolutionInfo
{
2014-01-08 03:53:53 +00:00
FilePath = reader . dict [ hash ] . FileInfo . FullName ,
2013-10-27 17:47:54 +00:00
KnownFirmwareFile = FirmwareDatabase . FirmwareFilesByHash [ hash ] ,
Hash = hash
} ;
2014-01-08 03:53:53 +00:00
_resolutionDictionary [ fr ] = ri ;
2013-10-25 00:59:34 +00:00
goto DONE_FIRMWARE ;
}
}
DONE_FIRMWARE : ;
}
2014-01-08 03:53:53 +00:00
// apply user overrides
2013-10-25 00:59:34 +00:00
foreach ( var fr in FirmwareDatabase . FirmwareRecords )
{
2013-10-27 17:47:54 +00:00
string userSpec ;
2013-10-25 00:59:34 +00:00
2014-01-08 03:53:53 +00:00
// do we have a user specification for this firmware record?
2013-10-25 00:59:34 +00:00
if ( Global . Config . FirmwareUserSpecifications . TryGetValue ( fr . ConfigKey , out userSpec ) )
{
2014-01-08 03:53:53 +00:00
// flag it as user specified
2013-10-27 17:47:54 +00:00
ResolutionInfo ri ;
2014-01-08 03:53:53 +00:00
if ( ! _resolutionDictionary . TryGetValue ( fr , out ri ) )
2013-10-25 00:59:34 +00:00
{
ri = new ResolutionInfo ( ) ;
2014-01-08 03:53:53 +00:00
_resolutionDictionary [ fr ] = ri ;
2013-10-25 00:59:34 +00:00
}
ri . UserSpecified = true ;
ri . KnownFirmwareFile = null ;
ri . FilePath = userSpec ;
ri . Hash = null ;
2014-01-08 03:53:53 +00:00
// check whether it exists
2013-10-25 00:59:34 +00:00
var fi = new FileInfo ( userSpec ) ;
if ( ! fi . Exists )
{
ri . Missing = true ;
continue ;
}
2014-01-08 03:53:53 +00:00
// compute its hash
2013-10-25 00:59:34 +00:00
var rff = reader . Read ( fi ) ;
2014-01-08 03:53:53 +00:00
ri . Hash = rff . Hash ;
2013-10-25 00:59:34 +00:00
2014-01-08 03:53:53 +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)
2013-10-27 17:47:54 +00:00
FirmwareDatabase . FirmwareFile ff ;
2014-01-08 03:53:53 +00:00
if ( FirmwareDatabase . FirmwareFilesByHash . TryGetValue ( rff . Hash , out ff ) )
2013-10-25 00:59:34 +00:00
{
ri . KnownFirmwareFile = ff ;
2014-01-08 03:53:53 +00:00
// if the known firmware file is for a different firmware, flag it so we can show a warning
2013-10-25 00:59:34 +00:00
var option =
( from fo in FirmwareDatabase . FirmwareOptions
2014-01-08 03:53:53 +00:00
where fo . hash = = rff . Hash & & fo . ConfigKey ! = fr . ConfigKey
2013-10-25 00:59:34 +00:00
select fr ) . FirstOrDefault ( ) ;
2014-01-08 03:53:53 +00:00
2013-10-25 00:59:34 +00:00
if ( option ! = null )
2014-01-08 03:53:53 +00:00
{
2013-10-25 00:59:34 +00:00
ri . KnownMismatching = true ;
2014-01-08 03:53:53 +00:00
}
2013-10-25 00:59:34 +00:00
}
}
}
}
}
}