1 #include "stdafx.h"
2 #include "../Utilities/IpsPatcher.h"
3 #include "Console.h"
4 #include "FDS.h"
5 #include "PPU.h"
6 #include "FdsAudio.h"
7 #include "MemoryManager.h"
8 #include "BatteryManager.h"
9 #include "MovieManager.h"
10
InitMapper()11 void FDS::InitMapper()
12 {
13 _settings = _console->GetSettings();
14
15 //FDS BIOS
16 SetCpuMemoryMapping(0xE000, 0xFFFF, 0, PrgMemoryType::PrgRom, MemoryAccessType::Read);
17
18 //Work RAM
19 SetCpuMemoryMapping(0x6000, 0xDFFF, 0, PrgMemoryType::WorkRam, MemoryAccessType::ReadWrite);
20
21 //8k of CHR RAM
22 SelectCHRPage(0, 0);
23 }
24
InitMapper(RomData & romData)25 void FDS::InitMapper(RomData &romData)
26 {
27 _audio.reset(new FdsAudio(_console));
28 _romFilepath = romData.Info.Filename;
29 _fdsDiskSides = romData.FdsDiskData;
30 _fdsDiskHeaders = romData.FdsDiskHeaders;
31 _fdsRawData = romData.RawData;
32
33 //Apply save data (saved as an IPS file), if found
34 vector<uint8_t> ipsData = _console->GetBatteryManager()->LoadBattery(".ips");
35 LoadDiskData(ipsData);
36 }
37
LoadDiskData(vector<uint8_t> ipsData)38 void FDS::LoadDiskData(vector<uint8_t> ipsData)
39 {
40 _fdsDiskSides.clear();
41 _fdsDiskHeaders.clear();
42
43 FdsLoader loader;
44 vector<uint8_t> patchedData;
45 if(ipsData.size() > 0 && IpsPatcher::PatchBuffer(ipsData, _fdsRawData, patchedData)) {
46 loader.LoadDiskData(patchedData, _fdsDiskSides, _fdsDiskHeaders);
47 } else {
48 loader.LoadDiskData(_fdsRawData, _fdsDiskSides, _fdsDiskHeaders);
49 }
50 }
51
CreateIpsPatch()52 vector<uint8_t> FDS::CreateIpsPatch()
53 {
54 FdsLoader loader;
55 bool needHeader = (memcmp(_fdsRawData.data(), "FDS\x1a", 4) == 0);
56 vector<uint8_t> newData = loader.RebuildFdsFile(_fdsDiskSides, needHeader);
57 return IpsPatcher::CreatePatch(_fdsRawData, newData);
58 }
59
SaveBattery()60 void FDS::SaveBattery()
61 {
62 vector<uint8_t> ipsData = CreateIpsPatch();
63 _console->GetBatteryManager()->SaveBattery(".ips", ipsData.data(), (uint32_t)ipsData.size());
64 }
65
Reset(bool softReset)66 void FDS::Reset(bool softReset)
67 {
68 _autoDiskEjectCounter = -1;
69 _autoDiskSwitchCounter = -1;
70 _disableAutoInsertDisk = false;
71 _gameStarted = false;
72 }
73
GetFdsDiskSideSize(uint8_t side)74 uint32_t FDS::GetFdsDiskSideSize(uint8_t side)
75 {
76 assert(side < _fdsDiskSides.size());
77 return (uint32_t)_fdsDiskSides[side].size();
78 }
79
ReadFdsDisk()80 uint8_t FDS::ReadFdsDisk()
81 {
82 assert(_diskNumber < _fdsDiskSides.size());
83 assert(_diskPosition < _fdsDiskSides[_diskNumber].size());
84 return _fdsDiskSides[_diskNumber][_diskPosition];
85 }
86
WriteFdsDisk(uint8_t value)87 void FDS::WriteFdsDisk(uint8_t value)
88 {
89 assert(_diskNumber < _fdsDiskSides.size());
90 assert(_diskPosition < _fdsDiskSides[_diskNumber].size());
91 _fdsDiskSides[_diskNumber][_diskPosition - 2] = value;
92 }
93
ClockIrq()94 void FDS::ClockIrq()
95 {
96 if(_irqEnabled) {
97 if(_irqCounter == 0) {
98 _console->GetCpu()->SetIrqSource(IRQSource::External);
99 _irqCounter = _irqReloadValue;
100 if(!_irqRepeatEnabled) {
101 _irqEnabled = false;
102 }
103 } else {
104 _irqCounter--;
105 }
106 }
107 }
108
ReadRAM(uint16_t addr)109 uint8_t FDS::ReadRAM(uint16_t addr)
110 {
111 if(addr == 0xE18C && !_gameStarted && (_console->GetMemoryManager()->DebugRead(0x100) & 0xC0) != 0) {
112 //$E18B is the NMI entry point (using $E18C due to dummy reads)
113 //When NMI occurs while $100 & $C0 != 0, it typically means that the game is starting.
114 _gameStarted = true;
115 } else if(addr == 0xE445 && IsAutoInsertDiskEnabled()) {
116 //Game is trying to check if a specific disk/side is inserted
117 //Find the matching disk and insert it automatically
118 uint16_t bufferAddr = _console->GetMemoryManager()->DebugReadWord(0);
119 uint8_t buffer[10];
120 for(int i = 0; i < 10; i++) {
121 //Prevent infinite recursion
122 if(bufferAddr + i != 0xE445) {
123 buffer[i] = _console->GetMemoryManager()->DebugRead(bufferAddr + i);
124 } else {
125 buffer[i] = 0;
126 }
127 }
128
129 int matchCount = 0;
130 int matchIndex = -1;
131 for(int j = 0; j < (int)_fdsDiskHeaders.size(); j++) {
132 bool match = true;
133 for(int i = 0; i < 10; i++) {
134 if(buffer[i] != 0xFF && buffer[i] != _fdsDiskHeaders[j][i + 14]) {
135 match = false;
136 break;
137 }
138 }
139
140 if(match) {
141 matchCount++;
142 matchIndex = matchCount > 1 ? -1 : j;
143 }
144 }
145
146 if(matchCount > 1) {
147 //More than 1 disk matches, can happen in unlicensed games - disable auto insert logic
148 _disableAutoInsertDisk = true;
149 }
150
151 if(matchIndex >= 0) {
152 //Found a single match, insert it
153 _diskNumber = matchIndex;
154 if(_diskNumber != _previousDiskNumber) {
155 MessageManager::Log("[FDS] Disk automatically inserted: Disk " + std::to_string((_diskNumber / 2) + 1) + ((_diskNumber & 0x01) ? " Side B" : " Side A"));
156 _previousDiskNumber = _diskNumber;
157 }
158
159 if(matchIndex > 0) {
160 //Make sure we disable fast forward
161 _gameStarted = true;
162 }
163 }
164
165 //Prevent disk from being switched again until the disk is actually read
166 _autoDiskSwitchCounter = -1;
167 _restartAutoInsertCounter = -1;
168 }
169
170 return BaseMapper::ReadRAM(addr);
171 }
172
ProcessAutoDiskInsert()173 void FDS::ProcessAutoDiskInsert()
174 {
175 if(IsAutoInsertDiskEnabled()) {
176 bool fastForwardEnabled = _settings->CheckFlag(EmulationFlags::FdsFastForwardOnLoad);
177 uint32_t currentFrame = _console->GetPpu()->GetFrameCount();
178 if(_previousFrame != currentFrame) {
179 _previousFrame = currentFrame;
180 if(_autoDiskEjectCounter > 0) {
181 //After reading a disk, wait until this counter reaches 0 before
182 //automatically ejecting the disk the next time $4032 is read
183 _autoDiskEjectCounter--;
184 _settings->SetFlagState(EmulationFlags::ForceMaxSpeed, fastForwardEnabled && _autoDiskEjectCounter != 0);
185 } else if(_autoDiskSwitchCounter > 0) {
186 //After ejecting the disk, wait a bit before we insert a new one
187 _autoDiskSwitchCounter--;
188 _settings->SetFlagState(EmulationFlags::ForceMaxSpeed, fastForwardEnabled && _autoDiskSwitchCounter != 0);
189 if(_autoDiskSwitchCounter == 0) {
190 //Insert a disk (real disk/side will be selected when game executes $E445
191 MessageManager::Log("[FDS] Auto-inserted dummy disk.");
192 InsertDisk(0);
193
194 //Restart process after 200 frames if the game hasn't read the disk yet
195 _restartAutoInsertCounter = 200;
196 }
197 } else if(_restartAutoInsertCounter > 0) {
198 //After ejecting the disk, wait a bit before we insert a new one
199 _restartAutoInsertCounter--;
200 _settings->SetFlagState(EmulationFlags::ForceMaxSpeed, fastForwardEnabled && _restartAutoInsertCounter != 0);
201 if(_restartAutoInsertCounter == 0) {
202 //Wait a bit before ejecting the disk (eject in ~34 frames)
203 MessageManager::Log("[FDS] Game failed to load disk, try again.");
204 _previousDiskNumber = FDS::NoDiskInserted;
205 _autoDiskEjectCounter = 34;
206 _autoDiskSwitchCounter = -1;
207 }
208 }
209 }
210 }
211 }
212
ProcessCpuClock()213 void FDS::ProcessCpuClock()
214 {
215 if(_settings->CheckFlag(EmulationFlags::FdsFastForwardOnLoad)) {
216 _settings->SetFlagState(EmulationFlags::ForceMaxSpeed, _scanningDisk || !_gameStarted);
217 } else {
218 _settings->ClearFlags(EmulationFlags::ForceMaxSpeed);
219 }
220
221 ProcessAutoDiskInsert();
222
223 ClockIrq();
224 _audio->Clock();
225
226 if(_diskNumber == FDS::NoDiskInserted || !_motorOn) {
227 //Disk has been ejected
228 _endOfHead = true;
229 _scanningDisk = false;
230 return;
231 }
232
233 if(_resetTransfer && !_scanningDisk) {
234 return;
235 }
236
237 if(_endOfHead) {
238 _delay = 50000;
239 _endOfHead = false;
240 _diskPosition = 0;
241 _gapEnded = false;
242 return;
243 }
244
245 if(_delay > 0) {
246 _delay--;
247 } else {
248 _scanningDisk = true;
249 _autoDiskEjectCounter = -1;
250 _autoDiskSwitchCounter = -1;
251
252 uint8_t diskData = 0;
253 bool needIrq = _diskIrqEnabled;
254
255 if(_readMode) {
256 diskData = ReadFdsDisk();
257
258 if(!_previousCrcControlFlag) {
259 UpdateCrc(diskData);
260 }
261
262 if(!_diskReady) {
263 _gapEnded = false;
264 _crcAccumulator = 0;
265 } else if(diskData && !_gapEnded) {
266 _gapEnded = true;
267 needIrq = false;
268 }
269
270 if(_gapEnded) {
271 _transferComplete = true;
272 _readDataReg = diskData;
273 if(needIrq) {
274 _console->GetCpu()->SetIrqSource(IRQSource::FdsDisk);
275 }
276 }
277 } else {
278 if(!_crcControl) {
279 _transferComplete = true;
280 diskData = _writeDataReg;
281 if(needIrq) {
282 _console->GetCpu()->SetIrqSource(IRQSource::FdsDisk);
283 }
284 }
285
286 if(!_diskReady) {
287 diskData = 0x00;
288 }
289
290 if(!_crcControl) {
291 UpdateCrc(diskData);
292 } else {
293 if(!_previousCrcControlFlag) {
294 //Finish CRC calculation
295 UpdateCrc(0x00);
296 UpdateCrc(0x00);
297 }
298 diskData = _crcAccumulator & 0xFF;
299 _crcAccumulator >>= 8;
300 }
301
302 WriteFdsDisk(diskData);
303 _gapEnded = false;
304 }
305
306 _previousCrcControlFlag = _crcControl;
307
308 _diskPosition++;
309 if(_diskPosition >= GetFdsDiskSideSize(_diskNumber)) {
310 _motorOn = false;
311
312 //Wait a bit before ejecting the disk (eject in ~77 frames)
313 _autoDiskEjectCounter = 77;
314 } else {
315 _delay = 150;
316 }
317 }
318 }
319
UpdateCrc(uint8_t value)320 void FDS::UpdateCrc(uint8_t value)
321 {
322 for(uint16_t n = 0x01; n <= 0x80; n <<= 1) {
323 uint8_t carry = (_crcAccumulator & 1);
324 _crcAccumulator >>= 1;
325 if(carry) {
326 _crcAccumulator ^= 0x8408;
327 }
328
329 if(value & n) {
330 _crcAccumulator ^= 0x8000;
331 }
332 }
333 }
334
WriteRegister(uint16_t addr,uint8_t value)335 void FDS::WriteRegister(uint16_t addr, uint8_t value)
336 {
337 if((!_diskRegEnabled && addr >= 0x4024 && addr <= 0x4026) || (!_soundRegEnabled && addr >= 0x4040)) {
338 return;
339 }
340
341 switch(addr) {
342 case 0x4020:
343 _irqReloadValue = (_irqReloadValue & 0xFF00) | value;
344 break;
345
346 case 0x4021:
347 _irqReloadValue = (_irqReloadValue & 0x00FF) | (value << 8);
348 break;
349
350 case 0x4022:
351 _irqRepeatEnabled = (value & 0x01) == 0x01;
352 _irqEnabled = (value & 0x02) == 0x02 && _diskRegEnabled;
353
354 if(_irqEnabled) {
355 _irqCounter = _irqReloadValue;
356 } else {
357 _console->GetCpu()->ClearIrqSource(IRQSource::External);
358 }
359 break;
360
361 case 0x4023:
362 _diskRegEnabled = (value & 0x01) == 0x01;
363 _soundRegEnabled = (value & 0x02) == 0x02;
364
365 if(!_diskRegEnabled) {
366 _irqEnabled = false;
367 _console->GetCpu()->ClearIrqSource(IRQSource::External);
368 _console->GetCpu()->ClearIrqSource(IRQSource::FdsDisk);
369 }
370 break;
371
372 case 0x4024:
373 _writeDataReg = value;
374 _transferComplete = false;
375
376 //Unsure about clearing irq here: FCEUX/Nintendulator don't do this, puNES does.
377 _console->GetCpu()->ClearIrqSource(IRQSource::FdsDisk);
378 break;
379
380 case 0x4025:
381 _motorOn = (value & 0x01) == 0x01;
382 _resetTransfer = (value & 0x02) == 0x02;
383 _readMode = (value & 0x04) == 0x04;
384 SetMirroringType(value & 0x08 ? MirroringType::Horizontal : MirroringType::Vertical);
385 _crcControl = (value & 0x10) == 0x10;
386 //Bit 6 is not used, always 1
387 _diskReady = (value & 0x40) == 0x40;
388 _diskIrqEnabled = (value & 0x80) == 0x80;
389
390 //Writing to $4025 clears IRQ according to FCEUX, puNES & Nintendulator
391 //Fixes issues in some unlicensed games (error $20 at power on)
392 _console->GetCpu()->ClearIrqSource(IRQSource::FdsDisk);
393 break;
394
395 case 0x4026:
396 _extConWriteReg = value;
397 break;
398
399 default:
400 if(addr >= 0x4040) {
401 _audio->WriteRegister(addr, value);
402 }
403 break;
404 }
405 }
406
ReadRegister(uint16_t addr)407 uint8_t FDS::ReadRegister(uint16_t addr)
408 {
409 uint8_t value = _console->GetMemoryManager()->GetOpenBus();
410 if(_soundRegEnabled && addr >= 0x4040) {
411 return _audio->ReadRegister(addr);
412 } else if(_diskRegEnabled && addr <= 0x4033) {
413 switch(addr) {
414 case 0x4030:
415 //These 3 pins are open bus
416 value &= 0x2C;
417
418 value |= _console->GetCpu()->HasIrqSource(IRQSource::External) ? 0x01 : 0x00;
419 value |= _transferComplete ? 0x02 : 0x00;
420 value |= _badCrc ? 0x10 : 0x00;
421 //value |= _endOfHead ? 0x40 : 0x00;
422 //value |= _diskRegEnabled ? 0x80 : 0x00;
423
424 _transferComplete = false;
425 _console->GetCpu()->ClearIrqSource(IRQSource::External);
426 _console->GetCpu()->ClearIrqSource(IRQSource::FdsDisk);
427 return value;
428
429 case 0x4031:
430 _transferComplete = false;
431 _console->GetCpu()->ClearIrqSource(IRQSource::FdsDisk);
432 return _readDataReg;
433
434 case 0x4032:
435 //These 5 pins are open bus
436 value &= 0xF8;
437
438 value |= !IsDiskInserted() ? 0x01 : 0x00; //Disk not in drive
439 value |= (!IsDiskInserted() || !_scanningDisk) ? 0x02 : 0x00; //Disk not ready
440 value |= !IsDiskInserted() ? 0x04 : 0x00; //Disk not writable
441
442 if(IsAutoInsertDiskEnabled()) {
443 if(_console->GetPpu()->GetFrameCount() - _lastDiskCheckFrame < 100) {
444 _successiveChecks++;
445 } else {
446 _successiveChecks = 0;
447 }
448 _lastDiskCheckFrame = _console->GetPpu()->GetFrameCount();
449
450 if(_successiveChecks > 20 && _autoDiskEjectCounter == 0 && _autoDiskSwitchCounter == -1) {
451 //Game tried to check if a disk was inserted or not - this is usually done when the disk needs to be changed
452 //Eject the current disk and insert the new one in ~77 frames
453 _lastDiskCheckFrame = 0;
454 _successiveChecks = 0;
455 _autoDiskSwitchCounter = 77;
456 _previousDiskNumber = _diskNumber;
457 _diskNumber = NoDiskInserted;
458
459 MessageManager::Log("[FDS] Disk automatically ejected.");
460 }
461 }
462 return value;
463
464 case 0x4033:
465 //Always return good battery
466 return _extConWriteReg;
467 }
468 }
469
470 return _console->GetMemoryManager()->GetOpenBus();
471 }
472
StreamState(bool saving)473 void FDS::StreamState(bool saving)
474 {
475 BaseMapper::StreamState(saving);
476
477 SnapshotInfo audio{ _audio.get() };
478
479 Stream(_irqReloadValue, _irqCounter, _irqEnabled, _irqRepeatEnabled, _diskRegEnabled, _soundRegEnabled, _writeDataReg, _motorOn, _resetTransfer,
480 _readMode, _crcControl, _diskReady, _diskIrqEnabled, _extConWriteReg, _badCrc, _endOfHead, _readWriteEnabled, _readDataReg, _diskWriteProtected,
481 _diskNumber, _diskPosition, _delay, _previousCrcControlFlag, _gapEnded, _scanningDisk, _transferComplete, audio);
482
483 if(saving) {
484 vector<uint8_t> ipsData = CreateIpsPatch();
485 VectorInfo<uint8_t> data{ &ipsData };
486 Stream(data);
487 } else {
488 vector<uint8_t> ipsData;
489 VectorInfo<uint8_t> data{ &ipsData };
490 Stream(data);
491
492 if(ipsData.size() > 0) {
493 LoadDiskData(ipsData);
494 }
495
496 //Make sure we disable fast forwarding when loading a state
497 //Otherwise it's possible to get stuck in fast forward mode
498 _gameStarted = true;
499 }
500 }
501
~FDS()502 FDS::~FDS()
503 {
504 //Restore emulation speed to normal when closing
505 _settings->ClearFlags(EmulationFlags::ForceMaxSpeed);
506 }
507
GetAvailableFeatures()508 ConsoleFeatures FDS::GetAvailableFeatures()
509 {
510 return ConsoleFeatures::Fds;
511 }
512
GetSideCount()513 uint32_t FDS::GetSideCount()
514 {
515 return (uint32_t)_fdsDiskSides.size();
516 }
517
EjectDisk()518 void FDS::EjectDisk()
519 {
520 _diskNumber = FDS::NoDiskInserted;
521 }
522
InsertDisk(uint32_t diskNumber)523 void FDS::InsertDisk(uint32_t diskNumber)
524 {
525 if(_diskNumber == FDS::NoDiskInserted) {
526 _diskNumber = diskNumber % GetSideCount();
527 }
528 }
529
GetCurrentDisk()530 uint32_t FDS::GetCurrentDisk()
531 {
532 return _diskNumber;
533 }
534
IsDiskInserted()535 bool FDS::IsDiskInserted()
536 {
537 return _diskNumber != FDS::NoDiskInserted;
538 }
539
IsAutoInsertDiskEnabled()540 bool FDS::IsAutoInsertDiskEnabled()
541 {
542 return !_disableAutoInsertDisk && _settings->CheckFlag(EmulationFlags::FdsAutoInsertDisk) && !MovieManager::Playing() && !MovieManager::Recording();
543 }