OXYGENHUB
◆ main openmsx / src / memory / ReproCartridgeV2.cc
10.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "ReproCartridgeV2.hh"
2
3
#include "DummyAY8910Periphery.hh"
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)]
27
Selects 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)]
34
If it contains value 0x50, the flash is writable and the mapper is disabled.
35
Otherwise, 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
45
Note: there is also a version 1 of this hardware, with the following
46
differences:
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
55
namespace openmsx {
56
57
ReproCartridgeV2::ReproCartridgeV2(DeviceConfig& config, Rom&& rom_)
58
	: MSXRom(config, std::move(rom_))
59
	, flash(rom, AmdFlashChip::M29W640GB, {}, config)
60
	, scc("ReproCartV2 SCC", config, getCurrentTime(), SCC::Mode::Compatible)
61
	, psg0x10("ReproCartV2 PSG@0x10", DummyAY8910Periphery::instance(), config,
62
	      getCurrentTime())
63
	, psg0xA0("ReproCartV2 PSG@0xA0", DummyAY8910Periphery::instance(), config,
64
	      getCurrentTime())
65
{
66
	// adjust PSG volume, see details in https://github.com/openMSX/openMSX/issues/1934
67
	// note: this is a theoretical value. The actual relative volume should be measured!
68
	psg0x10.setSoftwareVolume(21000.0f/9000.0f, getCurrentTime());
69
	psg0xA0.setSoftwareVolume(21000.0f/9000.0f, getCurrentTime());
70
71
	powerUp(getCurrentTime());
72
	auto& cpuInterface = getCPUInterface();
73
	for (auto port : {0x10, 0x11, 0x31, 0x33, 0xA0, 0xA1}) {
74
		cpuInterface.register_IO_Out(narrow_cast<byte>(port), this);
75
	}
76
}
77
78
ReproCartridgeV2::~ReproCartridgeV2()
79
{
80
	auto& cpuInterface = getCPUInterface();
81
	for (auto port : {0x10, 0x11, 0x31, 0x33, 0xA0, 0xA1}) {
82
		cpuInterface.unregister_IO_Out(narrow_cast<byte>(port), this);
83
	}
84
}
85
86
void ReproCartridgeV2::powerUp(EmuTime time)
87
{
88
	scc.powerUp(time);
89
	reset(time);
90
}
91
92
void ReproCartridgeV2::reset(EmuTime time)
93
{
94
	flashRomWriteEnabled = false;
95
	mainBankReg = 0;
96
	setVolume(time, 0b10'000'10);
97
	mapperTypeReg = 0;
98
	sccMode = 0;
99
	ranges::iota(bankRegs, byte(0));
100
101
	scc.reset(time);
102
	psg0x10Latch = 0;
103
	psg0x10.reset(time);
104
	psg0xA0Latch = 0;
105
	psg0xA0.reset(time);
106
107
	flash.reset();
108
109
	invalidateDeviceRCache(); // flush all to be sure
110
}
111
112
unsigned ReproCartridgeV2::getFlashAddr(unsigned addr) const
113
{
114
	unsigned page8kB = (addr >> 13) - 2;
115
	if (page8kB >= 4) return unsigned(-1); // outside [0x4000, 0xBFFF]
116
117
	byte bank = bankRegs[page8kB] & 127; // 1MB max
118
	return (mainBankReg << 20) | (bank << 13) | (addr & 0x1FFF);
119
}
120
121
// Note: implementation (mostly) copied from KUC
122
bool ReproCartridgeV2::isSCCAccess(uint16_t addr) const
123
{
124
	if ((mapperTypeReg != 0) || (sccMode & 0x10)) return false;
125
126
	if (addr & 0x0100) {
127
		// Address bit 8 must be zero, this is different from a real
128
		// SCC/SCC+. According to Manuel Pazos this is a leftover from
129
		// an earlier version that had 2 SCCs: the SCC on the left or
130
		// right channel reacts when address bit 8 is respectively 0/1.
131
		return false;
132
	}
133
134
	if (sccMode & 0x20) {
135
		// SCC+   range: 0xB800..0xBFFF,  excluding 0xBFFE-0xBFFF
136
		return  (bankRegs[3] & 0x80)          && (0xB800 <= addr) && (addr < 0xBFFE);
137
	} else {
138
		// SCC    range: 0x9800..0x9FFF,  excluding 0x9FFE-0x9FFF
139
		return ((bankRegs[2] & 0x3F) == 0x3F) && (0x9800 <= addr) && (addr < 0x9FFE);
140
	}
141
}
142
143
byte ReproCartridgeV2::readMem(uint16_t addr, EmuTime time)
144
{
145
	if (isSCCAccess(addr)) {
146
		return scc.readMem(narrow_cast<uint8_t>(addr & 0xFF), time);
147
	}
148
149
	unsigned flashAddr = getFlashAddr(addr);
150
	return (flashAddr != unsigned(-1))
151
		? flash.read(flashAddr, time)
152
		: 0xFF; // unmapped read
153
}
154
155
byte ReproCartridgeV2::peekMem(uint16_t addr, EmuTime time) const
156
{
157
	if (isSCCAccess(addr)) {
158
		return scc.peekMem(narrow_cast<uint8_t>(addr & 0xFF), time);
159
	}
160
161
	unsigned flashAddr = getFlashAddr(addr);
162
	return (flashAddr != unsigned(-1))
163
		? flash.peek(flashAddr, time)
164
		: 0xFF; // unmapped read
165
}
166
167
const byte* ReproCartridgeV2::getReadCacheLine(uint16_t addr) const
168
{
169
	if (isSCCAccess(addr)) return nullptr;
170
171
	unsigned flashAddr = getFlashAddr(addr);
172
	return (flashAddr != unsigned(-1))
173
		? flash.getReadCacheLine(flashAddr)
174
		: unmappedRead.data();
175
}
176
177
void ReproCartridgeV2::writeMem(uint16_t addr, byte value, EmuTime time)
178
{
179
	unsigned page8kB = (addr >> 13) - 2;
180
	if (page8kB >= 4) return; // outside [0x4000, 0xBFFF]
181
182
	// There are several overlapping functional regions in the address
183
	// space. A single write can trigger behaviour in multiple regions. In
184
	// other words there's no priority amongst the regions where a higher
185
	// priority region blocks the write from the lower priority regions.
186
	// This only goes for places where the flash is 'seen', so not for the
187
	// SCC registers
188
189
	if (isSCCAccess(addr)) {
190
		scc.writeMem(narrow_cast<uint8_t>(addr & 0xFF), value, time);
191
		return; // write to SCC blocks write to other functions
192
	}
193
194
	// address is calculated before writes to other regions take effect
195
	unsigned flashAddr = getFlashAddr(addr);
196
197
	// Main mapper register
198
	if (addr == 0x7FFF) {
199
		flashRomWriteEnabled = (value == 0x50);
200
		invalidateDeviceRCache(); // flush all to be sure
201
	}
202
	// Mapper selection register
203
	if (addr == 0x7FFE) {
204
		mapperTypeReg = value & 3; // other bits are ignored, so no need to store
205
		invalidateDeviceRCache(); // flush all to be sure
206
	}
207
208
	if (!flashRomWriteEnabled) {
209
		switch (mapperTypeReg) {
210
		case 0:
211
			// Konami-SCC
212
			if ((addr & 0x1800) == 0x1000) {
213
				// [0x5000,0x57FF] [0x7000,0x77FF]
214
				// [0x9000,0x97FF] [0xB000,0xB7FF]
215
				bankRegs[page8kB] = value;
216
				invalidateDeviceRCache(0x4000 + 0x2000 * page8kB, 0x2000);
217
			}
218
219
			// SCC mode register
220
			if ((addr & 0xFFFE) == 0xBFFE) {
221
				sccMode = value;
222
				scc.setMode((value & 0x20) ? SCC::Mode::Plus
223
				                           : SCC::Mode::Compatible);
224
				invalidateDeviceRCache(0x9800, 0x800);
225
				invalidateDeviceRCache(0xB800, 0x800);
226
			}
227
			break;
228
		case 1:
229
		{
230
			// Konami
231
			// (Copied from MegaFlashROMSCCPlus)
232
			// Masking of the mapper bits is done on
233
			// write (and only in Konami(-scc) mode)
234
			if ((addr < 0x5000) || ((0x5800 <= addr) && (addr < 0x6000))) break; // only SCC range works
235
			bankRegs[page8kB] = value & 0x7F;
236
			invalidateDeviceRCache(0x4000 + 0x2000 * page8kB, 0x2000);
237
			break;
238
		}
239
		case 2:
240
			// ASCII-8
241
			// (Copied from MegaFlashROMSCCPlus)
242
			if ((0x6000 <= addr) && (addr < 0x8000)) {
243
				byte bank = (addr >> 11) & 0x03;
244
				bankRegs[bank] = value;
245
				invalidateDeviceRCache(0x4000 + 0x2000 * bank, 0x2000);
246
			}
247
			break;
248
		case 3:
249
			// ASCII-16
250
			// (Copied from MegaFlashROMSCCPlus)
251
			// This behaviour is confirmed by Manuel Pazos (creator
252
			// of the cartridge): ASCII-16 uses all 4 bank registers
253
			// and one bank switch changes 2 registers at once.
254
			// This matters when switching mapper mode, because
255
			// the content of the bank registers is unchanged after
256
			// a switch.
257
			if ((0x6000 <= addr) && (addr < 0x6800)) {
258
				bankRegs[0] = narrow_cast<byte>(2 * value + 0);
259
				bankRegs[1] = narrow_cast<byte>(2 * value + 1);
260
				invalidateDeviceRCache(0x4000, 0x4000);
261
			}
262
			if ((0x7000 <= addr) && (addr < 0x7800)) {
263
				bankRegs[2] = narrow_cast<byte>(2 * value + 0);
264
				bankRegs[3] = narrow_cast<byte>(2 * value + 1);
265
				invalidateDeviceRCache(0x8000, 0x4000);
266
			}
267
			break;
268
		default:
269
			UNREACHABLE;
270
		}
271
	} else {
272
		if (flashAddr != unsigned(-1)) {
273
			flash.write(flashAddr, value, time);
274
		}
275
	}
276
}
277
278
byte* ReproCartridgeV2::getWriteCacheLine(uint16_t addr)
279
{
280
	return ((0x4000 <= addr) && (addr < 0xC000))
281
	       ? nullptr        // [0x4000,0xBFFF] isn't cacheable
282
	       : unmappedWrite.data();
283
}
284
285
void ReproCartridgeV2::writeIO(uint16_t port, byte value, EmuTime time)
286
{
287
	switch (port & 0xFF)
288
	{
289
		case 0x10:
290
			psg0x10Latch = value & 0x0F;
291
			break;
292
		case 0x11:
293
			psg0x10.writeRegister(psg0x10Latch, value, time);
294
			break;
295
		case 0xA0:
296
			psg0xA0Latch = value & 0x0F;
297
			break;
298
		case 0xA1:
299
			psg0xA0.writeRegister(psg0xA0Latch, value, time);
300
			break;
301
		case 0x31:
302
			setVolume(time, value);
303
			break;
304
		case 0x33:
305
			mainBankReg = value & 7;
306
			invalidateDeviceRCache(); // flush all to be sure
307
			break;
308
		default: UNREACHABLE;
309
	}
310
}
311
312
void ReproCartridgeV2::setVolume(EmuTime time, byte value)
313
{
314
	// store (mostly for the save/loadstate feature)
315
	volumeReg = value;
316
	// EE (7-6) = PSG#10. So values 0-3
317
	// III(5-3) = PSG#A0. So values 0-7
318
	// SSS(2-0) = SCC.    So values 0-7
319
	scc    .setSoftwareVolume(narrow<float>((volumeReg >> 0) & 7) * 0.5f, time);
320
	psg0xA0.setSoftwareVolume(narrow<float>((volumeReg >> 3) & 7) * 0.5f, time);
321
	psg0x10.setSoftwareVolume(narrow<float>((volumeReg >> 6) & 3) * 0.5f, time);
322
}
323
324
template<typename Archive>
325
void ReproCartridgeV2::serialize(Archive& ar, unsigned /*version*/)
326
{
327
	// skip MSXRom base class
328
	ar.template serializeBase<MSXDevice>(*this);
329
330
	ar.serialize("flash",         flash,
331
	             "scc",           scc,
332
	             "psg0x10",       psg0x10,
333
	             "psg0x10Latch",  psg0x10Latch,
334
	             "psg0xA0",       psg0xA0,
335
	             "psg0xA0Latch",  psg0xA0Latch,
336
	             "flashRomWriteEnabled", flashRomWriteEnabled,
337
	             "mainBankReg",   mainBankReg,
338
	             "volumeReg",     volumeReg,
339
	             "mapperTypeReg", mapperTypeReg,
340
	             "sccMode",       sccMode,
341
	             "bankRegs",      bankRegs);
342
343
	if constexpr (Archive::IS_LOADER) {
344
		auto time = getCurrentTime();
345
		setVolume(time, volumeReg);
346
	}
347
348
}
349
INSTANTIATE_SERIALIZE_METHODS(ReproCartridgeV2);
350
REGISTER_MSXDEVICE(ReproCartridgeV2, "ReproCartridgeV2");
351
352
} // namespace openmsx