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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
24 | CartridgeWD::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
47 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
60 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
92 | uInt8 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
136 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
160 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
175 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
190 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
204 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
218 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
238 | uInt16 CartridgeWD::getBank(uInt16) const |
239 | { |
240 | return myCurrentBank; |
241 | } |
242 | |
243 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
244 | uInt16 CartridgeWD::bankCount() const |
245 | { |
246 | return 16; |
247 | } |
248 | |
249 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
250 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
265 | const uInt8* CartridgeWD::getImage(size_t& size) const |
266 | { |
267 | size = mySize; |
268 | return myImage.data(); |
269 | } |
270 | |
271 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
272 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
291 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
312 | CartridgeWD::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 | |