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 "OSystem.hxx"
19#include "Serializer.hxx"
20#include "System.hxx"
21#include "TimerManager.hxx"
22#include "CartFA2.hxx"
23
24// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
25CartridgeFA2::CartridgeFA2(const ByteBuffer& image, size_t size,
26 const string& md5, const Settings& settings)
27 : Cartridge(settings, md5),
28 mySize(28_KB),
29 myRamAccessTimeout(0),
30 myBankOffset(0)
31{
32 // 29/32K version of FA2 has valid data @ 1K - 29K
33 const uInt8* img_ptr = image.get();
34 if(size >= 29_KB)
35 img_ptr += 1_KB;
36 else if(size < mySize)
37 mySize = size;
38
39 // Copy the ROM image into my buffer
40 std::copy_n(img_ptr, mySize, myImage.begin());
41 createCodeAccessBase(mySize);
42}
43
44// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
45void CartridgeFA2::reset()
46{
47 initializeRAM(myRAM.data(), myRAM.size());
48 initializeStartBank(0);
49
50 // Upon reset we switch to the startup bank
51 bank(startBank());
52}
53
54// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
55void CartridgeFA2::install(System& system)
56{
57 mySystem = &system;
58
59 System::PageAccess access(this, System::PageAccessType::READ);
60
61 // Set the page accessing method for the RAM writing pages
62 // Map access to this class, since we need to inspect all accesses to
63 // check if RWP happens
64 access.type = System::PageAccessType::WRITE;
65 for(uInt16 addr = 0x1000; addr < 0x1100; addr += System::PAGE_SIZE)
66 {
67 access.codeAccessBase = &myCodeAccessBase[addr & 0x00FF];
68 mySystem->setPageAccess(addr, access);
69 }
70
71 // Set the page accessing method for the RAM reading pages
72 access.directPokeBase = nullptr;
73 access.type = System::PageAccessType::READ;
74 for(uInt16 addr = 0x1100; addr < 0x1200; addr += System::PAGE_SIZE)
75 {
76 access.directPeekBase = &myRAM[addr & 0x00FF];
77 access.codeAccessBase = &myCodeAccessBase[0x100 + (addr & 0x00FF)];
78 mySystem->setPageAccess(addr, access);
79 }
80
81 // Install pages for the startup bank
82 bank(startBank());
83}
84
85// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
86uInt8 CartridgeFA2::peek(uInt16 address)
87{
88 uInt16 peekAddress = address;
89 address &= 0x0FFF;
90
91 // Switch banks if necessary
92 switch(address)
93 {
94 case 0x0FF4:
95 // Load/save RAM to/from Harmony cart flash
96 if(mySize == 28_KB && !bankLocked())
97 return ramReadWrite();
98 break;
99
100 case 0x0FF5:
101 // Set the current bank to the first 4k bank
102 bank(0);
103 break;
104
105 case 0x0FF6:
106 // Set the current bank to the second 4k bank
107 bank(1);
108 break;
109
110 case 0x0FF7:
111 // Set the current bank to the third 4k bank
112 bank(2);
113 break;
114
115 case 0x0FF8:
116 // Set the current bank to the fourth 4k bank
117 bank(3);
118 break;
119
120 case 0x0FF9:
121 // Set the current bank to the fifth 4k bank
122 bank(4);
123 break;
124
125 case 0x0FFA:
126 // Set the current bank to the sixth 4k bank
127 bank(5);
128 break;
129
130 case 0x0FFB:
131 // Set the current bank to the seventh 4k bank
132 // This is only available on 28K ROMs
133 if(mySize == 28_KB) bank(6);
134 break;
135
136 default:
137 break;
138 }
139
140 if(address < 0x0100) // Write port is at 0xF000 - 0xF0FF (256 bytes)
141 return peekRAM(myRAM[address], peekAddress);
142 else
143 return myImage[myBankOffset + address];
144}
145
146// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
147bool CartridgeFA2::poke(uInt16 address, uInt8 value)
148{
149 // Switch banks if necessary
150 switch(address & 0x0FFF)
151 {
152 case 0x0FF4:
153 // Load/save RAM to/from Harmony cart flash
154 if(mySize == 28_KB && !bankLocked())
155 ramReadWrite();
156 return false;
157
158 case 0x0FF5:
159 // Set the current bank to the first 4k bank
160 bank(0);
161 return false;
162
163 case 0x0FF6:
164 // Set the current bank to the second 4k bank
165 bank(1);
166 return false;
167
168 case 0x0FF7:
169 // Set the current bank to the third 4k bank
170 bank(2);
171 return false;
172
173 case 0x0FF8:
174 // Set the current bank to the fourth 4k bank
175 bank(3);
176 return false;
177
178 case 0x0FF9:
179 // Set the current bank to the fifth 4k bank
180 bank(4);
181 return false;
182
183 case 0x0FFA:
184 // Set the current bank to the sixth 4k bank
185 bank(5);
186 return false;
187
188 case 0x0FFB:
189 // Set the current bank to the seventh 4k bank
190 // This is only available on 28K ROMs
191 if(mySize == 28_KB) bank(6);
192 return false;
193
194 default:
195 break;
196 }
197
198 if(!(address & 0x100))
199 {
200 pokeRAM(myRAM[address & 0x00FF], address, value);
201 return true;
202 }
203 else
204 {
205 // Writing to the read port should be ignored, but trigger a break if option enabled
206 uInt8 dummy;
207
208 pokeRAM(dummy, address, value);
209 myRamWriteAccess = address;
210 return false;
211 }
212}
213
214// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
215bool CartridgeFA2::bank(uInt16 bank)
216{
217 if(bankLocked()) return false;
218
219 // Remember what bank we're in
220 myBankOffset = bank << 12;
221
222 System::PageAccess access(this, System::PageAccessType::READ);
223
224 // Set the page accessing methods for the hot spots
225 for(uInt16 addr = (0x1FF4 & ~System::PAGE_MASK); addr < 0x2000;
226 addr += System::PAGE_SIZE)
227 {
228 access.codeAccessBase = &myCodeAccessBase[myBankOffset + (addr & 0x0FFF)];
229 mySystem->setPageAccess(addr, access);
230 }
231
232 // Setup the page access methods for the current bank
233 for(uInt16 addr = 0x1200; addr < (0x1FF4U & ~System::PAGE_MASK);
234 addr += System::PAGE_SIZE)
235 {
236 access.directPeekBase = &myImage[myBankOffset + (addr & 0x0FFF)];
237 access.codeAccessBase = &myCodeAccessBase[myBankOffset + (addr & 0x0FFF)];
238 mySystem->setPageAccess(addr, access);
239 }
240 return myBankChanged = true;
241}
242
243// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
244uInt16 CartridgeFA2::getBank(uInt16) const
245{
246 return myBankOffset >> 12;
247}
248
249// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
250uInt16 CartridgeFA2::bankCount() const
251{
252 return uInt16(mySize / 4_KB);
253}
254
255// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
256bool CartridgeFA2::patch(uInt16 address, uInt8 value)
257{
258 address &= 0x0FFF;
259
260 if(address < 0x0200)
261 {
262 // Normally, a write to the read port won't do anything
263 // However, the patch command is special in that ignores such
264 // cart restrictions
265 myRAM[address & 0x00FF] = value;
266 }
267 else
268 myImage[myBankOffset + address] = value;
269
270 return myBankChanged = true;
271}
272
273// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
274const uInt8* CartridgeFA2::getImage(size_t& size) const
275{
276 size = mySize;
277 return myImage.data();
278}
279
280// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
281bool CartridgeFA2::save(Serializer& out) const
282{
283 try
284 {
285 out.putShort(myBankOffset);
286 out.putByteArray(myRAM.data(), myRAM.size());
287 }
288 catch(...)
289 {
290 cerr << "ERROR: CartridgeFA2::save" << endl;
291 return false;
292 }
293
294 return true;
295}
296
297// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
298bool CartridgeFA2::load(Serializer& in)
299{
300 try
301 {
302 myBankOffset = in.getShort();
303 in.getByteArray(myRAM.data(), myRAM.size());
304 }
305 catch(...)
306 {
307 cerr << "ERROR: CartridgeFA2::load" << endl;
308 return false;
309 }
310
311 // Remember what bank we were in
312 bank(myBankOffset >> 12);
313
314 return true;
315}
316
317// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
318void CartridgeFA2::setNVRamFile(const string& nvramdir, const string& romfile)
319{
320 myFlashFile = nvramdir + romfile + "_flash.dat";
321}
322
323// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
324uInt8 CartridgeFA2::ramReadWrite()
325{
326 /* The following algorithm implements accessing Harmony cart flash
327
328 1. Wait for an access to hotspot location $1FF4 (return 1 in bit 6
329 while busy).
330
331 2. Read byte 256 of RAM+ memory to determine the operation requested
332 (1 = read, 2 = write).
333
334 3. Save or load the entire 256 bytes of RAM+ memory to a file.
335
336 4. Set byte 256 of RAM+ memory to zero to indicate success (will
337 always happen in emulation).
338
339 5. Return 0 (in bit 6) on the next access to $1FF4, if enough time has
340 passed to complete the operation on a real system (0.5 ms for read,
341 101 ms for write).
342 */
343
344 // First access sets the timer
345 if(myRamAccessTimeout == 0)
346 {
347 // Remember when the first access was made
348 myRamAccessTimeout = TimerManager::getTicks();
349
350 // We go ahead and do the access now, and only return when a sufficient
351 // amount of time has passed
352 Serializer serializer(myFlashFile);
353 if(serializer)
354 {
355 if(myRAM[255] == 1) // read
356 {
357 try
358 {
359 serializer.getByteArray(myRAM.data(), myRAM.size());
360 }
361 catch(...)
362 {
363 myRAM.fill(0);
364 }
365 myRamAccessTimeout += 500; // Add 0.5 ms delay for read
366 }
367 else if(myRAM[255] == 2) // write
368 {
369 try
370 {
371 serializer.putByteArray(myRAM.data(), myRAM.size());
372 }
373 catch(...)
374 {
375 // Maybe add logging here that save failed?
376 cerr << name() << ": ERROR saving score table" << endl;
377 }
378 myRamAccessTimeout += 101000; // Add 101 ms delay for write
379 }
380 }
381 // Bit 6 is 1, busy
382 return myImage[myBankOffset + 0xFF4] | 0x40;
383 }
384 else
385 {
386 // Have we reached the timeout value yet?
387 if(TimerManager::getTicks() >= myRamAccessTimeout)
388 {
389 myRamAccessTimeout = 0; // Turn off timer
390 myRAM[255] = 0; // Successful operation
391
392 // Bit 6 is 0, ready/success
393 return myImage[myBankOffset + 0xFF4] & ~0x40;
394 }
395 else
396 // Bit 6 is 1, busy
397 return myImage[myBankOffset + 0xFF4] | 0x40;
398 }
399}
400
401// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
402void CartridgeFA2::flash(uInt8 operation)
403{
404 Serializer serializer(myFlashFile);
405 if(serializer)
406 {
407 if(operation == 0) // erase
408 {
409 try
410 {
411 std::array<uInt8, 256> buf = {};
412 serializer.putByteArray(buf.data(), buf.size());
413 }
414 catch(...)
415 {
416 }
417 }
418 else if(operation == 1) // read
419 {
420 try
421 {
422 serializer.getByteArray(myRAM.data(), myRAM.size());
423 }
424 catch(...)
425 {
426 myRAM.fill(0);
427 }
428 }
429 else if(operation == 2) // write
430 {
431 try
432 {
433 serializer.putByteArray(myRAM.data(), myRAM.size());
434 }
435 catch(...)
436 {
437 // Maybe add logging here that save failed?
438 cerr << name() << ": ERROR saving score table" << endl;
439 }
440 }
441 }
442}
443