From cb50a24c0cd4a03d2c64b1b404e63f489da700bf Mon Sep 17 00:00:00 2001 From: CasualPokePlayer <50538166+CasualPokePlayer@users.noreply.github.com> Date: Sun, 13 Apr 2025 01:08:24 -0700 Subject: [PATCH] Make RomPath consistent between xml and not xml Previous implementation was broken and differed between the two in practice in the case of archives. Standard single file just passed archive loaded, without archive binding info. Xml case was even more nonsense, giving a completely nonexistent path using the internal archived file name. RomPath now will properly report the binding info in the case of archives. Cores should be very careful with using RomPath with file apis, as the | used for binding info is not a valid file char and will throw most file apis (some cores were already doing this, I've fixed most of the cores not doing so save for UAE and DSDA). TODO: Need to fix the edge case of the file being in the same archive as the xml (represented specially in xml and that code path seems to already been broken) --- src/BizHawk.Client.Common/RomLoader.cs | 29 +++++++++++-------- src/BizHawk.Client.Common/XmlGame.cs | 26 ++++++++++------- src/BizHawk.Client.EmuHawk/MainForm.cs | 7 ++--- .../RetroAchievements/RCheevos.Debug.cs | 5 ++-- .../RetroAchievements.GameVerification.cs | 14 ++++----- .../MultiDiskBundler/MultiDiskBundler.cs | 2 +- .../Extensions/StringExtensions.cs | 17 +++++++++++ .../Arcades/MAME/MAME.cs | 6 ++-- .../Consoles/Nintendo/BSNES/BsnesCore.cs | 3 +- .../Consoles/Nintendo/SNES9X/Snes9x.cs | 3 +- .../Waterbox/NymaCore.cs | 3 +- 11 files changed, 73 insertions(+), 42 deletions(-) diff --git a/src/BizHawk.Client.Common/RomLoader.cs b/src/BizHawk.Client.Common/RomLoader.cs index d47520fa08..1326eaccbc 100644 --- a/src/BizHawk.Client.Common/RomLoader.cs +++ b/src/BizHawk.Client.Common/RomLoader.cs @@ -559,7 +559,7 @@ namespace BizHawk.Client.Common RomData = rom.RomData, FileData = rom.FileData, Extension = rom.Extension, - RomPath = file.FullPathWithoutMember, + RomPath = file.CanonicalFullPath, Game = game, }, }, @@ -657,6 +657,11 @@ namespace BizHawk.Client.Common // (in general, this is kind of bad as CHD hard drives might be useful for other future cores?) private static bool IsDiscForXML(string system, string path) { + if (HawkFile.PathContainsPipe(path)) + { + return false; + } + var ext = Path.GetExtension(path); if (system is VSystemID.Raw.Arcade && ".chd".EqualsIgnoreCase(ext)) { @@ -682,21 +687,21 @@ namespace BizHawk.Client.Common Comm = nextComm, Game = game, Roms = xmlGame.Assets - .Where(kvp => !IsDiscForXML(system, kvp.Key)) - .Select(kvp => (IRomAsset)new RomAsset + .Where(pfd => !IsDiscForXML(system, pfd.Filename)) + .Select(IRomAsset (pfd) => new RomAsset { - RomData = kvp.Value, - FileData = kvp.Value, // TODO: Hope no one needed anything special here - Extension = Path.GetExtension(kvp.Key), - RomPath = Path.GetFullPath(Path.Combine(Path.GetDirectoryName(path.SubstringBefore('|'))!, kvp.Key!)), - Game = Database.GetGameInfo(kvp.Value, Path.GetFileName(kvp.Key)), + RomData = pfd.FileData, // TODO: Do RomGame RomData conversions here + FileData = pfd.FileData, + Extension = Path.GetExtension(pfd.Filename), + RomPath = pfd.Path, + Game = Database.GetGameInfo(pfd.FileData, Path.GetFileName(pfd.Filename)), }) .ToList(), - Discs = xmlGame.AssetFullPaths - .Where(p => IsDiscForXML(system, p)) - .Select(discPath => (p: discPath, d: DiscExtensions.CreateAnyType(discPath, str => DoLoadErrorCallback(str, system, LoadErrorType.DiscError)))) + Discs = xmlGame.Assets + .Where(pfd => IsDiscForXML(system, pfd.Path)) + .Select(pfd => (p: pfd.Path, d: DiscExtensions.CreateAnyType(pfd.Path, str => DoLoadErrorCallback(str, system, LoadErrorType.DiscError)))) .Where(a => a.d != null) - .Select(a => (IDiscAsset)new DiscAsset + .Select(IDiscAsset (a) => new DiscAsset { DiscData = a.d, DiscType = new DiscIdentifier(a.d).DetectDiscType(), diff --git a/src/BizHawk.Client.Common/XmlGame.cs b/src/BizHawk.Client.Common/XmlGame.cs index bc69b49064..b91db2c33c 100644 --- a/src/BizHawk.Client.Common/XmlGame.cs +++ b/src/BizHawk.Client.Common/XmlGame.cs @@ -8,6 +8,7 @@ using BizHawk.Common.IOExtensions; using BizHawk.Common.StringExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Arcades.MAME; +using BizHawk.Emulation.DiscSystem; namespace BizHawk.Client.Common { @@ -15,8 +16,7 @@ namespace BizHawk.Client.Common { public XmlDocument Xml { get; set; } public GameInfo GI { get; } = new(); - public IList> Assets { get; } = new List>(); - public IList AssetFullPaths { get; } = new List(); // TODO: Hack work around, to avoid having to refactor Assets into a object array, should be refactored! + public IList<(string Path, string Filename, byte[] FileData)> Assets { get; } = [ ]; /// internal error public static XmlGame Create(HawkFile f) @@ -79,19 +79,26 @@ namespace BizHawk.Client.Common using var hf = new HawkFile(fullPath, allowArchives: !MAMEMachineDB.IsMAMEMachine(fullPath)); if (hf.IsArchive) { - var archiveItem = hf.ArchiveItems.First(ai => ai.Name == filename.Split('|').Skip(1).First()); + var archiveItem = hf.ArchiveItems.First(ai => ai.Name == filename.SubstringAfter('|')); hf.Unbind(); hf.BindArchiveMember(archiveItem); data = hf.GetStream().ReadAllBytes(); - filename = filename.Split('|').Skip(1).First(); + filename = filename.SubstringAfter('|'); + fullPath += $"|{filename}"; } else { - var path = fullPath.SubstringBefore('|'); - data = RomGame.Is3DSRom(Path.GetExtension(path).ToUpperInvariant()) - ? Array.Empty() - : File.ReadAllBytes(path); + var ext = Path.GetExtension(fullPath).ToUpperInvariant(); + var isArcadeChd = ret.GI.System == VSystemID.Raw.Arcade && ext == ".CHD"; + if (RomGame.Is3DSRom(ext) || (Disc.IsValidExtension(ext) && !isArcadeChd)) + { + data = [ ]; + } + else + { + data = File.ReadAllBytes(fullPath); + } } } catch (Exception e) @@ -100,8 +107,7 @@ namespace BizHawk.Client.Common } } - ret.Assets.Add(new(filename, data)); - ret.AssetFullPaths.Add(fullPath); + ret.Assets.Add((fullPath, filename, data)); var sha1 = SHA1Checksum.Compute(data); hashStream.Write(sha1, 0, sha1.Length); } diff --git a/src/BizHawk.Client.EmuHawk/MainForm.cs b/src/BizHawk.Client.EmuHawk/MainForm.cs index 44490cb5d4..307af89825 100644 --- a/src/BizHawk.Client.EmuHawk/MainForm.cs +++ b/src/BizHawk.Client.EmuHawk/MainForm.cs @@ -3841,10 +3841,9 @@ namespace BizHawk.Client.EmuHawk for (int xg = 0; xg < xmlGame.Assets.Count; xg++) { - var ext = Path.GetExtension(xmlGame.AssetFullPaths[xg])?.ToLowerInvariant(); - - var (filename, data) = xmlGame.Assets[xg]; - if (Disc.IsValidExtension(ext)) + var (_, filename, data) = xmlGame.Assets[xg]; + // data length is 0 in the case of discs or 3DS roms + if (data.Length == 0) { xSw.WriteLine(Path.GetFileNameWithoutExtension(filename)); xSw.WriteLine("SHA1:N/A"); diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Debug.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Debug.cs index 2bcfb1d0c3..911460c881 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Debug.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RCheevos.Debug.cs @@ -7,6 +7,7 @@ using System.Windows.Forms; using BizHawk.Client.Common; using BizHawk.Common; using BizHawk.Common.PathExtensions; +using BizHawk.Common.StringExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Arcades.MAME; using BizHawk.Emulation.DiscSystem; @@ -304,9 +305,9 @@ namespace BizHawk.Client.EmuHawk case ".xml": { var xml = XmlGame.Create(new(path)); - foreach (var kvp in xml.Assets) + foreach (var pfd in xml.Assets) { - InternalDebugHash(kvp.Key); + InternalDebugHash(pfd.Path); } break; diff --git a/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs b/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs index a5af1391cf..63e3e9c919 100644 --- a/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs +++ b/src/BizHawk.Client.EmuHawk/RetroAchievements/RetroAchievements.GameVerification.cs @@ -239,7 +239,7 @@ namespace BizHawk.Client.EmuHawk private uint HashArcade(string path) { // Arcade wants to just hash the filename (with no extension) - var name = Encoding.UTF8.GetBytes(Path.GetFileNameWithoutExtension(path)); + var name = Encoding.UTF8.GetBytes(Path.GetFileNameWithoutExtension(path.SubstringAfter('|'))); var hash = MD5Checksum.ComputeDigestHex(name); return IdentifyHash(hash); } @@ -451,23 +451,23 @@ namespace BizHawk.Client.EmuHawk else if (ext == ".xml") { var xml = XmlGame.Create(new(ioa.SimplePath)); - foreach (var kvp in xml.Assets) + foreach (var pfd in xml.Assets) { if (consoleID is ConsoleID.Arcade) { - ret.Add(HashArcade(kvp.Key)); + ret.Add(HashArcade(pfd.Path)); break; } if (consoleID is ConsoleID.Nintendo3DS) { - ret.Add(Hash3DS(kvp.Key)); + ret.Add(Hash3DS(pfd.Filename)); break; } - ret.Add(Disc.IsValidExtension(Path.GetExtension(kvp.Key)) - ? HashDisc(kvp.Key, consoleID) - : IdentifyRom(kvp.Value)); + ret.Add(pfd.FileData.Length == 0 + ? HashDisc(pfd.Path, consoleID) + : IdentifyRom(pfd.FileData)); } } else diff --git a/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs b/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs index 7a257bdf2d..93b6ec17fd 100644 --- a/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs +++ b/src/BizHawk.Client.EmuHawk/tools/MultiDiskBundler/MultiDiskBundler.cs @@ -101,7 +101,7 @@ namespace BizHawk.Client.EmuHawk try { var xmlGame = XmlGame.Create(new HawkFile(xmlPath)); - AddFiles(xmlGame.AssetFullPaths); + AddFiles(xmlGame.Assets.Select(static pfd => pfd.Path).ToList()); } catch { diff --git a/src/BizHawk.Common/Extensions/StringExtensions.cs b/src/BizHawk.Common/Extensions/StringExtensions.cs index f3b02bf1ea..34a39cb9fe 100644 --- a/src/BizHawk.Common/Extensions/StringExtensions.cs +++ b/src/BizHawk.Common/Extensions/StringExtensions.cs @@ -153,6 +153,23 @@ namespace BizHawk.Common.StringExtensions public static bool StartsWithIgnoreCase(this string haystack, string needle) => haystack.StartsWith(needle, StringComparison.OrdinalIgnoreCase); + /// + /// the substring of after the first occurrence of , or + /// the original if not found + /// + public static string SubstringAfter(this string str, char delimiter) + => str.SubstringAfter(delimiter, notFoundValue: str); + + /// + /// the substring of after the first occurrence of , or + /// the original if not found + /// + public static string SubstringAfter(this string str, char delimiter, string notFoundValue) + { + var index = str.IndexOf(delimiter); + return index < 0 ? notFoundValue : str.Substring(index + 1, str.Length - index - 1); + } + /// /// the substring of after the first occurrence of , or /// the original if not found diff --git a/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs b/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs index 7da111e2dc..9ad060b87e 100644 --- a/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs +++ b/src/BizHawk.Emulation.Cores/Arcades/MAME/MAME.cs @@ -22,7 +22,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME [CoreConstructor(VSystemID.Raw.Arcade)] public MAME(CoreLoadParameters lp) { - _gameFileName = Path.GetFileName(lp.Roms[0].RomPath).ToLowerInvariant(); + _gameFileName = Path.GetFileName(lp.Roms[0].RomPath.SubstringAfter('|')).ToLowerInvariant(); _syncSettings = lp.SyncSettings ?? new(); ServiceProvider = new BasicServiceProvider(this); @@ -172,8 +172,8 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME // mame expects chd files in a folder of the game name string MakeFileName(IRomAsset rom) => rom.Extension.ToLowerInvariant() is ".chd" - ? gameName + '/' + Path.GetFileNameWithoutExtension(rom.RomPath).ToLowerInvariant() + rom.Extension.ToLowerInvariant() - : Path.GetFileNameWithoutExtension(rom.RomPath).ToLowerInvariant() + rom.Extension.ToLowerInvariant(); + ? gameName + '/' + Path.GetFileNameWithoutExtension(rom.RomPath.SubstringAfter('|')).ToLowerInvariant() + rom.Extension.ToLowerInvariant() + : Path.GetFileNameWithoutExtension(rom.RomPath.SubstringAfter('|')).ToLowerInvariant() + rom.Extension.ToLowerInvariant(); foreach (var rom in roms) { diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs index f06897c9cf..2733d5477a 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs @@ -3,6 +3,7 @@ using System.Runtime.InteropServices; using BizHawk.Common; using BizHawk.Common.PathExtensions; +using BizHawk.Common.StringExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Components.W65816; using BizHawk.Emulation.Cores.Nintendo.SNES; @@ -24,7 +25,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES var ser = new BasicServiceProvider(this); ServiceProvider = ser; - this._romPath = Path.ChangeExtension(loadParameters.Roms[0].RomPath, null); + this._romPath = Path.ChangeExtension(loadParameters.Roms[0].RomPath.SubstringAfter('|'), null); CoreComm = loadParameters.Comm; _syncSettings = loadParameters.SyncSettings ?? new SnesSyncSettings(); SystemId = loadParameters.Game.System; diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs index 630606af94..bbd240c802 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/SNES9X/Snes9x.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.IO; using BizHawk.Common; +using BizHawk.Common.StringExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.Cores.Waterbox; @@ -29,7 +30,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.SNES9X SystemId = VSystemID.Raw.SNES, }) { - this._romPath = Path.ChangeExtension(loadParameters.Roms[0].RomPath, null); + this._romPath = Path.ChangeExtension(loadParameters.Roms[0].RomPath.SubstringAfter('|'), null); this._currentMsuTrack = new ProxiedFile(); LibSnes9x.OpenAudio openAudioCb = MsuOpenAudio; diff --git a/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs b/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs index be88a2155a..117bb2e532 100644 --- a/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs +++ b/src/BizHawk.Emulation.Cores/Waterbox/NymaCore.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using BizHawk.BizInvoke; using BizHawk.Common; +using BizHawk.Common.StringExtensions; using BizHawk.Emulation.Common; using BizHawk.Emulation.DiscSystem; @@ -56,7 +57,7 @@ namespace BizHawk.Emulation.Cores.Waterbox { return DoInit( lp.Roms.Select(r => (r.RomData, - Path.GetFileName(r.RomPath[(r.RomPath.LastIndexOf('|') + 1)..])!.ToLowerInvariant())).ToArray(), + Path.GetFileName(r.RomPath.SubstringAfter('|')).ToLowerInvariant())).ToArray(), lp.Discs.Select(d => d.DiscData).ToArray(), wbxFilename, lp.Roms.FirstOrDefault()?.Extension,