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 }