From 5b82cdacda1befcd8d84e36297a66cdcc8da6bc5 Mon Sep 17 00:00:00 2001 From: Deterous <138427222+Deterous@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:26:47 +0900 Subject: [PATCH] Improve system detection (#792) * Improve Sega system detection * Fix namespace * Ignore exception * Add Sega CD header * Improve system detection * Amiga CD check * Improve 3DO and Mac detection * Remove unused parameter * Try determine system even when drive inactive * Better logic for inactive drives * scope issues * GameWave and VIS * Fix Neo Geo CD system check * use var * put physicaltool behind try catch * better use var --- CHANGELIST.md | 1 + MPF.Frontend/Tools/PhysicalTool.cs | 103 ++++++++++++++++- MPF.Frontend/ViewModels/MainViewModel.cs | 136 +++++++++++++++++++---- 3 files changed, 218 insertions(+), 22 deletions(-) diff --git a/CHANGELIST.md b/CHANGELIST.md index 02e31522..57e1a0d0 100644 --- a/CHANGELIST.md +++ b/CHANGELIST.md @@ -124,6 +124,7 @@ - Check for partial dumps - Slightly reduce nesting of file pre-dump checks - Slightly increase nesting of file pre-dump checks +- Improve system detection ### 3.2.4 (2024-11-24) diff --git a/MPF.Frontend/Tools/PhysicalTool.cs b/MPF.Frontend/Tools/PhysicalTool.cs index fd642fc2..e73bb5dc 100644 --- a/MPF.Frontend/Tools/PhysicalTool.cs +++ b/MPF.Frontend/Tools/PhysicalTool.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SabreTools.IO; +using SabreTools.RedumpLib.Data; namespace MPF.Frontend.Tools { @@ -12,7 +13,7 @@ namespace MPF.Frontend.Tools { #region Generic - /// s + /// /// Get the last modified date for a file from a physical disc, if possible /// /// Drive to extract information from @@ -53,6 +54,45 @@ namespace MPF.Frontend.Tools return dt.ToString("yyyy-MM-dd"); } + /// + /// Get the first numBytes bytes from a disc drive + /// + /// Drive to get sector from + /// Number of bytes to read from drive, maximum of one sector (2048 bytes) + /// Byte array of first sector of data, null on error + public static byte[]? GetFirstBytes(Drive? drive, int numBytes) + { + if (drive == null || drive.Letter == null || drive.Letter == '\0') + return null; + + // Must read between 1 and 2048 bytes + if (numBytes < 1) + return null; + else if (numBytes > 2048) + numBytes = 2048; + + string drivePath = $"\\\\.\\{drive.Letter}:"; + var firstSector = new byte[numBytes]; + try + { + // Open the drive as a raw device + using (FileStream driveStream = new FileStream(drivePath, FileMode.Open, FileAccess.Read)) + { + // Read the first sector + int bytesRead = driveStream.Read(firstSector, 0, numBytes); + if (bytesRead < numBytes) + return null; + } + } + catch + { + // We don't care what the error is + return null; + } + + return firstSector; + } + #endregion #region BD-Video @@ -699,5 +739,66 @@ namespace MPF.Frontend.Tools } #endregion + + #region Sega + + /// + /// Detect the Sega system based on the CD ROM header + /// + /// Drive to detect system from + /// Detected RedumpSystem if detected, null otherwise + public static RedumpSystem? DetectSegaSystem(Drive? drive) + { + if (drive == null) + return null; + + byte[]? firstSector = GetFirstBytes(drive, 0x10); + if (firstSector == null || firstSector.Length < 0x10) + return null; + + string systemType = Encoding.ASCII.GetString(firstSector, 0x00, 0x10); + + if (systemType.Equals("SEGA SEGASATURN ", StringComparison.Ordinal)) + return RedumpSystem.SegaSaturn; + else if (systemType.Equals("SEGA SEGAKATANA ", StringComparison.Ordinal)) + return RedumpSystem.SegaDreamcast; + else if (systemType.Equals("SEGADISCSYSTEM ", StringComparison.Ordinal)) + return RedumpSystem.SegaMegaCDSegaCD; + else if (systemType.Equals("SEGA MEGA DRIVE ", StringComparison.Ordinal)) + return RedumpSystem.SegaMegaCDSegaCD; + else if (systemType.Equals("SEGA GENESIS ", StringComparison.Ordinal)) + return RedumpSystem.SegaMegaCDSegaCD; + + return null; + } + + #endregion + + #region Other + + /// + /// Detect a 3DO disc based on the CD ROM header + /// + /// Drive to detect 3DO disc from + /// RedumpSystem.Panasonic3DOInteractiveMultiplayer if detected, null otherwise + public static RedumpSystem? Detect3DOSystem(Drive? drive) + { + if (drive == null) + return null; + + byte[]? firstSector = GetFirstBytes(drive, 0xC0); + if (firstSector == null || firstSector.Length < 0xC0) + return null; + + string systemType = Encoding.ASCII.GetString(firstSector, 0xB0, 0x10); + + if (systemType.Equals("iamaduckiamaduck", StringComparison.Ordinal)) + return RedumpSystem.Panasonic3DOInteractiveMultiplayer; + + return null; + } + + #endregion + } } \ No newline at end of file diff --git a/MPF.Frontend/ViewModels/MainViewModel.cs b/MPF.Frontend/ViewModels/MainViewModel.cs index 06a7bae9..05c402c8 100644 --- a/MPF.Frontend/ViewModels/MainViewModel.cs +++ b/MPF.Frontend/ViewModels/MainViewModel.cs @@ -663,8 +663,7 @@ namespace MPF.Frontend.ViewModels CopyProtectScanButtonEnabled = true; // Get the current system type - if (index != -1) - DetermineSystemType(); + DetermineSystemType(); // Only enable the start/stop if we don't have the default selected StartStopButtonEnabled = ShouldEnableDumpingButton(); @@ -1256,16 +1255,27 @@ namespace MPF.Frontend.ViewModels { VerboseLogLn("Skipping system type detection because no valid drives found!"); } - else if (CurrentDrive?.MarkedActive != true) - { - VerboseLogLn("Skipping system type detection because drive not marked as active!"); - } else if (!Options.SkipSystemDetection) { VerboseLog($"Trying to detect system for drive {CurrentDrive.Name}.. "); var currentSystem = GetRedumpSystem(CurrentDrive); VerboseLogLn(currentSystem == null ? $"unable to detect, defaulting to {Options.DefaultSystem.LongName()}" : ($"detected {currentSystem.LongName()}.")); - currentSystem ??= Options.DefaultSystem; + + // If undetected system on inactive drive, and PC is the default system, check for potential Mac disc + if (currentSystem == null && CurrentDrive.MarkedActive == false && Options.DefaultSystem == RedumpSystem.IBMPCcompatible) + { + try + { + // If disc is readable on inactive drive, assume it is a Mac disc + if (PhysicalTool.GetFirstBytes(CurrentDrive, 1) != null) + currentSystem = RedumpSystem.AppleMacintosh; + } + catch {} + } + + // Fallback to default system only if drive is active + if (currentSystem == null && CurrentDrive.MarkedActive) + currentSystem = Options.DefaultSystem; if (currentSystem != null) { @@ -1466,12 +1476,33 @@ namespace MPF.Frontend.ViewModels private static RedumpSystem? GetRedumpSystem(Drive? drive) { // If the drive does not exist, we can't do anything - if (drive == null) + if (drive == null || string.IsNullOrEmpty(drive.Name)) return null; - // If we can't read the media in that drive, we can't do anything - if (string.IsNullOrEmpty(drive.Name) || !Directory.Exists(drive.Name)) + // If we can't read the files in the drive, we can only perform physical checks + if (drive.MarkedActive == false || !Directory.Exists(drive.Name)) + { + try + { + // Check for Panasonic 3DO - filesystem not readable on Windows + RedumpSystem? detected3DOSystem = PhysicalTool.Detect3DOSystem(drive); + if (detected3DOSystem != null) + { + return detected3DOSystem; + } + + // Sega Saturn / Sega Dreamcast / Sega Mega-CD / Sega-CD + RedumpSystem? detectedSegaSystem = PhysicalTool.DetectSegaSystem(drive); + if (detectedSegaSystem != null) + { + return detectedSegaSystem; + } + } + catch {} + + // Otherwise, return null return null; + } // We're going to assume for floppies, HDDs, and removable drives if (drive.InternalDriveType != InternalDriveType.Optical) @@ -1550,6 +1581,12 @@ namespace MPF.Frontend.ViewModels return RedumpSystem.MattelFisherPriceiXL; } + // Memorex - Visual Information System + if (File.Exists(Path.Combine(drive.Name, "CONTROL.TAT"))) + { + return RedumpSystem.MemorexVisualInformationSystem; + } + // Microsoft Xbox 360 try { @@ -1603,6 +1640,17 @@ namespace MPF.Frontend.ViewModels } catch { } + try + { + // Sega Saturn / Sega Dreamcast / Sega Mega-CD / Sega-CD + RedumpSystem? segaSystem = PhysicalTool.DetectSegaSystem(drive); + if (segaSystem != null) + { + return segaSystem; + } + } + catch {} + // Sega Dreamcast if (File.Exists(Path.Combine(drive.Name, "IP.BIN"))) { @@ -1625,6 +1673,19 @@ namespace MPF.Frontend.ViewModels return RedumpSystem.SegaMegaCDSegaCD; } + // SNK Neo-Geo CD + try + { + if (File.Exists(Path.Combine(drive.Name, "ABS.TXT")) + || File.Exists(Path.Combine(drive.Name, "BIB.TXT")) + || File.Exists(Path.Combine(drive.Name, "CPY.TXT")) + || File.Exists(Path.Combine(drive.Name, "IPL.TXT"))) + { + return RedumpSystem.SNKNeoGeoCD; + } + } + catch { } + // Sony PlayStation and Sony PlayStation 2 string psxExePath = Path.Combine(drive.Name, "PSX.EXE"); string systemCnfPath = Path.Combine(drive.Name, "SYSTEM.CNF"); @@ -1655,17 +1716,6 @@ namespace MPF.Frontend.ViewModels catch { } // Sony PlayStation 4 - // There are more possible paths that could be checked. - // There are some entries that can be found on most PS4 discs: - // "/app/GAME_SERIAL/app.pkg" - // "/bd/param.sfo" - // "/license/rif" - // There are also extra files that can be found on some discs: - // "/patch/GAME_SERIAL/patch.pkg" can be found in Redump entry 66816. - // Originally on disc as "/patch/CUSA11302/patch.pkg". - // Is used as an on-disc update for the base game app without needing to get update from the internet. - // "/addcont/GAME_SERIAL/CONTENT_ID/ac.pkg" can be found in Redump entry 97619. - // Originally on disc as "/addcont/CUSA00288/FFXIVEXPS400001A/ac.pkg". #if NET20 || NET35 if (File.Exists(Path.Combine(Path.Combine(Path.Combine(drive.Name, "PS4"), "UPDATE"), "PS4UPDATE.PUP"))) #else @@ -1675,16 +1725,60 @@ namespace MPF.Frontend.ViewModels return RedumpSystem.SonyPlayStation4; } + // Sony PlayStation 5 +#if NET20 || NET35 + if (File.Exists(Path.Combine(Path.Combine(Path.Combine(drive.Name, "PS5"), "UPDATE"), "PS5UPDATE.PUP"))) +#else + if (File.Exists(Path.Combine(drive.Name, "PS5", "UPDATE", "PS5UPDATE.PUP"))) +#endif + { + return RedumpSystem.SonyPlayStation5; + } + // V.Tech V.Flash / V.Smile Pro if (File.Exists(Path.Combine(drive.Name, "0SYSTEM"))) { return RedumpSystem.VTechVFlashVSmilePro; } + // VM Labs NUON +#if NET20 || NET35 + if (File.Exists(Path.Combine(Path.Combine(drive.Name, "NUON"), "nuon.run"))) +#else + if (File.Exists(Path.Combine(drive.Name, "NUON", "nuon.run"))) +#endif + { + return RedumpSystem.VMLabsNUON; + } + + // ZAPit Games - GameWave + if (File.Exists(Path.Combine(drive.Name, "gamewave.diz"))) + { + return RedumpSystem.ZAPiTGamesGameWaveFamilyEntertainmentSystem; + } + #endregion #region Computers + // Amiga CD (Do this check AFTER CD32/CDTV) + if (File.Exists(Path.Combine(drive.Name, "Disk.info"))) + { + return RedumpSystem.CommodoreAmigaCD; + } + + // Fujitsu FM Towns + try + { + if (File.Exists(Path.Combine(drive.Name, "TMENU.EXP")) + || File.Exists(Path.Combine(drive.Name, "TBIOS.SYS")) + || File.Exists(Path.Combine(drive.Name, "TBIOS.BIN"))) + { + return RedumpSystem.FujitsuFMTownsseries; + } + } + catch { } + // Sharp X68000 if (File.Exists(Path.Combine(drive.Name, "COMMAND.X"))) {