NES: Implement auto-configure inputs option

This commit is contained in:
Sour 2022-07-21 19:47:50 -04:00
parent 77692f323d
commit a15c9421a8
19 changed files with 201 additions and 24 deletions

View file

@ -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<BaseMapper> 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<BaseMapper> mapper(GetMapperFromID(romData));
if(mapper) {
result = LoadRomResult::Success;

View file

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

View file

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

View file

@ -14,7 +14,6 @@ class IInputProvider;
class Emulator;
class NesConsole;
enum class ControllerType;
enum class ExpansionPortDevice;
class NesControlManager : public ISerializable, public INesMemoryHandler, public BaseControlManager
{

View file

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

View file

@ -14,7 +14,6 @@ class Emulator;
class SystemActionManager;
struct ControllerData;
enum class ControllerType;
enum class ExpansionPortDevice;
class SnesControlManager : public ISerializable, public BaseControlManager
{

View file

@ -25,7 +25,8 @@ enum class ConsoleNotificationType
BeforeGameUnload,
BeforeGameLoad,
GameLoadFailed,
CheatsChanged
CheatsChanged,
RequestConfigChange
};
class INotificationListener

View file

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

View file

@ -481,6 +481,7 @@ struct NesConfig
ControllerConfig ExpPortSubPorts[4];
uint32_t LightDetectionRadius = 0;
bool AutoConfigureInput = true;
ConsoleRegion Region = ConsoleRegion::Auto;
bool EnableHdPacks = true;

View file

@ -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<BaseControlDevice> device = controlManager->GetControlDevice(player);

View file

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

View file

@ -136,7 +136,7 @@ namespace Mesen.Debugger.Utilities
try {
value();
} catch(Exception ex) {
MesenMsgBox.ShowException(ex);
Dispatcher.UIThread.Post(() => MesenMsgBox.ShowException(ex));
}
}
}

View file

@ -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<string> GetAudioDevices()

View file

@ -75,6 +75,7 @@ namespace Mesen.Interop
BeforeGameUnload,
BeforeGameLoad,
GameLoadFailed,
CheatsChanged
CheatsChanged,
RequestConfigChange
}
}

View file

@ -59,6 +59,7 @@
<Form ID="NesInputConfigView">
<Control ID="grpControllers">Controllers</Control>
<Control ID="grpGeneral">General</Control>
<Control ID="chkAutoConfigureInput">Automatically configure controllers when loading a game</Control>
<Control ID="lblLightDetectionRadius">Light detection radius for light guns:</Control>
<Control ID="lblSmall">Small</Control>
<Control ID="lblLarge">Large</Control>

View file

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

View file

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

View file

@ -148,7 +148,9 @@
</Grid>
</c:OptionSection>
<c:OptionSection Header="{l:Translate grpGeneral}">
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto,Auto,Auto,Auto">
<CheckBox IsChecked="{CompiledBinding Config.AutoConfigureInput}" Content="{l:Translate chkAutoConfigureInput}" />
<Grid ColumnDefinitions="Auto,*" RowDefinitions="Auto,Auto">
<TextBlock Grid.Row="0" Text="{l:Translate lblLightDetectionRadius}" VerticalAlignment="Top" Margin="0 15 5 0" />
<c:MesenSlider
Margin="12 12 10 0"

View file

@ -216,6 +216,10 @@ namespace Mesen.Windows
});
break;
case ConsoleNotificationType.RequestConfigChange:
UpdateInputConfiguration();
break;
case ConsoleNotificationType.EmulationStopped:
Dispatcher.UIThread.Post(() => {
_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);