2013-10-27 17:47:54 +00:00
using System.Linq ;
2013-10-25 00:59:34 +00:00
using System.IO ;
using System.Collections.Generic ;
2013-11-04 00:36:15 +00:00
using BizHawk.Common ;
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
//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
namespace BizHawk.Client.Common
{
public class FirmwareManager
{
//represents a file found on disk in the user's firmware directory matching a file in our database
class RealFirmwareFile
{
public FileInfo fi ;
public string hash ;
}
public class ResolutionInfo
{
public bool UserSpecified ;
public bool Missing ;
public bool KnownMismatching ;
public FirmwareDatabase . FirmwareFile KnownFirmwareFile ;
public string FilePath ;
public string Hash ;
}
2013-10-27 17:47:54 +00:00
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 ;
2013-10-25 00:59:34 +00:00
ResolutionDictionary . TryGetValue ( record , out resolved ) ;
//couldnt find it! do a scan and resolve to try harder
if ( resolved = = null & & first )
{
DoScanAndResolve ( ) ;
first = false ;
goto RETRY ;
}
return resolved ;
}
//Requests the spcified firmware. tries really hard to scan and resolve as necessary
public string Request ( string sysId , string firmwareId )
{
var resolved = Resolve ( sysId , firmwareId ) ;
if ( resolved = = null ) return null ;
return resolved . FilePath ;
}
class RealFirmwareReader
{
byte [ ] buffer = new byte [ 0 ] ;
public RealFirmwareFile Read ( FileInfo fi )
{
2013-10-27 17:47:54 +00:00
RealFirmwareFile rff = new RealFirmwareFile { fi = fi } ;
2013-10-25 00:59:34 +00:00
long len = fi . Length ;
if ( len > buffer . Length ) buffer = new byte [ len ] ;
using ( var fs = fi . OpenRead ( ) ) fs . Read ( buffer , 0 , ( int ) len ) ;
rff . hash = Util . Hash_SHA1 ( buffer , 0 , ( int ) len ) ;
dict [ rff . hash ] = rff ;
files . Add ( rff ) ;
return rff ;
}
2013-10-27 17:47:54 +00:00
public readonly Dictionary < string , RealFirmwareFile > dict = new Dictionary < string , RealFirmwareFile > ( ) ;
private readonly List < RealFirmwareFile > files = new List < RealFirmwareFile > ( ) ;
2013-10-25 00:59:34 +00:00
}
public void DoScanAndResolve ( )
{
RealFirmwareReader reader = new RealFirmwareReader ( ) ;
//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 ( ) ;
//we're going to allow recursing into subdirectories, now. its been verified to work OK
foreach ( var disub in di . GetDirectories ( ) ) todo . Enqueue ( disub ) ;
foreach ( var fi in di . GetFiles ( ) )
{
reader . Read ( fi ) ;
}
}
//now, for each firmware record, try to resolve it
foreach ( var fr in FirmwareDatabase . FirmwareRecords )
{
//clear previous resolution results
ResolutionDictionary . Remove ( fr ) ;
//get all options for this firmware (in order)
2013-10-27 17:47:54 +00:00
FirmwareDatabase . FirmwareRecord fr1 = fr ;
2013-10-25 00:59:34 +00:00
var options =
from fo in FirmwareDatabase . FirmwareOptions
2013-10-27 17:47:54 +00:00
where fo . systemId = = fr1 . systemId & & fo . firmwareId = = fr1 . firmwareId
2013-10-25 00:59:34 +00:00
select fo ;
//try each option
foreach ( var fo in options )
{
var hash = fo . hash ;
//did we find this firmware?
if ( reader . dict . ContainsKey ( hash ) )
{
//rad! then we can use it
2013-10-27 17:47:54 +00:00
var ri = new ResolutionInfo
{
FilePath = reader . dict [ hash ] . fi . FullName ,
KnownFirmwareFile = FirmwareDatabase . FirmwareFilesByHash [ hash ] ,
Hash = hash
} ;
2013-10-25 00:59:34 +00:00
ResolutionDictionary [ fr ] = ri ;
goto DONE_FIRMWARE ;
}
}
DONE_FIRMWARE : ;
}
//apply user overrides
foreach ( var fr in FirmwareDatabase . FirmwareRecords )
{
2013-10-27 17:47:54 +00:00
string userSpec ;
2013-10-25 00:59:34 +00:00
//do we have a user specification for this firmware record?
if ( Global . Config . FirmwareUserSpecifications . TryGetValue ( fr . ConfigKey , out userSpec ) )
{
//flag it as user specified
2013-10-27 17:47:54 +00:00
ResolutionInfo ri ;
2013-10-25 00:59:34 +00:00
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 ;
}
//compute its hash
var rff = reader . Read ( fi ) ;
ri . Hash = rff . hash ;
//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 ;
2013-10-25 00:59:34 +00:00
if ( FirmwareDatabase . FirmwareFilesByHash . TryGetValue ( rff . hash , out ff ) )
{
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 ;
}
}
}
}
}
}