OXYGENHUB
◆ main openmsx / src / memory / ROMHunterMk2.cc
4.5 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
1
#include "ROMHunterMk2.hh"
2
3
#include "narrow.hh"
4
#include "serialize.hh"
5
6
#include <algorithm>
7
#include <cassert>
8
9
/*
10
11
As reverse engineered by BiFi:
12
13
At 0x3FFF is a configuration register.
14
bit 1: page 1 control:
15
       0: ROM hunter ROM in page 1 for READs, writes ignored (default)
16
       1: writes go to RAM in page 1, mapper switching disabled
17
bits 2 and 0 is a mapper selector:
18
  00 = ASCII16 (default),
19
  01 = ASCII8,
20
  11 = Konami,
21
  10 = unknown (seems to be some Konami 16K variant)
22
bit 3: RAM write protect:
23
       0: write enable (default)
24
       1: write protect; also sets reads in page 1 to RAM (and not ROM)
25
26
When the ROM loads a MegaROM, bit 1 is left on 0. But for plain ROMs, it is set
27
to 1.
28
*/
29
30
namespace openmsx {
31
32
ROMHunterMk2::ROMHunterMk2(const DeviceConfig& config, Rom&& rom_)
33
	: MSXRom(config, std::move(rom_))
34
	, ram(config, getName() + " RAM", "ROM Hunter Mk 2 RAM", 0x40000)
35
{
36
	reset(getCurrentTime());
37
}
38
39
void ROMHunterMk2::reset(EmuTime /*time*/)
40
{
41
	configReg = 0;
42
	std::ranges::fill(bankRegs, 0);
43
	invalidateDeviceRCache(); // flush all to be sure
44
}
45
46
unsigned ROMHunterMk2::getRamAddr(unsigned addr) const
47
{
48
	unsigned page = (addr >> 13) - 2;
49
	assert(page < 4);
50
	unsigned bank = bankRegs[page];
51
	return (bank * 0x2000) + (addr & 0x1FFF);
52
}
53
54
const byte* ROMHunterMk2::getReadCacheLine(uint16_t addr) const
55
{
56
	// reads outside [0x4000, 0xC000) return 0xFF
57
	if ((addr < 0x4000) || (0xC000 <= addr)) {
58
		return unmappedRead.data();
59
	}
60
61
	// if bit 3 is 0, reads from page 1 come from the ROM, else from the RAM
62
	if (((configReg & 0b1000) == 0) && (addr < 0x8000)) {
63
		return &rom[addr & 0x1FFF];
64
	} else {
65
		return &ram[getRamAddr(addr)];
66
	}
67
}
68
69
byte ROMHunterMk2::peekMem(uint16_t addr, EmuTime /*time*/) const
70
{
71
	return *getReadCacheLine(addr);
72
}
73
74
byte ROMHunterMk2::readMem(uint16_t addr, EmuTime time)
75
{
76
	return peekMem(addr, time); // reads have no side effects
77
}
78
79
void ROMHunterMk2::writeMem(uint16_t addr, byte value, EmuTime /*time*/)
80
{
81
	// config register at address 0x3FFF
82
	if (addr == 0x3FFF) {
83
		configReg = value;
84
		invalidateDeviceRCache(0x4000, 0x8000);
85
		return;
86
	}
87
88
	// ignore (other) writes outside [0x4000, 0xC000)
89
	if ((addr < 0x4000) || (0xC000 <= addr)) {
90
		return;
91
	}
92
93
	// address is calculated before writes to other regions take effect
94
	unsigned ramAddr = getRamAddr(addr);
95
96
	// only write mapper registers if bit 1 is not set
97
	if ((configReg & 0b10) == 0) {
98
		// (possibly) write to bank registers
99
		switch (configReg & 0b101) {
100
		case 0b000: {
101
			// ASCII-16
102
			// Implemented similarly as the (much later) MFR SCC+,
103
			// TODO did we verify that ROMHunterMk2 behaves the same?
104
			//
105
			// ASCII-16 uses all 4 bank registers and one bank
106
			// switch changes 2 registers at once. This matters
107
			// when switching mapper mode, because the content of
108
			// the bank registers is unchanged after a switch.
109
			const byte maskedValue = value & 0xF;
110
			if ((0x6000 <= addr) && (addr < 0x6800)) {
111
				bankRegs[0] = narrow_cast<byte>(2 * maskedValue + 0);
112
				bankRegs[1] = narrow_cast<byte>(2 * maskedValue + 1);
113
				invalidateDeviceRCache(0x4000, 0x4000);
114
			}
115
			if ((0x7000 <= addr) && (addr < 0x7800)) {
116
				bankRegs[2] = narrow_cast<byte>(2 * maskedValue + 0);
117
				bankRegs[3] = narrow_cast<byte>(2 * maskedValue + 1);
118
				invalidateDeviceRCache(0x8000, 0x4000);
119
			}
120
			break;
121
		}
122
		case 0b001:
123
			// ASCII-8
124
			if ((0x6000 <= addr) && (addr < 0x8000)) {
125
				byte bank = (addr >> 11) & 0x03;
126
				bankRegs[bank] = value & 0x1F;
127
				invalidateDeviceRCache(0x4000 + 0x2000 * bank, 0x2000);
128
			}
129
			break;
130
		case 0b101:
131
			// Konami
132
			if ((0x6000 <= addr) && (addr < 0xC000)) {
133
				unsigned bank = (addr >> 13) - 2;
134
				bankRegs[bank] = value & 0x1F;
135
				invalidateDeviceRCache(0x4000 + 0x2000 * bank, 0x2000);
136
			}
137
			break;
138
		case 0b100:
139
			// TODO how does this configuration behave?
140
			break;
141
		}
142
	}
143
144
	// write to RAM, if not write-protected
145
	if ((configReg & 0b1000) == 0) {
146
		// if write to [0x8000, 0xC000), just do it
147
		// if write to [0x4000, 0x8000), only do it if bit 1 is set
148
		if ((addr >= 0x8000) || ((configReg & 0b10) == 0b10)) {
149
			ram[ramAddr] = value;
150
		}
151
	}
152
}
153
154
byte* ROMHunterMk2::getWriteCacheLine(uint16_t /*addr*/)
155
{
156
	return nullptr;
157
}
158
159
template<typename Archive>
160
void ROMHunterMk2::serialize(Archive& ar, unsigned /*version*/)
161
{
162
	// skip MSXRom base class
163
	ar.template serializeBase<MSXDevice>(*this);
164
	ar.serialize("ram",       ram,
165
	             "configReg", configReg,
166
	             "bankRegs",  bankRegs);
167
}
168
INSTANTIATE_SERIALIZE_METHODS(ROMHunterMk2);
169
REGISTER_MSXDEVICE(ROMHunterMk2, "ROMHunterMk2");
170
171
} // namespace openmsx