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)
This commit is contained in:
CasualPokePlayer 2025-04-13 01:08:24 -07:00
parent 75fc58041e
commit cb50a24c0c
11 changed files with 73 additions and 42 deletions

View File

@ -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(),

View File

@ -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<KeyValuePair<string, byte[]>> Assets { get; } = new List<KeyValuePair<string, byte[]>>();
public IList<string> AssetFullPaths { get; } = new List<string>(); // 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; } = [ ];
/// <exception cref="InvalidOperationException">internal error</exception>
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<byte>()
: 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);
}

View File

@ -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");

View File

@ -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;

View File

@ -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

View File

@ -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
{

View File

@ -153,6 +153,23 @@ namespace BizHawk.Common.StringExtensions
public static bool StartsWithIgnoreCase(this string haystack, string needle)
=> haystack.StartsWith(needle, StringComparison.OrdinalIgnoreCase);
/// <returns>
/// the substring of <paramref name="str"/> after the first occurrence of <paramref name="delimiter"/>, or
/// the original <paramref name="str"/> if not found
/// </returns>
public static string SubstringAfter(this string str, char delimiter)
=> str.SubstringAfter(delimiter, notFoundValue: str);
/// <returns>
/// the substring of <paramref name="str"/> after the first occurrence of <paramref name="delimiter"/>, or
/// the original <paramref name="str"/> if not found
/// </returns>
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);
}
/// <returns>
/// the substring of <paramref name="str"/> after the first occurrence of <paramref name="delimiter"/>, or
/// the original <paramref name="str"/> if not found

View File

@ -22,7 +22,7 @@ namespace BizHawk.Emulation.Cores.Arcades.MAME
[CoreConstructor(VSystemID.Raw.Arcade)]
public MAME(CoreLoadParameters<object, MAMESyncSettings> 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)
{

View File

@ -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;

View File

@ -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;

View File

@ -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<T>(
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,