BizHawk/BizHawk.Client.Common/FirmwareManager.cs

261 lines
7.2 KiB
C#
Raw Normal View History

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;
using BizHawk.Emulation.Common;
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 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; }
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 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>();
}
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 forbidScan = false)
2013-10-25 00:59:34 +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:
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)
{
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);
if (resolved == null)
{
return null;
}
2015-02-05 23:53:25 +00:00
RecentlyServed.Add(new FirmwareEventArgs
{
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();
public void Dispose()
{
2017-05-17 18:18:26 +00:00
_sha1.Dispose();
_sha1 = null;
}
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()
{
// 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);
}
using (var reader = new RealFirmwareReader())
2013-10-25 00:59:34 +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
if (!di.Exists)
{
continue;
}
// 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())
{
if (sizes.Contains(fi.Length))
{
reader.Read(fi);
}
}
2013-10-25 00:59:34 +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
{
// 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
{
var hash = fo.Hash;
// did we find this firmware?
2017-05-19 18:17:07 +00:00
if (reader.Dict.ContainsKey(hash))
{
// rad! then we can use it
var ri = new ResolutionInfo
{
2017-05-19 18:17:07 +00:00
FilePath = reader.Dict[hash].FileInfo.FullName,
KnownFirmwareFile = FirmwareDatabase.FirmwareFilesByHash[hash],
Hash = hash,
Size = fo.Size
};
_resolutionDictionary[fr] = ri;
goto DONE_FIRMWARE;
}
2013-10-25 00:59:34 +00:00
}
DONE_FIRMWARE: ;
}
2013-10-25 00:59:34 +00:00
// apply user overrides
foreach (var fr in FirmwareDatabase.FirmwareRecords)
2013-10-25 00:59:34 +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))
2013-10-25 00:59:34 +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
// compute its hash
var rff = reader.Read(fi);
ri.Size = fi.Length;
ri.Hash = rff.Hash;
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)
FirmwareDatabase.FirmwareFile ff;
if (FirmwareDatabase.FirmwareFilesByHash.TryGetValue(rff.Hash, out ff))
2014-01-08 03:53:53 +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
}
} // foreach(firmware record)
} // using(new RealFirmwareReader())
} // DoScanAndResolve()
} // class FirmwareManager
} // namespace