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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
29 | CartridgeDPCPlus::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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
77 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
89 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
117 | void CartridgeDPCPlus::consoleChanged(ConsoleTiming timing) |
118 | { |
119 | myThumbEmulator->setConsoleTiming(timing); |
120 | } |
121 | |
122 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
123 | void 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
137 | inline 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
145 | inline 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
154 | inline 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
172 | inline 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
213 | uInt8 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
395 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
585 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
605 | uInt16 CartridgeDPCPlus::getBank(uInt16) const |
606 | { |
607 | return myBankOffset >> 12; |
608 | } |
609 | |
610 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
611 | uInt16 CartridgeDPCPlus::bankCount() const |
612 | { |
613 | return 6; |
614 | } |
615 | |
616 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
617 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
632 | const uInt8* CartridgeDPCPlus::getImage(size_t& size) const |
633 | { |
634 | size = mySize; |
635 | return myImage.data() + (myImage.size() - mySize); |
636 | } |
637 | |
638 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
639 | bool 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 | // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
700 | bool 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 | |