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