2013-08-10 01:17:06 +00:00
using System ;
using System.Linq ;
using System.IO ;
using System.Collections.Generic ;
//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.MultiClient
{
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 ;
}
Dictionary < FirmwareDatabase . FirmwareRecord , ResolutionInfo > ResolutionDictionary = new Dictionary < FirmwareDatabase . FirmwareRecord , ResolutionInfo > ( ) ;
public ResolutionInfo Resolve ( string sysId , string firmwareId )
{
return Resolve ( FirmwareDatabase . LookupFirmwareRecord ( sysId , firmwareId ) ) ;
}
public ResolutionInfo Resolve ( FirmwareDatabase . FirmwareRecord record )
{
bool first = true ;
RETRY :
ResolutionInfo resolved = null ;
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 )
{
RealFirmwareFile rff = new RealFirmwareFile ( ) ;
rff . fi = fi ;
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 ;
}
public Dictionary < string , RealFirmwareFile > dict = new Dictionary < string , RealFirmwareFile > ( ) ;
public List < RealFirmwareFile > files = new List < RealFirmwareFile > ( ) ;
}
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-08-11 21:48:17 +00:00
var todo = new Queue < DirectoryInfo > ( new [ ] { new DirectoryInfo ( Global . Config . PathEntries . FirmwaresPath ) } ) ;
2013-08-10 01:17:06 +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)
var options =
from fo in FirmwareDatabase . FirmwareOptions
where fo . systemId = = fr . systemId & & fo . firmwareId = = fr . firmwareId
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
var ri = new ResolutionInfo ( ) ;
ri . FilePath = reader . dict [ hash ] . fi . FullName ;
ri . KnownFirmwareFile = FirmwareDatabase . FirmwareFilesByHash [ hash ] ;
ri . Hash = hash ;
ResolutionDictionary [ fr ] = ri ;
goto DONE_FIRMWARE ;
}
}
DONE_FIRMWARE : ;
}
//apply user overrides
foreach ( var fr in FirmwareDatabase . FirmwareRecords )
{
string userSpec = null ;
//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-08-23 17:17:43 +00:00
ResolutionInfo ri = null ;
if ( ! ResolutionDictionary . TryGetValue ( fr , out ri ) )
{
ri = new ResolutionInfo ( ) ;
ResolutionDictionary [ fr ] = ri ;
}
2013-08-10 01:17:06 +00:00
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)
FirmwareDatabase . FirmwareFile ff = null ;
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 ;
}
}
}
}
}
}