diff --git a/Assets/dll/bsnes.wbx.gz b/Assets/dll/bsnes.wbx.gz index fee6283fe4..bb874292f0 100644 Binary files a/Assets/dll/bsnes.wbx.gz and b/Assets/dll/bsnes.wbx.gz differ diff --git a/src/BizHawk.Client.Common/RomLoader.cs b/src/BizHawk.Client.Common/RomLoader.cs index 7412453353..71638d82b6 100644 --- a/src/BizHawk.Client.Common/RomLoader.cs +++ b/src/BizHawk.Client.Common/RomLoader.cs @@ -29,6 +29,7 @@ namespace BizHawk.Client.Common public byte[] RomData { get; set; } public byte[] FileData { get; set; } public string Extension { get; set; } + public string RomDirectory { get; set; } public GameInfo Game { get; set; } } private class CoreInventoryParameters : ICoreInventoryParameters @@ -481,6 +482,7 @@ namespace BizHawk.Client.Common RomData = rom.RomData, FileData = rom.FileData, Extension = rom.Extension, + RomDirectory = file.Directory, Game = game } }, diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs index ecdbbba794..8b85c53267 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesApi.cs @@ -67,6 +67,9 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES public abstract void snes_set_cpu_register(string register, uint value); [BizImport(CallingConvention.Cdecl)] public abstract bool snes_cpu_step(); + + [BizImport(CallingConvention.Cdecl)] + public abstract bool snes_msu_sync(); } public unsafe partial class BsnesApi : IDisposable, IMonitor, IStatable @@ -99,6 +102,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES } } + public void AddReadonlyFile(string path, string name) + { + if (!_readonlyFiles.Contains(name)) + { + try + { + exe.AddReadonlyFile(File.ReadAllBytes(path), name); + _readonlyFiles.Add(name); + } + catch + { + // ignored + } + } + } + public void SetCallbacks(SnesCallbacks callbacks) { var functionPointerArray = callbacks @@ -154,6 +173,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES public delegate void snes_read_hook_t(uint address); public delegate void snes_write_hook_t(uint address, byte value); public delegate void snes_exec_hook_t(uint address); + public delegate void snes_msu_open_t(ushort track_id); + public delegate void snes_msu_seek_t(long offset, bool relative); + public delegate byte snes_msu_read_t(); + public delegate bool snes_msu_end_t(); [StructLayout(LayoutKind.Sequential)] public struct CpuRegisters @@ -201,6 +224,10 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES public snes_read_hook_t readHookCb; public snes_write_hook_t writeHookCb; public snes_exec_hook_t execHookCb; + public snes_msu_open_t msuOpenCb; + public snes_msu_seek_t msuSeekCb; + public snes_msu_read_t msuReadCb; + public snes_msu_end_t msuEndCb; private static List FieldsInOrder; @@ -229,11 +256,12 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES public void Seal() { exe.Seal(); - foreach (var s in _readonlyFiles) + foreach (string s in _readonlyFiles.Where(s => !s.StartsWith("msu1/"))) { exe.RemoveReadonlyFile(s); } - _readonlyFiles.Clear(); + + _readonlyFiles.RemoveAll(s => !s.StartsWith("msu1/")); } // TODO: confirm that the serializedSize is CONSTANT for any given game, @@ -258,6 +286,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES // byte[] serializedData = reader.ReadBytes(serializedSize); // _core.snes_unserialize(serializedData, serializedSize); exe.LoadStateBinary(reader); + core.snes_msu_sync(); } } } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs index e01469a96f..9830ea6e4b 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.IEmulator.cs @@ -87,6 +87,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES Api.Dispose(); _resampler.Dispose(); + _currentMsuTrack?.Dispose(); _disposed = true; } diff --git a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs index 66c4533815..d4fed64383 100644 --- a/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs +++ b/src/BizHawk.Emulation.Cores/Consoles/Nintendo/BSNES/BsnesCore.cs @@ -19,6 +19,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES var ser = new BasicServiceProvider(this); ServiceProvider = ser; + this._romPath = Path.Combine(loadParameters.Roms[0].RomDirectory, loadParameters.Game.Name); CoreComm = loadParameters.Comm; _settings = loadParameters.Settings ?? new SnesSettings(); _syncSettings = loadParameters.SyncSettings ?? new SnesSyncSettings(); @@ -50,7 +51,11 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES traceCb = snes_trace, readHookCb = ReadHook, writeHookCb = WriteHook, - execHookCb = ExecHook + execHookCb = ExecHook, + msuOpenCb = msu_open, + msuSeekCb = msu_seek, + msuReadCb = msu_read, + msuEndCb = msu_end }; Api = new BsnesApi(CoreComm.CoreFileProvider.DllPath(), CoreComm, callbacks.AllDelegatesInMemoryOrder()); @@ -118,6 +123,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES private IController _controller; private SpeexResampler _resampler; + private readonly string _romPath; private bool _disposed; public bool IsSGB { get; } @@ -131,11 +137,18 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES private string snes_path_request(int slot, string hint, bool required) { - if (hint == "save.ram") + switch (hint) { - // core asked for saveram, but the interface isn't designed to be able to handle this. - // so, we'll just return nothing and the frontend will set the saveram itself later - return null; + case "manifest.bml": + Api.AddReadonlyFile($"{_romPath}.bml", hint); + return hint; + case "msu1/data.rom": + Api.AddReadonlyFile($"{_romPath}.msu", hint); + return hint; + case "save.ram": + // core asked for saveram, but the interface isn't designed to be able to handle this. + // so, we'll just return nothing and the frontend will set the saveram itself later + return null; } string firmwareId; @@ -303,5 +316,33 @@ namespace BizHawk.Emulation.Cores.Nintendo.BSNES MemoryCallbacks.CallMemoryCallbacks(addr, 0, (uint) MemoryCallbackFlags.AccessExecute, "System Bus"); } } + + private FileStream _currentMsuTrack; + + private void msu_seek(long offset, bool relative) + { + _currentMsuTrack?.Seek(offset, relative ? SeekOrigin.Current : SeekOrigin.Begin); + } + private byte msu_read() + { + return (byte) (_currentMsuTrack?.ReadByte() ?? 0); + } + + private void msu_open(ushort trackId) + { + _currentMsuTrack?.Dispose(); + try + { + _currentMsuTrack = File.OpenRead($"{_romPath}-{trackId}.pcm"); + } + catch + { + _currentMsuTrack = null; + } + } + private bool msu_end() + { + return _currentMsuTrack.Position == _currentMsuTrack.Length; + } } } diff --git a/src/BizHawk.Emulation.Cores/CoreInventory.cs b/src/BizHawk.Emulation.Cores/CoreInventory.cs index b410028569..6bf4a51156 100644 --- a/src/BizHawk.Emulation.Cores/CoreInventory.cs +++ b/src/BizHawk.Emulation.Cores/CoreInventory.cs @@ -26,6 +26,7 @@ namespace BizHawk.Emulation.Cores public byte[] RomData { get; set; } public byte[] FileData { get; set; } public string Extension { get; set; } + public string RomDirectory { get; set; } public GameInfo Game { get; set; } } @@ -105,7 +106,7 @@ namespace BizHawk.Emulation.Cores param.Roms = cip.Roms; param.Discs = cip.Discs; param.DeterministicEmulationRequested = cip.DeterministicEmulationRequested; - return (IEmulator)CTor.Invoke(new object[] { param }); + return (IEmulator)CTor.Invoke(new object[] { param }); } private IEmulator CreateUsingLegacyConstructorParameters(ICoreInventoryParameters cip) @@ -207,7 +208,7 @@ namespace BizHawk.Emulation.Cores /// The user has indicated in preferences that this is their favorite core /// UserPreference = -200, - + /// /// A very good core that should be preferred over normal cores. Don't use this? /// diff --git a/src/BizHawk.Emulation.Cores/CoreLoadParameters.cs b/src/BizHawk.Emulation.Cores/CoreLoadParameters.cs index b8114fdbe1..f223cf31f5 100644 --- a/src/BizHawk.Emulation.Cores/CoreLoadParameters.cs +++ b/src/BizHawk.Emulation.Cores/CoreLoadParameters.cs @@ -9,6 +9,7 @@ namespace BizHawk.Emulation.Cores byte[] RomData { get; } byte[] FileData { get; } string Extension { get; } + public string RomDirectory { get; } /// /// GameInfo for this individual asset. Doesn't make sense a lot of the time; /// only use this if your individual rom assets are full proper games when considered alone. diff --git a/waterbox/bsnescore/bsnes/nall/vfs/biz_file.hpp b/waterbox/bsnescore/bsnes/nall/vfs/biz_file.hpp new file mode 100644 index 0000000000..6da64b3b68 --- /dev/null +++ b/waterbox/bsnescore/bsnes/nall/vfs/biz_file.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include + +namespace nall::vfs { + +struct biz_file : file { + auto seek(intmax offset, index mode) -> void override { + return snesCallbacks.snes_msu_seek(offset, mode == index::relative); + } + + auto read() -> uint8_t override { + return snesCallbacks.snes_msu_read(); + } + + auto end() const -> bool override { + return snesCallbacks.snes_msu_end(); + } + + // not implemented and not necessary, but must be overridden + auto size() const -> uintmax override { return -1; } + auto offset() const -> uintmax override { return -1; } + auto write(uint8_t data) -> void override {} +}; + +} diff --git a/waterbox/bsnescore/bsnes/nall/vfs/vfs.hpp b/waterbox/bsnescore/bsnes/nall/vfs/vfs.hpp index 56488be0d7..fcd97a788a 100644 --- a/waterbox/bsnescore/bsnes/nall/vfs/vfs.hpp +++ b/waterbox/bsnescore/bsnes/nall/vfs/vfs.hpp @@ -19,7 +19,7 @@ struct file { virtual auto write(uint8_t data) -> void = 0; virtual auto flush() -> void {} - auto end() const -> bool { + virtual auto end() const -> bool { return offset() >= size(); } diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp index 6aed57adbb..b1ac3fd66c 100644 --- a/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp +++ b/waterbox/bsnescore/bsnes/target-bsnescore/bsnescore.cpp @@ -450,3 +450,12 @@ EXPORT bool snes_cpu_step() scheduler.StepOnce = false; return scheduler.event == Scheduler::Event::Frame; } + +// should be called on savestate load, to get msu files loaded and in the correct state +EXPORT void snes_msu_sync() +{ + if (cartridge.has.MSU1) { + msu1.dataOpen(); + msu1.audioOpen(); + } +} diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h b/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h index cd43b21f0f..59ceaa8c94 100644 --- a/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h +++ b/waterbox/bsnescore/bsnes/target-bsnescore/callbacks.h @@ -13,6 +13,10 @@ typedef void (*snes_trace_t)(const char* disassembly, const char* register_info) typedef void (*snes_read_hook_t)(uint32_t address); typedef void (*snes_write_hook_t)(uint32_t address, uint8_t value); typedef void (*snes_exec_hook_t)(uint32_t address); +typedef void (*snes_msu_open_t)(uint16_t track_id); +typedef void (*snes_msu_seek_t)(long offset, bool relative); +typedef uint8_t (*snes_msu_read_t)(void); +typedef bool (*snes_msu_end_t)(void); struct SnesCallbacks { snes_video_frame_t snes_video_frame; @@ -25,6 +29,10 @@ struct SnesCallbacks { snes_read_hook_t snes_read_hook; snes_write_hook_t snes_write_hook; snes_exec_hook_t snes_exec_hook; + snes_msu_open_t snes_msu_open; + snes_msu_seek_t snes_msu_seek; + snes_msu_read_t snes_msu_read; + snes_msu_end_t snes_msu_end; }; extern SnesCallbacks snesCallbacks; diff --git a/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp b/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp index 7c0de40390..3f9359516c 100644 --- a/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp +++ b/waterbox/bsnescore/bsnes/target-bsnescore/program.cpp @@ -7,6 +7,7 @@ #include #include "resources.hpp" +#include static Emulator::Interface *emulator; @@ -150,11 +151,17 @@ auto Program::openFileSuperFamicom(string name, vfs::file::mode mode, bool requi // TODO: the original bsnes code handles a lot more paths; *.data.ram, time.rtc and download.ram // I believe none of these can currently be correctly served by bizhawk and I therefor ignore them here // This should probably be changed? Not sure how much can break from not having them - if(name == "save.ram") + if(name == "msu1/data.rom" || name == "save.ram") { return vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, name, required), mode); } + if(name.match("msu1/track*.pcm")) + { + snesCallbacks.snes_msu_open(strtol(name.data() + 11, nullptr, 10)); + return shared_pointer{new vfs::biz_file}; + } + if(name == "arm6.program.rom" && mode == File::Read) { if(superFamicom.firmware.size() == 0x28000) { return vfs::memory::file::open(&superFamicom.firmware.data()[0x00000], 0x20000); @@ -363,7 +370,11 @@ auto Program::loadSuperFamicom() -> bool case 1: superFamicom.region = "NTSC"; break; case 2: superFamicom.region = "PAL"; break; } - superFamicom.manifest = heuristics.manifest(); + if (auto fp = vfs::fs::file::open(snesCallbacks.snes_path_request(ID::SuperFamicom, "manifest.bml", false), File::Read)) { + superFamicom.manifest = fp->reads(); + } else { + superFamicom.manifest = heuristics.manifest(); + } hackPatchMemory(rom); superFamicom.document = BML::unserialize(superFamicom.manifest);