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 "TIA.hxx"
20#include "CartDASH.hxx"
21
22// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
23CartridgeDASH::CartridgeDASH(const ByteBuffer& image, size_t size,
24 const string& md5, const Settings& settings)
25 : Cartridge(settings, md5),
26 mySize(size)
27{
28 // Allocate array for the ROM image
29 myImage = make_unique<uInt8[]>(mySize);
30
31 // Copy the ROM image into my buffer
32 std::copy_n(image.get(), mySize, myImage.get());
33 createCodeAccessBase(mySize + myRAM.size());
34}
35
36// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
37void CartridgeDASH::reset()
38{
39 initializeRAM(myRAM.data(), myRAM.size());
40
41 // Remember startup bank (0 per spec, rather than last per 3E scheme).
42 // Set this to go to 3rd 1K Bank.
43 initializeStartBank(0);
44
45 // Initialise bank values for all ROM/RAM access
46 // This is used to reverse-lookup from address to bank location
47 for(uInt32 b = 0; b < 8; ++b)
48 {
49 bankInUse[b] = BANK_UNDEFINED; // bank is undefined and inaccessible!
50 segmentInUse[b/2] = BANK_UNDEFINED;
51 }
52 initializeBankState();
53
54 // We'll map the startup banks 0 and 3 from the image into the third 1K bank upon reset
55 bankROM((0 << BANK_BITS) | 0);
56 bankROM((3 << BANK_BITS) | 0);
57}
58
59// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
60void CartridgeDASH::install(System& system)
61{
62 mySystem = &system;
63
64 System::PageAccess access(this, System::PageAccessType::READWRITE);
65
66 // The hotspots are in TIA address space, so we claim it here
67 for (uInt16 addr = 0x00; addr < 0x40; addr += System::PAGE_SIZE)
68 mySystem->setPageAccess(addr, access);
69
70 // Initialise bank values for all ROM/RAM access
71 // This is used to reverse-lookup from address to bank location
72 for (uInt32 b = 0; b < 8; ++b)
73 {
74 bankInUse[b] = BANK_UNDEFINED; // bank is undefined and inaccessible!
75 segmentInUse[b/2] = BANK_UNDEFINED;
76 }
77 initializeBankState();
78
79 // Setup the last segment (of 4, each 1K) to point to the first ROM slice
80 // Actually we DO NOT want "always". It's just on bootup, and can be out switched later
81 bankROM((0 << BANK_BITS) | 0);
82 bankROM((3 << BANK_BITS) | 0);
83}
84
85// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
86uInt8 CartridgeDASH::peek(uInt16 address)
87{
88 uInt16 peekAddress = address;
89 address &= 0x0FFF; // restrict to 4K address range
90
91 uInt8 value = 0;
92 uInt32 bank = (address >> (ROM_BANK_TO_POWER - 1)) & 7; // convert to 512 byte bank index (0-7)
93 uInt16 imageBank = bankInUse[bank]; // the ROM/RAM bank that's here
94
95 if(imageBank == BANK_UNDEFINED) // an uninitialised bank?
96 {
97 // accessing invalid bank, so return should be... random?
98 value = mySystem->randGenerator().next();
99
100 }
101 else if(imageBank & BITMASK_ROMRAM) // a RAM bank
102 {
103 Int32 ramBank = imageBank & BIT_BANK_MASK; // discard irrelevant bits
104 Int32 offset = ramBank << RAM_BANK_TO_POWER; // base bank address in RAM
105 offset += (address & BITMASK_RAM_BANK); // + byte offset in RAM bank
106
107 return peekRAM(myRAM[offset], peekAddress);
108 }
109
110 return value;
111}
112
113// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
114bool CartridgeDASH::poke(uInt16 address, uInt8 value)
115{
116 bool changed = false;
117
118 // Check for write to the bank switch address. RAM/ROM and bank # are encoded in 'value'
119 // There are NO mirrored hotspots.
120
121 if (address == BANK_SWITCH_HOTSPOT_RAM)
122 changed = bankRAM(value);
123 else if (address == BANK_SWITCH_HOTSPOT_ROM)
124 changed = bankROM(value);
125
126 if(!(address & 0x1000))
127 {
128 // Handle TIA space that we claimed above
129 changed = changed || mySystem->tia().poke(address, value);
130 }
131 else
132 {
133 uInt32 bankNumber = (address >> RAM_BANK_TO_POWER) & 7; // now 512 byte bank # (ie: 0-7)
134 Int16 whichBankIsThere = bankInUse[bankNumber]; // ROM or RAM bank reference
135
136 if(whichBankIsThere & BITMASK_ROMRAM)
137 {
138 uInt32 byteOffset = address & BITMASK_RAM_BANK;
139 uInt32 baseAddress = ((whichBankIsThere & BIT_BANK_MASK) << RAM_BANK_TO_POWER) + byteOffset;
140 pokeRAM(myRAM[baseAddress], address, value);
141 changed = true;
142 }
143 }
144
145 return changed;
146}
147
148// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
149bool CartridgeDASH::bankRAM(uInt8 bank)
150{
151 if (bankLocked()) // debugger can lock RAM
152 return false;
153
154 // Each RAM bank uses two slots, separated by 0x800 in memory -- one read, one write.
155 bankRAMSlot(bank | BITMASK_ROMRAM | 0);
156 bankRAMSlot(bank | BITMASK_ROMRAM | BITMASK_LOWERUPPER);
157
158 // Remember that this hotspot was accessed for RAM
159 segmentInUse[(bank >> BANK_BITS) & 3] = bank | BITMASK_ROMRAM;
160
161 return myBankChanged = true;
162}
163
164// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
165void CartridgeDASH::bankRAMSlot(uInt16 bank)
166{
167 uInt16 bankNumber = (bank >> BANK_BITS) & 3; // which bank # we are switching TO (BITS D6,D7) to 512 byte block
168 uInt16 currentBank = bank & BIT_BANK_MASK; // Wrap around/restrict to valid range
169 bool upper = bank & BITMASK_LOWERUPPER; // is this the read or write port
170
171 uInt32 startCurrentBank = currentBank << RAM_BANK_TO_POWER; // Effectively * 512 bytes
172
173 // Setup the page access methods for the current bank
174 System::PageAccess access(this, System::PageAccessType::READ);
175
176 if(upper) // We're mapping the write port
177 {
178 bankInUse[bankNumber + 4] = Int16(bank);
179 access.type = System::PageAccessType::WRITE;
180 }
181 else // We're mapping the read port
182 {
183 bankInUse[bankNumber] = Int16(bank);
184 access.type = System::PageAccessType::READ;
185 }
186
187 uInt16 start = 0x1000 + (bankNumber << RAM_BANK_TO_POWER) + (upper ? RAM_WRITE_OFFSET : 0);
188 uInt16 end = start + RAM_BANK_SIZE - 1;
189
190 for (uInt16 addr = start; addr <= end; addr += System::PAGE_SIZE)
191 {
192 if(!upper)
193 access.directPeekBase = &myRAM[startCurrentBank + (addr & (RAM_BANK_SIZE - 1))];
194
195 access.codeAccessBase = &myCodeAccessBase[mySize + startCurrentBank + (addr & (RAM_BANK_SIZE - 1))];
196 mySystem->setPageAccess(addr, access);
197 }
198}
199
200// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
201bool CartridgeDASH::bankROM(uInt8 bank)
202{
203 if (bankLocked()) // debugger can lock ROM
204 return false;
205
206 // Map ROM bank image into the system into the correct slot
207 // Memory map is 1K slots at 0x1000, 0x1400, 0x1800, 0x1C00
208 // Each ROM uses 2 consecutive 512 byte slots
209 bankROMSlot(bank | 0);
210 bankROMSlot(bank | BITMASK_LOWERUPPER);
211
212 // Remember that this hotspot was accessed for ROM
213 segmentInUse[(bank >> BANK_BITS) & 3] = bank;
214
215 return myBankChanged = true;
216}
217
218// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
219void CartridgeDASH::bankROMSlot(uInt16 bank)
220{
221 uInt16 bankNumber = (bank >> BANK_BITS) & 3; // which bank # we are switching TO (BITS D6,D7)
222 uInt16 currentBank = bank & BIT_BANK_MASK; // Wrap around/restrict to valid range
223 bool upper = bank & BITMASK_LOWERUPPER; // is this the lower or upper 512b
224
225 bankInUse[bankNumber * 2 + (upper ? 1 : 0)] = Int16(bank); // Record which bank switched in (as ROM)
226
227 uInt32 startCurrentBank = currentBank << ROM_BANK_TO_POWER; // Effectively *1K
228
229 // Setup the page access methods for the current bank
230 System::PageAccess access(this, System::PageAccessType::READ);
231
232 uInt16 start = 0x1000 + (bankNumber << ROM_BANK_TO_POWER) + (upper ? ROM_BANK_SIZE / 2 : 0);
233 uInt16 end = start + ROM_BANK_SIZE / 2 - 1;
234
235 for (uInt16 addr = start; addr <= end; addr += System::PAGE_SIZE)
236 {
237 access.directPeekBase = &myImage[startCurrentBank + (addr & (ROM_BANK_SIZE - 1))];
238 access.codeAccessBase = &myCodeAccessBase[startCurrentBank + (addr & (ROM_BANK_SIZE - 1))];
239 mySystem->setPageAccess(addr, access);
240 }
241}
242
243// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
244void CartridgeDASH::initializeBankState()
245{
246 // Switch in each 512b slot
247 for(uInt32 b = 0; b < 8; ++b)
248 {
249 if(bankInUse[b] == BANK_UNDEFINED)
250 {
251 // All accesses point to peek/poke above
252 System::PageAccess access(this, System::PageAccessType::READ);
253 uInt16 start = 0x1000 + (b << RAM_BANK_TO_POWER);
254 uInt16 end = start + RAM_BANK_SIZE - 1;
255 for (uInt16 addr = start; addr <= end; addr += System::PAGE_SIZE)
256 mySystem->setPageAccess(addr, access);
257 }
258 else if (bankInUse[b] & BITMASK_ROMRAM)
259 bankRAMSlot(bankInUse[b]);
260 else
261 bankROMSlot(bankInUse[b]);
262 }
263}
264
265// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
266bool CartridgeDASH::patch(uInt16 address, uInt8 value)
267{
268#if 0
269 // Patch the cartridge ROM (for debugger)
270
271 myBankChanged = true;
272
273 uInt32 bankNumber = (address >> RAM_BANK_TO_POWER) & 7; // now 512 byte bank # (ie: 0-7)
274 Int16 whichBankIsThere = bankInUse[bankNumber]; // ROM or RAM bank reference
275
276 if (whichBankIsThere == BANK_UNDEFINED) {
277
278 // We're trying to access undefined memory (no bank here yet). Fail!
279 myBankChanged = false;
280
281 } else if (whichBankIsThere & BITMASK_ROMRAM) { // patching RAM (512 byte banks)
282
283 uInt32 byteOffset = address & BITMASK_RAM_BANK;
284 uInt32 baseAddress = ((whichBankIsThere & BIT_BANK_MASK) << RAM_BANK_TO_POWER) + byteOffset;
285 myRAM[baseAddress] = value; // write to RAM
286
287 // TODO: Stephen -- should we set 'myBankChanged' true when there's a RAM write?
288
289 } else { // patching ROM (1K banks)
290
291 uInt32 byteOffset = address & BITMASK_ROM_BANK;
292 uInt32 baseAddress = (whichBankIsThere << ROM_BANK_TO_POWER) + byteOffset;
293 myImage[baseAddress] = value; // write to the image
294 }
295
296 return myBankChanged;
297#else
298 return false;
299#endif
300}
301
302// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
303const uInt8* CartridgeDASH::getImage(size_t& size) const
304{
305 size = mySize;
306 return myImage.get();
307}
308
309// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
310bool CartridgeDASH::save(Serializer& out) const
311{
312 try
313 {
314 out.putShortArray(bankInUse.data(), bankInUse.size());
315 out.putShortArray(segmentInUse.data(), segmentInUse.size());
316 out.putByteArray(myRAM.data(), myRAM.size());
317 }
318 catch (...)
319 {
320 cerr << "ERROR: CartridgeDASH::save" << endl;
321 return false;
322 }
323 return true;
324}
325
326// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
327bool CartridgeDASH::load(Serializer& in)
328{
329 try
330 {
331 in.getShortArray(bankInUse.data(), bankInUse.size());
332 in.getShortArray(segmentInUse.data(), segmentInUse.size());
333 in.getByteArray(myRAM.data(), myRAM.size());
334 }
335 catch (...)
336 {
337 cerr << "ERROR: CartridgeDASH::load" << endl;
338 return false;
339 }
340
341 initializeBankState();
342 return true;
343}
344