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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
24Cartridge4A50::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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
56void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
70void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
86uInt8 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
135bool 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
193uInt8 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
225void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
256void 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
327bool 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
359const uInt8* Cartridge4A50::getImage(size_t& size) const
360{
361 size = mySize;
362 return myImage.data();
363}
364
365// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
366bool 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
397bool 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