From 3c0edfeb38f0bdae012066a96e11af915b90ed13 Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Mon, 13 Apr 2026 15:00:38 +1200 Subject: [PATCH 01/13] FDS $4023 should not affect read-only registers Passes latest FDS-Mirroring-Test Disk registers behave closer to hardware in FDS-4023-Test --- Core/NES/Mappers/FDS/Fds.cpp | 47 ++++++++++++++++++++++--------- Core/NES/Mappers/FDS/Fds.h | 3 +- Core/NES/Mappers/FDS/FdsAudio.cpp | 1 + 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index a8da6fecd..c294ce1c4 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -254,6 +254,13 @@ void Fds::ProcessAutoDiskInsert() } } +/**TODO: + - Proper byte transfer flag handling (set every 1792 master cycles, or 149+1/3 CPU cycles, under normal conditions) + - Should allow for accurate handling of level 2->3 load bug in Ai Senshi Nicol + - Verify End of Head handling (may affect Kosodate Gokko and FMC Disk Card Checker) + - DRAM refresh watchdog implementation (must track PRG-RAM vs external access cycles...) + (Ongoing research, please consult TakuikaNinja for further details) +**/ void Fds::ProcessCpuClock() { BaseProcessCpuClock(); @@ -389,6 +396,18 @@ void Fds::UpdateCrc(uint8_t value) } } +void Fds::SetFdsControlReg(uint8_t value) +{ + _motorOn = (value & 0x01) == 0x01; + _resetTransfer = (value & 0x02) == 0x02; + _readMode = (value & 0x04) == 0x04; + SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical); + _crcControl = (value & 0x10) == 0x10; + //TODO $4025 bit 5 is unknown, all known software sets it to 1 + _diskReady = (value & 0x40) == 0x40; + _diskIrqEnabled = (value & 0x80) == 0x80; +} + void Fds::WriteRegister(uint16_t addr, uint8_t value) { if((!_diskRegEnabled && addr >= 0x4024 && addr <= 0x4026) || (!_soundRegEnabled && addr >= 0x4040)) { @@ -417,10 +436,15 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) case 0x4023: _diskRegEnabled = (value & 0x01) == 0x01; + //TODO Disabling sound registers should pause audio output? _soundRegEnabled = (value & 0x02) == 0x02; if(!_diskRegEnabled) { _irqEnabled = false; + //Disabling disk registers forces $4025 = $06 (bit 3 reflected in $4030 reads) + SetFdsControlReg(0x06); + //TODO Does disabling disk registers force the external connector = 0xFF ? + //_extConWriteReg = 0xFF; _cpu->ClearIrqSource(IRQSource::External); _cpu->ClearIrqSource(IRQSource::FdsDisk); } @@ -435,14 +459,7 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) break; case 0x4025: - _motorOn = (value & 0x01) == 0x01; - _resetTransfer = (value & 0x02) == 0x02; - _readMode = (value & 0x04) == 0x04; - SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical); - _crcControl = (value & 0x10) == 0x10; - //Bit 6 is not used, always 1 - _diskReady = (value & 0x40) == 0x40; - _diskIrqEnabled = (value & 0x80) == 0x80; + SetFdsControlReg(value); //Writing to $4025 clears IRQ according to FCEUX, puNES & Nintendulator //Fixes issues in some unlicensed games (error $20 at power on) @@ -464,20 +481,24 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) uint8_t Fds::ReadRegister(uint16_t addr) { uint8_t value = _memoryManager->GetOpenBus(); - if(_soundRegEnabled && addr >= 0x4040) { + if(addr >= 0x4040) { return _audio->ReadRegister(addr); - } else if(_diskRegEnabled && addr <= 0x4033) { + } else if(addr <= 0x4033) { switch(addr) { case 0x4030: - //These 3 pins are open bus + //These 2 pins are open bus value &= 0x24; - + /**TODO: + - DRAM refresh watchdog IRQ status is returned in bit 1, needs implementation + - End of Head is returned in bit 6, needs verification + - Byte transfer flag is returned in bit 7, not bit 1 (required by Tonkachi Editor) + **/ value |= _cpu->HasIrqSource(IRQSource::External) ? 0x01 : 0x00; value |= _transferComplete ? 0x02 : 0x00; value |= GetMirroringType() == MirroringType::Horizontal ? 0x08 : 0; value |= _useQdFormat && _badCrc ? 0x10 : 0x00; //value |= _endOfHead ? 0x40 : 0x00; - //value |= _diskRegEnabled ? 0x80 : 0x00; + //value |= _transferComplete ? 0x80 : 0x00; _transferComplete = false; _cpu->ClearIrqSource(IRQSource::External); diff --git a/Core/NES/Mappers/FDS/Fds.h b/Core/NES/Mappers/FDS/Fds.h index 584156010..e88e4c04b 100644 --- a/Core/NES/Mappers/FDS/Fds.h +++ b/Core/NES/Mappers/FDS/Fds.h @@ -106,6 +106,7 @@ class Fds : public BaseMapper void ProcessCpuClock() override; void UpdateCrc(uint8_t value); + void SetFdsControlReg(uint8_t value); void WriteRegister(uint16_t addr, uint8_t value) override; uint8_t ReadRegister(uint16_t addr) override; @@ -127,4 +128,4 @@ class Fds : public BaseMapper bool IsDiskInserted(); bool IsAutoInsertDiskEnabled(); -}; \ No newline at end of file +}; diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 9aa3f41f9..74ea73b87 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -70,6 +70,7 @@ FdsAudio::FdsAudio(NesConsole* console) : BaseExpansionAudio(console) { } +//TODO Update to switch statement and map read-only FDS audio registers at $4090-$4097 uint8_t FdsAudio::ReadRegister(uint16_t addr) { uint8_t value = _console->GetMemoryManager()->GetOpenBus(); From 46da458016dbfac7ececa6101748ecefed92cf2e Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Tue, 14 Apr 2026 14:56:11 +1200 Subject: [PATCH 02/13] FDS external connector improvements $4023.D0 = 0 forces output to $7F Always return good battery status in $4033.D7 --- Core/NES/Mappers/FDS/Fds.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index c294ce1c4..ae3e4889e 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -443,8 +443,8 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) _irqEnabled = false; //Disabling disk registers forces $4025 = $06 (bit 3 reflected in $4030 reads) SetFdsControlReg(0x06); - //TODO Does disabling disk registers force the external connector = 0xFF ? - //_extConWriteReg = 0xFF; + //Disabling disk registers forces $4026 (external connector) = 0x7F + _extConWriteReg = 0x7F; _cpu->ClearIrqSource(IRQSource::External); _cpu->ClearIrqSource(IRQSource::FdsDisk); } @@ -542,7 +542,7 @@ uint8_t Fds::ReadRegister(uint16_t addr) case 0x4033: //Always return good battery - return _extConWriteReg; + return _extConWriteReg | 0x80; } } From b626a32acd0af1ee126c525f4da0f0bdb6e66e02 Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Sun, 10 May 2026 15:27:44 +1200 Subject: [PATCH 03/13] FDS: Move byte transfer flag to $4030 bit 7 Eventually needed by Tonkachi Editor, though it still fails to access disks for editing --- Core/NES/Mappers/FDS/Fds.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index ae3e4889e..19d0f74ce 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -491,14 +491,13 @@ uint8_t Fds::ReadRegister(uint16_t addr) /**TODO: - DRAM refresh watchdog IRQ status is returned in bit 1, needs implementation - End of Head is returned in bit 6, needs verification - - Byte transfer flag is returned in bit 7, not bit 1 (required by Tonkachi Editor) **/ value |= _cpu->HasIrqSource(IRQSource::External) ? 0x01 : 0x00; - value |= _transferComplete ? 0x02 : 0x00; + //value |= _transferComplete ? 0x02 : 0x00; value |= GetMirroringType() == MirroringType::Horizontal ? 0x08 : 0; value |= _useQdFormat && _badCrc ? 0x10 : 0x00; //value |= _endOfHead ? 0x40 : 0x00; - //value |= _transferComplete ? 0x80 : 0x00; + value |= _transferComplete ? 0x80 : 0x00; _transferComplete = false; _cpu->ClearIrqSource(IRQSource::External); From e3aa90a02c1dcf7c929bc0549554030ae94b7e93 Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Thu, 21 May 2026 12:58:53 +1200 Subject: [PATCH 04/13] FDS: $4030 read does not clear disk IRQ Clarify external connector implementation Clarify comment for $4024 IRQ handling Move $4025 IRQ handling into SetFdsControlReg() --- Core/NES/Mappers/FDS/Fds.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index 19d0f74ce..0aa7ab98b 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -406,6 +406,11 @@ void Fds::SetFdsControlReg(uint8_t value) //TODO $4025 bit 5 is unknown, all known software sets it to 1 _diskReady = (value & 0x40) == 0x40; _diskIrqEnabled = (value & 0x80) == 0x80; + + //Writing to $4025 clears IRQ according to FCEUX, puNES & Nintendulator + //Fixes issues in some unlicensed games (error $20 at power on) + //TODO This probably depends on the bits set? + _cpu->ClearIrqSource(IRQSource::FdsDisk); } void Fds::WriteRegister(uint16_t addr, uint8_t value) @@ -454,20 +459,17 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) _writeDataReg = value; _transferComplete = false; - //Unsure about clearing irq here: FCEUX/Nintendulator don't do this, puNES does. + //Needed by some Super Magic Card games, which use this IRQ for raster effects _cpu->ClearIrqSource(IRQSource::FdsDisk); break; case 0x4025: SetFdsControlReg(value); - - //Writing to $4025 clears IRQ according to FCEUX, puNES & Nintendulator - //Fixes issues in some unlicensed games (error $20 at power on) - _cpu->ClearIrqSource(IRQSource::FdsDisk); break; case 0x4026: - _extConWriteReg = value; + //External connector only wires 7 bits + _extConWriteReg = value & 0x7F; break; default: @@ -499,9 +501,11 @@ uint8_t Fds::ReadRegister(uint16_t addr) //value |= _endOfHead ? 0x40 : 0x00; value |= _transferComplete ? 0x80 : 0x00; - _transferComplete = false; _cpu->ClearIrqSource(IRQSource::External); - _cpu->ClearIrqSource(IRQSource::FdsDisk); + + //Byte transfer flag is NOT cleared by this register! + //_transferComplete = false; + //_cpu->ClearIrqSource(IRQSource::FdsDisk); return value; case 0x4031: From 903ee2577e9a7f54dbdffdf0cda633567caacccd Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Thu, 21 May 2026 13:06:56 +1200 Subject: [PATCH 05/13] FDS: Improve audio reset handling ($4023.D1 = 0) Allows audio to play properly in FDS-Audio-Registers --- Core/NES/Mappers/FDS/Fds.cpp | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index 0aa7ab98b..5f5047611 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -415,7 +415,16 @@ void Fds::SetFdsControlReg(uint8_t value) void Fds::WriteRegister(uint16_t addr, uint8_t value) { - if((!_diskRegEnabled && addr >= 0x4024 && addr <= 0x4026) || (!_soundRegEnabled && addr >= 0x4040)) { + if(!_diskRegEnabled && addr >= 0x4024 && addr <= 0x4026) { + return; + } + + /**Only $4080 (volume envelope) seems to consistently deny writes during audio reset + TODO: + - $4085 (mod counter) denies writes too, but there is an unknown delay before being forced to 0 + - Determine $4088 (mod table write) behaviour while in audio reset state + **/ + if(!_soundRegEnabled && (addr == 0x4080 || addr == 0x4085 || addr == 0x4088)) { return; } @@ -441,7 +450,7 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) case 0x4023: _diskRegEnabled = (value & 0x01) == 0x01; - //TODO Disabling sound registers should pause audio output? + //TODO This is actually an audio reset, rename this variable in Fds.h _soundRegEnabled = (value & 0x02) == 0x02; if(!_diskRegEnabled) { @@ -453,6 +462,18 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) _cpu->ClearIrqSource(IRQSource::External); _cpu->ClearIrqSource(IRQSource::FdsDisk); } + + /**TODO Determine/implement audio reset behaviour, should probably go in FdsAudio: + - Proper method of resetting modulation state ($4085 write below doesn't always work) + - Reset wave accumulator to 0 + - Mod table appears to init with (or decay to) all 0s? + - There seems to be some kind of analogue "resume" window? + (Ongoing research, please consult TakuikaNinja for further details) + **/ + if(!_soundRegEnabled) { + _audio->WriteRegister(0x4080, 0x80); //Based on instant muting + _audio->WriteRegister(0x4085, 0x00); //Based on $4097 state + } break; case 0x4024: From 5a0a6f67f4a81a81d5bc1350c18ba3b5f958a6fa Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Thu, 21 May 2026 14:00:48 +1200 Subject: [PATCH 06/13] FDS: Implement $4090-$4097 reads --- Core/NES/Mappers/FDS/Fds.h | 2 +- Core/NES/Mappers/FDS/FdsAudio.cpp | 72 +++++++++++++++++++++++++++--- Core/NES/Mappers/FDS/FdsAudio.h | 4 +- Core/NES/Mappers/FDS/ModChannel.h | 11 +++++ Core/NES/Mappers/NSF/NsfMapper.cpp | 6 +-- 5 files changed, 83 insertions(+), 12 deletions(-) diff --git a/Core/NES/Mappers/FDS/Fds.h b/Core/NES/Mappers/FDS/Fds.h index e88e4c04b..aa5fec47e 100644 --- a/Core/NES/Mappers/FDS/Fds.h +++ b/Core/NES/Mappers/FDS/Fds.h @@ -85,7 +85,7 @@ class Fds : public BaseMapper uint32_t GetWorkRamPageSize() override { return 0x8000; } uint32_t GetWorkRamSize() override { return 0x8000; } uint16_t RegisterStartAddress() override { return 0x4020; } - uint16_t RegisterEndAddress() override { return 0x4092; } + uint16_t RegisterEndAddress() override { return 0x4097; } bool AllowRegisterRead() override { return true; } bool EnableCpuClockHook() override { return true; } diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 74ea73b87..8bc50dfa1 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -66,6 +66,11 @@ void FdsAudio::UpdateOutput() } } +uint32_t FdsAudio::GetWaveAccumulator() +{ + return (_wavePosition << 18) | _waveOverflowCounter; +} + FdsAudio::FdsAudio(NesConsole* console) : BaseExpansionAudio(console) { } @@ -82,12 +87,55 @@ uint8_t FdsAudio::ReadRegister(uint16_t addr) //"When writing is disabled ($4089.7), reading anywhere in 4040-407F returns the value at the current wave position" value |= _waveTable[_wavePosition]; } - } else if(addr == 0x4090) { - value &= 0xC0; - value |= _volume.GetGain(); - } else if(addr == 0x4092) { - value &= 0xC0; - value |= _mod.GetGain(); + } else if(addr >= 0x4090) { + switch(addr) { + case 0x4090: + value &= 0xC0; + value |= _volume.GetGain() & 0x3F; + break; + + case 0x4091: + //Wave accumulator (bits 12-19) + //value &= 0xC0; + value = (GetWaveAccumulator() >> 12) & 0xFF; + break; + + case 0x4092: + value &= 0xC0; + value |= _mod.GetGain() & 0x3F; + break; + + case 0x4093: + //Mod accumulator (bits 5-11) + value &= 0x80; + value |= (_mod.GetModAccumulator() >> 5) & 0x7F; + break; + + case 0x4094: + //Mod counter*gain intermediate result + //value &= 0xC0; + value = (_mod.GetOutput() >> 4) & 0xFF; + break; + + case 0x4095: + //Mod counter increment (lower nybble) + //TODO Determine upper nybble + value &= 0xC0; + value |= _mod.GetModIncrement(); + break; + + case 0x4096: + //Wavetable value + //TODO PWM masking + value &= 0xC0; + value |= _waveTable[_wavePosition] & 0x3F; + break; + + case 0x4097: + value &= 0x80; + value |= _mod.GetCounter() & 0x7F; + break; + } } return value; @@ -175,7 +223,7 @@ void FdsAudio::GetMapperStateEntries(vector& entries) entries.push_back(MapperStateEntry("$4084.7", "Envelope Disabled", _mod.IsEnvelopeDisabled(), MapperStateValueType::Bool)); int8_t modCounter = _mod.GetCounter(); - entries.push_back(MapperStateEntry("$4085.0-6", "Counter", std::to_string(modCounter), modCounter < 0 ? (modCounter + 128) : modCounter)); + entries.push_back(MapperStateEntry("$4085.0-6", "Counter", std::to_string(modCounter), (modCounter & 0x7F))); entries.push_back(MapperStateEntry("$4086/7.0-11", "Frequency", _mod.GetFrequency(), MapperStateValueType::Number16)); @@ -191,4 +239,14 @@ void FdsAudio::GetMapperStateEntries(vector& entries) entries.push_back(MapperStateEntry("$4089.7", "Wave Write Enabled", _waveWriteEnabled, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$408A", "Envelope Speed Multiplier", _volume.GetMasterSpeed(), MapperStateValueType::Number8)); + +entries.push_back(MapperStateEntry("$4090-$4097", "Audio Debug")); + entries.push_back(MapperStateEntry("$4090.0-5", "Volume Gain", _volume.GetGain(), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4091", "Wave Accumulator", ((GetWaveAccumulator() >> 12) & 0xFF), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4092.0-5", "Mod Gain", _mod.GetGain(), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4093.0-6", "Mod Accumulator", ((_mod.GetModAccumulator() >> 5) & 0x7F), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4094", "Mod Counter * Gain", ((_mod.GetOutput() >> 4) & 0xFF), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4095.0-3", "Mod Counter Increment", _mod.GetModIncrement(), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4096.0-5", "Wavetable Value", (_waveTable[_wavePosition] & 0x3F), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4097.0-6", "Mod Counter Value", std::to_string(modCounter), (modCounter & 0x7F))); } diff --git a/Core/NES/Mappers/FDS/FdsAudio.h b/Core/NES/Mappers/FDS/FdsAudio.h index ce570aa5b..186c17e80 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.h +++ b/Core/NES/Mappers/FDS/FdsAudio.h @@ -39,6 +39,8 @@ class FdsAudio : public BaseExpansionAudio void ClockAudio() override; void UpdateOutput(); + uint32_t GetWaveAccumulator(); + public: FdsAudio(NesConsole* console); @@ -46,4 +48,4 @@ class FdsAudio : public BaseExpansionAudio void WriteRegister(uint16_t addr, uint8_t value); void GetMapperStateEntries(vector& entries); -}; \ No newline at end of file +}; diff --git a/Core/NES/Mappers/FDS/ModChannel.h b/Core/NES/Mappers/FDS/ModChannel.h index a6bbddf55..fae8b5f37 100644 --- a/Core/NES/Mappers/FDS/ModChannel.h +++ b/Core/NES/Mappers/FDS/ModChannel.h @@ -142,4 +142,15 @@ class ModChannel : public BaseFdsChannel { return _modulationDisabled; } + + uint32_t GetModAccumulator() + { + return (_modTablePosition << 12) | _overflowCounter; + } + + int8_t GetModIncrement() + { + int16_t offset = _modLut[_modTable[_modTablePosition]]; + return offset == ModReset ? 0x0C : offset & 0x0F; + } }; diff --git a/Core/NES/Mappers/NSF/NsfMapper.cpp b/Core/NES/Mappers/NSF/NsfMapper.cpp index 945c2b57d..695566f41 100644 --- a/Core/NES/Mappers/NSF/NsfMapper.cpp +++ b/Core/NES/Mappers/NSF/NsfMapper.cpp @@ -70,7 +70,7 @@ void NsfMapper::InitMapper(RomData& romData) } if(_nsfHeader.SoundChips & NsfSoundChips::FDS) { - AddRegisterRange(0x4040, 0x4092, MemoryOperation::Any); + AddRegisterRange(0x4040, 0x4097, MemoryOperation::Any); } //Reset/IRQ vector @@ -227,7 +227,7 @@ void NsfMapper::ProcessCpuClock() uint8_t NsfMapper::ReadRegister(uint16_t addr) { - if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4092) { + if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4097) { return _fdsAudio->ReadRegister(addr); } else if((_nsfHeader.SoundChips & NsfSoundChips::Namco) && addr >= 0x4800 && addr <= 0x4FFF) { return _namcoAudio->ReadRegister(addr); @@ -249,7 +249,7 @@ uint8_t NsfMapper::ReadRegister(uint16_t addr) void NsfMapper::WriteRegister(uint16_t addr, uint8_t value) { - if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x4092) { + if((_nsfHeader.SoundChips & NsfSoundChips::FDS) && addr >= 0x4040 && addr <= 0x408A) { _fdsAudio->WriteRegister(addr, value); } else if((_nsfHeader.SoundChips & NsfSoundChips::MMC5) && addr >= 0x5000 && addr <= 0x5015) { _mmc5Audio->WriteRegister(addr, value); From 7084e679b804ddfc54bc9d9e68367e6f8b4930e3 Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Thu, 21 May 2026 14:26:16 +1200 Subject: [PATCH 07/13] FDS: Change $4094 to return the plain counter*gain result --- Core/NES/Mappers/FDS/FdsAudio.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 8bc50dfa1..349605d63 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -112,9 +112,9 @@ uint8_t FdsAudio::ReadRegister(uint16_t addr) break; case 0x4094: - //Mod counter*gain intermediate result + //Mod counter*gain intermediate result (bits 4-11) //value &= 0xC0; - value = (_mod.GetOutput() >> 4) & 0xFF; + value = (_mod.GetCounter() * _mod.GetGain() >> 4) & 0xFF; break; case 0x4095: @@ -245,7 +245,7 @@ entries.push_back(MapperStateEntry("$4090-$4097", "Audio Debug")); entries.push_back(MapperStateEntry("$4091", "Wave Accumulator", ((GetWaveAccumulator() >> 12) & 0xFF), MapperStateValueType::Number8)); entries.push_back(MapperStateEntry("$4092.0-5", "Mod Gain", _mod.GetGain(), MapperStateValueType::Number8)); entries.push_back(MapperStateEntry("$4093.0-6", "Mod Accumulator", ((_mod.GetModAccumulator() >> 5) & 0x7F), MapperStateValueType::Number8)); - entries.push_back(MapperStateEntry("$4094", "Mod Counter * Gain", ((_mod.GetOutput() >> 4) & 0xFF), MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4094", "Mod Counter * Gain", (((_mod.GetCounter() * _mod.GetGain()) >> 4) & 0xFF), MapperStateValueType::Number8)); entries.push_back(MapperStateEntry("$4095.0-3", "Mod Counter Increment", _mod.GetModIncrement(), MapperStateValueType::Number8)); entries.push_back(MapperStateEntry("$4096.0-5", "Wavetable Value", (_waveTable[_wavePosition] & 0x3F), MapperStateValueType::Number8)); entries.push_back(MapperStateEntry("$4097.0-6", "Mod Counter Value", std::to_string(modCounter), (modCounter & 0x7F))); From 84c897f627a7a84a88473ab800b911fa875ff3fa Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Thu, 21 May 2026 14:59:49 +1200 Subject: [PATCH 08/13] FDS: Fix off-by-one in envelope timer reset --- Core/NES/Mappers/FDS/BaseFdsChannel.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Core/NES/Mappers/FDS/BaseFdsChannel.h b/Core/NES/Mappers/FDS/BaseFdsChannel.h index aefb2d1ae..56801e81f 100644 --- a/Core/NES/Mappers/FDS/BaseFdsChannel.h +++ b/Core/NES/Mappers/FDS/BaseFdsChannel.h @@ -111,6 +111,6 @@ class BaseFdsChannel : public ISerializable void ResetTimer() { - _timer = 8 * (_speed + 1) * _masterSpeed; + _timer = 8 * (_speed + 1) * (_masterSpeed + 1); } -}; \ No newline at end of file +}; From dab3c10582ee61586e7d3fadbec83b487d1e2856 Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Thu, 21 May 2026 16:22:01 +1200 Subject: [PATCH 09/13] FDS: Add remaining registers to register view --- Core/NES/Mappers/FDS/Fds.cpp | 36 +++++++++++++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index 5f5047611..4c468939b 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -257,6 +257,7 @@ void Fds::ProcessAutoDiskInsert() /**TODO: - Proper byte transfer flag handling (set every 1792 master cycles, or 149+1/3 CPU cycles, under normal conditions) - Should allow for accurate handling of level 2->3 load bug in Ai Senshi Nicol + - Proper CRC handling using $4025 bits 4 and 6 - Verify End of Head handling (may affect Kosodate Gokko and FMC Disk Card Checker) - DRAM refresh watchdog implementation (must track PRG-RAM vs external access cycles...) (Ongoing research, please consult TakuikaNinja for further details) @@ -404,7 +405,8 @@ void Fds::SetFdsControlReg(uint8_t value) SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical); _crcControl = (value & 0x10) == 0x10; //TODO $4025 bit 5 is unknown, all known software sets it to 1 - _diskReady = (value & 0x40) == 0x40; + + _diskReady = (value & 0x40) == 0x40; //TODO $4025 bit 6 is CRC enable, not disk ready flag _diskIrqEnabled = (value & 0x80) == 0x80; //Writing to $4025 clears IRQ according to FCEUX, puNES & Nintendulator @@ -675,20 +677,48 @@ bool Fds::IsAutoInsertDiskEnabled() vector Fds::GetMapperStateEntries() { vector entries; - + + entries.push_back(MapperStateEntry("", "IRQ")); + entries.push_back(MapperStateEntry("$4020-$4022", "Timer IRQ")); entries.push_back(MapperStateEntry("$4020/1", "IRQ Reload Value", _irqReloadValue, MapperStateValueType::Number16)); entries.push_back(MapperStateEntry("$4022.0", "IRQ Repeat", _irqRepeatEnabled, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4022.1", "IRQ Enabled", _irqEnabled, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("", "IRQ Counter", _irqCounter, MapperStateValueType::Number16)); + entries.push_back(MapperStateEntry("$4023", "I/O Control")); entries.push_back(MapperStateEntry("$4023.0", "Disk Registers Enabled", _diskRegEnabled, MapperStateValueType::Bool)); - entries.push_back(MapperStateEntry("$4023.1", "Sound Registers Enabled", _soundRegEnabled, MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("$4023.1", "Audio Enabled", _soundRegEnabled, MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("", "Disk Drive")); + entries.push_back(MapperStateEntry("$4025", "Drive Control")); entries.push_back(MapperStateEntry("$4025.0", "Motor Enabled", _motorOn, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4025.1", "Reset Transfer", _resetTransfer, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4025.2", "Read Mode", _readMode, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4025.3", "Mirroring", GetMirroringType() == MirroringType::Horizontal ? "Horizontal" : "Vertical", GetMirroringType() == MirroringType::Horizontal ? 1 : 0)); + entries.push_back(MapperStateEntry("$4025.4", "Transfer CRC", _crcControl, MapperStateValueType::Bool)); + //entries.push_back(MapperStateEntry("$4025.6", "CRC Enabled", _crcEnabled, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4025.7", "Disk IRQ Enabled", _diskIrqEnabled, MapperStateValueType::Bool)); + + entries.push_back(MapperStateEntry("$4030", "Drive/IRQ Status")); + entries.push_back(MapperStateEntry("$4030.0", "Timer IRQ", _cpu->HasIrqSource(IRQSource::External), MapperStateValueType::Bool)); + //entries.push_back(MapperStateEntry("$4030.1", "DRAM Refresh Watchdog IRQ", false, MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("$4030.3", "Mirroring", GetMirroringType() == MirroringType::Horizontal ? "Horizontal" : "Vertical", GetMirroringType() == MirroringType::Horizontal ? 1 : 0)); + entries.push_back(MapperStateEntry("$4030.5", "CRC Error", (_useQdFormat && _badCrc), MapperStateValueType::Bool)); + //entries.push_back(MapperStateEntry("$4030.6", "End of Head", _endOfHead, MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("$4030.7", "Byte Transferred", _transferComplete, MapperStateValueType::Bool)); + + entries.push_back(MapperStateEntry("$4024/$4031", "Disk Data")); + entries.push_back(MapperStateEntry("$4024", "Disk Data Output", _writeDataReg, MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4031", "Disk Data Input", _readDataReg, MapperStateValueType::Number8)); + + entries.push_back(MapperStateEntry("$4032", "Disk Status")); + entries.push_back(MapperStateEntry("$4032.0", "Disk Inserted", IsDiskInserted(), MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("$4032.1", "Disk Ready", (IsDiskInserted() & _scanningDisk), MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("$4032.2", "Write Enabled", IsDiskInserted(), MapperStateValueType::Bool)); + + entries.push_back(MapperStateEntry("", "External Connector")); + entries.push_back(MapperStateEntry("$4026/$4033.0-6", "External Connector Value", _extConWriteReg, MapperStateValueType::Number8)); + entries.push_back(MapperStateEntry("$4033.7", "Drive Powered", true, MapperStateValueType::Bool)); _audio->GetMapperStateEntries(entries); From c23ff1bef17778f88b1ae838768fc172b148d633 Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Thu, 21 May 2026 16:24:50 +1200 Subject: [PATCH 10/13] FDS: Run clang-format on sources --- Core/NES/Mappers/FDS/Fds.cpp | 16 ++++++++-------- Core/NES/Mappers/FDS/FdsAudio.cpp | 4 ++-- Core/NES/Mappers/FDS/ModChannel.h | 4 ++-- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index 4c468939b..023bd6bf8 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -256,7 +256,7 @@ void Fds::ProcessAutoDiskInsert() /**TODO: - Proper byte transfer flag handling (set every 1792 master cycles, or 149+1/3 CPU cycles, under normal conditions) - - Should allow for accurate handling of level 2->3 load bug in Ai Senshi Nicol + - Should allow for accurate handling of level 2->3 load bug in Ai Senshi Nicol - Proper CRC handling using $4025 bits 4 and 6 - Verify End of Head handling (may affect Kosodate Gokko and FMC Disk Card Checker) - DRAM refresh watchdog implementation (must track PRG-RAM vs external access cycles...) @@ -405,10 +405,10 @@ void Fds::SetFdsControlReg(uint8_t value) SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical); _crcControl = (value & 0x10) == 0x10; //TODO $4025 bit 5 is unknown, all known software sets it to 1 - + _diskReady = (value & 0x40) == 0x40; //TODO $4025 bit 6 is CRC enable, not disk ready flag _diskIrqEnabled = (value & 0x80) == 0x80; - + //Writing to $4025 clears IRQ according to FCEUX, puNES & Nintendulator //Fixes issues in some unlicensed games (error $20 at power on) //TODO This probably depends on the bits set? @@ -420,7 +420,7 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) if(!_diskRegEnabled && addr >= 0x4024 && addr <= 0x4026) { return; } - + /**Only $4080 (volume envelope) seems to consistently deny writes during audio reset TODO: - $4085 (mod counter) denies writes too, but there is an unknown delay before being forced to 0 @@ -464,7 +464,7 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) _cpu->ClearIrqSource(IRQSource::External); _cpu->ClearIrqSource(IRQSource::FdsDisk); } - + /**TODO Determine/implement audio reset behaviour, should probably go in FdsAudio: - Proper method of resetting modulation state ($4085 write below doesn't always work) - Reset wave accumulator to 0 @@ -677,7 +677,7 @@ bool Fds::IsAutoInsertDiskEnabled() vector Fds::GetMapperStateEntries() { vector entries; - + entries.push_back(MapperStateEntry("", "IRQ")); entries.push_back(MapperStateEntry("$4020-$4022", "Timer IRQ")); entries.push_back(MapperStateEntry("$4020/1", "IRQ Reload Value", _irqReloadValue, MapperStateValueType::Number16)); @@ -698,7 +698,7 @@ vector Fds::GetMapperStateEntries() entries.push_back(MapperStateEntry("$4025.4", "Transfer CRC", _crcControl, MapperStateValueType::Bool)); //entries.push_back(MapperStateEntry("$4025.6", "CRC Enabled", _crcEnabled, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4025.7", "Disk IRQ Enabled", _diskIrqEnabled, MapperStateValueType::Bool)); - + entries.push_back(MapperStateEntry("$4030", "Drive/IRQ Status")); entries.push_back(MapperStateEntry("$4030.0", "Timer IRQ", _cpu->HasIrqSource(IRQSource::External), MapperStateValueType::Bool)); //entries.push_back(MapperStateEntry("$4030.1", "DRAM Refresh Watchdog IRQ", false, MapperStateValueType::Bool)); @@ -706,7 +706,7 @@ vector Fds::GetMapperStateEntries() entries.push_back(MapperStateEntry("$4030.5", "CRC Error", (_useQdFormat && _badCrc), MapperStateValueType::Bool)); //entries.push_back(MapperStateEntry("$4030.6", "End of Head", _endOfHead, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4030.7", "Byte Transferred", _transferComplete, MapperStateValueType::Bool)); - + entries.push_back(MapperStateEntry("$4024/$4031", "Disk Data")); entries.push_back(MapperStateEntry("$4024", "Disk Data Output", _writeDataReg, MapperStateValueType::Number8)); entries.push_back(MapperStateEntry("$4031", "Disk Data Input", _readDataReg, MapperStateValueType::Number8)); diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 349605d63..41525cc64 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -239,8 +239,8 @@ void FdsAudio::GetMapperStateEntries(vector& entries) entries.push_back(MapperStateEntry("$4089.7", "Wave Write Enabled", _waveWriteEnabled, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$408A", "Envelope Speed Multiplier", _volume.GetMasterSpeed(), MapperStateValueType::Number8)); - -entries.push_back(MapperStateEntry("$4090-$4097", "Audio Debug")); + + entries.push_back(MapperStateEntry("$4090-$4097", "Audio Debug")); entries.push_back(MapperStateEntry("$4090.0-5", "Volume Gain", _volume.GetGain(), MapperStateValueType::Number8)); entries.push_back(MapperStateEntry("$4091", "Wave Accumulator", ((GetWaveAccumulator() >> 12) & 0xFF), MapperStateValueType::Number8)); entries.push_back(MapperStateEntry("$4092.0-5", "Mod Gain", _mod.GetGain(), MapperStateValueType::Number8)); diff --git a/Core/NES/Mappers/FDS/ModChannel.h b/Core/NES/Mappers/FDS/ModChannel.h index fae8b5f37..cbacea47b 100644 --- a/Core/NES/Mappers/FDS/ModChannel.h +++ b/Core/NES/Mappers/FDS/ModChannel.h @@ -142,12 +142,12 @@ class ModChannel : public BaseFdsChannel { return _modulationDisabled; } - + uint32_t GetModAccumulator() { return (_modTablePosition << 12) | _overflowCounter; } - + int8_t GetModIncrement() { int16_t offset = _modLut[_modTable[_modTablePosition]]; From d67831fcfd62aedd25b37f325224a7cb19a07721 Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Thu, 21 May 2026 16:33:27 +1200 Subject: [PATCH 11/13] FDS: Tweak register viewer headings --- Core/NES/Mappers/FDS/Fds.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index 023bd6bf8..641a5fd07 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -678,14 +678,13 @@ vector Fds::GetMapperStateEntries() { vector entries; - entries.push_back(MapperStateEntry("", "IRQ")); - entries.push_back(MapperStateEntry("$4020-$4022", "Timer IRQ")); + entries.push_back(MapperStateEntry("", "Timer IRQ")); entries.push_back(MapperStateEntry("$4020/1", "IRQ Reload Value", _irqReloadValue, MapperStateValueType::Number16)); entries.push_back(MapperStateEntry("$4022.0", "IRQ Repeat", _irqRepeatEnabled, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4022.1", "IRQ Enabled", _irqEnabled, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("", "IRQ Counter", _irqCounter, MapperStateValueType::Number16)); - entries.push_back(MapperStateEntry("$4023", "I/O Control")); + entries.push_back(MapperStateEntry("", "I/O Control")); entries.push_back(MapperStateEntry("$4023.0", "Disk Registers Enabled", _diskRegEnabled, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4023.1", "Audio Enabled", _soundRegEnabled, MapperStateValueType::Bool)); From 9894d84d495d0c5a0e9c8cd4325cd6ff0da2a4f0 Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Mon, 25 May 2026 13:24:08 +1200 Subject: [PATCH 12/13] FDS: Improve $4025 handling Swap and invert bits 0-1 for now Directly set bools when $4023.D0 = 0 and revert SetFdsControlReg() --- Core/NES/Mappers/FDS/Fds.cpp | 56 +++++++++++++++++++----------------- Core/NES/Mappers/FDS/Fds.h | 3 +- 2 files changed, 31 insertions(+), 28 deletions(-) diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index 641a5fd07..71dc1071d 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -397,24 +397,6 @@ void Fds::UpdateCrc(uint8_t value) } } -void Fds::SetFdsControlReg(uint8_t value) -{ - _motorOn = (value & 0x01) == 0x01; - _resetTransfer = (value & 0x02) == 0x02; - _readMode = (value & 0x04) == 0x04; - SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical); - _crcControl = (value & 0x10) == 0x10; - //TODO $4025 bit 5 is unknown, all known software sets it to 1 - - _diskReady = (value & 0x40) == 0x40; //TODO $4025 bit 6 is CRC enable, not disk ready flag - _diskIrqEnabled = (value & 0x80) == 0x80; - - //Writing to $4025 clears IRQ according to FCEUX, puNES & Nintendulator - //Fixes issues in some unlicensed games (error $20 at power on) - //TODO This probably depends on the bits set? - _cpu->ClearIrqSource(IRQSource::FdsDisk); -} - void Fds::WriteRegister(uint16_t addr, uint8_t value) { if(!_diskRegEnabled && addr >= 0x4024 && addr <= 0x4026) { @@ -457,10 +439,21 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) if(!_diskRegEnabled) { _irqEnabled = false; + //Disabling disk registers forces $4025 = $06 (bit 3 reflected in $4030 reads) - SetFdsControlReg(0x06); + _resetTransfer = true; + _motorOn = false; + _readMode = true; + SetMirroringType(MirroringType::Vertical); + _crcControl = false; + //TODO Set $4025 bit 5 = 0 here once implemented + + _diskReady = false; //TODO $4025 bit 6 is CRC enable, not disk ready flag + _diskIrqEnabled = false; + //Disabling disk registers forces $4026 (external connector) = 0x7F _extConWriteReg = 0x7F; + _cpu->ClearIrqSource(IRQSource::External); _cpu->ClearIrqSource(IRQSource::FdsDisk); } @@ -487,7 +480,20 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) break; case 0x4025: - SetFdsControlReg(value); + _resetTransfer = !(value & 0x01); // 0 = reset transfer + _motorOn = !(value & 0x02); // 0 = start motor + _readMode = value & 0x04; + SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical); + _crcControl = value & 0x10; + //TODO $4025 bit 5 is unknown, all known software sets it to 1 + + _diskReady = value & 0x40; //TODO $4025 bit 6 is CRC enable, not disk ready flag + _diskIrqEnabled = value & 0x80; + + //Writing to $4025 clears IRQ according to FCEUX, puNES & Nintendulator + //Fixes issues in some unlicensed games (error $20 at power on) + //TODO This probably depends on the bits set? + _cpu->ClearIrqSource(IRQSource::FdsDisk); break; case 0x4026: @@ -525,10 +531,6 @@ uint8_t Fds::ReadRegister(uint16_t addr) value |= _transferComplete ? 0x80 : 0x00; _cpu->ClearIrqSource(IRQSource::External); - - //Byte transfer flag is NOT cleared by this register! - //_transferComplete = false; - //_cpu->ClearIrqSource(IRQSource::FdsDisk); return value; case 0x4031: @@ -567,7 +569,7 @@ uint8_t Fds::ReadRegister(uint16_t addr) return value; case 0x4033: - //Always return good battery + //Always return good battery status in bit 7 return _extConWriteReg | 0x80; } } @@ -690,8 +692,8 @@ vector Fds::GetMapperStateEntries() entries.push_back(MapperStateEntry("", "Disk Drive")); entries.push_back(MapperStateEntry("$4025", "Drive Control")); - entries.push_back(MapperStateEntry("$4025.0", "Motor Enabled", _motorOn, MapperStateValueType::Bool)); - entries.push_back(MapperStateEntry("$4025.1", "Reset Transfer", _resetTransfer, MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("$4025.0", "Scan Disk", !_resetTransfer, MapperStateValueType::Bool)); + entries.push_back(MapperStateEntry("$4025.1", "Stop Motor", !_motorOn, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4025.2", "Read Mode", _readMode, MapperStateValueType::Bool)); entries.push_back(MapperStateEntry("$4025.3", "Mirroring", GetMirroringType() == MirroringType::Horizontal ? "Horizontal" : "Vertical", GetMirroringType() == MirroringType::Horizontal ? 1 : 0)); entries.push_back(MapperStateEntry("$4025.4", "Transfer CRC", _crcControl, MapperStateValueType::Bool)); diff --git a/Core/NES/Mappers/FDS/Fds.h b/Core/NES/Mappers/FDS/Fds.h index aa5fec47e..9b0fe82b4 100644 --- a/Core/NES/Mappers/FDS/Fds.h +++ b/Core/NES/Mappers/FDS/Fds.h @@ -32,6 +32,8 @@ class Fds : public BaseMapper uint8_t _writeDataReg = 0; + //TODO Change $4025.D0-D1 namings/meanings so we don't have to invert the read/write logic + // (_resetTransfer (bit 0) -> _scanMedia, _motorOn (bit 1) -> _stopMotor) bool _motorOn = false; bool _resetTransfer = false; bool _readMode = false; @@ -106,7 +108,6 @@ class Fds : public BaseMapper void ProcessCpuClock() override; void UpdateCrc(uint8_t value); - void SetFdsControlReg(uint8_t value); void WriteRegister(uint16_t addr, uint8_t value) override; uint8_t ReadRegister(uint16_t addr) override; From 638540d9f7b66c8d39e887330eb3bb79c37e5973 Mon Sep 17 00:00:00 2001 From: TakuikaNinja Date: Mon, 25 May 2026 17:18:05 +1200 Subject: [PATCH 13/13] FDS: Fix disk auto-insert regression Optimise bitmask bool logic Log warning when auto-insert is disabled --- Core/NES/Mappers/FDS/BaseFdsChannel.h | 4 ++-- Core/NES/Mappers/FDS/Fds.cpp | 23 ++++++++++++++++------- Core/NES/Mappers/FDS/FdsAudio.cpp | 6 +++--- Core/NES/Mappers/FDS/ModChannel.h | 2 +- 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/Core/NES/Mappers/FDS/BaseFdsChannel.h b/Core/NES/Mappers/FDS/BaseFdsChannel.h index 56801e81f..f920f6613 100644 --- a/Core/NES/Mappers/FDS/BaseFdsChannel.h +++ b/Core/NES/Mappers/FDS/BaseFdsChannel.h @@ -39,8 +39,8 @@ class BaseFdsChannel : public ISerializable switch(addr & 0x03) { case 0: _speed = value & 0x3F; - _volumeIncrease = (value & 0x40) == 0x40; - _envelopeOff = (value & 0x80) == 0x80; + _volumeIncrease = value & 0x40; + _envelopeOff = value & 0x80; //"Writing to this register immediately resets the clock timer that ticks the volume envelope (delaying the next tick slightly)." ResetTimer(); diff --git a/Core/NES/Mappers/FDS/Fds.cpp b/Core/NES/Mappers/FDS/Fds.cpp index 71dc1071d..501554e85 100644 --- a/Core/NES/Mappers/FDS/Fds.cpp +++ b/Core/NES/Mappers/FDS/Fds.cpp @@ -152,7 +152,7 @@ void Fds::ClockIrq() uint8_t Fds::ReadRam(uint16_t addr) { - if(addr == 0xE18C && !_gameStarted && (_memoryManager->DebugRead(0x100) & 0xC0) != 0) { + if(addr == 0xE18C && !_gameStarted && (_memoryManager->DebugRead(0x100) & 0xC0)) { //$E18B is the NMI entry point (using $E18C due to dummy reads) //When NMI occurs while $100 & $C0 != 0, it typically means that the game is starting. _gameStarted = true; @@ -190,6 +190,7 @@ uint8_t Fds::ReadRam(uint16_t addr) if(matchCount > 1) { //More than 1 disk matches, can happen in unlicensed games - disable auto insert logic _disableAutoInsertDisk = true; + MessageManager::Log("[FDS] Multiple disks match, auto-insert disabled."); } if(matchIndex >= 0) { @@ -281,6 +282,13 @@ void Fds::ProcessCpuClock() //Disk has been ejected _endOfHead = true; _scanningDisk = false; + + //It appears the BIOS disk I/O routines can stop the motor well before the end of the disk + //(File transfer loop normally runs until accessed files == value in file amount block) + //Wait a bit before ejecting the disk (eject in ~77 frames) + if(_autoDiskEjectCounter < 0) { + _autoDiskEjectCounter = 77; + } return; } @@ -422,8 +430,8 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) break; case 0x4022: - _irqRepeatEnabled = (value & 0x01) == 0x01; - _irqEnabled = (value & 0x02) == 0x02 && _diskRegEnabled; + _irqRepeatEnabled = value & 0x01; + _irqEnabled = (value & 0x02) && _diskRegEnabled; if(_irqEnabled) { _irqCounter = _irqReloadValue; @@ -433,9 +441,9 @@ void Fds::WriteRegister(uint16_t addr, uint8_t value) break; case 0x4023: - _diskRegEnabled = (value & 0x01) == 0x01; + _diskRegEnabled = value & 0x01; //TODO This is actually an audio reset, rename this variable in Fds.h - _soundRegEnabled = (value & 0x02) == 0x02; + _soundRegEnabled = value & 0x02; if(!_diskRegEnabled) { _irqEnabled = false; @@ -525,7 +533,7 @@ uint8_t Fds::ReadRegister(uint16_t addr) **/ value |= _cpu->HasIrqSource(IRQSource::External) ? 0x01 : 0x00; //value |= _transferComplete ? 0x02 : 0x00; - value |= GetMirroringType() == MirroringType::Horizontal ? 0x08 : 0; + value |= GetMirroringType() == MirroringType::Horizontal ? 0x08 : 0x00; value |= _useQdFormat && _badCrc ? 0x10 : 0x00; //value |= _endOfHead ? 0x40 : 0x00; value |= _transferComplete ? 0x80 : 0x00; @@ -543,8 +551,9 @@ uint8_t Fds::ReadRegister(uint16_t addr) value &= 0xF8; value |= !IsDiskInserted() ? 0x01 : 0x00; //Disk not in drive + //TODO This should use _diskReady instead value |= (!IsDiskInserted() || !_scanningDisk) ? 0x02 : 0x00; //Disk not ready - value |= !IsDiskInserted() ? 0x04 : 0x00; //Disk not writable + value |= !IsDiskInserted() ? 0x04 : 0x00; //Simulate inserted disks as always being writable if(IsAutoInsertDiskEnabled()) { if(_emu->GetFrameCount() - _lastDiskCheckFrame < 100) { diff --git a/Core/NES/Mappers/FDS/FdsAudio.cpp b/Core/NES/Mappers/FDS/FdsAudio.cpp index 41525cc64..9066f3e2c 100644 --- a/Core/NES/Mappers/FDS/FdsAudio.cpp +++ b/Core/NES/Mappers/FDS/FdsAudio.cpp @@ -158,8 +158,8 @@ void FdsAudio::WriteRegister(uint16_t addr, uint8_t value) break; case 0x4083: - _disableEnvelopes = (value & 0x40) != 0; - _haltWaveform = (value & 0x80) != 0; + _disableEnvelopes = value & 0x40; + _haltWaveform = value & 0x80; if(_haltWaveform) { _wavePosition = 0; } @@ -192,7 +192,7 @@ void FdsAudio::WriteRegister(uint16_t addr, uint8_t value) case 0x4089: _masterVolume = value & 0x03; - _waveWriteEnabled = (value & 0x80) == 0x80; + _waveWriteEnabled = value & 0x80; break; case 0x408A: diff --git a/Core/NES/Mappers/FDS/ModChannel.h b/Core/NES/Mappers/FDS/ModChannel.h index cbacea47b..003d1d570 100644 --- a/Core/NES/Mappers/FDS/ModChannel.h +++ b/Core/NES/Mappers/FDS/ModChannel.h @@ -42,7 +42,7 @@ class ModChannel : public BaseFdsChannel break; case 0x4087: BaseFdsChannel::WriteReg(addr, value); - _modulationDisabled = (value & 0x80) == 0x80; + _modulationDisabled = value & 0x80; if(_modulationDisabled) { _overflowCounter = 0; }