openMSX
ReproCartridgeV2.cc
Go to the documentation of this file.
1#include "ReproCartridgeV2.hh"
2
4#include "MSXCPUInterface.hh"
5#include "serialize.hh"
6
7#include "narrow.hh"
8
9#include <array>
10
11
12/******************************************************************************
13 * DOCUMENTATION AS PROVIDED BY MANUEL PAZOS, WHO DEVELOPED THE CARTRIDGE *
14 ******************************************************************************
15
16 Repro Cartridge version 2 is similar to Konami Ultimate Collection. It
17 uses the same flashROM, SCC/SCC+. But 2 PSG's and volume control and
18 multiple mappers.
19
20 Released were cartridge with the following content: Only Metal Gear, only
21 Metal Gear 2, Vampire Killer (original, Castlevania patch, enhanced SCC),
22 Usas (original, enhanced), Nemesis 3 (enhanced) and Metal Gear Collection
23 (Metal Gear 1 and 2, both in 3 languages)
24
25
26[REGISTER (#7FFE)]
27Selects the mapper, using bits 0 and 1 (other bits are ignored):
28 00 = Konami SCC
29 01 = Konami
30 10 = ASCII8
31 11 = ASCII16
32
33[REGISTER (#7FFF)]
34If it contains value 0x50, the flash is writable and the mapper is disabled.
35Otherwise, the mapper is enabled and the flash is readonly.
36
37- Mapper supports 8 different ROMs of 1MB each, with the above mappers.
38- Cartridge has a PSG at 0x10 and a PSG at 0xA0, both write only
39- On I/O port 0x33 the 1MB block can be selected (default 0, so up to 7)
40- On I/O port 0x31 the volume can be selected of the 3 sound chips:
41 Bits EEIIISSS: EE(7-6) = PSG#10, III(5-3) = PSG#A0, SSS(2-0) = SCC
42 Default value is &B10000010. 0 means silent. So, clone PSG is silent by
43 default.
44
45Note: there is also a version 1 of this hardware, with the following
46differences:
47- Only Konami SCC mapper (no register at #7FFE)
48- No volume control register behind port #31
49- Main bank register is behind port #13 instead of #33
50- Main block size is 2MB instead of 1MB
51- No extra PSG at 0xA0 (but the PSG at #10 is there)
52
53******************************************************************************/
54
55namespace openmsx {
56
58 const DeviceConfig& config, Rom&& rom_)
59 : MSXRom(config, std::move(rom_))
60 , flash(rom, AmdFlashChip::M29W640GB, {}, config)
61 , scc("ReproCartV2 SCC", config, getCurrentTime(), SCC::Mode::Compatible)
62 , psg0x10("ReproCartV2 PSG@0x10", DummyAY8910Periphery::instance(), config,
63 getCurrentTime())
64 , psg0xA0("ReproCartV2 PSG@0xA0", DummyAY8910Periphery::instance(), config,
65 getCurrentTime())
66{
67 powerUp(getCurrentTime());
68 auto& cpuInterface = getCPUInterface();
69 for (auto port : {0x10, 0x11, 0x31, 0x33, 0xA0, 0xA1}) {
70 cpuInterface.register_IO_Out(narrow_cast<byte>(port), this);
71 }
72}
73
75{
76 auto& cpuInterface = getCPUInterface();
77 for (auto port : {0x10, 0x11, 0x31, 0x33, 0xA0, 0xA1}) {
78 cpuInterface.unregister_IO_Out(narrow_cast<byte>(port), this);
79 }
80}
81
82void ReproCartridgeV2::powerUp(EmuTime::param time)
83{
84 scc.powerUp(time);
85 reset(time);
86}
87
88void ReproCartridgeV2::reset(EmuTime::param time)
89{
90 flashRomWriteEnabled = false;
91 mainBankReg = 0;
92 setVolume(time, 0b10'000'10);
93 mapperTypeReg = 0;
94 sccMode = 0;
95 ranges::iota(bankRegs, byte(0));
96
97 scc.reset(time);
98 psg0x10Latch = 0;
99 psg0x10.reset(time);
100 psg0xA0Latch = 0;
101 psg0xA0.reset(time);
102
103 flash.reset();
104
105 invalidateDeviceRCache(); // flush all to be sure
106}
107
108unsigned ReproCartridgeV2::getFlashAddr(unsigned addr) const
109{
110 unsigned page8kB = (addr >> 13) - 2;
111 if (page8kB >= 4) return unsigned(-1); // outside [0x4000, 0xBFFF]
112
113 byte bank = bankRegs[page8kB] & 127; // 1MB max
114 return (mainBankReg << 20) | (bank << 13) | (addr & 0x1FFF);
115}
116
117// Note: implementation (mostly) copied from KUC
118bool ReproCartridgeV2::isSCCAccess(word addr) const
119{
120 if ((mapperTypeReg != 0) || (sccMode & 0x10)) return false;
121
122 if (addr & 0x0100) {
123 // Address bit 8 must be zero, this is different from a real
124 // SCC/SCC+. According to Manuel Pazos this is a leftover from
125 // an earlier version that had 2 SCCs: the SCC on the left or
126 // right channel reacts when address bit 8 is respectively 0/1.
127 return false;
128 }
129
130 if (sccMode & 0x20) {
131 // SCC+ range: 0xB800..0xBFFF, excluding 0xBFFE-0xBFFF
132 return (bankRegs[3] & 0x80) && (0xB800 <= addr) && (addr < 0xBFFE);
133 } else {
134 // SCC range: 0x9800..0x9FFF, excluding 0x9FFE-0x9FFF
135 return ((bankRegs[2] & 0x3F) == 0x3F) && (0x9800 <= addr) && (addr < 0x9FFE);
136 }
137}
138
139byte ReproCartridgeV2::readMem(word addr, EmuTime::param time)
140{
141 if (isSCCAccess(addr)) {
142 return scc.readMem(narrow_cast<uint8_t>(addr & 0xFF), time);
143 }
144
145 unsigned flashAddr = getFlashAddr(addr);
146 return (flashAddr != unsigned(-1))
147 ? flash.read(flashAddr)
148 : 0xFF; // unmapped read
149}
150
151byte ReproCartridgeV2::peekMem(word addr, EmuTime::param time) const
152{
153 if (isSCCAccess(addr)) {
154 return scc.peekMem(narrow_cast<uint8_t>(addr & 0xFF), time);
155 }
156
157 unsigned flashAddr = getFlashAddr(addr);
158 return (flashAddr != unsigned(-1))
159 ? flash.peek(flashAddr)
160 : 0xFF; // unmapped read
161}
162
164{
165 if (isSCCAccess(addr)) return nullptr;
166
167 unsigned flashAddr = getFlashAddr(addr);
168 return (flashAddr != unsigned(-1))
169 ? flash.getReadCacheLine(flashAddr)
170 : unmappedRead.data();
171}
172
173void ReproCartridgeV2::writeMem(word addr, byte value, EmuTime::param time)
174{
175 unsigned page8kB = (addr >> 13) - 2;
176 if (page8kB >= 4) return; // outside [0x4000, 0xBFFF]
177
178 // There are several overlapping functional regions in the address
179 // space. A single write can trigger behaviour in multiple regions. In
180 // other words there's no priority amongst the regions where a higher
181 // priority region blocks the write from the lower priority regions.
182 // This only goes for places where the flash is 'seen', so not for the
183 // SCC registers
184
185 if (isSCCAccess(addr)) {
186 scc.writeMem(narrow_cast<uint8_t>(addr & 0xFF), value, time);
187 return; // write to SCC blocks write to other functions
188 }
189
190 // address is calculated before writes to other regions take effect
191 unsigned flashAddr = getFlashAddr(addr);
192
193 // Main mapper register
194 if (addr == 0x7FFF) {
195 flashRomWriteEnabled = (value == 0x50);
196 invalidateDeviceRCache(); // flush all to be sure
197 }
198 // Mapper selection register
199 if (addr == 0x7FFE) {
200 mapperTypeReg = value & 3; // other bits are ignored, so no need to store
201 invalidateDeviceRCache(); // flush all to be sure
202 }
203
204 if (!flashRomWriteEnabled) {
205 switch (mapperTypeReg) {
206 case 0:
207 // Konami-SCC
208 if ((addr & 0x1800) == 0x1000) {
209 // [0x5000,0x57FF] [0x7000,0x77FF]
210 // [0x9000,0x97FF] [0xB000,0xB7FF]
211 bankRegs[page8kB] = value;
212 invalidateDeviceRCache(0x4000 + 0x2000 * page8kB, 0x2000);
213 }
214
215 // SCC mode register
216 if ((addr & 0xFFFE) == 0xBFFE) {
217 sccMode = value;
218 scc.setMode((value & 0x20) ? SCC::Mode::Plus
220 invalidateDeviceRCache(0x9800, 0x800);
221 invalidateDeviceRCache(0xB800, 0x800);
222 }
223 break;
224 case 1:
225 {
226 // Konami
227 // (Copied from MegaFlashROMSCCPlus)
228 // Masking of the mapper bits is done on
229 // write (and only in Konami(-scc) mode)
230 if ((addr < 0x5000) || ((0x5800 <= addr) && (addr < 0x6000))) break; // only SCC range works
231 bankRegs[page8kB] = value & 0x7F;
232 invalidateDeviceRCache(0x4000 + 0x2000 * page8kB, 0x2000);
233 break;
234 }
235 case 2:
236 // ASCII-8
237 // (Copied from MegaFlashROMSCCPlus)
238 if ((0x6000 <= addr) && (addr < 0x8000)) {
239 byte bank = (addr >> 11) & 0x03;
240 bankRegs[bank] = value;
241 invalidateDeviceRCache(0x4000 + 0x2000 * bank, 0x2000);
242 }
243 break;
244 case 3:
245 // ASCII-16
246 // (Copied from MegaFlashROMSCCPlus)
247 // This behaviour is confirmed by Manuel Pazos (creator
248 // of the cartridge): ASCII-16 uses all 4 bank registers
249 // and one bank switch changes 2 registers at once.
250 // This matters when switching mapper mode, because
251 // the content of the bank registers is unchanged after
252 // a switch.
253 if ((0x6000 <= addr) && (addr < 0x6800)) {
254 bankRegs[0] = narrow_cast<byte>(2 * value + 0);
255 bankRegs[1] = narrow_cast<byte>(2 * value + 1);
256 invalidateDeviceRCache(0x4000, 0x4000);
257 }
258 if ((0x7000 <= addr) && (addr < 0x7800)) {
259 bankRegs[2] = narrow_cast<byte>(2 * value + 0);
260 bankRegs[3] = narrow_cast<byte>(2 * value + 1);
261 invalidateDeviceRCache(0x8000, 0x4000);
262 }
263 break;
264 default:
266 }
267 } else {
268 if (flashAddr != unsigned(-1)) {
269 flash.write(flashAddr, value);
270 }
271 }
272}
273
275{
276 return ((0x4000 <= addr) && (addr < 0xC000))
277 ? nullptr // [0x4000,0xBFFF] isn't cacheable
278 : unmappedWrite.data();
279}
280
281void ReproCartridgeV2::writeIO(word port, byte value, EmuTime::param time)
282{
283 switch (port & 0xFF)
284 {
285 case 0x10:
286 psg0x10Latch = value & 0x0F;
287 break;
288 case 0x11:
289 psg0x10.writeRegister(psg0x10Latch, value, time);
290 break;
291 case 0xA0:
292 psg0xA0Latch = value & 0x0F;
293 break;
294 case 0xA1:
295 psg0xA0.writeRegister(psg0xA0Latch, value, time);
296 break;
297 case 0x31:
298 setVolume(time, value);
299 break;
300 case 0x33:
301 mainBankReg = value & 7;
302 invalidateDeviceRCache(); // flush all to be sure
303 break;
304 default: UNREACHABLE;
305 }
306}
307
308void ReproCartridgeV2::setVolume(EmuTime::param time, byte value)
309{
310 // store (mostly for the save/loadstate feature)
311 volumeReg = value;
312 // EE (7-6) = PSG#10. So values 0-3
313 // III(5-3) = PSG#A0. So values 0-7
314 // SSS(2-0) = SCC. So values 0-7
315 scc .setSoftwareVolume(narrow<float>((volumeReg >> 0) & 7) * 0.5f, time);
316 psg0xA0.setSoftwareVolume(narrow<float>((volumeReg >> 3) & 7) * 0.5f, time);
317 psg0x10.setSoftwareVolume(narrow<float>((volumeReg >> 6) & 3) * 0.5f, time);
318}
319
320template<typename Archive>
321void ReproCartridgeV2::serialize(Archive& ar, unsigned /*version*/)
322{
323 // skip MSXRom base class
324 ar.template serializeBase<MSXDevice>(*this);
325
326 ar.serialize("flash", flash,
327 "scc", scc,
328 "psg0x10", psg0x10,
329 "psg0x10Latch", psg0x10Latch,
330 "psg0xA0", psg0xA0,
331 "psg0xA0Latch", psg0xA0Latch,
332 "flashRomWriteEnabled", flashRomWriteEnabled,
333 "mainBankReg", mainBankReg,
334 "volumeReg", volumeReg,
335 "mapperTypeReg", mapperTypeReg,
336 "sccMode", sccMode,
337 "bankRegs", bankRegs);
338
339 if constexpr (Archive::IS_LOADER) {
340 auto time = getCurrentTime();
341 setVolume(time, volumeReg);
342 }
343
344}
347
348} // namespace openmsx
#define REGISTER_MSXDEVICE(CLASS, NAME)
Definition MSXDevice.hh:356
void reset(EmuTime::param time)
Definition AY8910.cc:521
void writeRegister(unsigned reg, uint8_t value, EmuTime::param time)
Definition AY8910.cc:578
void write(size_t address, uint8_t value)
Definition AmdFlash.cc:456
const uint8_t * getReadCacheLine(size_t address) const
Definition AmdFlash.cc:444
uint8_t read(size_t address)
Definition AmdFlash.cc:432
uint8_t peek(size_t address) const
Definition AmdFlash.cc:186
static DummyAY8910Periphery & instance()
void invalidateDeviceRCache()
Definition MSXDevice.hh:215
static std::array< byte, 0x10000 > unmappedRead
Definition MSXDevice.hh:306
static std::array< byte, 0x10000 > unmappedWrite
Definition MSXDevice.hh:307
EmuTime::param getCurrentTime() const
Definition MSXDevice.cc:125
MSXCPUInterface & getCPUInterface() const
Definition MSXDevice.cc:133
void writeIO(word port, byte value, EmuTime::param time) override
Write a byte to a given IO port at a certain time to this device.
byte readMem(word address, EmuTime::param time) override
Read a byte from a location at a certain time from this device.
byte * getWriteCacheLine(word address) override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for writing.
void reset(EmuTime::param time) override
This method is called on reset.
ReproCartridgeV2(const DeviceConfig &config, Rom &&rom)
void serialize(Archive &ar, unsigned version)
const byte * getReadCacheLine(word address) const override
Test that the memory in the interval [start, start + CacheLine::SIZE) is cacheable for reading.
void writeMem(word address, byte value, EmuTime::param time) override
Write a given byte to a given location at a certain time to this device.
void powerUp(EmuTime::param time) override
This method is called when MSX is powered up.
void setVolume(EmuTime::param time, byte value)
byte peekMem(word address, EmuTime::param time) const override
Read a byte from a given memory location.
void setMode(Mode newMode)
Definition SCC.cc:183
void powerUp(EmuTime::param time)
Definition SCC.cc:141
uint8_t readMem(uint8_t address, EmuTime::param time)
Definition SCC.cc:193
void reset(EmuTime::param time)
Definition SCC.cc:173
uint8_t peekMem(uint8_t address, EmuTime::param time) const
Definition SCC.cc:206
void writeMem(uint8_t address, uint8_t value, EmuTime::param time)
Definition SCC.cc:285
void setSoftwareVolume(float volume, EmuTime::param time)
Change the 'software volume' of this sound device.
This file implemented 3 utility functions:
Definition Autofire.cc:11
uint16_t word
16 bit unsigned integer
Definition openmsx.hh:29
constexpr void iota(ForwardIt first, ForwardIt last, T value)
Definition ranges.hh:322
STL namespace.
#define INSTANTIATE_SERIALIZE_METHODS(CLASS)
#define UNREACHABLE