From 41ae3cdcd2aed9c85d82cab122c6182f37090956 Mon Sep 17 00:00:00 2001 From: Souryo Date: Tue, 21 Jul 2015 23:05:27 -0400 Subject: [PATCH] PAL support (PPU & APU) --- Core/APU.cpp | 24 +++++++++++++++-- Core/APU.h | 5 ++++ Core/ApuFrameCounter.h | 26 +++++++++++++++--- Core/BaseApuChannel.h | 12 +++++++++ Core/BaseMapper.h | 7 +++++ Core/CPU.h | 3 ++- Core/Console.cpp | 19 +++++++++++-- Core/Console.h | 1 + Core/DeltaModulationChannel.h | 5 ++-- Core/EmulationSettings.cpp | 3 ++- Core/EmulationSettings.h | 18 +++++++++++++ Core/NoiseChannel.h | 5 ++-- Core/PPU.cpp | 25 +++++++++++++++--- Core/PPU.h | 17 ++++++++---- Core/ROMLoader.h | 10 +++++++ GUI.NET/Config/Configuration.cs | 10 +++++++ GUI.NET/Forms/frmMain.Designer.cs | 44 +++++++++++++++++++++++++++++-- GUI.NET/Forms/frmMain.cs | 19 +++++++++++-- GUI.NET/InteropEmu.cs | 8 ++++++ InteropDLL/ConsoleWrapper.cpp | 1 + 20 files changed, 236 insertions(+), 26 deletions(-) diff --git a/Core/APU.cpp b/Core/APU.cpp index 1d854543..11a15af0 100644 --- a/Core/APU.cpp +++ b/Core/APU.cpp @@ -19,7 +19,6 @@ APU::APU(MemoryManager* memoryManager) _memoryManager = memoryManager; _blipBuffer.reset(new Blip_Buffer()); _blipBuffer->sample_rate(APU::SampleRate); - _blipBuffer->clock_rate(CPU::ClockRate); _outputBuffer = new int16_t[APU::SamplesPerFrame]; @@ -45,6 +44,23 @@ APU::~APU() delete[] _outputBuffer; } +void APU::SetNesModel(NesModel model, bool forceInit) +{ + if(_nesModel != model || forceInit) { + //Finish the current apu frame before switching model + Run(); + + _nesModel = model; + _blipBuffer->clock_rate(model == NesModel::NTSC ? CPU::ClockRateNtsc : CPU::ClockRatePal); + _squareChannel[0]->SetNesModel(model); + _squareChannel[1]->SetNesModel(model); + _triangleChannel->SetNesModel(model); + _noiseChannel->SetNesModel(model); + _deltaModulationChannel->SetNesModel(model); + _frameCounter->SetNesModel(model); + } +} + void APU::FrameCounterTick(FrameType type) { //Quarter & half frame clock envelope & linear counter @@ -185,7 +201,6 @@ void APU::StopAudio() } } - void APU::Reset(bool softReset) { _currentCycle = 0; @@ -200,6 +215,7 @@ void APU::Reset(bool softReset) void APU::StreamState(bool saving) { + Stream(_nesModel); Stream(_currentCycle); Stream(_previousCycle); Stream(_squareChannel[0].get()); @@ -208,4 +224,8 @@ void APU::StreamState(bool saving) Stream(_noiseChannel.get()); Stream(_deltaModulationChannel.get()); Stream(_frameCounter.get()); + + if(!saving) { + SetNesModel(_nesModel, true); + } } \ No newline at end of file diff --git a/Core/APU.h b/Core/APU.h index 20a13315..779ca81f 100644 --- a/Core/APU.h +++ b/Core/APU.h @@ -13,6 +13,7 @@ class DeltaModulationChannel; class ApuFrameCounter; class Blip_Buffer; enum class FrameType; +enum class NesModel; class APU : public Snapshotable, public IMemoryHandler { @@ -34,6 +35,8 @@ class APU : public Snapshotable, public IMemoryHandler int16_t* _outputBuffer; MemoryManager* _memoryManager; + NesModel _nesModel; + private: bool NeedToRun(uint32_t currentCycle); void Run(); @@ -59,6 +62,8 @@ class APU : public Snapshotable, public IMemoryHandler APU::AudioDevice = audioDevice; } + void SetNesModel(NesModel model, bool forceInit = false); + uint8_t ReadRAM(uint16_t addr); void WriteRAM(uint16_t addr, uint8_t value); void GetMemoryRanges(MemoryRanges &ranges); diff --git a/Core/ApuFrameCounter.h b/Core/ApuFrameCounter.h index 0c04a781..1a861581 100644 --- a/Core/ApuFrameCounter.h +++ b/Core/ApuFrameCounter.h @@ -2,6 +2,7 @@ #include "stdafx.h" #include "IMemoryHandler.h" #include "CPU.h" +#include "EmulationSettings.h" enum class FrameType { @@ -13,11 +14,15 @@ enum class FrameType class ApuFrameCounter : public IMemoryHandler, public Snapshotable { private: - const vector> _stepCycles = { { { 7457, 14913, 22371, 29828, 29829, 29830}, - { 7457, 14913, 22371, 29829, 37281, 37282} } }; + const vector> _stepCyclesNtsc = { { { 7457, 14913, 22371, 29828, 29829, 29830}, + { 7457, 14913, 22371, 29829, 37281, 37282} } }; + const vector> _stepCyclesPal = { { { 8313, 16627, 24939, 33252, 33253, 33254}, + { 8313, 16627, 24939, 33253, 41565, 41566} } }; const vector> _frameType = { { { FrameType::QuarterFrame, FrameType::HalfFrame, FrameType::QuarterFrame, FrameType::None, FrameType::HalfFrame, FrameType::None }, { FrameType::QuarterFrame, FrameType::HalfFrame, FrameType::QuarterFrame, FrameType::None, FrameType::HalfFrame, FrameType::None } } }; + vector> _stepCycles; + NesModel _nesModel; int32_t _nextIrqCycle; int32_t _previousCycle; uint32_t _currentStep; @@ -57,7 +62,22 @@ public: Stream(_currentStep); Stream(_stepMode); Stream(_inhibitIRQ); - } + Stream(_nesModel); + + if(!saving) { + SetNesModel(_nesModel); + } + } + + void SetNesModel(NesModel model) + { + if(_nesModel != model || _stepCycles.size() == 0) { + _nesModel = model; + _stepCycles.clear(); + _stepCycles.push_back(model == NesModel::NTSC ? _stepCyclesNtsc[0] : _stepCyclesPal[0]); + _stepCycles.push_back(model == NesModel::NTSC ? _stepCyclesNtsc[1] : _stepCyclesPal[1]); + } + } uint32_t Run(int32_t &cyclesToRun) { diff --git a/Core/BaseApuChannel.h b/Core/BaseApuChannel.h index 26185c34..d5d44306 100644 --- a/Core/BaseApuChannel.h +++ b/Core/BaseApuChannel.h @@ -16,6 +16,7 @@ private: uint32_t _previousCycle; AudioChannel _channel; double _baseVolume; + NesModel _nesModel; protected: uint16_t _timer = 0; @@ -66,12 +67,23 @@ public: Stream(_previousCycle); Stream(_timer); Stream(_period); + Stream(_nesModel); if(!saving) { _buffer->clear(); } } + void SetNesModel(NesModel model) + { + _nesModel = model; + } + + NesModel GetNesModel() + { + return _nesModel; + } + virtual void Run(uint32_t targetCycle) { while(_previousCycle < targetCycle) { diff --git a/Core/BaseMapper.h b/Core/BaseMapper.h index 0b953e6a..338f41b6 100644 --- a/Core/BaseMapper.h +++ b/Core/BaseMapper.h @@ -27,6 +27,7 @@ class BaseMapper : public IMemoryHandler, public Snapshotable, public INotificat bool _hasCHRRAM; bool _hasBattery; + bool _isPalRom; string _romFilename; MirroringType _mirroringType; @@ -159,6 +160,7 @@ class BaseMapper : public IMemoryHandler, public Snapshotable, public INotificat _prgSize = romLoader.GetPRGSize(); _chrSize = romLoader.GetCHRSize(); _hasBattery = romLoader.HasBattery(); + _isPalRom = romLoader.IsPalRom(); _romFilename = romLoader.GetFilename(); _hasExpansionRAM = false; @@ -252,6 +254,11 @@ class BaseMapper : public IMemoryHandler, public Snapshotable, public INotificat return _hasBattery; } + bool IsPalRom() + { + return _isPalRom; + } + MirroringType GetMirroringType() { return _mirroringType; diff --git a/Core/CPU.h b/Core/CPU.h index ea83c984..8725666f 100644 --- a/Core/CPU.h +++ b/Core/CPU.h @@ -830,7 +830,8 @@ protected: void StreamState(bool saving); public: - static const uint32_t ClockRate = 1789773; + static const uint32_t ClockRateNtsc = 1789773; + static const uint32_t ClockRatePal = 1662607; CPU(MemoryManager *memoryManager); static int32_t GetCycleCount() { return CPU::Instance->_cycleCount; } diff --git a/Core/Console.cpp b/Core/Console.cpp index 6b55762e..2165baaa 100644 --- a/Core/Console.cpp +++ b/Core/Console.cpp @@ -153,13 +153,15 @@ void Console::Resume() void Console::Run() { Timer clockTimer; + double targetTime; double elapsedTime = 0; - double targetTime = 16.63926405550947; //~60.0988fps + uint32_t lastFrameNumber = -1; _runLock.Acquire(); _stopLock.Acquire(); - uint32_t lastFrameNumber = -1; + UpdateNesModel(targetTime, true); + while(true) { _cpu->Exec(); uint32_t currentFrameNumber = PPU::GetFrameCount(); @@ -200,6 +202,8 @@ void Console::Run() _runLock.Acquire(); MessageManager::SendNotification(ConsoleNotificationType::GameResumed); } + + UpdateNesModel(targetTime, false); clockTimer.Reset(); if(_stop) { @@ -213,6 +217,17 @@ void Console::Run() _runLock.Release(); } +void Console::UpdateNesModel(double &frameDelay, bool showMessage) +{ + NesModel model = EmulationSettings::GetNesModel(); + if(model == NesModel::Auto) { + model = _mapper->IsPalRom() ? NesModel::PAL : NesModel::NTSC; + } + frameDelay = (model == NesModel::NTSC ? 16.63926405550947 : 19.99720920217466); //60.1fps (NTSC), 50.01fps (PAL) + _ppu->SetNesModel(model); + _apu->SetNesModel(model); +} + void Console::SaveState(ostream &saveStream) { if(Instance->_initialized) { diff --git a/Core/Console.h b/Core/Console.h index 6672af22..a5da8a07 100644 --- a/Core/Console.h +++ b/Core/Console.h @@ -35,6 +35,7 @@ class Console void ResetComponents(bool softReset); void Initialize(string filename); + void UpdateNesModel(double &frameDelay, bool showMessage); public: Console(); diff --git a/Core/DeltaModulationChannel.h b/Core/DeltaModulationChannel.h index cdf1c683..64f0362a 100644 --- a/Core/DeltaModulationChannel.h +++ b/Core/DeltaModulationChannel.h @@ -8,7 +8,8 @@ class DeltaModulationChannel : public BaseApuChannel<127> { private: - const vector _dmcPeriodLookupTable = { { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 } }; + const vector _dmcPeriodLookupTableNtsc = { { 428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54 } }; + const vector _dmcPeriodLookupTablePal = { { 398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50 } }; MemoryManager *_memoryManager = nullptr; @@ -164,7 +165,7 @@ public: //"The rate determines for how many CPU cycles happen between changes in the output level during automatic delta-encoded sample playback." //Because BaseApuChannel does not decrement when setting _timer, we need to actually set the value to 1 less than the lookup table - _period = _dmcPeriodLookupTable[value & 0x0F] - 1; + _period = (GetNesModel() == NesModel::NTSC ? _dmcPeriodLookupTableNtsc : _dmcPeriodLookupTablePal)[value & 0x0F] - 1; if(!_irqEnabled) { CPU::ClearIRQSource(IRQSource::DMC); diff --git a/Core/EmulationSettings.cpp b/Core/EmulationSettings.cpp index 660dd5a2..75c0511e 100644 --- a/Core/EmulationSettings.cpp +++ b/Core/EmulationSettings.cpp @@ -3,4 +3,5 @@ uint32_t EmulationSettings::Flags = 0; uint32_t EmulationSettings::AudioLatency = 20000; -double EmulationSettings::ChannelVolume[5] = { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; \ No newline at end of file +double EmulationSettings::ChannelVolume[5] = { 0.5f, 0.5f, 0.5f, 0.5f, 0.5f }; +NesModel EmulationSettings::Model; \ No newline at end of file diff --git a/Core/EmulationSettings.h b/Core/EmulationSettings.h index 82d6baf4..58b14523 100644 --- a/Core/EmulationSettings.h +++ b/Core/EmulationSettings.h @@ -18,12 +18,20 @@ enum class AudioChannel DMC = 4 }; +enum class NesModel +{ + Auto = 0, + NTSC = 1, + PAL = 2, +}; + class EmulationSettings { private: static uint32_t Flags; static uint32_t AudioLatency; static double ChannelVolume[5]; + static NesModel Model; public: static void SetFlags(uint32_t flags) @@ -41,6 +49,16 @@ public: return (Flags & flag) == flag; } + static void SetNesModel(NesModel model) + { + Model = model; + } + + static NesModel GetNesModel() + { + return Model; + } + //0: Muted, 0.5: Default, 1.0: Max volume static void SetChannelVolume(AudioChannel channel, double volume) { diff --git a/Core/NoiseChannel.h b/Core/NoiseChannel.h index 63de9c53..e8372dc8 100644 --- a/Core/NoiseChannel.h +++ b/Core/NoiseChannel.h @@ -8,7 +8,8 @@ class NoiseChannel : public ApuEnvelope { private: - const vector _noisePeriodLookupTable = { { 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 } }; + const vector _noisePeriodLookupTableNtsc = { { 4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068 } }; + const vector _noisePeriodLookupTablePal = { { 4, 8, 14, 30, 60, 88, 118, 148, 188, 236, 354, 472, 708, 944, 1890, 3778 } }; //On power-up, the shift register is loaded with the value 1. uint16_t _shiftRegister = 1; @@ -73,7 +74,7 @@ public: break; case 2: //400E - _period = _noisePeriodLookupTable[value & 0x0F]; + _period = (GetNesModel() == NesModel::NTSC ? _noisePeriodLookupTableNtsc : _noisePeriodLookupTablePal)[value & 0x0F]; break; case 3: //400F diff --git a/Core/PPU.cpp b/Core/PPU.cpp index 561d2ea7..47a0662c 100644 --- a/Core/PPU.cpp +++ b/Core/PPU.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "PPU.h" #include "CPU.h" +#include "EmulationSettings.h" PPU* PPU::Instance = nullptr; IVideoDevice *PPU::VideoDevice = nullptr; @@ -50,6 +51,12 @@ void PPU::Reset() memset(_spriteRAM, 0xFF, 0x100); } +void PPU::SetNesModel(NesModel model) +{ + _nesModel = model; + _vblankEnd = (model == NesModel::NTSC ? 260 : 311); +} + PPUDebugState PPU::GetState() { PPUDebugState state; @@ -473,7 +480,8 @@ void PPU::ProcessPrerenderScanline() //copy vertical scrolling value from t _state.VideoRamAddr = (_state.VideoRamAddr & ~0x7BE0) | (_state.TmpVideoRamAddr & 0x7BE0); } - } else if(_cycle == 339 && IsRenderingEnabled() && (_frameCount & 0x01)) { + } else if(_nesModel == NesModel::NTSC && _cycle == 339 && IsRenderingEnabled() && (_frameCount & 0x01)) { + //This behavior is NTSC-specific - PAL frames are always the same number of cycles //"With rendering enabled, each odd PPU frame is one PPU clock shorter than normal" (skip from 339 to 0, going over 340) _cycle = -1; _scanline = 0; @@ -626,15 +634,14 @@ void PPU::Exec() ProcessPrerenderScanline(); } else if(_scanline == 241) { BeginVBlank(); - } else if(_scanline == 260) { + } else if(_scanline == _vblankEnd) { EndVBlank(); } if(_cycle == 340) { _cycle = -1; - _scanline++; - if(_scanline == 261) { + if(_scanline++ == _vblankEnd) { _scanline = -1; } } @@ -646,6 +653,10 @@ void PPU::ExecStatic() PPU::Instance->Exec(); PPU::Instance->Exec(); PPU::Instance->Exec(); + if(PPU::Instance->_nesModel == NesModel::PAL && CPU::GetCycleCount() % 5 == 0) { + //PAL PPU runs 3.2 clocks for every CPU clock, so we need to run an extra clock every 5 CPU clocks + PPU::Instance->Exec(); + } } void PPU::StreamState(bool saving) @@ -717,4 +728,10 @@ void PPU::StreamState(bool saving) Stream(_writeOAMData); Stream(_overflowCounter); Stream(_sprite0Added); + + Stream(_nesModel); + + if(!saving) { + SetNesModel(_nesModel); + } } \ No newline at end of file diff --git a/Core/PPU.h b/Core/PPU.h index 56d0dbe7..01647062 100644 --- a/Core/PPU.h +++ b/Core/PPU.h @@ -5,6 +5,8 @@ #include "MemoryManager.h" #include "IVideoDevice.h" +enum class NesModel; + enum PPURegisters { Control = 0x00, @@ -92,11 +94,11 @@ class PPU : public IMemoryHandler, public Snapshotable MemoryManager *_memoryManager; PPUState _state; - int32_t _scanline = 0; - uint32_t _cycle = 0; - uint32_t _frameCount = 0; - uint8_t _memoryReadBuffer = 0; - + int32_t _scanline; + uint32_t _cycle; + uint32_t _frameCount; + uint8_t _memoryReadBuffer; + uint8_t _paletteRAM[0x100]; uint8_t _spriteRAM[0x100]; @@ -104,6 +106,9 @@ class PPU : public IMemoryHandler, public Snapshotable uint32_t *_outputBuffer; + NesModel _nesModel; + uint16_t _vblankEnd; + PPUControlFlags _flags; PPUStatusFlags _statusFlags; @@ -192,6 +197,8 @@ class PPU : public IMemoryHandler, public Snapshotable uint8_t ReadRAM(uint16_t addr); void WriteRAM(uint16_t addr, uint8_t value); + void SetNesModel(NesModel model); + void Exec(); static void ExecStatic(); diff --git a/Core/ROMLoader.h b/Core/ROMLoader.h index 6a657088..d2f6bd2c 100644 --- a/Core/ROMLoader.h +++ b/Core/ROMLoader.h @@ -40,6 +40,11 @@ struct NESHeader return (Flags1 & 0x04) == 0x04; } + bool IsPalRom() + { + return (CartType & 0x01) == 0x01; + } + MirroringType GetMirroringType() { if(Flags1 & 0x08) { @@ -218,6 +223,11 @@ class ROMLoader return _header.HasBattery(); } + bool IsPalRom() + { + return _header.IsPalRom() || _filename.find("(e)") != string::npos || _filename.find("(E)") != string::npos; + } + string GetFilename() { return _filename; diff --git a/GUI.NET/Config/Configuration.cs b/GUI.NET/Config/Configuration.cs index 9a80ebae..6fdff4a8 100644 --- a/GUI.NET/Config/Configuration.cs +++ b/GUI.NET/Config/Configuration.cs @@ -21,6 +21,7 @@ namespace Mesen.GUI.Config public List Cheats; public List Controllers; public bool ShowOnlyCheatsForCurrentGame; + public NesModel Region; public Configuration() { @@ -33,6 +34,15 @@ namespace Mesen.GUI.Config Controllers = new List(); } + public void ApplyConfig() + { + ControllerInfo.ApplyConfig(); + VideoInfo.ApplyConfig(); + AudioInfo.ApplyConfig(); + + InteropEmu.SetNesModel(Region); + } + private void InitializeDefaults() { while(Controllers.Count < 4) { diff --git a/GUI.NET/Forms/frmMain.Designer.cs b/GUI.NET/Forms/frmMain.Designer.cs index 5f73f224..d6e79ce9 100644 --- a/GUI.NET/Forms/frmMain.Designer.cs +++ b/GUI.NET/Forms/frmMain.Designer.cs @@ -78,6 +78,10 @@ this.toolStripMenuItem5 = new System.Windows.Forms.ToolStripSeparator(); this.mnuAbout = new System.Windows.Forms.ToolStripMenuItem(); this.dxViewer = new Mesen.GUI.Controls.DXViewer(); + this.mnuRegion = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuRegionAuto = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuRegionNtsc = new System.Windows.Forms.ToolStripMenuItem(); + this.mnuRegionPal = new System.Windows.Forms.ToolStripMenuItem(); this.menuStrip.SuspendLayout(); this.SuspendLayout(); // @@ -201,9 +205,10 @@ this.mnuLimitFPS, this.mnuShowFPS, this.toolStripMenuItem1, + this.mnuAudioConfig, this.mnuInput, - this.mnuVideoConfig, - this.mnuAudioConfig}); + this.mnuRegion, + this.mnuVideoConfig}); this.mnuOptions.Name = "mnuOptions"; this.mnuOptions.Size = new System.Drawing.Size(61, 20); this.mnuOptions.Text = "Options"; @@ -439,6 +444,37 @@ this.dxViewer.Size = new System.Drawing.Size(1024, 896); this.dxViewer.TabIndex = 1; // + // mnuRegion + // + this.mnuRegion.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.mnuRegionAuto, + this.mnuRegionNtsc, + this.mnuRegionPal}); + this.mnuRegion.Name = "mnuRegion"; + this.mnuRegion.Size = new System.Drawing.Size(152, 22); + this.mnuRegion.Text = "Region"; + // + // mnuRegionAuto + // + this.mnuRegionAuto.Name = "mnuRegionAuto"; + this.mnuRegionAuto.Size = new System.Drawing.Size(152, 22); + this.mnuRegionAuto.Text = "Auto"; + this.mnuRegionAuto.Click += new System.EventHandler(this.mnuRegion_Click); + // + // mnuRegionNtsc + // + this.mnuRegionNtsc.Name = "mnuRegionNtsc"; + this.mnuRegionNtsc.Size = new System.Drawing.Size(152, 22); + this.mnuRegionNtsc.Text = "NTSC"; + this.mnuRegionNtsc.Click += new System.EventHandler(this.mnuRegion_Click); + // + // mnuRegionPal + // + this.mnuRegionPal.Name = "mnuRegionPal"; + this.mnuRegionPal.Size = new System.Drawing.Size(152, 22); + this.mnuRegionPal.Text = "PAL"; + this.mnuRegionPal.Click += new System.EventHandler(this.mnuRegion_Click); + // // frmMain // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); @@ -504,6 +540,10 @@ private System.Windows.Forms.ToolStripMenuItem mnuLoadState; private System.Windows.Forms.ToolStripSeparator toolStripMenuItem7; private System.Windows.Forms.ToolStripMenuItem mnuCheats; + private System.Windows.Forms.ToolStripMenuItem mnuRegion; + private System.Windows.Forms.ToolStripMenuItem mnuRegionAuto; + private System.Windows.Forms.ToolStripMenuItem mnuRegionNtsc; + private System.Windows.Forms.ToolStripMenuItem mnuRegionPal; } } diff --git a/GUI.NET/Forms/frmMain.cs b/GUI.NET/Forms/frmMain.cs index 9ea48aa2..69ca3ff9 100644 --- a/GUI.NET/Forms/frmMain.cs +++ b/GUI.NET/Forms/frmMain.cs @@ -58,8 +58,7 @@ namespace Mesen.GUI.Forms InteropEmu.AddKnowGameFolder(System.IO.Path.GetDirectoryName(romPath).ToLowerInvariant()); } - ControllerInfo.ApplyConfig(); - AudioInfo.ApplyConfig(); + ConfigManager.Config.ApplyConfig(); UpdateEmulationFlags(); } @@ -135,6 +134,10 @@ namespace Mesen.GUI.Forms mnuDebugger.Enabled = !netPlay && _emuThread != null; mnuTakeScreenshot.Enabled = _emuThread != null; + + mnuRegionAuto.Checked = ConfigManager.Config.Region == NesModel.Auto; + mnuRegionNtsc.Checked = ConfigManager.Config.Region == NesModel.NTSC; + mnuRegionPal.Checked = ConfigManager.Config.Region == NesModel.PAL; } } catch { } } @@ -403,5 +406,17 @@ namespace Mesen.GUI.Forms frmAudioConfig frm = new frmAudioConfig(); frm.ShowDialog(); } + + private void mnuRegion_Click(object sender, EventArgs e) + { + if(sender == mnuRegionAuto) { + ConfigManager.Config.Region = NesModel.Auto; + } else if(sender == mnuRegionNtsc) { + ConfigManager.Config.Region = NesModel.NTSC; + } else if(sender == mnuRegionPal) { + ConfigManager.Config.Region = NesModel.PAL; + } + ConfigManager.Config.ApplyConfig(); + } } } diff --git a/GUI.NET/InteropEmu.cs b/GUI.NET/InteropEmu.cs index 93aabbbc..1608a502 100644 --- a/GUI.NET/InteropEmu.cs +++ b/GUI.NET/InteropEmu.cs @@ -65,6 +65,7 @@ namespace Mesen.GUI [DllImport(DLLPath)] public static extern void ClearFlags(UInt32 flags); [DllImport(DLLPath)] public static extern void SetChannelVolume(UInt32 channel, double volume); [DllImport(DLLPath)] public static extern void SetAudioLatency(UInt32 msLatency); + [DllImport(DLLPath)] public static extern void SetNesModel(NesModel model); [DllImport(DLLPath)] public static extern void DebugInitialize(); [DllImport(DLLPath)] public static extern void DebugRelease(); @@ -272,6 +273,13 @@ namespace Mesen.GUI Write = 2 }; + public enum NesModel + { + Auto = 0, + NTSC = 1, + PAL = 2 + } + public class MD5Helper { public static string GetMD5Hash(string filename) diff --git a/InteropDLL/ConsoleWrapper.cpp b/InteropDLL/ConsoleWrapper.cpp index 0200e803..7c725c92 100644 --- a/InteropDLL/ConsoleWrapper.cpp +++ b/InteropDLL/ConsoleWrapper.cpp @@ -162,6 +162,7 @@ namespace InteropEmu { DllExport void __stdcall ClearFlags(uint32_t flags) { EmulationSettings::ClearFlags(flags); } DllExport void __stdcall SetChannelVolume(uint32_t channel, double volume) { EmulationSettings::SetChannelVolume((AudioChannel)channel, volume); } DllExport void __stdcall SetAudioLatency(uint32_t msLatency) { EmulationSettings::SetAudioLatency(msLatency); } + DllExport void __stdcall SetNesModel(uint32_t model) { EmulationSettings::SetNesModel((NesModel)model); } } } \ No newline at end of file