1 //============================================================================
2 //
3 // SSSS tt lll lll
4 // SS SS tt ll ll
5 // SS tttttt eeee ll ll aaaa
6 // SSSS tt ee ee ll ll aa
7 // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8 // SS SS tt ee ll ll aa aa
9 // SSSS ttt eeeee llll llll aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17
18 #include "CompuMate.hxx"
19 #include "System.hxx"
20 #include "M6532.hxx"
21 #include "CartCM.hxx"
22
23 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartridgeCM(const ByteBuffer & image,size_t size,const string & md5,const Settings & settings)24 CartridgeCM::CartridgeCM(const ByteBuffer& image, size_t size,
25 const string& md5, const Settings& settings)
26 : Cartridge(settings, md5),
27 myImage{make_unique<uInt8[]>(16_KB)}
28 {
29 // Copy the ROM image into my buffer
30 std::copy_n(image.get(), std::min(16_KB, size), myImage.get());
31 createRomAccessArrays(16_KB);
32 }
33
34 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
reset()35 void CartridgeCM::reset()
36 {
37 initializeRAM(myRAM.data(), myRAM.size());
38
39 // On powerup, the last bank of ROM is enabled and RAM is disabled
40 mySWCHA = 0xFF;
41 initializeStartBank(mySWCHA & 0x3);
42
43 // Upon reset we switch to the startup bank
44 bank(startBank());
45 }
46
47 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
install(System & system)48 void CartridgeCM::install(System& system)
49 {
50 mySystem = &system;
51
52 // Mirror all access in RIOT; by doing so we're taking responsibility
53 // for that address space in peek and poke below.
54 mySystem->m6532().installDelegate(system, *this);
55
56 // Install pages for the startup bank
57 bank(startBank());
58 }
59
60 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
peek(uInt16 address)61 uInt8 CartridgeCM::peek(uInt16 address)
62 {
63 // NOTE: This does not handle accessing cart ROM/RAM, however, this method
64 // should never be called for ROM/RAM because of the way page accessing
65 // has been setup (it will only ever be called for RIOT reads)
66 return mySystem->m6532().peek(address);
67 }
68
69 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
poke(uInt16 address,uInt8 value)70 bool CartridgeCM::poke(uInt16 address, uInt8 value)
71 {
72 // NOTE: This could be called for RIOT writes or cart ROM writes
73 // In the latter case, the write is ignored
74 if(!(address & 0x1000))
75 {
76 // RIOT mirroring, check bankswitch
77 if(address == 0x280)
78 {
79 mySWCHA = value;
80 bank(mySWCHA & 0x3);
81 if(myCompuMate)
82 {
83 uInt8& column = myCompuMate->column();
84 if(value & 0x20)
85 column = 0;
86 if(value & 0x40)
87 column = (column + 1) % 10;
88 }
89 }
90 mySystem->m6532().poke(address, value);
91 }
92 return myBankChanged;
93 }
94
95 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
column() const96 uInt8 CartridgeCM::column() const
97 {
98 return myCompuMate->column();
99 }
100
101 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bank(uInt16 bank,uInt16)102 bool CartridgeCM::bank(uInt16 bank, uInt16)
103 {
104 if(hotspotsLocked()) return false;
105
106 // Remember what bank we're in
107 myBankOffset = bank << 12;
108
109 // Although this scheme contains four 4K ROM banks and one 2K RAM bank,
110 // it's easier to think of things in terms of 2K slices, as follows:
111 //
112 // The lower 2K of cart address space always points to the lower 2K of the
113 // current ROM bank
114 // The upper 2K of cart address space can point to either the 2K of RAM or
115 // the upper 2K of the current ROM bank
116
117 System::PageAccess access(this, System::PageAccessType::READ);
118
119 // Lower 2K (always ROM)
120 for(uInt16 addr = 0x1000; addr < 0x1800; addr += System::PAGE_SIZE)
121 {
122 access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)];
123 access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)];
124 access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)];
125 access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize];
126 mySystem->setPageAccess(addr, access);
127 }
128
129 // Upper 2K (RAM or ROM)
130 for(uInt16 addr = 0x1800; addr < 0x2000; addr += System::PAGE_SIZE)
131 {
132 access.type = System::PageAccessType::READWRITE;
133
134 if(mySWCHA & 0x10)
135 {
136 access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)];
137 access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x0FFF)];
138 access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF)];
139 access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x0FFF) + myAccessSize];
140 }
141 else
142 {
143 access.directPeekBase = &myRAM[addr & 0x7FF];
144 access.romAccessBase = &myRomAccessBase[myBankOffset + (addr & 0x07FF)];
145 access.romPeekCounter = &myRomAccessCounter[myBankOffset + (addr & 0x07FF)];
146 access.romPokeCounter = &myRomAccessCounter[myBankOffset + (addr & 0x07FF) + myAccessSize];
147 }
148
149 if((mySWCHA & 0x30) == 0x20)
150 access.directPokeBase = &myRAM[addr & 0x7FF];
151 else
152 access.directPokeBase = nullptr;
153
154 mySystem->setPageAccess(addr, access);
155 }
156
157 return myBankChanged = true;
158 }
159
160 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getBank(uInt16) const161 uInt16 CartridgeCM::getBank(uInt16) const
162 {
163 return myBankOffset >> 12;
164 }
165
166 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
romBankCount() const167 uInt16 CartridgeCM::romBankCount() const
168 {
169 // We report 4 banks (of ROM), even though RAM can overlap the upper 2K
170 // of cart address space at some times
171 // However, this RAM isn't enabled in the normal way that bankswitching
172 // works, so it is ignored here
173 return 4;
174 }
175
176 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
patch(uInt16 address,uInt8 value)177 bool CartridgeCM::patch(uInt16 address, uInt8 value)
178 {
179 if((mySWCHA & 0x30) == 0x20)
180 myRAM[address & 0x7FF] = value;
181 else
182 myImage[myBankOffset + address] = value;
183
184 return myBankChanged = true;
185 }
186
187 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
getImage(size_t & size) const188 const ByteBuffer& CartridgeCM::getImage(size_t& size) const
189 {
190 size = 16_KB;
191 return myImage;
192 }
193
194 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
save(Serializer & out) const195 bool CartridgeCM::save(Serializer& out) const
196 {
197 try
198 {
199 out.putShort(myBankOffset);
200 out.putByte(mySWCHA);
201 out.putByte(myCompuMate->column());
202 out.putByteArray(myRAM.data(), myRAM.size());
203 }
204 catch(...)
205 {
206 cerr << "ERROR: CartridgeCM::save" << endl;
207 return false;
208 }
209
210 return true;
211 }
212
213 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
load(Serializer & in)214 bool CartridgeCM::load(Serializer& in)
215 {
216 try
217 {
218 myBankOffset = in.getShort();
219 mySWCHA = in.getByte();
220 myCompuMate->column() = in.getByte();
221 in.getByteArray(myRAM.data(), myRAM.size());
222 }
223 catch(...)
224 {
225 cerr << "ERROR: CartridgeCM::load" << endl;
226 return false;
227 }
228
229 // Remember what bank we were in
230 bank(myBankOffset >> 12);
231
232 return true;
233 }
234