diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8c23067 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6d9f542 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/gaseous-server/bin/Debug/net7.0/gaseous-server.dll", + "args": [], + "cwd": "${workspaceFolder}/gaseous-server", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..04fd839 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,41 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/gaseous-server/gaseous-server.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/gaseous-server/gaseous-server.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "--project", + "${workspaceFolder}/gaseous-server/gaseous-server.csproj" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/Gaseous.sln b/Gaseous.sln index 0666cea..f7adb01 100644 --- a/Gaseous.sln +++ b/Gaseous.sln @@ -1,9 +1,21 @@ - + Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 25.0.1704.4 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-identifier", "gaseous-identifier\gaseous-identifier.csproj", "{F5C42134-5372-430A-A9AE-1871981850DB}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-identifier-testapp", "gaseous-identifier\gaseous-identifier-testapp.csproj", "{F5C42134-5372-430A-A9AE-1871981850DB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-signature-parser", "gaseous-signature-parser\gaseous-signature-parser.csproj", "{DAEBBB82-5051-43FD-A406-F9D64A38F468}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-romsignatureobject", "gaseous-romsignatureobject\gaseous-romsignatureobject.csproj", "{9DCD243D-37CE-4562-8411-B5242B687D4F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-signature-ingestor", "gaseous-signature-ingestor\gaseous-signature-ingestor.csproj", "{86DF6E45-2C2B-4C30-AEC1-E7EF8C5CEA7D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-tools", "gaseous-tools\gaseous-tools.csproj", "{08FE408A-5EC1-4110-ABD8-D19A1155B8CE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "gaseous-server", "gaseous-server\gaseous-server.csproj", "{A01D2EFF-C82E-473B-84D7-7C25E736F5D2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{B07A4655-A003-416B-A790-ADAA5B548E1A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -15,6 +27,38 @@ Global {F5C42134-5372-430A-A9AE-1871981850DB}.Debug|Any CPU.Build.0 = Debug|Any CPU {F5C42134-5372-430A-A9AE-1871981850DB}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5C42134-5372-430A-A9AE-1871981850DB}.Release|Any CPU.Build.0 = Release|Any CPU + {08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08699C93-15CD-4E39-9053-D3C8056CE938}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08699C93-15CD-4E39-9053-D3C8056CE938}.Release|Any CPU.Build.0 = Release|Any CPU + {DAEBBB82-5051-43FD-A406-F9D64A38F468}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DAEBBB82-5051-43FD-A406-F9D64A38F468}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DAEBBB82-5051-43FD-A406-F9D64A38F468}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DAEBBB82-5051-43FD-A406-F9D64A38F468}.Release|Any CPU.Build.0 = Release|Any CPU + {FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FFCEC386-033F-4772-A45B-D33579F2E5EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FFCEC386-033F-4772-A45B-D33579F2E5EE}.Release|Any CPU.Build.0 = Release|Any CPU + {9DCD243D-37CE-4562-8411-B5242B687D4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9DCD243D-37CE-4562-8411-B5242B687D4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9DCD243D-37CE-4562-8411-B5242B687D4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9DCD243D-37CE-4562-8411-B5242B687D4F}.Release|Any CPU.Build.0 = Release|Any CPU + {86DF6E45-2C2B-4C30-AEC1-E7EF8C5CEA7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {86DF6E45-2C2B-4C30-AEC1-E7EF8C5CEA7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {86DF6E45-2C2B-4C30-AEC1-E7EF8C5CEA7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {86DF6E45-2C2B-4C30-AEC1-E7EF8C5CEA7D}.Release|Any CPU.Build.0 = Release|Any CPU + {08FE408A-5EC1-4110-ABD8-D19A1155B8CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08FE408A-5EC1-4110-ABD8-D19A1155B8CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08FE408A-5EC1-4110-ABD8-D19A1155B8CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08FE408A-5EC1-4110-ABD8-D19A1155B8CE}.Release|Any CPU.Build.0 = Release|Any CPU + {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A01D2EFF-C82E-473B-84D7-7C25E736F5D2}.Release|Any CPU.Build.0 = Release|Any CPU + {B07A4655-A003-416B-A790-ADAA5B548E1A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B07A4655-A003-416B-A790-ADAA5B548E1A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B07A4655-A003-416B-A790-ADAA5B548E1A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B07A4655-A003-416B-A790-ADAA5B548E1A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/gaseous-identifier/Program.cs b/gaseous-identifier/Program.cs index 27d7a5f..e7a4ef2 100644 --- a/gaseous-identifier/Program.cs +++ b/gaseous-identifier/Program.cs @@ -3,6 +3,9 @@ using System.Security.Cryptography; using System.Text; using System.Xml; using System.Xml.Serialization; +using Newtonsoft.Json; +using gaseous_romsignatureobject; +using gaseous_signature_parser.parsers; string[] commandLineArgs = Environment.GetCommandLineArgs(); @@ -49,9 +52,10 @@ foreach (string commandLineArg in commandLineArgs) scanPath = Path.GetFullPath(scanPath); Console.WriteLine("ROM search path: " + scanPath); -System.Collections.ArrayList TOSEC = new System.Collections.ArrayList(); -List tosecLists = new List(); -UInt32 GameCounter = 0; +List romSignatures = new List(); +System.Collections.ArrayList availablePlatforms = new System.Collections.ArrayList(); + +// load TOSEC XML files if (tosecXML != null && tosecXML.Length > 0) { tosecXML = Path.GetFullPath(tosecXML); @@ -59,146 +63,43 @@ if (tosecXML != null && tosecXML.Length > 0) Console.WriteLine("TOSEC XML search path: " + tosecXML); string[] tosecPathContents = Directory.GetFiles(tosecXML); - foreach (string tosecXMLFile in tosecPathContents) + int lastCLILineLength = 0; + for (UInt16 i = 0; i < tosecPathContents.Length; ++i) { - XmlDocument tosecXmlDoc = new XmlDocument(); - tosecXmlDoc.Load(tosecXMLFile); + string tosecXMLFile = tosecPathContents[i]; - gaseous_identifier.classes.tosecXML tosecObject = new gaseous_identifier.classes.tosecXML(); + TosecParser tosecParser = new TosecParser(); + RomSignatureObject tosecObject = tosecParser.Parse(tosecXMLFile); - // get header - XmlNode xmlHeader = tosecXmlDoc.DocumentElement.SelectSingleNode("/datafile/header"); - foreach (XmlNode childNode in xmlHeader.ChildNodes) + string statusOutput = i + " / " + tosecPathContents.Length + " : " + Path.GetFileName(tosecXMLFile); + Console.Write("\r " + statusOutput.PadRight(lastCLILineLength, ' ') + "\r"); + lastCLILineLength = statusOutput.Length; + + foreach (RomSignatureObject.Game gameRom in tosecObject.Games) { - switch (childNode.Name.ToLower()) + if (!availablePlatforms.Contains(gameRom.System)) { - case "name": - tosecObject.Name = childNode.InnerText; - break; - - case "description": - tosecObject.Description = childNode.InnerText; - break; - - case "category": - tosecObject.Category = childNode.InnerText; - break; - - case "version": - tosecObject.Version = childNode.InnerText; - break; - - case "author": - tosecObject.Author = childNode.InnerText; - break; - - case "email": - tosecObject.Email = childNode.InnerText; - break; - - case "homepage": - tosecObject.Homepage = childNode.InnerText; - break; - - case "url": - try - { - tosecObject.Url = new Uri(childNode.InnerText); - } catch - { - tosecObject.Url = null; - } - break; + availablePlatforms.Add(gameRom.System); } } - // get games - tosecObject.Games = new List(); - XmlNodeList xmlGames = tosecXmlDoc.DocumentElement.SelectNodes("/datafile/game"); - foreach (XmlNode xmlGame in xmlGames) - { - gaseous_identifier.classes.tosecXML.Game gameObject = new gaseous_identifier.classes.tosecXML.Game(); - - // parse game name - string gameName = xmlGame.Attributes["name"].Value; - string[] gameNameTokens = gameName.Split("("); - // game title should be first item - gameObject.Name = gameNameTokens[0].Trim(); - // game year should be second item - if (gameNameTokens.Length == 2) - { - gameObject.Year = gameNameTokens[1].Replace(")", "").Trim(); - } else - { - gameObject.Year = ""; - } - // game publisher should be third item - if (gameNameTokens.Length == 3) - { - gameObject.Publisher = gameNameTokens[2].Replace(")", "").Trim(); - } else - { - gameObject.Publisher = ""; - } - - gameObject.Roms = new List(); - - // get the roms - foreach (XmlNode xmlGameDetail in xmlGame.ChildNodes) - { - switch (xmlGameDetail.Name.ToLower()) - { - case "description": - gameObject.Description = xmlGameDetail.InnerText; - break; - - case "rom": - gaseous_identifier.classes.tosecXML.Game.Rom romObject = new gaseous_identifier.classes.tosecXML.Game.Rom(); - romObject.Name = xmlGameDetail.Attributes["name"]?.Value; - romObject.Size = UInt64.Parse(xmlGameDetail.Attributes["size"]?.Value); - romObject.Crc = xmlGameDetail.Attributes["crc"]?.Value; - romObject.Md5 = xmlGameDetail.Attributes["md5"]?.Value; - romObject.Sha1 = xmlGameDetail.Attributes["sha1"]?.Value; - - gameObject.Roms.Add(romObject); - break; - } - } - - // search for existing gameObject to update - bool existingGameFound = false; - foreach (gaseous_identifier.classes.tosecXML.Game existingGame in tosecObject.Games) - { - if (existingGame.Name == gameObject.Name && existingGame.Year == gameObject.Year && existingGame.Publisher == gameObject.Publisher) - { - existingGame.Roms.AddRange(gameObject.Roms); - existingGameFound = true; - break; - } - } - if (existingGameFound == false) - { - tosecObject.Games.Add(gameObject); - GameCounter += 1; - } - } - - Console.Write("."); - tosecLists.Add(tosecObject); + romSignatures.Add(tosecObject); } Console.WriteLine(""); } else { - Console.WriteLine("TOSEC is disabled, title matching will be by file name only."); + Console.WriteLine("TOSEC is disabled."); } -Console.WriteLine(tosecLists.Count + " TOSEC files loaded - " + GameCounter + " games cataloged"); +Console.WriteLine(romSignatures.Count + " TOSEC files loaded"); -if (tosecLists.Count > 0) +// Summarise signatures +if (availablePlatforms.Count > 0) { - Console.WriteLine("TOSEC lists available:"); - foreach (gaseous_identifier.classes.tosecXML tosecList in tosecLists) + availablePlatforms.Sort(); + Console.WriteLine("Platforms loaded:"); + foreach (string platform in availablePlatforms) { - Console.WriteLine(" * " + tosecList.Name); + Console.WriteLine(" * " + platform); } } @@ -216,20 +117,43 @@ foreach (string romFile in romPathContents) var sha1 = SHA1.Create(); byte[] sha1HashByte = sha1.ComputeHash(stream); - string sha1Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant(); + string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant(); bool gameFound = false; - foreach (gaseous_identifier.classes.tosecXML tosecList in tosecLists) + foreach (RomSignatureObject tosecList in romSignatures) { - foreach (gaseous_identifier.classes.tosecXML.Game gameObject in tosecList.Games) + foreach (RomSignatureObject.Game gameObject in tosecList.Games) { - foreach (gaseous_identifier.classes.tosecXML.Game.Rom romObject in gameObject.Roms) + foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms) { - if (md5Hash == romObject.Md5) + if (romObject.Md5 != null) + { + if (md5Hash == romObject.Md5.ToLowerInvariant()) + { + // match + gameFound = true; + } + } + if (romObject.Sha1 != null) + { + if (md5Hash == romObject.Sha1.ToLowerInvariant()) + { + // match + gameFound = true; + } + } + if (gameFound == true) { - // match - gameFound = true; Console.WriteLine(romObject.Name); + + RomSignatureObject.Game gameSignature = gameObject; + gameSignature.Roms.Clear(); + gameSignature.Roms.Add(romObject); + + var jsonSerializerSettings = new JsonSerializerSettings(); + jsonSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); + jsonSerializerSettings.NullValueHandling = NullValueHandling.Ignore; + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(gameSignature, Newtonsoft.Json.Formatting.Indented, jsonSerializerSettings)); break; } } @@ -241,4 +165,19 @@ foreach (string romFile in romPathContents) { Console.WriteLine("File not found in TOSEC library"); } +} + +string SearchTitle = "California Games"; +foreach (RomSignatureObject romSignatureObject in romSignatures) +{ + foreach (RomSignatureObject.Game gameObject in romSignatureObject.Games) + { + if (gameObject.Name == SearchTitle) + { + var jsonSerializerSettings = new JsonSerializerSettings(); + jsonSerializerSettings.Converters.Add(new Newtonsoft.Json.Converters.StringEnumConverter()); + jsonSerializerSettings.NullValueHandling = NullValueHandling.Ignore; + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(gameObject, Newtonsoft.Json.Formatting.Indented, jsonSerializerSettings)); + } + } } \ No newline at end of file diff --git a/gaseous-identifier/classes/tosecXML.cs b/gaseous-identifier/classes/tosecXML.cs deleted file mode 100644 index 72c2502..0000000 --- a/gaseous-identifier/classes/tosecXML.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace gaseous_identifier.classes -{ - public class tosecXML - { - public string Name { get; set; } - public string Description { get; set; } - public string Category { get; set; } - public string Version { get; set; } - public string Author { get; set; } - public string Email { get; set; } - public string Homepage { get; set; } - public Uri? Url { get; set; } - - public List Games { get; set; } - - public class Game - { - public string Name { get; set; } - public string Description { get; set; } - public string Year { get; set; } - public string Publisher { get; set; } - public List Roms { get; set; } - - public class Rom - { - public string Name { get; set; } - public UInt64 Size { get; set; } - public string Crc { get; set; } - public string Md5 { get; set; } - public string Sha1 { get; set; } - - public string flags { get; set; } - - public RomTypes RomType { get; set; } - public UInt16 DiskNumber { get; set; } - public string DiskSide { get; set; } - - public enum RomTypes - { - Cartridge = 0, - Cassette = 1, - Floppy = 2, - CD = 3, - DVD = 4, - Unknown = 100 - } - } - - } - } -} - diff --git a/gaseous-identifier/gaseous-identifier.csproj b/gaseous-identifier/gaseous-identifier-testapp.csproj similarity index 51% rename from gaseous-identifier/gaseous-identifier.csproj rename to gaseous-identifier/gaseous-identifier-testapp.csproj index 4aa190e..02cc813 100644 --- a/gaseous-identifier/gaseous-identifier.csproj +++ b/gaseous-identifier/gaseous-identifier-testapp.csproj @@ -9,9 +9,13 @@ - + - + + + + + diff --git a/gaseous-romsignatureobject/RomSignatureObject.cs b/gaseous-romsignatureobject/RomSignatureObject.cs new file mode 100644 index 0000000..5c04359 --- /dev/null +++ b/gaseous-romsignatureobject/RomSignatureObject.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; + +namespace gaseous_romsignatureobject +{ + /// + /// Object returned by all signature engines containing metadata about the ROM's in the data files + /// + /// This class was based on the TOSEC dataset, so may need to be expanded as new signature engines are added + /// + public class RomSignatureObject + { + public string? Name { get; set; } + public string? Description { get; set; } + public string? Category { get; set; } + public string? Version { get; set; } + public string? Author { get; set; } + public string? Email { get; set; } + public string? Homepage { get; set; } + public Uri? Url { get; set; } + public string? SourceType { get; set; } + public string SourceMd5 { get; set; } = ""; + public string SourceSHA1 { get; set; } = ""; + + public List Games { get; set; } = new List(); + + public class Game + { + public string? Name { get; set; } + public string? Description { get; set; } + public string? Year { get; set; } + public string? Publisher { get; set; } + public DemoTypes Demo { get; set; } + public string? System { get; set; } + public string? SystemVariant { get; set; } + public string? Video { get; set; } + public string? Country { get; set; } + public string? Language { get; set; } + public string? Copyright { get; set; } + public List Roms { get; set; } = new List(); + public int RomCount + { + get + { + return Roms.Count(); + } + } + + public enum DemoTypes + { + NotDemo = 0, + demo = 1, + demo_kiosk = 2, + demo_playable = 3, + demo_rolling = 4, + demo_slideshow = 5 + } + + public class Rom + { + public string? Name { get; set; } + public UInt64? Size { get; set; } + public string? Crc { get; set; } + public string? Md5 { get; set; } + public string? Sha1 { get; set; } + + public string? DevelopmentStatus { get; set; } + + public List flags { get; set; } = new List(); + + public RomTypes RomType { get; set; } + public string? RomTypeMedia { get; set; } + public string? MediaLabel { get; set; } + + public enum RomTypes + { + /// + /// Media type is unknown + /// + Unknown = 0, + + /// + /// Optical media + /// + Disc = 1, + + /// + /// Magnetic media + /// + Disk = 2, + + /// + /// Individual files + /// + File = 3, + + /// + /// Individual pars + /// + Part = 4, + + /// + /// Tape base media + /// + Tape = 5, + + /// + /// Side of the media + /// + Side = 6 + } + } + + } + } +} + diff --git a/gaseous-romsignatureobject/gaseous-romsignatureobject.csproj b/gaseous-romsignatureobject/gaseous-romsignatureobject.csproj new file mode 100644 index 0000000..f46a538 --- /dev/null +++ b/gaseous-romsignatureobject/gaseous-romsignatureobject.csproj @@ -0,0 +1,13 @@ + + + + net7.0 + gaseous_romsignatureobject + enable + enable + + + + 4 + + diff --git a/gaseous-server/Controllers/SignaturesController.cs b/gaseous-server/Controllers/SignaturesController.cs new file mode 100644 index 0000000..7f69893 --- /dev/null +++ b/gaseous-server/Controllers/SignaturesController.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Security.Cryptography; +using System.Threading.Tasks; +using gaseous_tools; +using Microsoft.AspNetCore.Mvc; + +// For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 + +namespace gaseous_server.Controllers +{ + [ApiController] + [Route("api/[controller]/[action]")] + public class SignaturesController : ControllerBase + { + /// + /// Get the current signature counts from the database + /// + /// Number of sources, publishers, games, and rom signatures in the database + [HttpGet] + public Models.Signatures_Status Status() + { + return new Models.Signatures_Status(); + } + + [HttpGet] + [Route("api/[controller]/[action]")] + public List GetSignature(string md5 = "", string sha1 = "") + { + if (md5.Length > 0) + { + return _GetSignature("signatures_roms.md5 = @searchstring", md5); + } else + { + return _GetSignature("signatures_roms.sha1 = @searchstring", sha1); + } + } + + private List _GetSignature(string sqlWhere, string searchString) + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "SELECT \n view_signatures_games.*,\n signatures_roms.id AS romid,\n signatures_roms.name AS romname,\n signatures_roms.size,\n signatures_roms.crc,\n signatures_roms.md5,\n signatures_roms.sha1,\n signatures_roms.developmentstatus,\n signatures_roms.flags,\n signatures_roms.romtype,\n signatures_roms.romtypemedia,\n signatures_roms.medialabel\nFROM\n signatures_roms\n INNER JOIN\n view_signatures_games ON signatures_roms.gameid = view_signatures_games.id WHERE " + sqlWhere; + Dictionary dbDict = new Dictionary(); + dbDict.Add("searchString", searchString); + + DataTable sigDb = db.ExecuteCMD(sql, dbDict); + + List GamesList = new List(); + + foreach (DataRow sigDbRow in sigDb.Rows) + { + Models.Signatures_Games gameItem = new Models.Signatures_Games + { + Game = new Models.Signatures_Games.GameItem + { + Id = (Int32)sigDbRow["id"], + Name = (string)sigDbRow["name"], + Description = (string)sigDbRow["description"], + Year = (string)sigDbRow["year"], + Publisher = (string)sigDbRow["publisher"], + Demo = (Models.Signatures_Games.GameItem.DemoTypes)(int)sigDbRow["demo"], + System = (string)sigDbRow["platform"], + SystemVariant = (string)sigDbRow["systemvariant"], + Video = (string)sigDbRow["video"], + Country = (string)sigDbRow["country"], + Language = (string)sigDbRow["language"], + Copyright = (string)sigDbRow["copyright"] + }, + Rom = new Models.Signatures_Games.RomItem + { + Id = (Int32)sigDbRow["romid"], + Name = (string)sigDbRow["romname"], + Size = (Int64)sigDbRow["size"], + Crc = (string)sigDbRow["crc"], + Md5 = (string)sigDbRow["md5"], + Sha1 = (string)sigDbRow["sha1"], + DevelopmentStatus = (string)sigDbRow["developmentstatus"], + flags = Newtonsoft.Json.JsonConvert.DeserializeObject>((string)sigDbRow["flags"]), + RomType = (Models.Signatures_Games.RomItem.RomTypes)(int)sigDbRow["romtype"], + RomTypeMedia = (string)sigDbRow["romtypemedia"], + MediaLabel = (string)sigDbRow["medialabel"] + } + }; + GamesList.Add(gameItem); + } + return GamesList; + } + } +} + diff --git a/gaseous-server/Models/Signatures_Games.cs b/gaseous-server/Models/Signatures_Games.cs new file mode 100644 index 0000000..9765714 --- /dev/null +++ b/gaseous-server/Models/Signatures_Games.cs @@ -0,0 +1,98 @@ +using System; +using static gaseous_romsignatureobject.RomSignatureObject.Game; + +namespace gaseous_server.Models +{ + public class Signatures_Games + { + public Signatures_Games() + { + } + + public GameItem? Game { get; set; } + public RomItem? Rom { get; set; } + + public class GameItem + { + public Int32? Id { get; set; } + public string? Name { get; set; } + public string? Description { get; set; } + public string? Year { get; set; } + public string? Publisher { get; set; } + public DemoTypes Demo { get; set; } + public string? System { get; set; } + public string? SystemVariant { get; set; } + public string? Video { get; set; } + public string? Country { get; set; } + public string? Language { get; set; } + public string? Copyright { get; set; } + + public enum DemoTypes + { + NotDemo = 0, + demo = 1, + demo_kiosk = 2, + demo_playable = 3, + demo_rolling = 4, + demo_slideshow = 5 + } + } + + public class RomItem + { + public Int32? Id { get; set; } + public string? Name { get; set; } + public Int64? Size { get; set; } + public string? Crc { get; set; } + public string? Md5 { get; set; } + public string? Sha1 { get; set; } + + public string? DevelopmentStatus { get; set; } + + public List flags { get; set; } = new List(); + + public RomTypes RomType { get; set; } + public string? RomTypeMedia { get; set; } + public string? MediaLabel { get; set; } + + public enum RomTypes + { + /// + /// Media type is unknown + /// + Unknown = 0, + + /// + /// Optical media + /// + Disc = 1, + + /// + /// Magnetic media + /// + Disk = 2, + + /// + /// Individual files + /// + File = 3, + + /// + /// Individual pars + /// + Part = 4, + + /// + /// Tape base media + /// + Tape = 5, + + /// + /// Side of the media + /// + Side = 6 + } + } + } +} + diff --git a/gaseous-server/Models/Signatures_Status.cs b/gaseous-server/Models/Signatures_Status.cs new file mode 100644 index 0000000..8d2c904 --- /dev/null +++ b/gaseous-server/Models/Signatures_Status.cs @@ -0,0 +1,63 @@ +using System; +using System.Data; +using gaseous_tools; + +namespace gaseous_server.Models +{ + public class Signatures_Status + { + + private Int64 _SourceCount = 0; + private Int64 _PlatformCount = 0; + private Int64 _GameCount = 0; + private Int64 _RomCount = 0; + + public Signatures_Status() + { + Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); + string sql = "select (select count(*) from signatures_sources) as SourceCount, (select count(*) from signatures_platforms) as PlatformCount, (select count(*) from signatures_games) as GameCount, (select count(*) from signatures_roms) as RomCount;"; + + DataTable sigDb = db.ExecuteCMD(sql); + if (sigDb.Rows.Count > 0) + { + _SourceCount = (Int64)sigDb.Rows[0]["SourceCount"]; + _PlatformCount = (Int64)sigDb.Rows[0]["PlatformCount"]; + _GameCount = (Int64)sigDb.Rows[0]["GameCount"]; + _RomCount = (Int64)sigDb.Rows[0]["RomCount"]; + } + } + + public Int64 Sources + { + get + { + return _SourceCount; + } + } + + public Int64 Platforms + { + get + { + return _PlatformCount; + } + } + + public Int64 Games + { + get + { + return _GameCount; + } + } + + public Int64 Roms + { + get + { + return _RomCount; + } + } + } +} + diff --git a/gaseous-server/Program.cs b/gaseous-server/Program.cs new file mode 100644 index 0000000..6fd856e --- /dev/null +++ b/gaseous-server/Program.cs @@ -0,0 +1,38 @@ +using System.Text.Json.Serialization; +using gaseous_tools; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers().AddJsonOptions(x => +{ + // serialize enums as strings in api responses (e.g. Role) + x.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); +}); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +// set up db +Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); +db.InitDB(); + +// start the app +app.Run(); + diff --git a/gaseous-server/Properties/launchSettings.json b/gaseous-server/Properties/launchSettings.json new file mode 100644 index 0000000..6cb3fd1 --- /dev/null +++ b/gaseous-server/Properties/launchSettings.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:38715", + "sslPort": 44314 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5198", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true + }, + "https": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "https://localhost:7282;http://localhost:5198", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "swagger", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/gaseous-server/appsettings.Development.json b/gaseous-server/appsettings.Development.json new file mode 100644 index 0000000..ce16a2e --- /dev/null +++ b/gaseous-server/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} + diff --git a/gaseous-server/appsettings.json b/gaseous-server/appsettings.json new file mode 100644 index 0000000..af0538f --- /dev/null +++ b/gaseous-server/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} + diff --git a/gaseous-server/gaseous-server.csproj b/gaseous-server/gaseous-server.csproj new file mode 100644 index 0000000..35f78b0 --- /dev/null +++ b/gaseous-server/gaseous-server.csproj @@ -0,0 +1,34 @@ + + + + net7.0 + enable + enable + gaseous_server + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-signature-ingestor/Program.cs b/gaseous-signature-ingestor/Program.cs new file mode 100644 index 0000000..6746f5f --- /dev/null +++ b/gaseous-signature-ingestor/Program.cs @@ -0,0 +1,284 @@ +using System; +using System.IO; +using MySql.Data.MySqlClient; +using gaseous_romsignatureobject; +using gaseous_signature_parser.parsers; +using gaseous_tools; +using MySqlX.XDevAPI; + +// process command line +string[] commandLineArgs = Environment.GetCommandLineArgs(); + +string tosecXML = ""; +bool showGames = false; +string inArgument = ""; +foreach (string commandLineArg in commandLineArgs) +{ + if (commandLineArg != commandLineArgs[0]) + { + if (inArgument == "") + { + switch (commandLineArg.ToLower()) + { + case "-tosecpath": + inArgument = commandLineArg.ToLower(); + break; + case "-showgames": + showGames = true; + break; + default: + break; + } + } + else + { + switch (inArgument) + { + case "-tosecpath": + tosecXML = commandLineArg; + break; + default: + break; + } + inArgument = ""; + } + } +} + +// check if Config.ConfigurationPath is valid and create it if not +if (!Directory.Exists(Config.ConfigurationPath)) +{ + Directory.CreateDirectory(Config.ConfigurationPath); +} + +// connect to database +Database db = new gaseous_tools.Database(Database.databaseType.MySql, Config.DatabaseConfiguration.ConnectionString); +// initialise the db +db.InitDB(); + +// process provided files +Console.WriteLine("Processing input files:"); +if (Directory.Exists(tosecXML)) +{ + Console.WriteLine("Processing TOSEC data files", ConsoleColor.Green); + Console.WriteLine(""); + Console.WriteLine(""); + + tosecXML = Path.GetFullPath(tosecXML); + string[] tosecPathContents = Directory.GetFiles(tosecXML); + int lineFileNameLength = 0; + int lineGameNameLength = 0; + + string sql = ""; + Dictionary dbDict = new Dictionary(); + System.Data.DataTable sigDB; + + for (UInt16 i = 0; i < tosecPathContents.Length; ++i) + { + string tosecXMLFile = tosecPathContents[i]; + + string statusOutput = (i + 1).ToString().PadLeft(7, ' ') + " / " + tosecPathContents.Length.ToString().PadLeft(7, ' ') + " : " + Path.GetFileName(tosecXMLFile); + Console.SetCursorPosition(0, Console.CursorTop - 2); + Console.WriteLine("\r " + statusOutput.PadRight(lineFileNameLength, ' ') + "\r"); + lineFileNameLength = statusOutput.Length; + + // check tosec file md5 + Console.WriteLine(" ==> Checking input file "); + Common.hashObject hashObject = new Common.hashObject(tosecXMLFile); + sql = "SELECT * FROM signatures_sources WHERE sourcemd5=@sourcemd5"; + dbDict = new Dictionary(); + dbDict.Add("sourcemd5", hashObject.md5hash); + sigDB = db.ExecuteCMD(sql, dbDict); + + if (sigDB.Rows.Count == 0) + { + // start parsing file + Console.SetCursorPosition(0, Console.CursorTop - 1); + Console.WriteLine(" ==> Parsing file "); + TosecParser tosecParser = new TosecParser(); + RomSignatureObject tosecObject = tosecParser.Parse(tosecXMLFile); + + // store in database + + // store source object + bool processGames = false; + if (tosecObject.SourceMd5 != null) + { + Console.SetCursorPosition(0, Console.CursorTop - 1); + Console.WriteLine(" ==> Storing file in database "); + + sql = "SELECT * FROM signatures_sources WHERE sourcemd5=@sourcemd5"; + dbDict = new Dictionary(); + dbDict.Add("name", Common.ReturnValueIfNull(tosecObject.Name, "")); + dbDict.Add("description", Common.ReturnValueIfNull(tosecObject.Description, "")); + dbDict.Add("category", Common.ReturnValueIfNull(tosecObject.Category, "")); + dbDict.Add("version", Common.ReturnValueIfNull(tosecObject.Version, "")); + dbDict.Add("author", Common.ReturnValueIfNull(tosecObject.Author, "")); + dbDict.Add("email", Common.ReturnValueIfNull(tosecObject.Email, "")); + dbDict.Add("homepage", Common.ReturnValueIfNull(tosecObject.Homepage, "")); + dbDict.Add("uri", Common.ReturnValueIfNull(tosecObject.Url, "")); + dbDict.Add("sourcetype", Common.ReturnValueIfNull(tosecObject.SourceType, "")); + dbDict.Add("sourcemd5", tosecObject.SourceMd5); + dbDict.Add("sourcesha1", tosecObject.SourceSHA1); + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO signatures_sources (name, description, category, version, author, email, homepage, url, sourcetype, sourcemd5, sourcesha1) VALUES (@name, @description, @category, @version, @author, @email, @homepage, @uri, @sourcetype, @sourcemd5, @sourcesha1)"; + + db.ExecuteCMD(sql, dbDict); + + processGames = true; + } + + if (processGames == true) + { + for (int x = 0; x < tosecObject.Games.Count; ++x) + { + RomSignatureObject.Game gameObject = tosecObject.Games[x]; + + // update display + if (showGames == true) + { + Console.SetCursorPosition(0, Console.CursorTop - 1); + string statusGameOutput = " ==> " + (x + 1).ToString().PadLeft(7, ' ') + " / " + tosecObject.Games.Count.ToString().PadLeft(7, ' ') + " : " + gameObject.Name; + Console.WriteLine("\r " + statusGameOutput.PadRight(lineGameNameLength, ' ') + "\r"); + lineGameNameLength = statusGameOutput.Length; + } + + // set up game dictionary + dbDict = new Dictionary(); + dbDict.Add("name", Common.ReturnValueIfNull(gameObject.Name, "")); + dbDict.Add("description", Common.ReturnValueIfNull(gameObject.Description, "")); + dbDict.Add("year", Common.ReturnValueIfNull(gameObject.Year, "")); + dbDict.Add("publisher", Common.ReturnValueIfNull(gameObject.Publisher, "")); + dbDict.Add("demo", (int)gameObject.Demo); + dbDict.Add("system", Common.ReturnValueIfNull(gameObject.System, "")); + dbDict.Add("platform", Common.ReturnValueIfNull(gameObject.System, "")); + dbDict.Add("systemvariant", Common.ReturnValueIfNull(gameObject.SystemVariant, "")); + dbDict.Add("video", Common.ReturnValueIfNull(gameObject.Video, "")); + dbDict.Add("country", Common.ReturnValueIfNull(gameObject.Country, "")); + dbDict.Add("language", Common.ReturnValueIfNull(gameObject.Language, "")); + dbDict.Add("copyright", Common.ReturnValueIfNull(gameObject.Copyright, "")); + + // store platform + int gameSystem = 0; + if (gameObject.System != null) + { + sql = "SELECT id FROM signatures_platforms WHERE platform=@platform"; + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO signatures_platforms (platform) VALUES (@platform); SELECT LAST_INSERT_ID()"; + sigDB = db.ExecuteCMD(sql, dbDict); + + gameSystem = (int)sigDB.Rows[0][0]; + } + else + { + gameSystem = (int)sigDB.Rows[0][0]; + } + } + dbDict.Add("systemid", gameSystem); + + // store publisher + int gamePublisher = 0; + if (gameObject.Publisher != null) + { + sql = "SELECT * FROM signatures_publishers WHERE publisher=@publisher"; + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO signatures_publishers (publisher) VALUES (@publisher); SELECT LAST_INSERT_ID()"; + sigDB = db.ExecuteCMD(sql, dbDict); + gamePublisher = (int)sigDB.Rows[0][0]; + } + else + { + gamePublisher = (int)sigDB.Rows[0][0]; + } + } + dbDict.Add("publisherid", gamePublisher); + + // store game + int gameId = 0; + sql = "SELECT * FROM signatures_games WHERE name=@name AND year=@year AND publisherid=@publisher AND systemid=@systemid AND country=@country AND language=@language"; + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO signatures_games " + + "(name, description, year, publisherid, demo, systemid, systemvariant, video, country, language, copyright) VALUES " + + "(@name, @description, @year, @publisherid, @demo, @systemid, @systemvariant, @video, @country, @language, @copyright); SELECT LAST_INSERT_ID()"; + sigDB = db.ExecuteCMD(sql, dbDict); + + gameId = (int)sigDB.Rows[0][0]; + } + else + { + gameId = (int)sigDB.Rows[0][0]; + } + + // store rom + foreach (RomSignatureObject.Game.Rom romObject in gameObject.Roms) + { + if (romObject.Md5 != null) + { + int romId = 0; + sql = "SELECT * FROM signatures_roms WHERE gameid=@gameid AND md5=@md5"; + dbDict = new Dictionary(); + dbDict.Add("gameid", gameId); + dbDict.Add("name", Common.ReturnValueIfNull(romObject.Name, "")); + dbDict.Add("size", Common.ReturnValueIfNull(romObject.Size, "")); + dbDict.Add("crc", Common.ReturnValueIfNull(romObject.Crc, "")); + dbDict.Add("md5", romObject.Md5); + dbDict.Add("sha1", Common.ReturnValueIfNull(romObject.Sha1, "")); + dbDict.Add("developmentstatus", Common.ReturnValueIfNull(romObject.DevelopmentStatus, "")); + + if (romObject.flags != null) + { + if (romObject.flags.Count > 0) + { + dbDict.Add("flags", Newtonsoft.Json.JsonConvert.SerializeObject(romObject.flags)); + } + else + { + dbDict.Add("flags", "[ ]"); + } + } + else + { + dbDict.Add("flags", "[ ]"); + } + dbDict.Add("romtype", (int)romObject.RomType); + dbDict.Add("romtypemedia", Common.ReturnValueIfNull(romObject.RomTypeMedia, "")); + dbDict.Add("medialabel", Common.ReturnValueIfNull(romObject.MediaLabel, "")); + + sigDB = db.ExecuteCMD(sql, dbDict); + if (sigDB.Rows.Count == 0) + { + // entry not present, insert it + sql = "INSERT INTO signatures_roms (gameid, name, size, crc, md5, sha1, developmentstatus, flags, romtype, romtypemedia, medialabel) VALUES (@gameid, @name, @size, @crc, @md5, @sha1, @developmentstatus, @flags, @romtype, @romtypemedia, @medialabel); SELECT LAST_INSERT_ID()"; + sigDB = db.ExecuteCMD(sql, dbDict); + + + romId = (int)sigDB.Rows[0][0]; + } + else + { + romId = (int)sigDB.Rows[0][0]; + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/gaseous-signature-ingestor/gaseous-signature-ingestor.csproj b/gaseous-signature-ingestor/gaseous-signature-ingestor.csproj new file mode 100644 index 0000000..473788c --- /dev/null +++ b/gaseous-signature-ingestor/gaseous-signature-ingestor.csproj @@ -0,0 +1,20 @@ + + + + Exe + net7.0 + gaseous_signature_ingestor + enable + enable + + + + + + + + + + + + diff --git a/gaseous-signature-parser/Parsers/TosecParser.cs b/gaseous-signature-parser/Parsers/TosecParser.cs new file mode 100644 index 0000000..d63c4f8 --- /dev/null +++ b/gaseous-signature-parser/Parsers/TosecParser.cs @@ -0,0 +1,525 @@ +using System; +using System.Xml; +using System.IO; +using System.Reflection; +using System.Security.Cryptography; +using gaseous_romsignatureobject; + +namespace gaseous_signature_parser.parsers +{ + public class TosecParser + { + public RomSignatureObject Parse(string XMLFile) + { + // load resources + var assembly = Assembly.GetExecutingAssembly(); + // load systems list + List TOSECSystems = new List(); + var resourceName = "gaseous_signature_parser.Support.Parsers.TOSEC.Systems.txt"; + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + TOSECSystems = reader.ReadToEnd().Split(Environment.NewLine).ToList(); + } + // load video list + List TOSECVideo = new List(); + resourceName = "gaseous_signature_parser.Support.Parsers.TOSEC.Video.txt"; + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + TOSECVideo = reader.ReadToEnd().Split(Environment.NewLine).ToList(); + } + // load country list + Dictionary TOSECCountry = new Dictionary(); + resourceName = "gaseous_signature_parser.Support.Parsers.TOSEC.Country.txt"; + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + do + { + string[] line = reader.ReadLine().Split(","); + TOSECCountry.Add(line[0], line[1]); + } while (reader.EndOfStream == false); + } + // load language list + Dictionary TOSECLanguage = new Dictionary(); + resourceName = "gaseous_signature_parser.Support.Parsers.TOSEC.Language.txt"; + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + do + { + string[] line = reader.ReadLine().Split(","); + TOSECLanguage.Add(line[0], line[1]); + } while (reader.EndOfStream == false); + } + // load copyright list + Dictionary TOSECCopyright = new Dictionary(); + resourceName = "gaseous_signature_parser.Support.Parsers.TOSEC.Copyright.txt"; + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + do + { + string[] line = reader.ReadLine().Split(","); + TOSECCopyright.Add(line[0], line[1]); + } while (reader.EndOfStream == false); + } + // load development status list + Dictionary TOSECDevelopment = new Dictionary(); + resourceName = "gaseous_signature_parser.Support.Parsers.TOSEC.DevelopmentStatus.txt"; + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + do + { + string[] line = reader.ReadLine().Split(","); + TOSECDevelopment.Add(line[0], line[1]); + } while (reader.EndOfStream == false); + } + + // get hashes of TOSEC file + var xmlStream = File.OpenRead(XMLFile); + + var md5 = MD5.Create(); + byte[] md5HashByte = md5.ComputeHash(xmlStream); + string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant(); + + var sha1 = SHA1.Create(); + byte[] sha1HashByte = sha1.ComputeHash(xmlStream); + string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant(); + + // load TOSEC file + XmlDocument tosecXmlDoc = new XmlDocument(); + tosecXmlDoc.Load(XMLFile); + + RomSignatureObject tosecObject = new RomSignatureObject(); + + // get header + XmlNode xmlHeader = tosecXmlDoc.DocumentElement.SelectSingleNode("/datafile/header"); + tosecObject.SourceType = "TOSEC"; + tosecObject.SourceMd5 = md5Hash; + tosecObject.SourceSHA1 = sha1Hash; + foreach (XmlNode childNode in xmlHeader.ChildNodes) + { + switch (childNode.Name.ToLower()) + { + case "name": + tosecObject.Name = childNode.InnerText; + break; + + case "description": + tosecObject.Description = childNode.InnerText; + break; + + case "category": + tosecObject.Category = childNode.InnerText; + break; + + case "version": + tosecObject.Version = childNode.InnerText; + break; + + case "author": + tosecObject.Author = childNode.InnerText; + break; + + case "email": + tosecObject.Email = childNode.InnerText; + break; + + case "homepage": + tosecObject.Homepage = childNode.InnerText; + break; + + case "url": + try + { + tosecObject.Url = new Uri(childNode.InnerText); + } + catch + { + tosecObject.Url = null; + } + break; + } + } + + // get games + tosecObject.Games = new List(); + XmlNodeList xmlGames = tosecXmlDoc.DocumentElement.SelectNodes("/datafile/game"); + foreach (XmlNode xmlGame in xmlGames) + { + RomSignatureObject.Game gameObject = new RomSignatureObject.Game(); + + // parse game name + string[] gameNameTitleParts = xmlGame.Attributes["name"].Value.Split("["); + string gameName = gameNameTitleParts[0]; + + // before split, save and remove the demo tag if present + if (gameName.Contains("(demo) ", StringComparison.CurrentCulture)) + { + gameObject.Demo = RomSignatureObject.Game.DemoTypes.demo; + gameName = gameName.Replace("(demo) ", ""); + } + else if (gameName.Contains("(demo-kiosk) ", StringComparison.CurrentCulture)) + { + gameObject.Demo = RomSignatureObject.Game.DemoTypes.demo_kiosk; + gameName = gameName.Replace("(demo-kiosk) ", ""); + } + else if (gameName.Contains("(demo-playable) ", StringComparison.CurrentCulture)) + { + gameObject.Demo = RomSignatureObject.Game.DemoTypes.demo_playable; + gameName = gameName.Replace("(demo-playable) ", ""); + } + else if (gameName.Contains("(demo-rolling) ", StringComparison.CurrentCulture)) + { + gameObject.Demo = RomSignatureObject.Game.DemoTypes.demo_rolling; + gameName = gameName.Replace("(demo-rolling) ", ""); + } + else if (gameName.Contains("(demo-slideshow) ", StringComparison.CurrentCulture)) + { + gameObject.Demo = RomSignatureObject.Game.DemoTypes.demo_slideshow; + gameName = gameName.Replace("(demo-slideshow) ", ""); + } + else + { + gameObject.Demo = RomSignatureObject.Game.DemoTypes.NotDemo; + } + + string[] gameNameTokens = gameName.Split("("); + // game title should be first item + gameObject.Name = gameNameTokens[0].Trim(); + + // game year should be second item + if (gameNameTokens.Length >= 2) + { + bool dateFound = false; + + // verify the value + string dateToken = gameNameTokens[1].Replace(")", ""); + if (dateToken.Length >= 4) + { + // test for possible year values + // first up - centuries + if (dateToken == "19xx" || dateToken == "20xx") + { + // date is a century + gameObject.Year = dateToken; + dateFound = true; + } else + { + // check for decades + for (UInt16 i = 0; i < 10; i++) + { + if (dateToken == "19" + i + "x" || dateToken == "20" + i + "x") + { + // date is a decade + gameObject.Year = dateToken; + dateFound = true; + break; + } + } + + if (dateFound == false) + { + // check if the year is a four digit number + DateTime dateTime = new DateTime(); + if (DateTime.TryParse(string.Format("1/1/{0}", dateToken), out dateTime)) + { + // is a valid year! + gameObject.Year = dateToken; + dateFound = true; + } + + // if we still haven't found a valid date, check if the whole string is a valid date object + if (dateFound == false) + { + if (DateTime.TryParse(dateToken, out dateTime)) + { + // is a valid year! + gameObject.Year = dateToken; + dateFound = true; + } + } + + // if we still haven't found a valid date, check if the whole string is a valid date object, but with x's + // example: 19xx-12-2x + if (dateFound == false) + { + if (DateTime.TryParse(dateToken.Replace("x", "0"), out dateTime)) + { + // is a valid year! + gameObject.Year = dateToken; + dateFound = true; + } + } + + // if we still haven't found a valid date, perhaps it a year and month? + // example: 19xx-12 + if (dateFound == false) + { + if (DateTime.TryParse(dateToken.Replace("x", "0") + "-01", out dateTime)) + { + // is a valid year! + gameObject.Year = dateToken; + dateFound = true; + } + } + } + } + } + } + else + { + gameObject.Year = ""; + } + // game publisher should be third item + if (gameNameTokens.Length >= 3) + { + gameObject.Publisher = gameNameTokens[2].Replace(")", "").Trim(); + } + else + { + gameObject.Publisher = ""; + } + // process remaining tokens + // set default values + gameObject.System = tosecObject.Name.Split(" - ")[0]; + // process title values + UInt16 StartToken = 0; + foreach (string rawToken in gameNameTokens) + { + if (StartToken > 2) + { + string[] tokenSplit = rawToken.Split("["); + + // replace the extra closing bracket + string token = tokenSplit[0].Replace(")", "").Trim(); + + // perform tests on the token to see what it is + // exclude strings that start with [ in this part + if (!(token.StartsWith("[") && token.EndsWith("]"))) + { + // check for systems + if (TOSECSystems.Contains(token, StringComparer.CurrentCulture)) + { + // this is a system token + gameObject.SystemVariant = token; + } + + // check for video + if (TOSECVideo.Contains(token, StringComparer.CurrentCulture)) + { + // this is a system token + gameObject.Video = token; + } + + // check for country + if (TOSECCountry.ContainsKey(token)) + { + gameObject.Country = token; + } + + // check for language + if (TOSECLanguage.ContainsKey(token)) + { + gameObject.Language = token; + } + + // check for copyright + if (TOSECCopyright.ContainsKey(token)) + { + gameObject.Copyright = token; + } + } + } + StartToken += 1; + } + + gameObject.Roms = new List(); + + // get the roms + string romDescription = ""; + foreach (XmlNode xmlGameDetail in xmlGame.ChildNodes) + { + switch (xmlGameDetail.Name.ToLower()) + { + case "description": + romDescription = xmlGameDetail.InnerText; + break; + + case "rom": + RomSignatureObject.Game.Rom romObject = new RomSignatureObject.Game.Rom(); + if (xmlGameDetail != null) + { + romObject.Name = xmlGameDetail.Attributes["name"]?.Value; + if (xmlGameDetail.Attributes["size"]?.Value != null) + { + romObject.Size = UInt64.Parse(xmlGameDetail.Attributes["size"]?.Value); + } + else + { + romObject.Size = 0; + } + romObject.Crc = xmlGameDetail.Attributes["crc"]?.Value; + romObject.Md5 = xmlGameDetail.Attributes["md5"]?.Value; + romObject.Sha1 = xmlGameDetail.Attributes["sha1"]?.Value; + + // parse name + string[] romNameTokens = romDescription.Split("("); + foreach (string rawToken in romNameTokens) + { + string[] tokenSplit = rawToken.Split("["); + + // replace the extra closing bracket + string token = tokenSplit[0].Replace(")", "").Trim(); + + // check for copyright + if (TOSECDevelopment.ContainsKey(token)) + { + romObject.DevelopmentStatus = token; + } + + // check for media type + if (token.StartsWith("Disc") || + token.StartsWith("Disk") || + token.StartsWith("File") || + token.StartsWith("Part") || + token.StartsWith("Side") || + token.StartsWith("Tape")) + { + string[] tokens = token.Split(" "); + switch (tokens[0]) + { + case "Disc": + romObject.RomType = RomSignatureObject.Game.Rom.RomTypes.Disc; + break; + case "Disk": + romObject.RomType = RomSignatureObject.Game.Rom.RomTypes.Disk; + break; + case "File": + romObject.RomType = RomSignatureObject.Game.Rom.RomTypes.File; + break; + case "Part": + romObject.RomType = RomSignatureObject.Game.Rom.RomTypes.Part; + break; + case "Side": + romObject.RomType = RomSignatureObject.Game.Rom.RomTypes.Side; + break; + case "Tape": + romObject.RomType = RomSignatureObject.Game.Rom.RomTypes.Tape; + break; + } + romObject.RomTypeMedia = token; + } + + // check for media label + if (token.Length > 0 && + (token + ")") == gameNameTokens.Last() && + ( + token != romObject.RomTypeMedia && + token != gameObject.Publisher && + token != gameObject.SystemVariant && + token != gameObject.Video && + token != gameObject.Country && + token != gameObject.Copyright && + token != gameObject.Language && + token != romObject.DevelopmentStatus + ) + ) + { + // likely the media label? + romObject.MediaLabel = token; + } + + // process dump flags + if (rawToken.IndexOf("[") > 0) + { + // has dump flags + string rawDumpFlags = rawToken.Substring(rawToken.IndexOf("[")); + string[] dumpFlags = rawDumpFlags.Split("["); + foreach (string dumpFlag in dumpFlags) + { + string dToken = dumpFlag.Replace("]", ""); + if (dToken.Length > 0) + { + string[] dTokenCompare = dToken.Split(" "); + if (dTokenCompare[0].Trim().ToLower().StartsWith("a")) + { + romObject.flags.Add(dTokenCompare[0].Trim()); + } + else + { + + switch (dTokenCompare[0].Trim().ToLower()) + { + case "cr": + // cracked + case "f": + // fixed + case "h": + // hacked + case "m": + // modified + case "p": + // pirated + case "t": + // trained + case "tr": + // translated + case "o": + // overdump + case "u": + // underdump + case "v": + // virus + case "b": + // bad dump + case "a": + // alternate + case "!": + // known verified dump + // ------------------- + romObject.flags.Add(dToken); + break; + } + } + + } + } + } + } + } + + gameObject.Roms.Add(romObject); + break; + } + } + + // search for existing gameObject to update + bool existingGameFound = false; + foreach (RomSignatureObject.Game existingGame in tosecObject.Games) + { + if (existingGame.Name == gameObject.Name && + existingGame.Year == gameObject.Year && + existingGame.Publisher == gameObject.Publisher && + existingGame.Country == gameObject.Country && + existingGame.Language == gameObject.Language) + { + existingGame.Roms.AddRange(gameObject.Roms); + existingGameFound = true; + break; + } + } + if (existingGameFound == false) + { + tosecObject.Games.Add(gameObject); + } + } + + return tosecObject; + } + } +} + diff --git a/gaseous-signature-parser/Support/Parsers/TOSEC/Copyright.txt b/gaseous-signature-parser/Support/Parsers/TOSEC/Copyright.txt new file mode 100644 index 0000000..6874cd5 --- /dev/null +++ b/gaseous-signature-parser/Support/Parsers/TOSEC/Copyright.txt @@ -0,0 +1,9 @@ +CW,Cardware +CW-R,Cardware-Registered +FW,Freeware +GW,Giftware +GW-R,Giftware-Registered +LW,Licenceware +PD,Public Domain +SW,Shareware +SW-R,Shareware-Registered \ No newline at end of file diff --git a/gaseous-signature-parser/Support/Parsers/TOSEC/Country.txt b/gaseous-signature-parser/Support/Parsers/TOSEC/Country.txt new file mode 100644 index 0000000..3acd129 --- /dev/null +++ b/gaseous-signature-parser/Support/Parsers/TOSEC/Country.txt @@ -0,0 +1,68 @@ +AE,United Arab Emirates +AL,Albania +AS,Asia +AT,Austria +AU,Australia +BA,Bosnia and Herzegovina +BE,Belgium +BG,Bulgaria +BR,Brazil +CA,Canada +CH,Switzerland +CL,Chile +CN,China +CS,Serbia and Montenegro +CY,Cyprus +CZ,Czech Republic +DE,Germany +DK,Denmark +EE,Estonia +EG,Egypt +ES,Spain +EU,Europe +FI,Finland +FR,France +GB,United Kingdom +GR,Greece +HK,Hong Kong +HR,Croatia +HU,Hungary +ID,Indonesia +IE,Ireland +IL,Israel +IN,India +IR,Iran +IS,Iceland +IT,Italy +JO,Jordan +JP,Japan +KR,South Korea +LT,Lithuania +LU,Luxembourg +LV,Latvia +MN,Mongolia +MX,Mexico +MY,Malaysia +NL,Netherlands +NO,Norway +NP,Nepal +NZ,New Zealand +OM,Oman +PE,Peru +PH,Philippines +PL,Poland +PT,Portugal +QA,Qatar +RO,Romania +RU,Russia +SE,Sweden +SG,Singapore +SI,Slovenia +SK,Slovakia +TH,Thailand +TR,Turkey +TW,Taiwan +US,United States +VN,Vietnam +YU,Yugoslavia +ZA,South Africa \ No newline at end of file diff --git a/gaseous-signature-parser/Support/Parsers/TOSEC/DevelopmentStatus.txt b/gaseous-signature-parser/Support/Parsers/TOSEC/DevelopmentStatus.txt new file mode 100644 index 0000000..c153038 --- /dev/null +++ b/gaseous-signature-parser/Support/Parsers/TOSEC/DevelopmentStatus.txt @@ -0,0 +1,5 @@ +alpha,Early test build +beta,Later; feature complete test build +preview,Near complete build +pre-release,Near complete build +proto,Unreleased; prototype software \ No newline at end of file diff --git a/gaseous-signature-parser/Support/Parsers/TOSEC/Language.txt b/gaseous-signature-parser/Support/Parsers/TOSEC/Language.txt new file mode 100644 index 0000000..fb89374 --- /dev/null +++ b/gaseous-signature-parser/Support/Parsers/TOSEC/Language.txt @@ -0,0 +1,45 @@ +ar,Arabic +bg,Bulgarian +bs,Bosnian +cs,Czech +cy,Welsh +da,Danish +de,German +el,Greek +en,English +eo,Esperanto +es,Spanish +et,Estonian +fa,Persian +fi,Finnish +fr,French +ga,Irish +gu,Gujarati +he,Hebrew +hi,Hindi +hr,Croatian +hu,Hungarian +is,Icelandic +it,Italian +ja,Japanese +ko,Korean +lt,Lithuanian +lv,Latvian +ms,Malay +nl,Dutch +no,Norwegian +pl,Polish +pt,Portuguese +ro,Romanian +ru,Russian +sk,Slovakian +sl,Slovenian +sq,Albanian +sr,Serbian +sv,Swedish +th,Thai +tr,Turkish +ur,Urdu +vi,Vietnamese +yi,Yiddish +zh,Chinese \ No newline at end of file diff --git a/gaseous-signature-parser/Support/Parsers/TOSEC/Systems.txt b/gaseous-signature-parser/Support/Parsers/TOSEC/Systems.txt new file mode 100644 index 0000000..fadd02b --- /dev/null +++ b/gaseous-signature-parser/Support/Parsers/TOSEC/Systems.txt @@ -0,0 +1,57 @@ ++2 ++2a ++3 +130XE +A1000 +A1200 +A1200-A4000 +A2000 +A2000-A3000 +A2024 +A2500-A3000UX +A3000 +A4000 +A4000T +A500 +A500+ +A500-A1000-A2000 +A500-A1000-A2000-CDTV +A500-A1200 +A500-A1200-A2000-A4000 +A500-A2000 +A500-A600-A2000 +A570 +A600 +A600HD +AGA +AGA-CD32 +Aladdin Deck Enhancer +CD32 +CDTV +Computrainer +Doctor PC Jr. +ECS +ECS-AGA +Executive +Mega ST +Mega-STE +OCS +OCS-AGA +ORCH80 +Osbourne 1 +PIANO90 +PlayChoice-10 +Plus4 +Primo-A +Primo-A64 +Primo-B +Primo-B64 +Pro-Primo +ST +STE +STE-Falcon +TT +TURBO-R GT +TURBO-R ST +VS DualSystem +VS UniSystem \ No newline at end of file diff --git a/gaseous-signature-parser/Support/Parsers/TOSEC/Video.txt b/gaseous-signature-parser/Support/Parsers/TOSEC/Video.txt new file mode 100644 index 0000000..f8ab8ff --- /dev/null +++ b/gaseous-signature-parser/Support/Parsers/TOSEC/Video.txt @@ -0,0 +1,13 @@ +CGA +EGA +HGC +MCGA +MDA +NTSC +NTSC-PAL +PAL +PAL-60 +PAL-NTSC +SVGA +VGA +XGA \ No newline at end of file diff --git a/gaseous-signature-parser/gaseous-signature-parser.csproj b/gaseous-signature-parser/gaseous-signature-parser.csproj new file mode 100644 index 0000000..f09d93a --- /dev/null +++ b/gaseous-signature-parser/gaseous-signature-parser.csproj @@ -0,0 +1,38 @@ + + + + net7.0 + gaseous_signature_parser + enable + enable + + + + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/gaseous-tools/Common.cs b/gaseous-tools/Common.cs new file mode 100644 index 0000000..00bc713 --- /dev/null +++ b/gaseous-tools/Common.cs @@ -0,0 +1,63 @@ +using System; +using System.Security.Cryptography; + +namespace gaseous_tools +{ + public class Common + { + /// + /// Returns IfNullValue if the ObjectToCheck is null + /// + /// Any nullable object to check for null + /// Any object to return if ObjectToCheck is null + /// + static public object ReturnValueIfNull(object? ObjectToCheck, object IfNullValue) + { + if (ObjectToCheck == null) + { + return IfNullValue; + } else + { + return ObjectToCheck; + } + } + + public class hashObject + { + public hashObject(string FileName) + { + var xmlStream = File.OpenRead(FileName); + + var md5 = MD5.Create(); + byte[] md5HashByte = md5.ComputeHash(xmlStream); + string md5Hash = BitConverter.ToString(md5HashByte).Replace("-", "").ToLowerInvariant(); + _md5hash = md5hash; + + var sha1 = SHA1.Create(); + byte[] sha1HashByte = sha1.ComputeHash(xmlStream); + string sha1Hash = BitConverter.ToString(sha1HashByte).Replace("-", "").ToLowerInvariant(); + _sha1hash = sha1hash; + } + + string _md5hash = ""; + string _sha1hash = ""; + + public string md5hash + { + get + { + return _md5hash; + } + } + + public string sha1hash + { + get + { + return _sha1hash; + } + } + } + } +} + diff --git a/gaseous-tools/Config.cs b/gaseous-tools/Config.cs new file mode 100644 index 0000000..c9e8e4e --- /dev/null +++ b/gaseous-tools/Config.cs @@ -0,0 +1,112 @@ +using System; +using Newtonsoft.Json; + +namespace gaseous_tools +{ + public static class Config + { + static ConfigFile _config; + + public static string ConfigurationPath + { + get + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".gaseous-server"); + } + } + + static string ConfigurationFilePath + { + get + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".gaseous-server", "config.json"); + } + } + + static string ConfigurationFilePath_Backup + { + get + { + return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".gaseous-server", "config.json.backup"); + } + } + + public static ConfigFile.Database DatabaseConfiguration + { + get + { + return _config.DatabaseConfiguration; + } + } + + static Config() + { + if (_config == null) + { + // load the config file + if (File.Exists(ConfigurationFilePath)) + { + string configRaw = File.ReadAllText(ConfigurationFilePath); + ConfigFile? _tempConfig = Newtonsoft.Json.JsonConvert.DeserializeObject(configRaw); + if (_tempConfig != null) + { + _config = _tempConfig; + } else + { + throw new Exception("There was an error reading the config file: Json returned null"); + } + } else + { + // no config file! + // use defaults and save + _config = new ConfigFile(); + string configRaw = Newtonsoft.Json.JsonConvert.SerializeObject(_config, Newtonsoft.Json.Formatting.Indented); + File.WriteAllText(ConfigurationFilePath, configRaw); + } + } + + Console.WriteLine("Using configuration:"); + Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(_config, Formatting.Indented)); + } + + public static void UpdateConfig() + { + // save any updates to the configuration + string configRaw = Newtonsoft.Json.JsonConvert.SerializeObject(_config, Newtonsoft.Json.Formatting.Indented); + if (File.Exists(ConfigurationFilePath_Backup)) + { + File.Delete(ConfigurationFilePath_Backup); + } + if (File.Exists(ConfigurationFilePath)) + { + File.Move(ConfigurationFilePath, ConfigurationFilePath_Backup); + } + File.WriteAllText(ConfigurationFilePath, configRaw); + } + + public class ConfigFile + { + public Database DatabaseConfiguration = new Database(); + + public class Database + { + public string HostName = "localhost"; + public string UserName = "gaseous"; + public string Password = "gaseous"; + public string DatabaseName = "gaseous"; + public int Port = 3306; + + [JsonIgnore] + public string ConnectionString + { + get + { + string dbConnString = "server=" + HostName + ";port=" + Port + ";userid=" + UserName + ";password=" + Password + ";database=" + DatabaseName + ""; + return dbConnString; + } + } + } + } + } +} + diff --git a/gaseous-tools/Database.cs b/gaseous-tools/Database.cs new file mode 100644 index 0000000..81bc5f7 --- /dev/null +++ b/gaseous-tools/Database.cs @@ -0,0 +1,192 @@ +using System; +using System.Data; +using System.Data.SqlClient; +using System.Diagnostics; +using System.Reflection; +using MySql.Data.MySqlClient; + +namespace gaseous_tools +{ + public class Database + { + public Database() + { + + } + + public Database(databaseType Type, string ConnectionString) + { + _ConnectorType = Type; + _ConnectionString = ConnectionString; + } + + public enum databaseType + { + MySql + } + + string _ConnectionString = ""; + + public string ConnectionString + { + get + { + return _ConnectionString; + } + set + { + _ConnectionString = value; + } + } + + databaseType? _ConnectorType = null; + + public databaseType? ConnectorType + { + get + { + return _ConnectorType; + } + set + { + _ConnectorType = value; + } + } + + public void InitDB() + { + // load resources + var assembly = Assembly.GetExecutingAssembly(); + + switch (_ConnectorType) + { + case databaseType.MySql: + // check if the database exists first - first run must have permissions to create a database + string sql = "CREATE DATABASE IF NOT EXISTS `" + Config.DatabaseConfiguration.DatabaseName + "`;"; + Dictionary dbDict = new Dictionary(); + ExecuteCMD(sql, dbDict, 30, "server=" + Config.DatabaseConfiguration.HostName + ";port=" + Config.DatabaseConfiguration.Port + ";userid=" + Config.DatabaseConfiguration.UserName + ";password=" + Config.DatabaseConfiguration.Password); + + // check if schema version table is in place - if not, create the schema version table + sql = "SELECT TABLE_SCHEMA, TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'gaseous' AND TABLE_NAME = 'schema_version';"; + DataTable SchemaVersionPresent = ExecuteCMD(sql, dbDict); + if (SchemaVersionPresent.Rows.Count == 0) + { + // no schema table present - create it + sql = "CREATE TABLE `schema_version` (`schema_version` INT NOT NULL, PRIMARY KEY (`schema_version`)); INSERT INTO `schema_version` (`schema_version`) VALUES (0);"; + ExecuteCMD(sql, dbDict); + } + + for (int i = 1000; i < 10000; i++) + { + string resourceName = "gaseous_tools.Database.MySQL.gaseous-" + i + ".sql"; + string dbScript = ""; + + string[] resources = Assembly.GetExecutingAssembly().GetManifestResourceNames(); + if (resources.Contains(resourceName)) + { + using (Stream stream = assembly.GetManifestResourceStream(resourceName)) + using (StreamReader reader = new StreamReader(stream)) + { + dbScript = reader.ReadToEnd(); + + // apply script + sql = "SELECT schema_version FROM schema_version;"; + DataTable SchemaVersion = ExecuteCMD(sql, dbDict); + if (SchemaVersion.Rows.Count == 0) + { + // something is broken here... where's the table? + throw new Exception("schema_version table is missing!"); + } + else + { + int SchemaVer = (int)SchemaVersion.Rows[0][0]; + if (SchemaVer < i) + { + // apply schema! + ExecuteCMD(dbScript, dbDict); + + sql = "UPDATE schema_version SET schema_version=@schemaver"; + dbDict.Add("schemaver", i); + ExecuteCMD(sql, dbDict); + } + } + } + } + } + break; + } + } + + public DataTable ExecuteCMD(string Command) + { + Dictionary dbDict = new Dictionary(); + return _ExecuteCMD(Command, dbDict, 30, ""); + } + + public DataTable ExecuteCMD(string Command, Dictionary Parameters) + { + return _ExecuteCMD(Command, Parameters, 30, ""); + } + + public DataTable ExecuteCMD(string Command, Dictionary Parameters, int Timeout = 30, string ConnectionString = "") + { + return _ExecuteCMD(Command, Parameters, Timeout, ConnectionString); + } + + private DataTable _ExecuteCMD(string Command, Dictionary Parameters, int Timeout = 30, string ConnectionString = "") + { + if (ConnectionString == "") { ConnectionString = _ConnectionString; } + switch (_ConnectorType) + { + case databaseType.MySql: + MySQLServerConnector conn = new MySQLServerConnector(ConnectionString); + return (DataTable)conn.ExecCMD(Command, Parameters, Timeout); + default: + return new DataTable(); + } + } + + private partial class MySQLServerConnector + { + private string DBConn = ""; + + public MySQLServerConnector(string ConnectionString) + { + DBConn = ConnectionString; + } + + public DataTable ExecCMD(string SQL, Dictionary Parameters, int Timeout) + { + DataTable RetTable = new DataTable(); + + MySqlConnection conn = new MySqlConnection(DBConn); + conn.Open(); + + MySqlCommand cmd = new MySqlCommand + { + Connection = conn, + CommandText = SQL, + CommandTimeout = Timeout + }; + + foreach (string Parameter in Parameters.Keys) + { + cmd.Parameters.AddWithValue(Parameter, Parameters[Parameter]); + } + + try + { + RetTable.Load(cmd.ExecuteReader()); + } catch (Exception ex) { + Trace.WriteLine("Error executing " + SQL); + Trace.WriteLine("Full exception: " + ex.ToString()); + } + + conn.Close(); + + return RetTable; + } + } + } +} + diff --git a/gaseous-tools/Database/MySQL/gaseous-1000.sql b/gaseous-tools/Database/MySQL/gaseous-1000.sql new file mode 100644 index 0000000..83b73cb --- /dev/null +++ b/gaseous-tools/Database/MySQL/gaseous-1000.sql @@ -0,0 +1,146 @@ +-- MySQL dump 10.13 Distrib 8.0.32, for macos13.0 (arm64) +-- +-- Host: localhost Database: gaseous +-- ------------------------------------------------------ +-- Server version 8.0.32 + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!50503 SET NAMES utf8mb4 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `signatures_games` +-- + +DROP TABLE IF EXISTS `signatures_games`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `signatures_games` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `description` varchar(255) DEFAULT NULL, + `year` varchar(15) DEFAULT NULL, + `publisherid` int DEFAULT NULL, + `demo` int DEFAULT NULL, + `systemid` int DEFAULT NULL, + `systemvariant` varchar(100) DEFAULT NULL, + `video` varchar(10) DEFAULT NULL, + `country` varchar(5) DEFAULT NULL, + `language` varchar(5) DEFAULT NULL, + `copyright` varchar(15) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id_UNIQUE` (`id`), + KEY `publisher_idx` (`publisherid`), + KEY `system_idx` (`systemid`), + KEY `ingest_idx` (`name`,`year`,`publisherid`,`systemid`,`country`,`language`) USING BTREE, + CONSTRAINT `publisher` FOREIGN KEY (`publisherid`) REFERENCES `signatures_publishers` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT `system` FOREIGN KEY (`systemid`) REFERENCES `signatures_platforms` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=1466355 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `signatures_platforms` +-- + +DROP TABLE IF EXISTS `signatures_platforms`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `signatures_platforms` ( + `id` int NOT NULL AUTO_INCREMENT, + `platform` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `idsignatures_platforms_UNIQUE` (`id`), + KEY `platforms_idx` (`platform`,`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=1231 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `signatures_publishers` +-- + +DROP TABLE IF EXISTS `signatures_publishers`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `signatures_publishers` ( + `id` int NOT NULL AUTO_INCREMENT, + `publisher` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id_UNIQUE` (`id`), + KEY `publisher_idx` (`publisher`,`id`) +) ENGINE=InnoDB AUTO_INCREMENT=97693 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `signatures_roms` +-- + +DROP TABLE IF EXISTS `signatures_roms`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `signatures_roms` ( + `id` int NOT NULL AUTO_INCREMENT, + `gameid` int DEFAULT NULL, + `name` varchar(255) DEFAULT NULL, + `size` bigint DEFAULT NULL, + `crc` varchar(20) DEFAULT NULL, + `md5` varchar(100) DEFAULT NULL, + `sha1` varchar(100) DEFAULT NULL, + `developmentstatus` varchar(100) DEFAULT NULL, + `flags` json DEFAULT NULL, + `romtype` int DEFAULT NULL, + `romtypemedia` varchar(100) DEFAULT NULL, + `medialabel` varchar(100) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id_UNIQUE` (`id`,`gameid`) USING BTREE, + KEY `gameid_idx` (`gameid`), + KEY `md5_idx` (`md5`) USING BTREE, + KEY `sha1_idx` (`sha1`) USING BTREE, + KEY `flags_idx` ((cast(`flags` as char(255) array))), + CONSTRAINT `gameid` FOREIGN KEY (`gameid`) REFERENCES `signatures_games` (`id`) ON DELETE CASCADE ON UPDATE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=3350963 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; + +-- +-- Table structure for table `signatures_sources` +-- + +DROP TABLE IF EXISTS `signatures_sources`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!50503 SET character_set_client = utf8mb4 */; +CREATE TABLE `signatures_sources` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + `description` varchar(255) DEFAULT NULL, + `category` varchar(45) DEFAULT NULL, + `version` varchar(45) DEFAULT NULL, + `author` varchar(255) DEFAULT NULL, + `email` varchar(45) DEFAULT NULL, + `homepage` varchar(45) DEFAULT NULL, + `url` varchar(45) DEFAULT NULL, + `sourcetype` varchar(45) DEFAULT NULL, + `sourcemd5` varchar(45) DEFAULT NULL, + `sourcesha1` varchar(45) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `id_UNIQUE` (`id`), + KEY `sourcemd5_idx` (`sourcemd5`,`id`) USING BTREE, + KEY `sourcesha1_idx` (`sourcesha1`,`id`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=7573 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2023-02-27 8:54:22 diff --git a/gaseous-tools/Database/MySQL/gaseous-1001.sql b/gaseous-tools/Database/MySQL/gaseous-1001.sql new file mode 100644 index 0000000..1b39072 --- /dev/null +++ b/gaseous-tools/Database/MySQL/gaseous-1001.sql @@ -0,0 +1,25 @@ +CREATE + ALGORITHM = UNDEFINED + DEFINER = `root`@`localhost` + SQL SECURITY DEFINER +VIEW `view_signatures_games` AS + SELECT + `signatures_games`.`id` AS `id`, + `signatures_games`.`name` AS `name`, + `signatures_games`.`description` AS `description`, + `signatures_games`.`year` AS `year`, + `signatures_games`.`publisherid` AS `publisherid`, + `signatures_publishers`.`publisher` AS `publisher`, + `signatures_games`.`demo` AS `demo`, + `signatures_games`.`systemid` AS `platformid`, + `signatures_platforms`.`platform` AS `platform`, + `signatures_games`.`systemvariant` AS `systemvariant`, + `signatures_games`.`video` AS `video`, + `signatures_games`.`country` AS `country`, + `signatures_games`.`language` AS `language`, + `signatures_games`.`copyright` AS `copyright` + FROM + ((`signatures_games` + JOIN `signatures_publishers` ON ((`signatures_games`.`publisherid` = `signatures_publishers`.`id`))) + JOIN `signatures_platforms` ON ((`signatures_games`.`systemid` = `signatures_platforms`.`id`))); + diff --git a/gaseous-tools/gaseous-tools.csproj b/gaseous-tools/gaseous-tools.csproj new file mode 100644 index 0000000..33e2fc7 --- /dev/null +++ b/gaseous-tools/gaseous-tools.csproj @@ -0,0 +1,28 @@ + + + + net7.0 + gaseous_tools + enable + enable + + + + + + + + + + + + + + + + + + + + +