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#ifdef DEBUGGER_SUPPORT
19 #include "Debugger.hxx"
20#endif
21#include "MD5.hxx"
22#include "System.hxx"
23#include "Thumbulator.hxx"
24#include "CartDPCPlus.hxx"
25#include "TIA.hxx"
26#include "exception/FatalEmulationError.hxx"
27
28// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
29CartridgeDPCPlus::CartridgeDPCPlus(const ByteBuffer& image, size_t size,
30 const string& md5, const Settings& settings)
31 : Cartridge(settings, md5),
32 mySize(std::min(size, myImage.size())),
33 myFastFetch(false),
34 myLDAimmediate(false),
35 myParameterPointer(0),
36 myAudioCycles(0),
37 myARMCycles(0),
38 myFractionalClocks(0.0),
39 myBankOffset(0),
40 myFractionalLowMask(0x0F00FF)
41{
42 // Image is always 32K, but in the case of ROM > 29K, the image is
43 // copied to the end of the buffer
44 if(mySize < myImage.size())
45 myImage.fill(0);
46 std::copy_n(image.get(), size, myImage.begin() + (myImage.size() - mySize));
47 createCodeAccessBase(24_KB);
48
49 // Pointer to the program ROM (24K @ 3K offset; ignore first 3K)
50 myProgramImage = myImage.data() + 3_KB;
51
52 // Pointer to the display RAM
53 myDisplayImage = myDPCRAM.data() + 3_KB;
54
55 // Pointer to the Frequency RAM
56 myFrequencyImage = myDisplayImage + 4_KB;
57
58 // Create Thumbulator ARM emulator
59 bool devSettings = settings.getBool("dev.settings");
60 myThumbEmulator = make_unique<Thumbulator>
61 (reinterpret_cast<uInt16*>(myImage.data()),
62 reinterpret_cast<uInt16*>(myDPCRAM.data()),
63 static_cast<uInt32>(myImage.size()),
64 devSettings ? settings.getBool("dev.thumb.trapfatal") : false,
65 Thumbulator::ConfigureFor::DPCplus,
66 this);
67
68 // Currently only one known DPC+ ARM driver exhibits a problem
69 // with the default mask to use for DFxFRACLOW
70 if(MD5::hash(image, 3_KB) == "8dd73b44fd11c488326ce507cbeb19d1")
71 myFractionalLowMask = 0x0F0000;
72
73 setInitialState();
74}
75
76// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
77void CartridgeDPCPlus::reset()
78{
79 setInitialState();
80
81 // DPC+ always starts in bank 5
82 initializeStartBank(5);
83
84 // Upon reset we switch to the startup bank
85 bank(startBank());
86}
87
88// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
89void CartridgeDPCPlus::setInitialState()
90{
91 // Reset various ROM and RAM locations
92 myDPCRAM.fill(0);
93
94 // Copy initial DPC display data and Frequency table state to Harmony RAM
95 std::copy_n(myProgramImage + 24_KB, 5_KB, myDisplayImage);
96
97 // Initialize the DPC data fetcher registers
98 myTops.fill(0);
99 myBottoms.fill(0);
100 myFractionalIncrements.fill(0);
101 myFractionalCounters.fill(0);
102 myCounters.fill(0);
103
104 // Set waveforms to first waveform entry
105 myMusicWaveforms.fill(0);
106
107 // Initialize the DPC's random number generator register (must be non-zero)
108 myRandomNumber = 0x2B435044; // "DPC+"
109
110 // Initialize various other parameters
111 myFastFetch = myLDAimmediate = false;
112 myAudioCycles = myARMCycles = 0;
113 myFractionalClocks = 0.0;
114}
115
116// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
117void CartridgeDPCPlus::consoleChanged(ConsoleTiming timing)
118{
119 myThumbEmulator->setConsoleTiming(timing);
120}
121
122// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
123void CartridgeDPCPlus::install(System& system)
124{
125 mySystem = &system;
126
127 // Map all of the accesses to call peek and poke
128 System::PageAccess access(this, System::PageAccessType::READ);
129 for(uInt16 addr = 0x1000; addr < 0x1080; addr += System::PAGE_SIZE)
130 mySystem->setPageAccess(addr, access);
131
132 // Install pages for the startup bank
133 bank(startBank());
134}
135
136// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
137inline void CartridgeDPCPlus::clockRandomNumberGenerator()
138{
139 // Update random number generator (32-bit LFSR)
140 myRandomNumber = ((myRandomNumber & (1<<10)) ? 0x10adab1e: 0x00) ^
141 ((myRandomNumber >> 11) | (myRandomNumber << 21));
142}
143
144// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
145inline void CartridgeDPCPlus::priorClockRandomNumberGenerator()
146{
147 // Update random number generator (32-bit LFSR, reversed)
148 myRandomNumber = ((myRandomNumber & (1u<<31)) ?
149 ((0x10adab1e^myRandomNumber) << 11) | ((0x10adab1e^myRandomNumber) >> 21) :
150 (myRandomNumber << 11) | (myRandomNumber >> 21));
151}
152
153// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
154inline void CartridgeDPCPlus::updateMusicModeDataFetchers()
155{
156 // Calculate the number of cycles since the last update
157 uInt32 cycles = uInt32(mySystem->cycles() - myAudioCycles);
158 myAudioCycles = mySystem->cycles();
159
160 // Calculate the number of DPC+ OSC clocks since the last update
161 double clocks = ((20000.0 * cycles) / 1193191.66666667) + myFractionalClocks;
162 uInt32 wholeClocks = uInt32(clocks);
163 myFractionalClocks = clocks - double(wholeClocks);
164
165 // Let's update counters and flags of the music mode data fetchers
166 if(wholeClocks > 0)
167 for(int x = 0; x <= 2; ++x)
168 myMusicCounters[x] += myMusicFrequencies[x] * wholeClocks;
169}
170
171// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
172inline void CartridgeDPCPlus::callFunction(uInt8 value)
173{
174 // myParameter
175 uInt16 ROMdata = (myParameter[1] << 8) + myParameter[0];
176 switch (value)
177 {
178 case 0: // Parameter Pointer reset
179 myParameterPointer = 0;
180 break;
181 case 1: // Copy ROM to fetcher
182 for(int i = 0; i < myParameter[3]; ++i)
183 myDisplayImage[myCounters[myParameter[2] & 0x7]+i] = myProgramImage[ROMdata+i];
184 myParameterPointer = 0;
185 break;
186 case 2: // Copy value to fetcher
187 for(int i = 0; i < myParameter[3]; ++i)
188 myDisplayImage[myCounters[myParameter[2]]+i] = myParameter[0];
189 myParameterPointer = 0;
190 break;
191 // Call user written ARM code (most likely be C compiled for ARM)
192 case 254: // call with IRQ driven audio, no special handling needed at this
193 // time for Stella as ARM code "runs in zero 6507 cycles".
194 case 255: // call without IRQ driven audio
195 try {
196 Int32 cycles = Int32(mySystem->cycles() - myARMCycles);
197 myARMCycles = mySystem->cycles();
198
199 myThumbEmulator->run(cycles);
200 }
201 catch(const runtime_error& e) {
202 if(!mySystem->autodetectMode())
203 {
204 FatalEmulationError::raise(e.what());
205 }
206 }
207 break;
208 // reserved
209 }
210}
211
212// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
213uInt8 CartridgeDPCPlus::peek(uInt16 address)
214{
215 address &= 0x0FFF;
216
217 uInt8 peekvalue = myProgramImage[myBankOffset + address];
218 uInt8 flag;
219
220 // In debugger/bank-locked mode, we ignore all hotspots and in general
221 // anything that can change the internal state of the cart
222 if(bankLocked())
223 return peekvalue;
224
225 // Check if we're in Fast Fetch mode and the prior byte was an A9 (LDA #value)
226 if(myFastFetch && myLDAimmediate)
227 {
228 if(peekvalue < 0x0028)
229 // if #value is a read-register then we want to use that as the address
230 address = peekvalue;
231 }
232 myLDAimmediate = false;
233
234 if(address < 0x0028)
235 {
236 uInt8 result = 0;
237
238 // Get the index of the data fetcher that's being accessed
239 uInt32 index = address & 0x07;
240 uInt32 function = (address >> 3) & 0x07;
241
242 // Update flag for selected data fetcher
243 flag = (((myTops[index]-(myCounters[index] & 0x00ff)) & 0xFF) > ((myTops[index]-myBottoms[index]) & 0xFF)) ? 0xFF : 0;
244
245 switch(function)
246 {
247 case 0x00:
248 {
249 switch(index)
250 {
251 case 0x00: // RANDOM0NEXT - advance and return byte 0 of random
252 clockRandomNumberGenerator();
253 result = myRandomNumber & 0xFF;
254 break;
255
256 case 0x01: // RANDOM0PRIOR - return to prior and return byte 0 of random
257 priorClockRandomNumberGenerator();
258 result = myRandomNumber & 0xFF;
259 break;
260
261 case 0x02: // RANDOM1
262 result = (myRandomNumber>>8) & 0xFF;
263 break;
264
265 case 0x03: // RANDOM2
266 result = (myRandomNumber>>16) & 0xFF;
267 break;
268
269 case 0x04: // RANDOM3
270 result = (myRandomNumber>>24) & 0xFF;
271 break;
272
273 case 0x05: // AMPLITUDE
274 {
275 // Update the music data fetchers (counter & flag)
276 updateMusicModeDataFetchers();
277
278 // using myDisplayImage[] instead of myProgramImage[] because waveforms
279 // can be modified during runtime.
280 uInt32 i = myDisplayImage[(myMusicWaveforms[0] << 5) + (myMusicCounters[0] >> 27)] +
281 myDisplayImage[(myMusicWaveforms[1] << 5) + (myMusicCounters[1] >> 27)] +
282 myDisplayImage[(myMusicWaveforms[2] << 5) + (myMusicCounters[2] >> 27)];
283
284 result = uInt8(i);
285 break;
286 }
287
288 case 0x06: // reserved
289 case 0x07: // reserved
290 break;
291 }
292 break;
293 }
294
295 // DFxDATA - display data read
296 case 0x01:
297 {
298 result = myDisplayImage[myCounters[index]];
299 myCounters[index] = (myCounters[index] + 0x1) & 0x0fff;
300 break;
301 }
302
303 // DFxDATAW - display data read AND'd w/flag ("windowed")
304 case 0x02:
305 {
306 result = myDisplayImage[myCounters[index]] & flag;
307 myCounters[index] = (myCounters[index] + 0x1) & 0x0fff;
308 break;
309 }
310
311 // DFxFRACDATA - display data read w/fractional increment
312 case 0x03:
313 {
314 result = myDisplayImage[myFractionalCounters[index] >> 8];
315 myFractionalCounters[index] = (myFractionalCounters[index] + myFractionalIncrements[index]) & 0x0fffff;
316 break;
317 }
318
319 case 0x04:
320 {
321 switch (index)
322 {
323 case 0x00: // DF0FLAG
324 case 0x01: // DF1FLAG
325 case 0x02: // DF2FLAG
326 case 0x03: // DF3FLAG
327 {
328 result = flag;
329 break;
330 }
331 case 0x04: // reserved
332 case 0x05: // reserved
333 case 0x06: // reserved
334 case 0x07: // reserved
335 break;
336 }
337 break;
338 }
339
340 default:
341 {
342 result = 0;
343 }
344 }
345
346 return result;
347 }
348 else
349 {
350 // Switch banks if necessary
351 switch(address)
352 {
353 case 0x0FF6:
354 // Set the current bank to the first 4k bank
355 bank(0);
356 break;
357
358 case 0x0FF7:
359 // Set the current bank to the second 4k bank
360 bank(1);
361 break;
362
363 case 0x0FF8:
364 // Set the current bank to the third 4k bank
365 bank(2);
366 break;
367
368 case 0x0FF9:
369 // Set the current bank to the fourth 4k bank
370 bank(3);
371 break;
372
373 case 0x0FFA:
374 // Set the current bank to the fifth 4k bank
375 bank(4);
376 break;
377
378 case 0x0FFB:
379 // Set the current bank to the last 4k bank
380 bank(5);
381 break;
382
383 default:
384 break;
385 }
386
387 if(myFastFetch)
388 myLDAimmediate = (peekvalue == 0xA9);
389
390 return peekvalue;
391 }
392}
393
394// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
395bool CartridgeDPCPlus::poke(uInt16 address, uInt8 value)
396{
397 address &= 0x0FFF;
398
399 if((address >= 0x0028) && (address < 0x0080))
400 {
401 // Get the index of the data fetcher that's being accessed
402 uInt32 index = address & 0x07;
403 uInt32 function = ((address - 0x28) >> 3) & 0x0f;
404
405 switch(function)
406 {
407 // DFxFRACLOW - fractional data pointer low byte
408 case 0x00:
409 myFractionalCounters[index] =
410 (myFractionalCounters[index] & myFractionalLowMask) | (uInt16(value) << 8);
411 break;
412
413 // DFxFRACHI - fractional data pointer high byte
414 case 0x01:
415 myFractionalCounters[index] = ((uInt16(value) & 0x0F) << 16) | (myFractionalCounters[index] & 0x00ffff);
416 break;
417
418 //DFxFRACINC - Fractional Increment amount
419 case 0x02:
420 myFractionalIncrements[index] = value;
421 myFractionalCounters[index] = myFractionalCounters[index] & 0x0FFF00;
422 break;
423
424 // DFxTOP - set top of window (for reads of DFxDATAW)
425 case 0x03:
426 myTops[index] = value;
427 break;
428
429 // DFxBOT - set bottom of window (for reads of DFxDATAW)
430 case 0x04:
431 myBottoms[index] = value;
432 break;
433
434 // DFxLOW - data pointer low byte
435 case 0x05:
436 myCounters[index] = (myCounters[index] & 0x0F00) | value ;
437 break;
438
439 // Control registers
440 case 0x06:
441 switch (index)
442 {
443 case 0x00: // FASTFETCH - turns on LDA #<DFxDATA mode of value is 0
444 myFastFetch = (value == 0);
445 break;
446
447 case 0x01: // PARAMETER - set parameter used by CALLFUNCTION (not all functions use the parameter)
448 if(myParameterPointer < 8)
449 myParameter[myParameterPointer++] = value;
450 break;
451
452 case 0x02: // CALLFUNCTION
453 callFunction(value);
454 break;
455
456 case 0x03: // reserved
457 case 0x04: // reserved
458 break;
459
460 case 0x05: // WAVEFORM0
461 case 0x06: // WAVEFORM1
462 case 0x07: // WAVEFORM2
463 myMusicWaveforms[index - 5] = value & 0x7f;
464 break;
465 }
466 break;
467
468 // DFxPUSH - Push value into data bank
469 case 0x07:
470 {
471 myCounters[index] = (myCounters[index] - 0x1) & 0x0fff;
472 myDisplayImage[myCounters[index]] = value;
473 break;
474 }
475
476 // DFxHI - data pointer high byte
477 case 0x08:
478 {
479 myCounters[index] = ((uInt16(value) & 0x0F) << 8) | (myCounters[index] & 0x00ff);
480 break;
481 }
482
483 case 0x09:
484 {
485 switch (index)
486 {
487 case 0x00: // RRESET - Random Number Generator Reset
488 {
489 myRandomNumber = 0x2B435044; // "DPC+"
490 break;
491 }
492 case 0x01: // RWRITE0 - update byte 0 of random number
493 {
494 myRandomNumber = (myRandomNumber & 0xFFFFFF00) | value;
495 break;
496 }
497 case 0x02: // RWRITE1 - update byte 1 of random number
498 {
499 myRandomNumber = (myRandomNumber & 0xFFFF00FF) | (value<<8);
500 break;
501 }
502 case 0x03: // RWRITE2 - update byte 2 of random number
503 {
504 myRandomNumber = (myRandomNumber & 0xFF00FFFF) | (value<<16);
505 break;
506 }
507 case 0x04: // RWRITE3 - update byte 3 of random number
508 {
509 myRandomNumber = (myRandomNumber & 0x00FFFFFF) | (value<<24);
510 break;
511 }
512 case 0x05: // NOTE0
513 case 0x06: // NOTE1
514 case 0x07: // NOTE2
515 {
516 myMusicFrequencies[index-5] = myFrequencyImage[(value<<2)] +
517 (myFrequencyImage[(value<<2)+1]<<8) +
518 (myFrequencyImage[(value<<2)+2]<<16) +
519 (myFrequencyImage[(value<<2)+3]<<24);
520 break;
521 }
522 default:
523 break;
524 }
525 break;
526 }
527
528 // DFxWRITE - write into data bank
529 case 0x0a:
530 {
531 myDisplayImage[myCounters[index]] = value;
532 myCounters[index] = (myCounters[index] + 0x1) & 0x0fff;
533 break;
534 }
535
536 default:
537 {
538 break;
539 }
540 }
541 }
542 else
543 {
544 // Switch banks if necessary
545 switch(address)
546 {
547 case 0x0FF6:
548 // Set the current bank to the first 4k bank
549 bank(0);
550 break;
551
552 case 0x0FF7:
553 // Set the current bank to the second 4k bank
554 bank(1);
555 break;
556
557 case 0x0FF8:
558 // Set the current bank to the third 4k bank
559 bank(2);
560 break;
561
562 case 0x0FF9:
563 // Set the current bank to the fourth 4k bank
564 bank(3);
565 break;
566
567 case 0x0FFA:
568 // Set the current bank to the fifth 4k bank
569 bank(4);
570 break;
571
572 case 0x0FFB:
573 // Set the current bank to the last 4k bank
574 bank(5);
575 break;
576
577 default:
578 break;
579 }
580 }
581 return false;
582}
583
584// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
585bool CartridgeDPCPlus::bank(uInt16 bank)
586{
587 if(bankLocked()) return false;
588
589 // Remember what bank we're in
590 myBankOffset = bank << 12;
591
592 // Setup the page access methods for the current bank
593 System::PageAccess access(this, System::PageAccessType::READ);
594
595 // Map Program ROM image into the system
596 for(uInt16 addr = 0x1080; addr < 0x2000; addr += System::PAGE_SIZE)
597 {
598 access.codeAccessBase = &myCodeAccessBase[myBankOffset + (addr & 0x0FFF)];
599 mySystem->setPageAccess(addr, access);
600 }
601 return myBankChanged = true;
602}
603
604// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
605uInt16 CartridgeDPCPlus::getBank(uInt16) const
606{
607 return myBankOffset >> 12;
608}
609
610// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
611uInt16 CartridgeDPCPlus::bankCount() const
612{
613 return 6;
614}
615
616// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
617bool CartridgeDPCPlus::patch(uInt16 address, uInt8 value)
618{
619 address &= 0x0FFF;
620
621 // For now, we ignore attempts to patch the DPC address space
622 if(address >= 0x0080)
623 {
624 myProgramImage[myBankOffset + (address & 0x0FFF)] = value;
625 return myBankChanged = true;
626 }
627 else
628 return false;
629}
630
631// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
632const uInt8* CartridgeDPCPlus::getImage(size_t& size) const
633{
634 size = mySize;
635 return myImage.data() + (myImage.size() - mySize);
636}
637
638// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
639bool CartridgeDPCPlus::save(Serializer& out) const
640{
641 try
642 {
643 // Indicates which bank is currently active
644 out.putShort(myBankOffset);
645
646 // Harmony RAM
647 out.putByteArray(myDPCRAM.data(), myDPCRAM.size());
648
649 // The top registers for the data fetchers
650 out.putByteArray(myTops.data(), myTops.size());
651
652 // The bottom registers for the data fetchers
653 out.putByteArray(myBottoms.data(), myBottoms.size());
654
655 // The counter registers for the data fetchers
656 out.putShortArray(myCounters.data(), myCounters.size());
657
658 // The counter registers for the fractional data fetchers
659 out.putIntArray(myFractionalCounters.data(), myFractionalCounters.size());
660
661 // The fractional registers for the data fetchers
662 out.putByteArray(myFractionalIncrements.data(), myFractionalIncrements.size());
663
664 // The Fast Fetcher Enabled flag
665 out.putBool(myFastFetch);
666 out.putBool(myLDAimmediate);
667
668 // Control Byte to update
669 out.putByteArray(myParameter.data(), myParameter.size());
670
671 // The music counters
672 out.putIntArray(myMusicCounters.data(), myMusicCounters.size());
673
674 // The music frequencies
675 out.putIntArray(myMusicFrequencies.data(), myMusicFrequencies.size());
676
677 // The music waveforms
678 out.putShortArray(myMusicWaveforms.data(), myMusicWaveforms.size());
679
680 // The random number generator register
681 out.putInt(myRandomNumber);
682
683 // Get system cycles and fractional clocks
684 out.putLong(myAudioCycles);
685 out.putDouble(myFractionalClocks);
686
687 // Clock info for Thumbulator
688 out.putLong(myARMCycles);
689 }
690 catch(...)
691 {
692 cerr << "ERROR: CartridgeDPCPlus::save" << endl;
693 return false;
694 }
695
696 return true;
697}
698
699// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
700bool CartridgeDPCPlus::load(Serializer& in)
701{
702 try
703 {
704 // Indicates which bank is currently active
705 myBankOffset = in.getShort();
706
707 // Harmony RAM
708 in.getByteArray(myDPCRAM.data(), myDPCRAM.size());
709
710 // The top registers for the data fetchers
711 in.getByteArray(myTops.data(), myTops.size());
712
713 // The bottom registers for the data fetchers
714 in.getByteArray(myBottoms.data(), myBottoms.size());
715
716 // The counter registers for the data fetchers
717 in.getShortArray(myCounters.data(), myCounters.size());
718
719 // The counter registers for the fractional data fetchers
720 in.getIntArray(myFractionalCounters.data(), myFractionalCounters.size());
721
722 // The fractional registers for the data fetchers
723 in.getByteArray(myFractionalIncrements.data(), myFractionalIncrements.size());
724
725 // The Fast Fetcher Enabled flag
726 myFastFetch = in.getBool();
727 myLDAimmediate = in.getBool();
728
729 // Control Byte to update
730 in.getByteArray(myParameter.data(), myParameter.size());
731
732 // The music mode counters for the data fetchers
733 in.getIntArray(myMusicCounters.data(), myMusicCounters.size());
734
735 // The music mode frequency addends for the data fetchers
736 in.getIntArray(myMusicFrequencies.data(), myMusicFrequencies.size());
737
738 // The music waveforms
739 in.getShortArray(myMusicWaveforms.data(), myMusicWaveforms.size());
740
741 // The random number generator register
742 myRandomNumber = in.getInt();
743
744 // Get audio cycles and fractional clocks
745 myAudioCycles = in.getLong();
746 myFractionalClocks = in.getDouble();
747
748 // Clock info for Thumbulator
749 myARMCycles = in.getLong();
750 }
751 catch(...)
752 {
753 cerr << "ERROR: CartridgeDPCPlus::load" << endl;
754 return false;
755 }
756
757 // Now, go to the current bank
758 bank(myBankOffset >> 12);
759
760 return true;
761}
762