2014-11-12 00:04:08 +00:00
using System ;
using System.Collections.Generic ;
2017-05-17 18:18:26 +00:00
using System.Security.Cryptography ;
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
}
2017-04-14 19:59:01 +00:00
public List < FirmwareEventArgs > RecentlyServed { get ; }
2015-02-05 23:53:25 +00:00
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
2015-02-05 23:25:28 +00:00
public class FirmwareEventArgs
{
public string Hash { get ; set ; }
public long Size { get ; set ; }
public string SystemId { get ; set ; }
public string FirmwareId { get ; set ; }
}
2015-02-05 23:53:25 +00:00
public FirmwareManager ( )
{
RecentlyServed = new List < FirmwareEventArgs > ( ) ;
}
2015-02-05 23:25:28 +00:00
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
{
2017-04-14 19:59:01 +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 )
{
2017-04-14 19:59:01 +00:00
if ( ! forbidScan )
{
DoScanAndResolve ( ) ;
}
2017-05-09 12:21:38 +00:00
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 ) ;
2017-04-14 19:59:01 +00:00
if ( resolved = = null )
{
return null ;
}
2015-02-05 23:53:25 +00:00
RecentlyServed . Add ( new FirmwareEventArgs
2017-04-14 19:59:01 +00:00
{
SystemId = sysId ,
FirmwareId = firmwareId ,
Hash = resolved . Hash ,
Size = resolved . Size
} ) ;
2013-10-25 00:59:34 +00:00
return resolved . FilePath ;
}
2017-05-19 18:17:07 +00:00
private class RealFirmwareReader : IDisposable
2013-10-25 00:59:34 +00:00
{
2017-05-19 18:17:07 +00:00
private readonly List < RealFirmwareFile > _files = new List < RealFirmwareFile > ( ) ;
2017-05-17 18:18:26 +00:00
private SHA1 _sha1 = SHA1 . Create ( ) ;
2014-11-12 00:04:08 +00:00
public void Dispose ( )
{
2017-05-17 18:18:26 +00:00
_sha1 . Dispose ( ) ;
_sha1 = null ;
2014-11-12 00:04:08 +00:00
}
2017-05-17 18:18:26 +00:00
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 ( ) )
{
2017-05-17 18:18:26 +00:00
_sha1 . ComputeHash ( fs ) ;
2014-01-08 03:53:53 +00:00
}
2017-05-17 18:18:26 +00:00
rff . Hash = _sha1 . Hash . BytesToHexString ( ) ;
2017-05-19 18:17:07 +00:00
Dict [ rff . Hash ] = rff ;
2014-01-08 03:53:53 +00:00
_files . Add ( rff ) ;
2013-10-25 00:59:34 +00:00
return rff ;
}
2014-01-08 03:53:53 +00:00
2017-05-19 18:17:07 +00:00
public Dictionary < string , RealFirmwareFile > Dict { get ; } = new Dictionary < string , RealFirmwareFile > ( ) ;
2013-10-25 00:59:34 +00:00
}
public void DoScanAndResolve ( )
{
2017-04-14 19:59:01 +00:00
// build a list of file sizes. Only those will be checked during scanning
2014-11-13 00:09:16 +00:00
HashSet < long > sizes = new HashSet < long > ( ) ;
foreach ( var ff in FirmwareDatabase . FirmwareFiles )
2017-04-14 19:59:01 +00:00
{
2017-04-27 15:55:22 +00:00
sizes . Add ( ff . Size ) ;
2017-04-14 19:59:01 +00:00
}
2014-11-13 00:09:16 +00:00
2017-04-14 19:59:01 +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 )
2017-04-14 19:59:01 +00:00
{
2014-11-12 00:04:08 +00:00
continue ;
2017-04-14 19:59:01 +00:00
}
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 ( ) )
{
2017-04-14 19:59:01 +00:00
if ( sizes . Contains ( fi . Length ) )
{
2014-11-13 00:09:16 +00:00
reader . Read ( fi ) ;
2017-04-14 19:59:01 +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
// 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
2017-04-27 15:55:22 +00:00
where fo . SystemId = = fr1 . SystemId & & fo . FirmwareId = = fr1 . FirmwareId & & fo . IsAcceptableOrIdeal
2014-11-12 00:04:08 +00:00
select fo ;
// try each option
foreach ( var fo in options )
2013-10-25 00:59:34 +00:00
{
2017-04-27 15:55:22 +00:00
var hash = fo . Hash ;
2014-11-12 00:04:08 +00:00
// did we find this firmware?
2017-05-19 18:17:07 +00:00
if ( reader . Dict . ContainsKey ( hash ) )
2014-11-12 00:04:08 +00:00
{
// rad! then we can use it
var ri = new ResolutionInfo
{
2017-05-19 18:17:07 +00:00
FilePath = reader . Dict [ hash ] . FileInfo . FullName ,
2014-11-12 00:04:08 +00:00
KnownFirmwareFile = FirmwareDatabase . FirmwareFilesByHash [ hash ] ,
2014-11-13 00:09:16 +00:00
Hash = hash ,
2017-04-27 15:55:22 +00:00
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
// 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 ;
}
2017-04-14 19:59:01 +00:00
2014-11-12 00:04:08 +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 ;
}
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
2017-04-27 15:55:22 +00:00
where fo . Hash = = rff . Hash & & fo . ConfigKey ! = fr . ConfigKey
2014-11-12 00:04:08 +00:00
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
}
2017-04-14 19:59:01 +00:00
} // foreach(firmware record)
} // using(new RealFirmwareReader())
} // DoScanAndResolve()
} // class FirmwareManager
} // namespace