1 #pragma once
2 #include "stdafx.h"
3 #include "CPU.h"
4 #include "BaseMapper.h"
5 
6 class MMC1 : public BaseMapper
7 {
8 	private:
9 		enum class MMC1Registers
10 		{
11 			Reg8000 = 0,
12 			RegA000 = 1,
13 			RegC000 = 2,
14 			RegE000 = 3
15 		};
16 
17 		enum class PrgMode
18 		{
19 			_16k = 16,
20 			_32k = 32,
21 		};
22 
23 		enum class ChrMode
24 		{
25 			_4k = 4,
26 			_8k = 8,
27 		};
28 
29 		enum class SlotSelect
30 		{
31 			x8000 = 0x8000,
32 			xC000 = 0xC000,
33 		};
34 
35 		uint8_t _writeBuffer = 0;
36 		uint8_t _shiftCount = 0;
37 
38 		bool _wramDisable;
39 		ChrMode _chrMode;
40 		PrgMode _prgMode;
41 		SlotSelect _slotSelect;
42 
43 		uint8_t _chrReg0;
44 		uint8_t _chrReg1;
45 		uint8_t _prgReg;
46 
47 		uint64_t _lastWriteCycle = 0;
48 
49 		bool _forceWramOn;
50 		MMC1Registers _lastChrReg;
51 
52 	private:
HasResetFlag(uint8_t value)53 		bool HasResetFlag(uint8_t value)
54 		{
55 			return (value & 0x80) == 0x80;
56 		}
57 
ResetBuffer()58 		void ResetBuffer()
59 		{
60 			_shiftCount = 0;
61 			_writeBuffer = 0;
62 		}
63 
IsBufferFull(uint8_t value)64 		bool IsBufferFull(uint8_t value)
65 		{
66 			if(HasResetFlag(value)) {
67 				//When 'r' is set:
68 				//	- 'd' is ignored
69 				//	- hidden temporary reg is reset (so that the next write is the "first" write)
70 				//	- bits 2,3 of reg $8000 are set (16k PRG mode, $8000 swappable)
71 				//	- other bits of $8000 (and other regs) are unchanged
72 				ResetBuffer();
73 				_state.Reg8000 |= 0x0C;
74 				UpdateState();
75 				return false;
76 			} else {
77 				_writeBuffer >>= 1;
78 				_writeBuffer |= ((value << 4) & 0x10);
79 
80 				_shiftCount++;
81 
82 				return _shiftCount == 5;
83 			}
84 		}
85 
86 	protected:
87 		struct
88 		{
89 			uint8_t Reg8000;
90 			uint8_t RegA000;
91 			uint8_t RegC000;
92 			uint8_t RegE000;
93 		} _state;
94 
UpdateState()95 		virtual void UpdateState()
96 		{
97 			switch(_state.Reg8000 & 0x03) {
98 				case 0: SetMirroringType(MirroringType::ScreenAOnly); break;
99 				case 1: SetMirroringType(MirroringType::ScreenBOnly); break;
100 				case 2: SetMirroringType(MirroringType::Vertical); break;
101 				case 3: SetMirroringType(MirroringType::Horizontal); break;
102 			}
103 
104 			_wramDisable = (_state.RegE000 & 0x10) == 0x10;
105 
106 			_slotSelect = ((_state.Reg8000 & 0x04) == 0x04) ? SlotSelect::x8000 : SlotSelect::xC000;
107 			_prgMode = ((_state.Reg8000 & 0x08) == 0x08) ? PrgMode::_16k : PrgMode::_32k;
108 			_chrMode = ((_state.Reg8000 & 0x10) == 0x10) ? ChrMode::_4k : ChrMode::_8k;
109 
110 			_chrReg0 = _state.RegA000 & 0x1F;
111 			_chrReg1 = _state.RegC000 & 0x1F;
112 			_prgReg = _state.RegE000 & 0x0F;
113 
114 			uint8_t extraReg = _lastChrReg == MMC1Registers::RegC000 && _chrMode == ChrMode::_4k ? _chrReg1 : _chrReg0;
115 			uint8_t prgBankSelect = 0;
116 			if(_prgSize == 0x80000) {
117 				//512kb carts use bit 7 of $A000/$C000 to select page
118 				//This is used for SUROM (Dragon Warrior 3/4, Dragon Quest 4)
119 				prgBankSelect = extraReg & 0x10;
120 			}
121 
122 			if(_wramDisable && !_forceWramOn) {
123 				RemoveCpuMemoryMapping(0x6000, 0x7FFF);
124 			} else {
125 				if(_saveRamSize + _workRamSize > 0x4000) {
126 					//SXROM, 32kb of save ram
127 					SetCpuMemoryMapping(0x6000, 0x7FFF, (extraReg >> 2) & 0x03, HasBattery() ? PrgMemoryType::SaveRam : PrgMemoryType::WorkRam);
128 				} else if(_saveRamSize + _workRamSize > 0x2000) {
129 					if(_saveRamSize == 0x2000 && _workRamSize == 0x2000) {
130 						//SOROM, half of the 16kb ram is battery backed
131 						SetCpuMemoryMapping(0x6000, 0x7FFF, 0, (extraReg >> 3) & 0x01 ? PrgMemoryType::WorkRam : PrgMemoryType::SaveRam);
132 					} else {
133 						//Unknown, shouldn't happen
134 						SetCpuMemoryMapping(0x6000, 0x7FFF, (extraReg >> 2) & 0x01, HasBattery() ? PrgMemoryType::SaveRam : PrgMemoryType::WorkRam);
135 					}
136 				} else {
137 					//Everything else - 8kb of work or save ram
138 					SetCpuMemoryMapping(0x6000, 0x7FFF, 0, HasBattery() ? PrgMemoryType::SaveRam : PrgMemoryType::WorkRam);
139 				}
140 			}
141 
142 			if(_romInfo.SubMapperID == 5) {
143 				//SubMapper 5
144 				//"001: 5 Fixed PRG    SEROM, SHROM, SH1ROM use a fixed 32k PRG ROM with no banking support.
145 				SelectPrgPage2x(0, 0);
146 			} else {
147 				if(_prgMode == PrgMode::_32k) {
148 					SelectPrgPage2x(0, (_prgReg & 0xFE) | prgBankSelect);
149 				} else if(_prgMode == PrgMode::_16k) {
150 					if(_slotSelect == SlotSelect::x8000) {
151 						SelectPRGPage(0, _prgReg | prgBankSelect);
152 						SelectPRGPage(1, 0x0F | prgBankSelect);
153 					} else if(_slotSelect == SlotSelect::xC000) {
154 						SelectPRGPage(0, 0 | prgBankSelect);
155 						SelectPRGPage(1, _prgReg | prgBankSelect);
156 					}
157 				}
158 			}
159 
160 			if(_chrMode == ChrMode::_8k) {
161 				SelectCHRPage(0, _chrReg0 & 0x1E);
162 				SelectCHRPage(1, (_chrReg0 & 0x1E) + 1);
163 			} else if(_chrMode == ChrMode::_4k) {
164 				SelectCHRPage(0, _chrReg0);
165 				SelectCHRPage(1, _chrReg1);
166 			}
167 		}
168 
StreamState(bool saving)169 		virtual void StreamState(bool saving) override
170 		{
171 			BaseMapper::StreamState(saving);
172 			Stream(_state.Reg8000, _state.RegA000, _state.RegC000, _state.RegE000, _writeBuffer, _shiftCount, _lastWriteCycle, _lastChrReg);
173 			if(!saving) {
174 				UpdateState();
175 			}
176 		}
177 
GetPRGPageSize()178 		virtual uint16_t GetPRGPageSize() override { return 0x4000; }
GetCHRPageSize()179 		virtual uint16_t GetCHRPageSize() override {	return 0x1000; }
180 
InitMapper()181 		virtual void InitMapper() override
182 		{
183 			_state.Reg8000 = GetPowerOnByte() | 0x0C; //On powerup: bits 2,3 of $8000 are set (this ensures the $8000 is bank 0, and $C000 is the last bank - needed for SEROM/SHROM/SH1ROM which do no support banking)
184 			_state.RegA000 = GetPowerOnByte();
185 			_state.RegC000 = GetPowerOnByte();
186 			_state.RegE000 = (_romInfo.DatabaseInfo.Board.find("MMC1B") != string::npos ? 0x10 : 0x00); //WRAM Disable: enabled by default for MMC1B
187 
188 			//"MMC1A: PRG RAM is always enabled" - Normally these roms should be classified as mapper 155
189 			_forceWramOn = (_romInfo.DatabaseInfo.Board.compare("MMC1A") == 0);
190 
191 			_lastChrReg = MMC1Registers::RegA000;
192 
193 			UpdateState();
194 		}
195 
WriteRegister(uint16_t addr,uint8_t value)196 		virtual void WriteRegister(uint16_t addr, uint8_t value) override
197 		{
198 			uint64_t currentCycle = _console->GetCpu()->GetCycleCount();
199 
200 			//Ignore write if within 2 cycles of another write (i.e the real write after a dummy write)
201 			if(currentCycle - _lastWriteCycle >= 2) {
202 				if(IsBufferFull(value)) {
203 					switch((MMC1Registers)((addr & 0x6000) >> 13)) {
204 						case MMC1Registers::Reg8000: _state.Reg8000 = _writeBuffer; break;
205 						case MMC1Registers::RegA000:
206 							_lastChrReg = MMC1Registers::RegA000;
207 							_state.RegA000 = _writeBuffer;
208 							break;
209 
210 						case MMC1Registers::RegC000:
211 							_lastChrReg = MMC1Registers::RegC000;
212 							_state.RegC000 = _writeBuffer;
213 							break;
214 
215 						case MMC1Registers::RegE000: _state.RegE000 = _writeBuffer; break;
216 					}
217 
218 					UpdateState();
219 
220 					//Reset buffer after writing 5 bits
221 					ResetBuffer();
222 				}
223 			}
224 			_lastWriteCycle = currentCycle;
225 		}
226 };
227