BizHawk/BizHawk.Emulation.Common/Database/Database.cs

410 lines
8.7 KiB
C#
Raw Normal View History

2011-01-11 02:55:51 +00:00
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
2011-01-11 02:55:51 +00:00
using System.Threading;
using BizHawk.Common.BufferExtensions;
2017-11-23 17:26:15 +00:00
using System.Linq;
namespace BizHawk.Emulation.Common
2011-01-11 02:55:51 +00:00
{
2012-03-06 07:51:41 +00:00
public static class Database
{
private static readonly Dictionary<string, CompactGameInfo> DB = new Dictionary<string, CompactGameInfo>();
2011-01-11 02:55:51 +00:00
private static string RemoveHashType(string hash)
{
hash = hash.ToUpper();
if (hash.StartsWith("MD5:"))
{
hash = hash.Substring(4);
}
if (hash.StartsWith("SHA1:"))
{
hash = hash.Substring(5);
}
return hash;
}
public static GameInfo CheckDatabase(string hash)
{
var hashNoType = RemoveHashType(hash);
DB.TryGetValue(hashNoType, out var cgi);
2012-03-06 07:51:41 +00:00
if (cgi == null)
{
2019-03-20 05:24:33 +00:00
Console.WriteLine($"DB: hash {hash} not in game database.");
2012-03-06 07:51:41 +00:00
return null;
}
2012-03-06 07:51:41 +00:00
return new GameInfo(cgi);
}
private static void LoadDatabase_Escape(string line, string path)
2011-03-15 03:17:40 +00:00
{
if (!line.ToUpperInvariant().StartsWith("#INCLUDE"))
{
return;
}
2011-03-15 03:17:40 +00:00
line = line.Substring(8).TrimStart();
var filename = Path.Combine(path, line);
2012-10-09 01:34:21 +00:00
if (File.Exists(filename))
2011-03-15 03:17:40 +00:00
{
2012-10-09 01:34:21 +00:00
Console.WriteLine("loading external game database {0}", line);
LoadDatabase(filename);
2011-03-15 03:17:40 +00:00
}
else
{
2011-03-15 03:17:40 +00:00
Console.WriteLine("BENIGN: missing external game database {0}", line);
}
2011-03-15 03:17:40 +00:00
}
public static void SaveDatabaseEntry(string path, CompactGameInfo gameInfo)
{
var sb = new StringBuilder();
sb
.Append("sha1:") // TODO: how do we know it is sha1?
.Append(gameInfo.Hash)
.Append('\t');
switch (gameInfo.Status)
{
case RomStatus.BadDump:
sb.Append("B");
break;
case RomStatus.TranslatedRom:
sb.Append("T");
break;
case RomStatus.Overdump:
sb.Append("O");
break;
2017-04-27 16:45:44 +00:00
case RomStatus.Bios:
sb.Append("I");
break;
case RomStatus.Homebrew:
sb.Append("D");
break;
case RomStatus.Hack:
sb.Append("H");
break;
case RomStatus.Unknown:
sb.Append("U");
break;
}
sb
.Append('\t')
.Append(gameInfo.Name)
.Append('\t')
.Append(gameInfo.System)
.Append('\t')
.Append(gameInfo.MetaData)
.Append(Environment.NewLine);
File.AppendAllText(path, sb.ToString());
}
2012-03-06 07:51:41 +00:00
public static void LoadDatabase(string path)
{
using var reader = new StreamReader(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite));
while (reader.EndOfStream == false)
2012-03-06 07:51:41 +00:00
{
var line = reader.ReadLine() ?? "";
try
2012-03-06 07:51:41 +00:00
{
if (line.StartsWith(";"))
2012-03-06 07:51:41 +00:00
{
continue; // comment
2012-03-06 07:51:41 +00:00
}
if (line.StartsWith("#"))
{
LoadDatabase_Escape(line, Path.GetDirectoryName(path));
continue;
}
if (line.Trim().Length == 0)
{
continue;
}
var items = line.Split('\t');
var game = new CompactGameInfo
{
Hash = RemoveHashType(items[0].ToUpper())
};
// remove a hash type identifier. well don't really need them for indexing (they're just there for human purposes)
switch (items[1].Trim())
{
case "B":
game.Status = RomStatus.BadDump;
break;
case "V":
game.Status = RomStatus.BadDump;
break;
case "T":
game.Status = RomStatus.TranslatedRom;
break;
case "O":
game.Status = RomStatus.Overdump;
break;
case "I":
game.Status = RomStatus.Bios;
break;
case "D":
game.Status = RomStatus.Homebrew;
break;
case "H":
game.Status = RomStatus.Hack;
break;
case "U":
game.Status = RomStatus.Unknown;
break;
default:
game.Status = RomStatus.GoodDump;
break;
}
game.Name = items[2];
game.System = items[3];
game.MetaData = items.Length >= 6 ? items[5] : null;
game.Region = items.Length >= 7 ? items[6] : "";
game.ForcedCore = items.Length >= 8 ? items[7].ToLowerInvariant() : "";
if (DB.ContainsKey(game.Hash))
2012-03-06 07:51:41 +00:00
{
Console.WriteLine("gamedb: Multiple hash entries {0}, duplicate detected on \"{1}\" and \"{2}\"", game.Hash, game.Name, DB[game.Hash].Name);
2012-03-06 07:51:41 +00:00
}
DB[game.Hash] = game;
}
catch
{
Console.WriteLine($"Error parsing database entry: {line}");
2012-03-06 07:51:41 +00:00
}
}
}
public static GameInfo GetGameInfo(byte[] romData, string fileName)
2012-03-06 07:51:41 +00:00
{
2017-04-24 12:41:55 +00:00
var hash = $"{CRC32.Calculate(romData):X8}";
if (DB.TryGetValue(hash, out var cgi))
{
2012-03-06 07:51:41 +00:00
return new GameInfo(cgi);
}
2014-07-03 19:20:34 +00:00
hash = romData.HashMD5();
if (DB.TryGetValue(hash, out cgi))
{
2012-03-06 07:51:41 +00:00
return new GameInfo(cgi);
}
2014-07-03 19:20:34 +00:00
hash = romData.HashSHA1();
if (DB.TryGetValue(hash, out cgi))
{
2012-03-06 07:51:41 +00:00
return new GameInfo(cgi);
}
2012-03-06 07:51:41 +00:00
// rom is not in database. make some best-guesses
var game = new GameInfo
{
Hash = hash,
Status = RomStatus.NotInDatabase,
NotInDatabase = true
};
Console.WriteLine(
"Game was not in DB. CRC: {0:X8} MD5: {1}",
CRC32.Calculate(romData),
System.Security.Cryptography.MD5.Create().ComputeHash(romData).BytesToHexString());
2012-03-06 07:51:41 +00:00
2017-04-27 16:37:26 +00:00
var ext = Path.GetExtension(fileName)?.ToUpperInvariant();
2012-03-06 07:51:41 +00:00
switch (ext)
{
case ".NES":
case ".UNF":
case ".FDS":
game.System = "NES";
break;
case ".SFC":
case ".SMC":
game.System = "SNES";
break;
case ".GB":
game.System = "GB";
break;
case ".GBC":
game.System = "GBC";
break;
case ".GBA":
game.System = "GBA";
break;
case ".SMS":
game.System = "SMS";
break;
case ".GG":
game.System = "GG";
break;
case ".SG":
game.System = "SG";
2012-11-13 20:10:06 +00:00
break;
2012-03-06 07:51:41 +00:00
case ".GEN":
2012-11-13 20:10:06 +00:00
case ".MD":
case ".SMD":
game.System = "GEN";
break;
case ".PSF":
2015-07-21 04:11:00 +00:00
case ".MINIPSF":
game.System = "PSX";
break;
case ".PCE":
game.System = "PCE";
break;
case ".SGX":
game.System = "SGX";
break;
case ".A26":
game.System = "A26";
break;
case ".A78":
game.System = "A78";
break;
case ".COL":
game.System = "Coleco";
break;
case ".INT":
game.System = "INTV";
break;
case ".PRG":
case ".D64":
case ".T64":
case ".G64":
case ".CRT":
game.System = "C64";
break;
2012-11-19 22:43:34 +00:00
case ".TZX":
case ".PZX":
case ".CSW":
case ".WAV":
game.System = "ZXSpectrum";
break;
2017-11-23 17:26:15 +00:00
case ".CDT":
game.System = "AmstradCPC";
break;
case ".TAP":
byte[] head = romData.Take(8).ToArray();
game.System = Encoding.Default.GetString(head).Contains("C64-TAPE")
? "C64"
: "ZXSpectrum";
break;
2017-11-23 17:26:15 +00:00
case ".Z64":
2013-04-29 01:57:41 +00:00
case ".V64":
case ".N64":
game.System = "N64";
2013-04-29 01:57:41 +00:00
break;
case ".DEBUG":
game.System = "DEBUG";
break;
2014-05-30 05:09:54 +00:00
case ".WS":
case ".WSC":
game.System = "WSWAN";
break;
case ".LNX":
game.System = "Lynx";
break;
2015-02-08 21:51:15 +00:00
case ".83P":
game.System = "83P";
break;
2015-02-17 22:58:25 +00:00
case ".DSK":
var dId = new DSKIdentifier(romData);
game.System = dId.IdentifiedSystem;
break;
2018-04-26 11:54:10 +00:00
case ".PO":
case ".DO":
2015-02-17 22:58:25 +00:00
game.System = "AppleII";
break;
case ".VB":
game.System = "VB";
break;
case ".NGP":
case ".NGC":
game.System = "NGP";
break;
2017-06-18 14:29:03 +00:00
case ".O2":
game.System = "O2";
break;
case ".UZE":
game.System = "UZE";
break;
case ".32X":
2017-07-11 01:54:41 +00:00
game.System = "32X";
game.AddOption("32X", "true");
break;
2019-06-17 13:06:37 +00:00
case ".VEC":
game.System = "VEC";
game.AddOption("VEC", "true");
break;
// refactor to use mame db (output of "mame -listxml" command)
// there's no good definition for Arcade anymore, so we might limit to coin-based machines?
case ".ZIP":
game.System = "Arcade";
break;
2012-03-06 07:51:41 +00:00
}
2017-04-27 16:37:26 +00:00
game.Name = Path.GetFileNameWithoutExtension(fileName)?.Replace('_', ' ');
2012-03-06 07:51:41 +00:00
// If filename is all-caps, then attempt to proper-case the title.
2017-04-27 16:37:26 +00:00
if (!string.IsNullOrWhiteSpace(game.Name) && game.Name == game.Name.ToUpperInvariant())
{
game.Name = Thread.CurrentThread.CurrentCulture.TextInfo.ToTitleCase(game.Name.ToLower());
}
2012-03-06 07:51:41 +00:00
return game;
2012-03-06 07:51:41 +00:00
}
}
2017-04-27 16:37:26 +00:00
public class CompactGameInfo
{
public string Name { get; set; }
public string System { get; set; }
public string MetaData { get; set; }
public string Hash { get; set; }
public string Region { get; set; }
public RomStatus Status { get; set; }
public string ForcedCore { get; set; }
}
2011-01-11 02:55:51 +00:00
}