From a15c9421a884ad98aad89ea69aeecedb1a8d5104 Mon Sep 17 00:00:00 2001 From: Sour Date: Thu, 21 Jul 2022 19:47:50 -0400 Subject: [PATCH] NES: Implement auto-configure inputs option --- Core/NES/MapperFactory.cpp | 11 -- Core/NES/NesConsole.cpp | 125 +++++++++++++++++- Core/NES/NesConsole.h | 4 + Core/NES/NesControlManager.h | 1 - Core/SNES/BaseCartridge.cpp | 6 +- Core/SNES/SnesControlManager.h | 1 - .../Shared/Interfaces/INotificationListener.h | 3 +- Core/Shared/RecordedRomTest.cpp | 6 +- Core/Shared/SettingTypes.h | 1 + InteropDLL/ConfigApiWrapper.cpp | 6 + NewUI/Config/NesConfig.cs | 19 +++ NewUI/Debugger/Utilities/ContextMenuAction.cs | 2 +- NewUI/Interop/ConfigApi.cs | 1 + NewUI/Interop/NotificationListener.cs | 3 +- NewUI/Localization/resources.en.xml | 1 + NewUI/Utilities/LoadRomHelper.cs | 4 + NewUI/ViewModels/NesConfigViewModel.cs | 17 +++ NewUI/Views/NesInputConfigView.axaml | 4 +- NewUI/Windows/MainWindow.axaml.cs | 10 ++ 19 files changed, 201 insertions(+), 24 deletions(-) diff --git a/Core/NES/MapperFactory.cpp b/Core/NES/MapperFactory.cpp index c09ebaca..61229d09 100644 --- a/Core/NES/MapperFactory.cpp +++ b/Core/NES/MapperFactory.cpp @@ -1,5 +1,4 @@ #include "stdafx.h" -#include "Shared/NotificationManager.h" #include "NES/MapperFactory.h" #include "NES/NesConsole.h" #include "NES/Loaders/RomLoader.h" @@ -671,16 +670,6 @@ unique_ptr MapperFactory::InitializeFromFile(NesConsole* console, Vi romData = {}; bool databaseEnabled = !console->GetNesConfig().DisableGameDatabase; if(RomLoader::LoadFile(romFile, romData, databaseEnabled)) { - if((romData.Info.IsInDatabase || romData.Info.IsNes20Header) && romData.Info.InputType != GameInputType::Unspecified) { - //If in DB or a NES 2.0 file, auto-configure the inputs - //TODO NES - /* - if(console->GetNesConfig()->CheckFlag(EmulationFlags::AutoConfigureInput)) { - console->GetSettings()->InitializeInputDevices(romData.Info.InputType, romData.Info.System, false); - } - */ - } - unique_ptr mapper(GetMapperFromID(romData)); if(mapper) { result = LoadRomResult::Success; diff --git a/Core/NES/NesConsole.cpp b/Core/NES/NesConsole.cpp index b69b8871..a895f437 100644 --- a/Core/NES/NesConsole.cpp +++ b/Core/NES/NesConsole.cpp @@ -23,11 +23,12 @@ #include "NES/Mappers/FDS/Fds.h" #include "Shared/Emulator.h" #include "Shared/CheatManager.h" -#include "Netplay/GameClient.h" #include "Shared/Movies/MovieManager.h" #include "Shared/BaseControlManager.h" #include "Shared/Interfaces/IBattery.h" #include "Shared/EmuSettings.h" +#include "Shared/NotificationManager.h" +#include "Netplay/GameClient.h" #include "Debugger/DebugTypes.h" #include "Utilities/Serializer.h" @@ -133,6 +134,11 @@ LoadRomResult NesConsole::LoadRom(VirtualFile& romFile) return result; } } + + //If in DB or a NES 2.0 file, auto-configure the inputs (if option is enabled) + if(GetNesConfig().AutoConfigureInput && (romData.Info.IsInDatabase || romData.Info.IsNes20Header) && romData.Info.InputType != GameInputType::Unspecified) { + InitializeInputDevices(romData.Info.InputType, romData.Info.System); + } _mapper.swap(mapper); _mixer.reset(new NesSoundMixer(this)); @@ -465,6 +471,123 @@ void NesConsole::DebugWriteVram(uint16_t addr, uint8_t value) } } +void NesConsole::InitializeInputDevices(GameInputType inputType, GameSystem system) +{ + ControllerType port1 = ControllerType::NesController; + ControllerType port2 = ControllerType::NesController; + ControllerType expDevice = ControllerType::None; + + auto log = [](string text) { + MessageManager::Log(text); + }; + + bool isFamicom = (system == GameSystem::Famicom || system == GameSystem::FDS || system == GameSystem::Dendy); + + if(inputType == GameInputType::VsZapper) { + //VS Duck Hunt, etc. need the zapper in the first port + log("[Input] VS Zapper connected"); + port1 = ControllerType::NesZapper; + } else if(inputType == GameInputType::Zapper) { + log("[Input] Zapper connected"); + if(isFamicom) { + expDevice = ControllerType::FamicomZapper; + } else { + port2 = ControllerType::NesZapper; + } + } else if(inputType == GameInputType::FourScore) { + log("[Input] Four score connected"); + port1 = ControllerType::FourScore; + port2 = ControllerType::FourScore; + } else if(inputType == GameInputType::FourPlayerAdapter) { + log("[Input] Four player adapter connected"); + expDevice = ControllerType::TwoPlayerAdapter; + } else if(inputType == GameInputType::ArkanoidControllerFamicom) { + log("[Input] Arkanoid controller (Famicom) connected"); + expDevice = ControllerType::FamicomArkanoidController; + } else if(inputType == GameInputType::ArkanoidControllerNes) { + log("[Input] Arkanoid controller (NES) connected"); + port2 = ControllerType::NesArkanoidController; + } else if(inputType == GameInputType::DoubleArkanoidController) { + log("[Input] 2x arkanoid controllers (NES) connected"); + port1 = ControllerType::NesArkanoidController; + port2 = ControllerType::NesArkanoidController; + } else if(inputType == GameInputType::OekaKidsTablet) { + log("[Input] Oeka Kids Tablet connected"); + expDevice = ControllerType::OekaKidsTablet; + } else if(inputType == GameInputType::KonamiHyperShot) { + log("[Input] Konami Hyper Shot connected"); + expDevice = ControllerType::KonamiHyperShot; + } else if(inputType == GameInputType::FamilyBasicKeyboard) { + log("[Input] Family Basic Keyboard connected"); + expDevice = ControllerType::FamilyBasicKeyboard; + } else if(inputType == GameInputType::PartyTap) { + log("[Input] Party Tap connected"); + expDevice = ControllerType::PartyTap; + } else if(inputType == GameInputType::PachinkoController) { + log("[Input] Pachinko controller connected"); + expDevice = ControllerType::Pachinko; + } else if(inputType == GameInputType::ExcitingBoxing) { + log("[Input] Exciting Boxing controller connected"); + expDevice = ControllerType::ExcitingBoxing; + } else if(inputType == GameInputType::SuborKeyboardMouse1) { + log("[Input] Subor mouse connected"); + log("[Input] Subor keyboard connected"); + expDevice = ControllerType::SuborKeyboard; + port2 = ControllerType::SuborMouse; + } else if(inputType == GameInputType::JissenMahjong) { + log("[Input] Jissen Mahjong controller connected"); + expDevice = ControllerType::JissenMahjong; + } else if(inputType == GameInputType::BarcodeBattler) { + log("[Input] Barcode Battler barcode reader connected"); + expDevice = ControllerType::BarcodeBattler; + } else if(inputType == GameInputType::BandaiHypershot) { + log("[Input] Bandai Hyper Shot gun connected"); + expDevice = ControllerType::BandaiHyperShot; + } else if(inputType == GameInputType::BattleBox) { + log("[Input] Battle Box connected"); + expDevice = ControllerType::BattleBox; + } else if(inputType == GameInputType::TurboFile) { + log("[Input] Ascii Turbo File connected"); + expDevice = ControllerType::AsciiTurboFile; + } else if(inputType == GameInputType::FamilyTrainerSideA) { + log("[Input] Family Trainer mat connected (Side A)"); + expDevice = ControllerType::FamilyTrainerMatSideA; + } else if(inputType == GameInputType::FamilyTrainerSideB) { + log("[Input] Family Trainer mat connected (Side B)"); + expDevice = ControllerType::FamilyTrainerMatSideB; + } else if(inputType == GameInputType::PowerPadSideA) { + log("[Input] Power Pad connected (Side A)"); + port2 = ControllerType::PowerPadSideA; + } else if(inputType == GameInputType::PowerPadSideB) { + log("[Input] Power Pad connected (Side B)"); + port2 = ControllerType::PowerPadSideB; + } else if(inputType == GameInputType::SnesControllers) { + log("[Input] 2 SNES controllers connected"); + port1 = ControllerType::SnesController; + port2 = ControllerType::SnesController; + } else { + log("[Input] 2 standard controllers connected"); + } + + isFamicom = (system == GameSystem::Famicom || system == GameSystem::FDS || system == GameSystem::Dendy); + + NesConfig& cfg = GetNesConfig(); + cfg.Port1.Type = port1; + cfg.Port2.Type = port2; + cfg.ExpPort.Type = expDevice; + + if(port1 == ControllerType::FourScore) { + cfg.Port1SubPorts[0].Type = ControllerType::NesController; + cfg.Port1SubPorts[1].Type = ControllerType::NesController; + cfg.Port1SubPorts[2].Type = ControllerType::NesController; + cfg.Port1SubPorts[3].Type = ControllerType::NesController; + } else if(expDevice == ControllerType::TwoPlayerAdapter) { + cfg.ExpPortSubPorts[0].Type = ControllerType::NesController; + cfg.ExpPortSubPorts[1].Type = ControllerType::NesController; + } + _emu->GetNotificationManager()->SendNotification(ConsoleNotificationType::RequestConfigChange); +} + void NesConsole::ProcessCheatCode(InternalCheatCode& code, uint32_t addr, uint8_t& value) { if(code.Type == CheatType::NesGameGenie && addr >= 0xC020) { diff --git a/Core/NES/NesConsole.h b/Core/NES/NesConsole.h index c2d186da..f4362310 100644 --- a/Core/NES/NesConsole.h +++ b/Core/NES/NesConsole.h @@ -22,6 +22,8 @@ struct HdPackData; enum class DebugEventType; enum class EventType; enum class ConsoleRegion; +enum class GameInputType; +enum class GameSystem; class NesConsole final : public IConsole { @@ -48,6 +50,8 @@ private: void UpdateRegion(bool forceUpdate = false); void LoadHdPack(VirtualFile& romFile); + + void InitializeInputDevices(GameInputType inputType, GameSystem system); public: NesConsole(Emulator* emulator); diff --git a/Core/NES/NesControlManager.h b/Core/NES/NesControlManager.h index b6e85a1e..c780e081 100644 --- a/Core/NES/NesControlManager.h +++ b/Core/NES/NesControlManager.h @@ -14,7 +14,6 @@ class IInputProvider; class Emulator; class NesConsole; enum class ControllerType; -enum class ExpansionPortDevice; class NesControlManager : public ISerializable, public INesMemoryHandler, public BaseControlManager { diff --git a/Core/SNES/BaseCartridge.cpp b/Core/SNES/BaseCartridge.cpp index f1269048..d65638fd 100644 --- a/Core/SNES/BaseCartridge.cpp +++ b/Core/SNES/BaseCartridge.cpp @@ -584,14 +584,12 @@ void BaseCartridge::ApplyConfigOverrides() string name = GetCartName(); if(name == "POWERDRIVE" || name == "DEATH BRADE" || name == "RPG SAILORMOON") { //These games work better when ram is initialized to $FF - SnesConfig cfg = _emu->GetSettings()->GetSnesConfig(); + SnesConfig& cfg = _emu->GetSettings()->GetSnesConfig(); cfg.RamPowerOnState = RamState::AllOnes; - _emu->GetSettings()->SetSnesConfig(cfg); } else if(name == "SUPER KEIBA 2") { //Super Keiba 2 behaves incorrectly if save ram is filled with 0s - SnesConfig cfg = _emu->GetSettings()->GetSnesConfig(); + SnesConfig& cfg = _emu->GetSettings()->GetSnesConfig(); cfg.RamPowerOnState = RamState::Random; - _emu->GetSettings()->SetSnesConfig(cfg); } } diff --git a/Core/SNES/SnesControlManager.h b/Core/SNES/SnesControlManager.h index 0cac8470..15cadeb6 100644 --- a/Core/SNES/SnesControlManager.h +++ b/Core/SNES/SnesControlManager.h @@ -14,7 +14,6 @@ class Emulator; class SystemActionManager; struct ControllerData; enum class ControllerType; -enum class ExpansionPortDevice; class SnesControlManager : public ISerializable, public BaseControlManager { diff --git a/Core/Shared/Interfaces/INotificationListener.h b/Core/Shared/Interfaces/INotificationListener.h index 8279d87f..bb9080f4 100644 --- a/Core/Shared/Interfaces/INotificationListener.h +++ b/Core/Shared/Interfaces/INotificationListener.h @@ -25,7 +25,8 @@ enum class ConsoleNotificationType BeforeGameUnload, BeforeGameLoad, GameLoadFailed, - CheatsChanged + CheatsChanged, + RequestConfigChange }; class INotificationListener diff --git a/Core/Shared/RecordedRomTest.cpp b/Core/Shared/RecordedRomTest.cpp index 27b48fd9..145d1ccc 100644 --- a/Core/Shared/RecordedRomTest.cpp +++ b/Core/Shared/RecordedRomTest.cpp @@ -119,7 +119,8 @@ void RecordedRomTest::Record(string filename, bool reset) _emu->Lock(); Reset(); - SnesConfig snesCfg = _emu->GetSettings()->GetSnesConfig(); + //TODO - remove snes-specific code + SnesConfig& snesCfg = _emu->GetSettings()->GetSnesConfig(); snesCfg.DisableFrameSkipping = true; snesCfg.RamPowerOnState = RamState::AllZeros; _emu->GetSettings()->SetSnesConfig(snesCfg); @@ -178,7 +179,8 @@ int32_t RecordedRomTest::Run(string filename) _currentCount = _repetitionCount.front(); _repetitionCount.pop_front(); - SnesConfig snesCfg = _emu->GetSettings()->GetSnesConfig(); + //TODO - remove snes-specific code + SnesConfig& snesCfg = _emu->GetSettings()->GetSnesConfig(); snesCfg.DisableFrameSkipping = true; snesCfg.RamPowerOnState = RamState::AllZeros; _emu->GetSettings()->SetSnesConfig(snesCfg); diff --git a/Core/Shared/SettingTypes.h b/Core/Shared/SettingTypes.h index 44e71e59..d1d749c0 100644 --- a/Core/Shared/SettingTypes.h +++ b/Core/Shared/SettingTypes.h @@ -481,6 +481,7 @@ struct NesConfig ControllerConfig ExpPortSubPorts[4]; uint32_t LightDetectionRadius = 0; + bool AutoConfigureInput = true; ConsoleRegion Region = ConsoleRegion::Auto; bool EnableHdPacks = true; diff --git a/InteropDLL/ConfigApiWrapper.cpp b/InteropDLL/ConfigApiWrapper.cpp index cee9a0be..f7c6e9c8 100644 --- a/InteropDLL/ConfigApiWrapper.cpp +++ b/InteropDLL/ConfigApiWrapper.cpp @@ -72,8 +72,14 @@ extern "C" { _emu->GetSettings()->SetShortcutKeys(shortcutList); } + DllExport NesConfig __stdcall GetNesConfig() + { + return _emu->GetSettings()->GetNesConfig(); + } + DllExport ControllerType __stdcall GetControllerType(int player) { + //TODO - used by netplay? BaseControlManager* controlManager = _emu->GetControlManager(); if(controlManager) { shared_ptr device = controlManager->GetControlDevice(player); diff --git a/NewUI/Config/NesConfig.cs b/NewUI/Config/NesConfig.cs index 43a4d80a..3f3ff078 100644 --- a/NewUI/Config/NesConfig.cs +++ b/NewUI/Config/NesConfig.cs @@ -28,6 +28,7 @@ namespace Mesen.Config [Reactive] public NesControllerConfig ExpPortD { get; set; } = new(); [Reactive] public UInt32 LightDetectionRadius { get; set; } = 0; + [Reactive] public bool AutoConfigureInput { get; set; } = true; //General [Reactive] public ConsoleRegion Region { get; set; } = ConsoleRegion.Auto; @@ -140,6 +141,7 @@ namespace Mesen.Config ExpPortD = ExpPortD.ToInterop(), LightDetectionRadius = LightDetectionRadius, + AutoConfigureInput = AutoConfigureInput, Region = Region, EnableHdPacks = EnableHdPacks, @@ -267,6 +269,22 @@ namespace Mesen.Config } } } + + public void UpdateInputFromCoreConfig() + { + //Used to update input devices when the core requests changes + InteropNesConfig cfg = ConfigApi.GetNesConfig(); + Port1.Type = cfg.Port1.Type; + Port1A.Type = cfg.Port1A.Type; + Port1B.Type = cfg.Port1B.Type; + Port1C.Type = cfg.Port1C.Type; + Port1D.Type = cfg.Port1D.Type; + Port2.Type = cfg.Port2.Type; + ExpPort.Type = cfg.ExpPort.Type; + ExpPortA.Type = cfg.ExpPortA.Type; + ExpPortB.Type = cfg.ExpPortB.Type; + ApplyConfig(); + } } [StructLayout(LayoutKind.Sequential)] @@ -287,6 +305,7 @@ namespace Mesen.Config public InteropControllerConfig ExpPortD; public UInt32 LightDetectionRadius; + [MarshalAs(UnmanagedType.I1)] public bool AutoConfigureInput; public ConsoleRegion Region; [MarshalAs(UnmanagedType.I1)] public bool EnableHdPacks; diff --git a/NewUI/Debugger/Utilities/ContextMenuAction.cs b/NewUI/Debugger/Utilities/ContextMenuAction.cs index 571af564..5446f6c9 100644 --- a/NewUI/Debugger/Utilities/ContextMenuAction.cs +++ b/NewUI/Debugger/Utilities/ContextMenuAction.cs @@ -136,7 +136,7 @@ namespace Mesen.Debugger.Utilities try { value(); } catch(Exception ex) { - MesenMsgBox.ShowException(ex); + Dispatcher.UIThread.Post(() => MesenMsgBox.ShowException(ex)); } } } diff --git a/NewUI/Interop/ConfigApi.cs b/NewUI/Interop/ConfigApi.cs index f394479e..b2cb4a16 100644 --- a/NewUI/Interop/ConfigApi.cs +++ b/NewUI/Interop/ConfigApi.cs @@ -35,6 +35,7 @@ namespace Mesen.Interop [DllImport(DllPath)] public static extern void SetDebuggerFlag(DebuggerFlags flag, bool enabled); [DllImport(DllPath)] public static extern ControllerType GetControllerType(int player); + [DllImport(DllPath)] public static extern InteropNesConfig GetNesConfig(); [DllImport(DllPath, EntryPoint = "GetAudioDevices")] private static extern void GetAudioDevicesWrapper(IntPtr outDeviceList, Int32 maxSize); public unsafe static List GetAudioDevices() diff --git a/NewUI/Interop/NotificationListener.cs b/NewUI/Interop/NotificationListener.cs index fc89c4d1..87c919e1 100644 --- a/NewUI/Interop/NotificationListener.cs +++ b/NewUI/Interop/NotificationListener.cs @@ -75,6 +75,7 @@ namespace Mesen.Interop BeforeGameUnload, BeforeGameLoad, GameLoadFailed, - CheatsChanged + CheatsChanged, + RequestConfigChange } } diff --git a/NewUI/Localization/resources.en.xml b/NewUI/Localization/resources.en.xml index a28e0c81..695ae7a9 100644 --- a/NewUI/Localization/resources.en.xml +++ b/NewUI/Localization/resources.en.xml @@ -59,6 +59,7 @@
Controllers General + Automatically configure controllers when loading a game Light detection radius for light guns: Small Large diff --git a/NewUI/Utilities/LoadRomHelper.cs b/NewUI/Utilities/LoadRomHelper.cs index 656171f2..44023add 100644 --- a/NewUI/Utilities/LoadRomHelper.cs +++ b/NewUI/Utilities/LoadRomHelper.cs @@ -40,6 +40,10 @@ namespace Mesen.Utilities private static void InternalLoadRom(ResourcePath romPath, ResourcePath? patchPath) { + //Update core config before loading a new game to ensure any changes done + //by the core (e.g ram init overrides for specific games) are reset + ConfigManager.Config.ApplyConfig(); + //Run in another thread to prevent deadlocks etc. when emulator notifications are processed UI-side Task.Run(() => { if(EmuApi.LoadRom(romPath, patchPath)) { diff --git a/NewUI/ViewModels/NesConfigViewModel.cs b/NewUI/ViewModels/NesConfigViewModel.cs index 7bcb2851..1fd3223b 100644 --- a/NewUI/ViewModels/NesConfigViewModel.cs +++ b/NewUI/ViewModels/NesConfigViewModel.cs @@ -1,6 +1,8 @@ using Avalonia.Controls; +using Avalonia.Threading; using Mesen.Config; using Mesen.Controls; +using Mesen.Interop; using Mesen.Utilities; using ReactiveUI; using ReactiveUI.Fody.Helpers; @@ -14,6 +16,8 @@ namespace Mesen.ViewModels { public class NesConfigViewModel : DisposableViewModel { + private NotificationListener? _listener = null; + [Reactive] public NesConfig Config { get; set; } [Reactive] public bool ShowExpansionVolume { get; set; } @@ -50,6 +54,9 @@ namespace Mesen.ViewModels return; } + _listener = AddDisposable(new NotificationListener()); + _listener.OnNotification += listener_OnNotification; + AddDisposable(Input); AddDisposable(ReactiveHelper.RegisterRecursiveObserver(Config, (s, e) => { Config.ApplyConfig(); })); AddDisposable(this.WhenAnyValue(x => x.Config.StereoFilter).Select(x => x == StereoFilter.Delay).ToPropertyEx(this, x => x.IsDelayStereoEffect)); @@ -57,6 +64,16 @@ namespace Mesen.ViewModels AddDisposable(this.WhenAnyValue(x => x.Config.StereoFilter).Select(x => x == StereoFilter.CombFilter).ToPropertyEx(this, x => x.IsCombStereoEffect)); } + private void listener_OnNotification(NotificationEventArgs e) + { + if(e.NotificationType == ConsoleNotificationType.RequestConfigChange) { + //Update configuration when game is loaded + Dispatcher.UIThread.Post(() => { + Config.UpdateInputFromCoreConfig(); + }); + } + } + public void LoadPaletteFile(string filename) { using(FileStream paletteFile = File.OpenRead(filename)) { diff --git a/NewUI/Views/NesInputConfigView.axaml b/NewUI/Views/NesInputConfigView.axaml index b576f7e6..74ceace8 100644 --- a/NewUI/Views/NesInputConfigView.axaml +++ b/NewUI/Views/NesInputConfigView.axaml @@ -148,7 +148,9 @@ - + + + { _model.RomInfo = new RomInfo(); @@ -251,6 +255,12 @@ namespace Mesen.Windows } } + private static void UpdateInputConfiguration() + { + //Used to update input devices when the core requests changes (NES-only for now) + ConfigManager.Config.Nes.UpdateInputFromCoreConfig(); + } + private void InitializeComponent() { AvaloniaXamlLoader.Load(this);