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