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-2019 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 "System.hxx" |
19 | #include "M6532.hxx" |
20 | #include "TIA.hxx" |
21 | #include "Cart4A50.hxx" |
22 | |
23 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
24 | Cartridge4A50::Cartridge4A50(const ByteBuffer& image, size_t size, |
25 | const string& md5, const Settings& settings) |
26 | : Cartridge(settings, md5), |
27 | mySize(size), |
28 | mySliceLow(0), |
29 | mySliceMiddle(0), |
30 | mySliceHigh(0), |
31 | myIsRomLow(true), |
32 | myIsRomMiddle(true), |
33 | myIsRomHigh(true), |
34 | myLastAddress(0), |
35 | myLastData(0) |
36 | { |
37 | // Copy the ROM image into my buffer |
38 | // Supported file sizes are 32/64/128K, which are duplicated if necessary |
39 | if(size < 64_KB) size = 32_KB; |
40 | else if(size < 128_KB) size = 64_KB; |
41 | else size = 128_KB; |
42 | for(uInt32 slice = 0; slice < 128_KB / size; ++slice) |
43 | std::copy_n(image.get(), size, myImage.begin() + (slice*size)); |
44 | |
45 | // We use System::PageAccess.codeAccessBase, but don't allow its use |
46 | // through a pointer, since the address space of 4A50 carts can change |
47 | // at the instruction level, and PageAccess is normally defined at an |
48 | // interval of 64 bytes |
49 | // |
50 | // Instead, access will be through the getAccessFlags and setAccessFlags |
51 | // methods below |
52 | createCodeAccessBase(myImage.size() + myRAM.size()); |
53 | } |
54 | |
55 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
56 | void Cartridge4A50::reset() |
57 | { |
58 | initializeRAM(myRAM.data(), myRAM.size()); |
59 | |
60 | mySliceLow = mySliceMiddle = mySliceHigh = 0; |
61 | myIsRomLow = myIsRomMiddle = myIsRomHigh = true; |
62 | |
63 | myLastData = 0xff; |
64 | myLastAddress = 0xffff; |
65 | |
66 | myBankChanged = true; |
67 | } |
68 | |
69 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
70 | void Cartridge4A50::install(System& system) |
71 | { |
72 | mySystem = &system; |
73 | |
74 | // Map all of the accesses to call peek and poke (We don't yet indicate RAM areas) |
75 | System::PageAccess access(this, System::PageAccessType::READ); |
76 | for(uInt16 addr = 0x1000; addr < 0x2000; addr += System::PAGE_SIZE) |
77 | mySystem->setPageAccess(addr, access); |
78 | |
79 | // Mirror all access in TIA and RIOT; by doing so we're taking responsibility |
80 | // for that address space in peek and poke below. |
81 | mySystem->tia().installDelegate(system, *this); |
82 | mySystem->m6532().installDelegate(system, *this); |
83 | } |
84 | |
85 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
86 | uInt8 Cartridge4A50::peek(uInt16 address) |
87 | { |
88 | uInt8 value = 0; |
89 | |
90 | if(!(address & 0x1000)) // Hotspots below 0x1000 |
91 | { |
92 | // Check for RAM or TIA mirroring |
93 | uInt16 lowAddress = address & 0x3ff; |
94 | if(lowAddress & 0x80) |
95 | value = mySystem->m6532().peek(address); |
96 | else if(!(lowAddress & 0x200)) |
97 | value = mySystem->tia().peek(address); |
98 | |
99 | checkBankSwitch(address, value); |
100 | } |
101 | else |
102 | { |
103 | if((address & 0x1800) == 0x1000) // 2K region from 0x1000 - 0x17ff |
104 | { |
105 | value = myIsRomLow ? myImage[(address & 0x7ff) + mySliceLow] |
106 | : myRAM[(address & 0x7ff) + mySliceLow]; |
107 | } |
108 | else if(((address & 0x1fff) >= 0x1800) && // 1.5K region from 0x1800 - 0x1dff |
109 | ((address & 0x1fff) <= 0x1dff)) |
110 | { |
111 | value = myIsRomMiddle ? myImage[(address & 0x7ff) + mySliceMiddle + 0x10000] |
112 | : myRAM[(address & 0x7ff) + mySliceMiddle]; |
113 | } |
114 | else if((address & 0x1f00) == 0x1e00) // 256B region from 0x1e00 - 0x1eff |
115 | { |
116 | value = myIsRomHigh ? myImage[(address & 0xff) + mySliceHigh + 0x10000] |
117 | : myRAM[(address & 0xff) + mySliceHigh]; |
118 | } |
119 | else if((address & 0x1f00) == 0x1f00) // 256B region from 0x1f00 - 0x1fff |
120 | { |
121 | value = myImage[(address & 0xff) + 0x1ff00]; |
122 | if(!bankLocked() && ((myLastData & 0xe0) == 0x60) && |
123 | ((myLastAddress >= 0x1000) || (myLastAddress < 0x200))) |
124 | mySliceHigh = (mySliceHigh & 0xf0ff) | ((address & 0x8) << 8) | |
125 | ((address & 0x70) << 4); |
126 | } |
127 | } |
128 | myLastData = value; |
129 | myLastAddress = address & 0x1fff; |
130 | |
131 | return value; |
132 | } |
133 | |
134 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
135 | bool Cartridge4A50::poke(uInt16 address, uInt8 value) |
136 | { |
137 | if(!(address & 0x1000)) // Hotspots below 0x1000 |
138 | { |
139 | // Check for RAM or TIA mirroring |
140 | uInt16 lowAddress = address & 0x3ff; |
141 | if(lowAddress & 0x80) |
142 | mySystem->m6532().poke(address, value); |
143 | else if(!(lowAddress & 0x200)) |
144 | mySystem->tia().poke(address, value); |
145 | |
146 | checkBankSwitch(address, value); |
147 | } |
148 | else |
149 | { |
150 | if((address & 0x1800) == 0x1000) // 2K region at 0x1000 - 0x17ff |
151 | { |
152 | if(!myIsRomLow) |
153 | { |
154 | myRAM[(address & 0x7ff) + mySliceLow] = value; |
155 | myBankChanged = true; |
156 | } |
157 | } |
158 | else if(((address & 0x1fff) >= 0x1800) && // 1.5K region at 0x1800 - 0x1dff |
159 | ((address & 0x1fff) <= 0x1dff)) |
160 | { |
161 | if(!myIsRomMiddle) |
162 | { |
163 | myRAM[(address & 0x7ff) + mySliceMiddle] = value; |
164 | myBankChanged = true; |
165 | } |
166 | } |
167 | else if((address & 0x1f00) == 0x1e00) // 256B region at 0x1e00 - 0x1eff |
168 | { |
169 | if(!myIsRomHigh) |
170 | { |
171 | myRAM[(address & 0xff) + mySliceHigh] = value; |
172 | myBankChanged = true; |
173 | } |
174 | } |
175 | else if((address & 0x1f00) == 0x1f00) // 256B region at 0x1f00 - 0x1fff |
176 | { |
177 | if(!bankLocked() && ((myLastData & 0xe0) == 0x60) && |
178 | ((myLastAddress >= 0x1000) || (myLastAddress < 0x200))) |
179 | { |
180 | mySliceHigh = (mySliceHigh & 0xf0ff) | ((address & 0x8) << 8) | |
181 | ((address & 0x70) << 4); |
182 | myBankChanged = true; |
183 | } |
184 | } |
185 | } |
186 | myLastData = value; |
187 | myLastAddress = address & 0x1fff; |
188 | |
189 | return myBankChanged; |
190 | } |
191 | |
192 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
193 | uInt8 Cartridge4A50::getAccessFlags(uInt16 address) const |
194 | { |
195 | if((address & 0x1800) == 0x1000) // 2K region from 0x1000 - 0x17ff |
196 | { |
197 | if(myIsRomLow) |
198 | return myCodeAccessBase[(address & 0x7ff) + mySliceLow]; |
199 | else |
200 | return myCodeAccessBase[131072 + (address & 0x7ff) + mySliceLow]; |
201 | } |
202 | else if(((address & 0x1fff) >= 0x1800) && // 1.5K region from 0x1800 - 0x1dff |
203 | ((address & 0x1fff) <= 0x1dff)) |
204 | { |
205 | if(myIsRomMiddle) |
206 | return myCodeAccessBase[(address & 0x7ff) + mySliceMiddle + 0x10000]; |
207 | else |
208 | return myCodeAccessBase[131072 + (address & 0x7ff) + mySliceMiddle]; |
209 | } |
210 | else if((address & 0x1f00) == 0x1e00) // 256B region from 0x1e00 - 0x1eff |
211 | { |
212 | if(myIsRomHigh) |
213 | return myCodeAccessBase[(address & 0xff) + mySliceHigh + 0x10000]; |
214 | else |
215 | return myCodeAccessBase[131072 + (address & 0xff) + mySliceHigh]; |
216 | } |
217 | else if((address & 0x1f00) == 0x1f00) // 256B region from 0x1f00 - 0x1fff |
218 | { |
219 | return myCodeAccessBase[(address & 0xff) + 0x1ff00]; |
220 | } |
221 | return 0; |
222 | } |
223 | |
224 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
225 | void Cartridge4A50::setAccessFlags(uInt16 address, uInt8 flags) |
226 | { |
227 | if((address & 0x1800) == 0x1000) // 2K region from 0x1000 - 0x17ff |
228 | { |
229 | if(myIsRomLow) |
230 | myCodeAccessBase[(address & 0x7ff) + mySliceLow] |= flags; |
231 | else |
232 | myCodeAccessBase[131072 + (address & 0x7ff) + mySliceLow] |= flags; |
233 | } |
234 | else if(((address & 0x1fff) >= 0x1800) && // 1.5K region from 0x1800 - 0x1dff |
235 | ((address & 0x1fff) <= 0x1dff)) |
236 | { |
237 | if(myIsRomMiddle) |
238 | myCodeAccessBase[(address & 0x7ff) + mySliceMiddle + 0x10000] |= flags; |
239 | else |
240 | myCodeAccessBase[131072 + (address & 0x7ff) + mySliceMiddle] |= flags; |
241 | } |
242 | else if((address & 0x1f00) == 0x1e00) // 256B region from 0x1e00 - 0x1eff |
243 | { |
244 | if(myIsRomHigh) |
245 | myCodeAccessBase[(address & 0xff) + mySliceHigh + 0x10000] |= flags; |
246 | else |
247 | myCodeAccessBase[131072 + (address & 0xff) + mySliceHigh] |= flags; |
248 | } |
249 | else if((address & 0x1f00) == 0x1f00) // 256B region from 0x1f00 - 0x1fff |
250 | { |
251 | myCodeAccessBase[(address & 0xff) + 0x1ff00] |= flags; |
252 | } |
253 | } |
254 | |
255 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
256 | void Cartridge4A50::checkBankSwitch(uInt16 address, uInt8 value) |
257 | { |
258 | if(bankLocked()) return; |
259 | |
260 | // This scheme contains so many hotspots that it's easier to just check |
261 | // all of them |
262 | if(((myLastData & 0xe0) == 0x60) && // Switch lower/middle/upper bank |
263 | ((myLastAddress >= 0x1000) || (myLastAddress < 0x200))) |
264 | { |
265 | if((address & 0x0f00) == 0x0c00) // Enable 256B of ROM at 0x1e00 - 0x1eff |
266 | bankROMHigh(address & 0xff); |
267 | else if((address & 0x0f00) == 0x0d00) // Enable 256B of RAM at 0x1e00 - 0x1eff |
268 | bankRAMHigh(address & 0x7f); |
269 | else if((address & 0x0f40) == 0x0e00) // Enable 2K of ROM at 0x1000 - 0x17ff |
270 | bankROMLower(address & 0x1f); |
271 | else if((address & 0x0f40) == 0x0e40) // Enable 2K of RAM at 0x1000 - 0x17ff |
272 | bankRAMLower(address & 0xf); |
273 | else if((address & 0x0f40) == 0x0f00) // Enable 1.5K of ROM at 0x1800 - 0x1dff |
274 | bankROMMiddle(address & 0x1f); |
275 | else if((address & 0x0f50) == 0x0f40) // Enable 1.5K of RAM at 0x1800 - 0x1dff |
276 | bankRAMMiddle(address & 0xf); |
277 | |
278 | // Stella helper functions |
279 | else if((address & 0x0f00) == 0x0400) // Toggle bit A11 of lower block address |
280 | { |
281 | mySliceLow = mySliceLow ^ 0x800; |
282 | myBankChanged = true; |
283 | } |
284 | else if((address & 0x0f00) == 0x0500) // Toggle bit A12 of lower block address |
285 | { |
286 | mySliceLow = mySliceLow ^ 0x1000; |
287 | myBankChanged = true; |
288 | } |
289 | else if((address & 0x0f00) == 0x0800) // Toggle bit A11 of middle block address |
290 | { |
291 | mySliceMiddle = mySliceMiddle ^ 0x800; |
292 | myBankChanged = true; |
293 | } |
294 | else if((address & 0x0f00) == 0x0900) // Toggle bit A12 of middle block address |
295 | { |
296 | mySliceMiddle = mySliceMiddle ^ 0x1000; |
297 | myBankChanged = true; |
298 | } |
299 | } |
300 | |
301 | // Zero-page hotspots for upper page |
302 | // 0xf4, 0xf6, 0xfc, 0xfe for ROM |
303 | // 0xf5, 0xf7, 0xfd, 0xff for RAM |
304 | // 0x74 - 0x7f (0x80 bytes lower) |
305 | if((address & 0xf75) == 0x74) // Enable 256B of ROM at 0x1e00 - 0x1eff |
306 | bankROMHigh(value); |
307 | else if((address & 0xf75) == 0x75) // Enable 256B of RAM at 0x1e00 - 0x1eff |
308 | bankRAMHigh(value & 0x7f); |
309 | |
310 | // Zero-page hotspots for lower and middle blocks |
311 | // 0xf8, 0xf9, 0xfa, 0xfb |
312 | // 0x78, 0x79, 0x7a, 0x7b (0x80 bytes lower) |
313 | else if((address & 0xf7c) == 0x78) |
314 | { |
315 | if((value & 0xf0) == 0) // Enable 2K of ROM at 0x1000 - 0x17ff |
316 | bankROMLower(value & 0xf); |
317 | else if((value & 0xf0) == 0x40) // Enable 2K of RAM at 0x1000 - 0x17ff |
318 | bankRAMLower(value & 0xf); |
319 | else if((value & 0xf0) == 0x90) // Enable 1.5K of ROM at 0x1800 - 0x1dff |
320 | bankROMMiddle((value & 0xf) | 0x10); |
321 | else if((value & 0xf0) == 0xc0) // Enable 1.5K of RAM at 0x1800 - 0x1dff |
322 | bankRAMMiddle(value & 0xf); |
323 | } |
324 | } |
325 | |
326 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
327 | bool Cartridge4A50::patch(uInt16 address, uInt8 value) |
328 | { |
329 | if((address & 0x1800) == 0x1000) // 2K region from 0x1000 - 0x17ff |
330 | { |
331 | if(myIsRomLow) |
332 | myImage[(address & 0x7ff) + mySliceLow] = value; |
333 | else |
334 | myRAM[(address & 0x7ff) + mySliceLow] = value; |
335 | } |
336 | else if(((address & 0x1fff) >= 0x1800) && // 1.5K region from 0x1800 - 0x1dff |
337 | ((address & 0x1fff) <= 0x1dff)) |
338 | { |
339 | if(myIsRomMiddle) |
340 | myImage[(address & 0x7ff) + mySliceMiddle + 0x10000] = value; |
341 | else |
342 | myRAM[(address & 0x7ff) + mySliceMiddle] = value; |
343 | } |
344 | else if((address & 0x1f00) == 0x1e00) // 256B region from 0x1e00 - 0x1eff |
345 | { |
346 | if(myIsRomHigh) |
347 | myImage[(address & 0xff) + mySliceHigh + 0x10000] = value; |
348 | else |
349 | myRAM[(address & 0xff) + mySliceHigh] = value; |
350 | } |
351 | else if((address & 0x1f00) == 0x1f00) // 256B region from 0x1f00 - 0x1fff |
352 | { |
353 | myImage[(address & 0xff) + 0x1ff00] = value; |
354 | } |
355 | return myBankChanged = true; |
356 | } |
357 | |
358 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
359 | const uInt8* Cartridge4A50::getImage(size_t& size) const |
360 | { |
361 | size = mySize; |
362 | return myImage.data(); |
363 | } |
364 | |
365 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
366 | bool Cartridge4A50::save(Serializer& out) const |
367 | { |
368 | try |
369 | { |
370 | // The 32K bytes of RAM |
371 | out.putByteArray(myRAM.data(), myRAM.size()); |
372 | |
373 | // Index pointers |
374 | out.putShort(mySliceLow); |
375 | out.putShort(mySliceMiddle); |
376 | out.putShort(mySliceHigh); |
377 | |
378 | // Whether index pointers are for ROM or RAM |
379 | out.putBool(myIsRomLow); |
380 | out.putBool(myIsRomMiddle); |
381 | out.putBool(myIsRomHigh); |
382 | |
383 | // Last address and data values |
384 | out.putByte(myLastData); |
385 | out.putShort(myLastAddress); |
386 | } |
387 | catch(...) |
388 | { |
389 | cerr << "ERROR: Cartridge4A40::save" << endl; |
390 | return false; |
391 | } |
392 | |
393 | return true; |
394 | } |
395 | |
396 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
397 | bool Cartridge4A50::load(Serializer& in) |
398 | { |
399 | try |
400 | { |
401 | in.getByteArray(myRAM.data(), myRAM.size()); |
402 | |
403 | // Index pointers |
404 | mySliceLow = in.getShort(); |
405 | mySliceMiddle = in.getShort(); |
406 | mySliceHigh = in.getShort(); |
407 | |
408 | // Whether index pointers are for ROM or RAM |
409 | myIsRomLow = in.getBool(); |
410 | myIsRomMiddle = in.getBool(); |
411 | myIsRomHigh = in.getBool(); |
412 | |
413 | // Last address and data values |
414 | myLastData = in.getByte(); |
415 | myLastAddress = in.getShort(); |
416 | } |
417 | catch(...) |
418 | { |
419 | cerr << "ERROR: Cartridge4A50::load" << endl; |
420 | return false; |
421 | } |
422 | |
423 | return true; |
424 | } |
425 | |