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 "TIA.hxx"
19#include "M6502.hxx"
20#include "System.hxx"
21#include "CartWD.hxx"
22
23// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
24CartridgeWD::CartridgeWD(const ByteBuffer& image, size_t size,
25 const string& md5, const Settings& settings)
26 : Cartridge(settings, md5),
27 mySize(std::min<size_t>(8_KB + 3, size)),
28 myCyclesAtBankswitchInit(0),
29 myPendingBank(0),
30 myCurrentBank(0)
31{
32 // Copy the ROM image into my buffer
33 if (mySize == 8_KB + 3)
34 {
35 // swap slices 2 & 3
36 std::copy_n(image.get(), 1_KB * 2, myImage.begin());
37 std::copy_n(image.get() + 1_KB * 3, 1_KB * 1, myImage.begin() + 1_KB * 2);
38 std::copy_n(image.get() + 1_KB * 2, 1_KB * 1, myImage.begin() + 1_KB * 3);
39 std::copy_n(image.get() + 1_KB * 4, 1_KB * 4, myImage.begin() + 1_KB * 4);
40 }
41 else
42 std::copy_n(image.get(), mySize, myImage.begin());
43 createCodeAccessBase(8_KB);
44}
45
46// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
47void CartridgeWD::reset()
48{
49 initializeRAM(myRAM.data(), myRAM.size());
50 initializeStartBank(0);
51
52 myCyclesAtBankswitchInit = 0;
53 myPendingBank = 0xF0; // one more than the allowable bank #
54
55 // Setup segments to some default slices
56 bank(startBank());
57}
58
59// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
60void CartridgeWD::install(System& system)
61{
62 mySystem = &system;
63
64 // Set the page accessing method for the RAM reading pages
65 System::PageAccess read(this, System::PageAccessType::READ);
66 for(uInt16 addr = 0x1000; addr < 0x1040; addr += System::PAGE_SIZE)
67 {
68 read.directPeekBase = &myRAM[addr & 0x003F];
69 read.codeAccessBase = &myCodeAccessBase[addr & 0x003F];
70 mySystem->setPageAccess(addr, read);
71 }
72
73 // Set the page accessing method for the RAM writing pages
74 // Map access to this class, since we need to inspect all accesses to
75 // check if RWP happens
76 System::PageAccess write(this, System::PageAccessType::WRITE);
77 for(uInt16 addr = 0x1040; addr < 0x1080; addr += System::PAGE_SIZE)
78 {
79 write.codeAccessBase = &myCodeAccessBase[addr & 0x003F];
80 mySystem->setPageAccess(addr, write);
81 }
82
83 // Mirror all access in TIA; by doing so we're taking responsibility
84 // for that address space in peek and poke below.
85 mySystem->tia().installDelegate(system, *this);
86
87 // Setup segments to some default slices
88 bank(startBank());
89}
90
91// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
92uInt8 CartridgeWD::peek(uInt16 address)
93{
94 // Is it time to do an actual bankswitch?
95 if(myPendingBank != 0xF0 && !bankLocked() &&
96 mySystem->cycles() > (myCyclesAtBankswitchInit + 3))
97 {
98 bank(myPendingBank);
99 myPendingBank = 0xF0;
100 }
101
102 if(!(address & 0x1000)) // Hotspots below 0x1000 are also TIA addresses
103 {
104 // Hotspots at $30 - $3F
105 // Note that a hotspot read triggers a bankswitch after at least 3 cycles
106 // have passed, so we only initiate the switch here
107 if(!bankLocked() && (address & 0x00FF) >= 0x30 && (address & 0x00FF) <= 0x3F)
108 {
109 myCyclesAtBankswitchInit = mySystem->cycles();
110 myPendingBank = address & 0x000F;
111 }
112 return mySystem->tia().peek(address);
113 }
114 else
115 {
116 uInt16 peekAddress = address;
117 address &= 0x0FFF;
118
119 if(address < 0x0040) // RAM read port
120 return myRAM[address];
121 else if(address < 0x0080) // RAM write port
122 // Reading from the write port @ $1040 - $107F triggers an unwanted write
123 return peekRAM(myRAM[address & 0x003F], peekAddress);
124 else if(address < 0x0400)
125 return myImage[myOffset[0] + (address & 0x03FF)];
126 else if(address < 0x0800)
127 return myImage[myOffset[1] + (address & 0x03FF)];
128 else if(address < 0x0C00)
129 return myImage[myOffset[2] + (address & 0x03FF)];
130 else
131 return mySegment3[address & 0x03FF];
132 }
133}
134
135// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
136bool CartridgeWD::poke(uInt16 address, uInt8 value)
137{
138 if(!(address & 0x1000)) // TIA addresses
139 return mySystem->tia().poke(address, value);
140 else
141 {
142 if(address & 0x040)
143 {
144 pokeRAM(myRAM[address & 0x003F], address, value);
145 return true;
146 }
147 else
148 {
149 // Writing to the read port should be ignored, but trigger a break if option enabled
150 uInt8 dummy;
151
152 pokeRAM(dummy, address, value);
153 myRamWriteAccess = address;
154 return false;
155 }
156 }
157}
158
159// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
160bool CartridgeWD::bank(uInt16 bank)
161{
162 if(bankLocked() || bank > 15) return false;
163
164 myCurrentBank = bank;
165
166 segmentZero(ourBankOrg[bank & 0x7].zero);
167 segmentOne(ourBankOrg[bank & 0x7].one);
168 segmentTwo(ourBankOrg[bank & 0x7].two);
169 segmentThree(ourBankOrg[bank & 0x7].three);
170
171 return myBankChanged = true;
172}
173
174// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
175void CartridgeWD::segmentZero(uInt8 slice)
176{
177 uInt16 offset = slice << 10;
178 System::PageAccess access(this, System::PageAccessType::READ);
179
180 // Skip first 128 bytes; it is always RAM
181 for(uInt16 addr = 0x1080; addr < 0x1400; addr += System::PAGE_SIZE)
182 {
183 access.codeAccessBase = &myCodeAccessBase[offset + (addr & 0x03FF)];
184 mySystem->setPageAccess(addr, access);
185 }
186 myOffset[0] = offset;
187}
188
189// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
190void CartridgeWD::segmentOne(uInt8 slice)
191{
192 uInt16 offset = slice << 10;
193 System::PageAccess access(this, System::PageAccessType::READ);
194
195 for(uInt16 addr = 0x1400; addr < 0x1800; addr += System::PAGE_SIZE)
196 {
197 access.codeAccessBase = &myCodeAccessBase[offset + (addr & 0x03FF)];
198 mySystem->setPageAccess(addr, access);
199 }
200 myOffset[1] = offset;
201}
202
203// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
204void CartridgeWD::segmentTwo(uInt8 slice)
205{
206 uInt16 offset = slice << 10;
207 System::PageAccess access(this, System::PageAccessType::READ);
208
209 for(uInt16 addr = 0x1800; addr < 0x1C00; addr += System::PAGE_SIZE)
210 {
211 access.codeAccessBase = &myCodeAccessBase[offset + (addr & 0x03FF)];
212 mySystem->setPageAccess(addr, access);
213 }
214 myOffset[2] = offset;
215}
216
217// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
218void CartridgeWD::segmentThree(uInt8 slice)
219{
220 uInt16 offset = slice << 10;
221
222 // Make a copy of the address space pointed to by the slice
223 // Then overwrite one byte with 0
224 std::copy_n(myImage.begin()+offset, mySegment3.size(), mySegment3.begin());
225 mySegment3[0x3FC] = 0;
226
227 System::PageAccess access(this, System::PageAccessType::READ);
228
229 for(uInt16 addr = 0x1C00; addr < 0x2000; addr += System::PAGE_SIZE)
230 {
231 access.codeAccessBase = &myCodeAccessBase[offset + (addr & 0x03FF)];
232 mySystem->setPageAccess(addr, access);
233 }
234 myOffset[3] = offset;
235}
236
237// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
238uInt16 CartridgeWD::getBank(uInt16) const
239{
240 return myCurrentBank;
241}
242
243// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
244uInt16 CartridgeWD::bankCount() const
245{
246 return 16;
247}
248
249// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
250bool CartridgeWD::patch(uInt16 address, uInt8 value)
251{
252 address &= 0x0FFF;
253
254 uInt16 idx = address >> 10;
255 myImage[myOffset[idx] + (address & 0x03FF)] = value;
256
257 // The upper segment is mirrored, so we need to patch its buffer too
258 if(idx == 3)
259 mySegment3[(address & 0x03FF)] = value;
260
261 return true;
262}
263
264// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
265const uInt8* CartridgeWD::getImage(size_t& size) const
266{
267 size = mySize;
268 return myImage.data();
269}
270
271// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
272bool CartridgeWD::save(Serializer& out) const
273{
274 try
275 {
276 out.putShort(myCurrentBank);
277 out.putByteArray(myRAM.data(), myRAM.size());
278 out.putLong(myCyclesAtBankswitchInit);
279 out.putShort(myPendingBank);
280 }
281 catch(...)
282 {
283 cerr << "ERROR: CartridgeWD::save" << endl;
284 return false;
285 }
286
287 return true;
288}
289
290// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
291bool CartridgeWD::load(Serializer& in)
292{
293 try
294 {
295 myCurrentBank = in.getShort();
296 in.getByteArray(myRAM.data(), myRAM.size());
297 myCyclesAtBankswitchInit = in.getLong();
298 myPendingBank = in.getShort();
299
300 bank(myCurrentBank);
301 }
302 catch(...)
303 {
304 cerr << "ERROR: CartridgeWD::load" << endl;
305 return false;
306 }
307
308 return true;
309}
310
311// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
312CartridgeWD::BankOrg CartridgeWD::ourBankOrg[8] = {
313 // 0 1 2 3 4 5 6 7
314 { 0, 0, 1, 3 }, // Bank 0, 8 2 1 - 1 - - - -
315 { 0, 1, 2, 3 }, // Bank 1, 9 1 1 1 1 - - - -
316 { 4, 5, 6, 7 }, // Bank 2, 10 - - - - 1 1 1 1
317 { 7, 4, 2, 3 }, // Bank 3, 11 - - 1 1 1 - - 1
318 { 0, 0, 6, 7 }, // Bank 4, 12 2 - - - - - 1 1
319 { 0, 1, 7, 6 }, // Bank 5, 13 1 1 - - - - 1 1
320 { 2, 3, 4, 5 }, // Bank 6, 14 - - 1 1 1 1 - -
321 { 6, 0, 5, 1 } // Bank 7, 15 1 1 - - - 1 1 -
322 // count 7 4 3 4 3 3 4 4
323};
324