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 "CartCTY.hxx"
23
24// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
25CartridgeCTY::CartridgeCTY(const ByteBuffer& image, size_t size,
26 const string& md5, const Settings& settings)
27 : Cartridge(settings, md5),
28 myOperationType(0),
29 myTunePosition(0),
30 myLDAimmediate(false),
31 myRandomNumber(0x2B435044),
32 myRamAccessTimeout(0),
33 myAudioCycles(0),
34 myFractionalClocks(0.0),
35 myBankOffset(0)
36{
37 // Copy the ROM image into my buffer
38 std::copy_n(image.get(), std::min(myImage.size(), size), myImage.begin());
39 createCodeAccessBase(myImage.size());
40
41 // Default to no tune data in case user is utilizing an old ROM
42 myTuneData.fill(0);
43
44 // Extract tune data if it exists
45 if(size > myImage.size())
46 std::copy_n(image.get() + myImage.size(), size - myImage.size(), myTuneData.begin());
47
48 // Point to the first tune
49 myFrequencyImage = myTuneData.data();
50
51 myMusicCounters.fill(0);
52 myMusicFrequencies.fill(0);
53}
54
55// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
56void CartridgeCTY::reset()
57{
58 initializeRAM(myRAM.data(), myRAM.size());
59 initializeStartBank(1);
60
61 myRAM[0] = myRAM[1] = myRAM[2] = myRAM[3] = 0xFF;
62
63 myLDAimmediate = false;
64 myRandomNumber = 0x2B435044;
65 myRamAccessTimeout = 0;
66
67 myAudioCycles = 0;
68 myFractionalClocks = 0.0;
69
70 // Upon reset we switch to the startup bank
71 bank(startBank());
72}
73
74// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
75void CartridgeCTY::install(System& system)
76{
77 mySystem = &system;
78
79 // Map all RAM accesses to call peek and poke
80 System::PageAccess access(this, System::PageAccessType::READ);
81 for(uInt16 addr = 0x1000; addr < 0x1080; addr += System::PAGE_SIZE)
82 mySystem->setPageAccess(addr, access);
83
84 // Install pages for the startup bank
85 bank(startBank());
86}
87
88// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
89uInt8 CartridgeCTY::peek(uInt16 address)
90{
91 uInt16 peekAddress = address;
92 address &= 0x0FFF;
93 uInt8 peekValue = myImage[myBankOffset + address];
94
95 // In debugger/bank-locked mode, we ignore all hotspots and in general
96 // anything that can change the internal state of the cart
97 if(bankLocked())
98 return peekValue;
99
100 // Check for aliasing to 'LDA #$F2'
101 if(myLDAimmediate && peekValue == 0xF2)
102 {
103 myLDAimmediate = false;
104
105 // Update the music data fetchers (counter & flag)
106 updateMusicModeDataFetchers();
107
108 uInt8 i = 0;
109
110 /*
111 in the ARM driver registers 8-10 are the music counters 0-2
112 lsr r2, r8, #31
113 add r2, r2, r9, lsr #31
114 add r2, r2, r10, lsr #31
115 lsl r2, r2, #2
116 */
117
118 i = myMusicCounters[0] >> 31;
119 i = i + (myMusicCounters[1] >> 31);
120 i = i + (myMusicCounters[2] >> 31);
121 i <<= 2;
122
123 return i;
124
125 }
126 else
127 myLDAimmediate = false;
128
129 if(address < 0x0040) // Write port is at $1000 - $103F (64 bytes)
130 {
131 // Reading from the write port triggers an unwanted write
132 return peekRAM(myRAM[address], peekAddress);
133 }
134 else if(address < 0x0080) // Read port is at $1040 - $107F (64 bytes)
135 {
136 address -= 0x40;
137 switch(address)
138 {
139 case 0x00: // Error code after operation
140 return myRAM[0];
141 case 0x01: // Get next Random Number (8-bit LFSR)
142 myRandomNumber = ((myRandomNumber & (1<<10)) ? 0x10adab1e: 0x00) ^
143 ((myRandomNumber >> 11) | (myRandomNumber << 21));
144 return myRandomNumber & 0xFF;
145 case 0x02: // Get Tune position (low byte)
146 return myTunePosition & 0xFF;
147 case 0x03: // Get Tune position (high byte)
148 return (myTunePosition >> 8) & 0xFF;
149 default:
150 return myRAM[address];
151 }
152 }
153 else // Check hotspots
154 {
155 switch(address)
156 {
157 case 0x0FF4:
158 // Bank 0 is ARM code and not actually accessed
159 return ramReadWrite();
160 case 0x0FF5:
161 case 0x0FF6:
162 case 0x0FF7:
163 case 0x0FF8:
164 case 0x0FF9:
165 case 0x0FFA:
166 case 0x0FFB:
167 // Banks 1 through 7
168 bank(address - 0x0FF4);
169 break;
170 default:
171 break;
172 }
173
174 // Is this instruction an immediate mode LDA?
175 myLDAimmediate = (peekValue == 0xA9);
176
177 return peekValue;
178 }
179}
180
181// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
182bool CartridgeCTY::poke(uInt16 address, uInt8 value)
183{
184 uInt16 pokeAddress = address;
185 address &= 0x0FFF;
186
187 if(address < 0x0040) // Write port is at $1000 - $103F (64 bytes)
188 {
189 switch(address)
190 {
191 case 0x00: // Operation type for $1FF4
192 myOperationType = value;
193 break;
194 case 0x01: // Set Random seed value (reset)
195 myRandomNumber = 0x2B435044;
196 break;
197 case 0x02: // Reset fetcher to beginning of tune
198 myTunePosition = 0;
199 myMusicCounters[0] = 0;
200 myMusicCounters[1] = 0;
201 myMusicCounters[2] = 0;
202 myMusicFrequencies[0] = 0;
203 myMusicFrequencies[1] = 0;
204 myMusicFrequencies[2] = 0;
205 break;
206 case 0x03: // Advance fetcher to next tune position
207 updateTune();
208 break;
209 default:
210 pokeRAM(myRAM[address], pokeAddress, value);
211 break;
212 }
213 }
214 else // Check hotspots
215 {
216 switch(address)
217 {
218 case 0x0FF4:
219 // Bank 0 is ARM code and not actually accessed
220 ramReadWrite();
221 break;
222 case 0x0FF5:
223 case 0x0FF6:
224 case 0x0FF7:
225 case 0x0FF8:
226 case 0x0FF9:
227 case 0x0FFA:
228 case 0x0FFB:
229 // Banks 1 through 7
230 bank(address - 0x0FF4);
231 break;
232 default:
233 break;
234 }
235 }
236 return false;
237}
238
239// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
240bool CartridgeCTY::bank(uInt16 bank)
241{
242 if(bankLocked()) return false;
243
244 // Remember what bank we're in
245 myBankOffset = bank << 12;
246
247 // Setup the page access methods for the current bank
248 System::PageAccess access(this, System::PageAccessType::READ);
249 for(uInt16 addr = 0x1080; addr < 0x2000; addr += System::PAGE_SIZE)
250 {
251 access.codeAccessBase = &myCodeAccessBase[myBankOffset + (addr & 0x0FFF)];
252 mySystem->setPageAccess(addr, access);
253 }
254 return myBankChanged = true;
255}
256
257// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
258uInt16 CartridgeCTY::getBank(uInt16) const
259{
260 return myBankOffset >> 12;
261}
262
263// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
264uInt16 CartridgeCTY::bankCount() const
265{
266 return 8;
267}
268
269// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
270bool CartridgeCTY::patch(uInt16 address, uInt8 value)
271{
272 address &= 0x0FFF;
273
274 if(address < 0x0080)
275 {
276 // Normally, a write to the read port won't do anything
277 // However, the patch command is special in that ignores such
278 // cart restrictions
279 myRAM[address & 0x003F] = value;
280 }
281 else
282 myImage[myBankOffset + address] = value;
283
284 return myBankChanged = true;
285}
286
287// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
288const uInt8* CartridgeCTY::getImage(size_t& size) const
289{
290 size = myImage.size();
291 return myImage.data();
292}
293
294// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
295bool CartridgeCTY::save(Serializer& out) const
296{
297 try
298 {
299 out.putShort(getBank());
300 out.putByteArray(myRAM.data(), myRAM.size());
301
302 out.putByte(myOperationType);
303 out.putShort(myTunePosition);
304 out.putBool(myLDAimmediate);
305 out.putInt(myRandomNumber);
306 out.putLong(myAudioCycles);
307 out.putDouble(myFractionalClocks);
308 out.putIntArray(myMusicCounters.data(), myMusicCounters.size());
309 out.putIntArray(myMusicFrequencies.data(), myMusicFrequencies.size());
310 out.putLong(myFrequencyImage - myTuneData.data()); // FIXME - storing pointer diff!
311 }
312 catch(...)
313 {
314 cerr << "ERROR: CartridgeCTY::save" << endl;
315 return false;
316 }
317
318 return true;
319}
320
321// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
322bool CartridgeCTY::load(Serializer& in)
323{
324 try
325 {
326 // Remember what bank we were in
327 bank(in.getShort());
328 in.getByteArray(myRAM.data(), myRAM.size());
329
330 myOperationType = in.getByte();
331 myTunePosition = in.getShort();
332 myLDAimmediate = in.getBool();
333 myRandomNumber = in.getInt();
334 myAudioCycles = in.getLong();
335 myFractionalClocks = in.getDouble();
336 in.getIntArray(myMusicCounters.data(), myMusicCounters.size());
337 in.getIntArray(myMusicFrequencies.data(), myMusicFrequencies.size());
338 myFrequencyImage = myTuneData.data() + in.getLong();
339 }
340 catch(...)
341 {
342 cerr << "ERROR: CartridgeCTY::load" << endl;
343 return false;
344 }
345 return true;
346}
347
348// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
349void CartridgeCTY::setNVRamFile(const string& nvramdir, const string& romfile)
350{
351 myEEPROMFile = nvramdir + romfile + "_eeprom.dat";
352}
353
354// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
355uInt8 CartridgeCTY::ramReadWrite()
356{
357 /* The following algorithm implements accessing Harmony cart EEPROM
358
359 1. Wait for an access to hotspot location $1FF4 (return 1 in bit 6
360 while busy).
361
362 2. Determine operation from myOperationType.
363
364 3. Save or load relevant EEPROM memory to/from a file.
365
366 4. Set byte 0 of RAM+ memory to zero to indicate success (will
367 always happen in emulation).
368
369 5. Return 0 (in bit 6) on the next access to $1FF4, if enough time has
370 passed to complete the operation on a real system (0.5 s for read,
371 1 s for write).
372 */
373
374 if(bankLocked()) return 0xff;
375
376 // First access sets the timer
377 if(myRamAccessTimeout == 0)
378 {
379 // Opcode and value in form of XXXXYYYY (from myOperationType), where:
380 // XXXX = index and YYYY = operation
381 uInt8 index = myOperationType >> 4;
382 switch(myOperationType & 0xf)
383 {
384 case 1: // Load tune (index = tune)
385 if(index < 7)
386 {
387 // Add 0.5 s delay for read
388 myRamAccessTimeout = TimerManager::getTicks() + 500000;
389 loadTune(index);
390 }
391 break;
392 case 2: // Load score table (index = table)
393 if(index < 4)
394 {
395 // Add 0.5 s delay for read
396 myRamAccessTimeout = TimerManager::getTicks() + 500000;
397 loadScore(index);
398 }
399 break;
400 case 3: // Save score table (index = table)
401 if(index < 4)
402 {
403 // Add 1 s delay for write
404 myRamAccessTimeout = TimerManager::getTicks() + 1000000;
405 saveScore(index);
406 }
407 break;
408 case 4: // Wipe all score tables
409 // Add 1 s delay for write
410 myRamAccessTimeout = TimerManager::getTicks() + 1000000;
411 wipeAllScores();
412 break;
413 }
414 // Bit 6 is 1, busy
415 return myImage[myBankOffset + 0xFF4] | 0x40;
416 }
417 else
418 {
419 // Have we reached the timeout value yet?
420 if(TimerManager::getTicks() >= myRamAccessTimeout)
421 {
422 myRamAccessTimeout = 0; // Turn off timer
423 myRAM[0] = 0; // Successful operation
424
425 // Bit 6 is 0, ready/success
426 return myImage[myBankOffset + 0xFF4] & ~0x40;
427 }
428 else
429 // Bit 6 is 1, busy
430 return myImage[myBankOffset + 0xFF4] | 0x40;
431 }
432}
433
434// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
435void CartridgeCTY::loadTune(uInt8 index)
436{
437 // Each tune is offset by 4096 bytes
438 // Instead of copying non-modifiable data around (as would happen on the
439 // Harmony), we simply point to the appropriate tune
440 myFrequencyImage = myTuneData.data() + (index << 12);
441
442 // Reset to beginning of tune
443 myTunePosition = 0;
444}
445
446// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
447void CartridgeCTY::updateTune()
448{
449//UpdateTune:
450// /* Float data bus */
451// strb r8, [r0, #+0x01]
452//
453// /* Increment song position */
454// add r7, r7, #1 r7 = songPosition
455//
456// /* Read song data (0 = continue) */
457// msr cpsr_c, #MODE_FIQ|I_BIT|F_BIT
458// ldrb r2, [r14], #1 r14 = myTunePosition, r2 = note
459// cmp r2, #0
460// ldrne r11, [r6, +r2, lsl #2] r6 +r2 = ourFrequencyTable[note]. Why lsl #2?
461// ldrb r2, [r14], #1 r11 = myMusicFrequency[0]
462// cmp r2, #0
463// ldrne r12, [r6, +r2, lsl #2] r12 = myMusicFrequency[1]
464// ldrb r2, [r14], #1
465// cmp r2, #1
466// ldrcs r13, [r6, +r2, lsl #2] r13 = myMusicFrequency[2]
467//
468// /* Reset tune */
469// mvneq r7, #0
470// moveq r14, r4 r4 = start of tune data
471// msr cpsr_c, #MODE_SYS|I_BIT|F_BIT
472//
473// /* Wait until address changes */
474//WaitAddrChangeA:
475// ldrh r2, [r0, #+0x16]
476// cmp r1, r2
477// beq WaitAddrChangeA
478// b NewAddress
479
480 myTunePosition += 1;
481 uInt16 songPosition = (myTunePosition - 1) *3;
482
483 uInt8 note = myFrequencyImage[songPosition + 0];
484 if (note)
485 myMusicFrequencies[0] = ourFrequencyTable[note];
486
487 note = myFrequencyImage[songPosition + 1];
488 if (note)
489 myMusicFrequencies[1] = ourFrequencyTable[note];
490
491 note = myFrequencyImage[songPosition + 2];
492 if (note == 1)
493 myTunePosition = 0;
494 else
495 myMusicFrequencies[2] = ourFrequencyTable[note];
496}
497
498// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
499void CartridgeCTY::loadScore(uInt8 index)
500{
501 Serializer serializer(myEEPROMFile, Serializer::Mode::ReadOnly);
502 if(serializer)
503 {
504 std::array<uInt8, 256> scoreRAM;
505 try
506 {
507 serializer.getByteArray(scoreRAM.data(), scoreRAM.size());
508 }
509 catch(...)
510 {
511 scoreRAM.fill(0);
512 }
513
514 // Grab 60B slice @ given index (first 4 bytes are ignored)
515 std::copy_n(scoreRAM.begin() + (index << 6) + 4, 60, myRAM.begin() + 4);
516 }
517}
518
519// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
520void CartridgeCTY::saveScore(uInt8 index)
521{
522 Serializer serializer(myEEPROMFile);
523 if(serializer)
524 {
525 // Load score RAM
526 std::array<uInt8, 256> scoreRAM;
527 try
528 {
529 serializer.getByteArray(scoreRAM.data(), scoreRAM.size());
530 }
531 catch(...)
532 {
533 scoreRAM.fill(0);
534 }
535
536 // Add 60B RAM to score table @ given index (first 4 bytes are ignored)
537 std::copy_n(myRAM.begin() + 4, 60, scoreRAM.begin() + (index << 6) + 4);
538
539 // Save score RAM
540 serializer.rewind();
541 try
542 {
543 serializer.putByteArray(scoreRAM.data(), scoreRAM.size());
544 }
545 catch(...)
546 {
547 // Maybe add logging here that save failed?
548 cerr << name() << ": ERROR saving score table " << int(index) << endl;
549 }
550 }
551}
552
553// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
554void CartridgeCTY::wipeAllScores()
555{
556 Serializer serializer(myEEPROMFile);
557 if(serializer)
558 {
559 // Erase score RAM
560 std::array<uInt8, 256> scoreRAM = {};
561 try
562 {
563 serializer.putByteArray(scoreRAM.data(), scoreRAM.size());
564 }
565 catch(...)
566 {
567 // Maybe add logging here that save failed?
568 cerr << name() << ": ERROR wiping score tables" << endl;
569 }
570 }
571}
572
573// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
574inline void CartridgeCTY::updateMusicModeDataFetchers()
575{
576 // Calculate the number of cycles since the last update
577 uInt32 cycles = uInt32(mySystem->cycles() - myAudioCycles);
578 myAudioCycles = mySystem->cycles();
579
580 // Calculate the number of CTY OSC clocks since the last update
581 double clocks = ((20000.0 * cycles) / 1193191.66666667) + myFractionalClocks;
582 uInt32 wholeClocks = uInt32(clocks);
583 myFractionalClocks = clocks - double(wholeClocks);
584
585 // Let's update counters and flags of the music mode data fetchers
586 if(wholeClocks > 0)
587 for(int x = 0; x <= 2; ++x)
588 myMusicCounters[x] += myMusicFrequencies[x] * wholeClocks;
589}
590
591// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
592const uInt32 CartridgeCTY::ourFrequencyTable[63] =
593{
594 // this should really be referenced from within the ROM, but its part of
595 // the Harmony/Melody CTY Driver, which does not appear to be in the ROM.
596
597 0, // CONT 0 Continue Note
598 0, // REPEAT 1 Repeat Song
599 0, // REST 2 Note Rest
600 /*
601 3511350 // C0
602 3720300 // C0s
603 3941491 // D0
604 4175781 // D0s
605 4424031 // E0
606 4687313 // F0
607 4965841 // F0s
608 5261120 // G0
609 5496699 // G0s
610
611 5905580 // A1
612 6256694 // A1s
613 6628853 // B1
614 7022916 // C1
615 7440601 // C1s
616 7882983 // D1
617 8351778 // D1s
618 8848277 // E1
619 9374625 // F1
620 9931897 // F1s
621 10522455 // G1
622 11148232 // G1s
623 */
624 11811160, // A2
625 12513387, // A2s
626 13257490, // B2
627 14045832, // C2
628 14881203, // C2s
629 15765966, // D2
630 16703557, // D2s
631 17696768, // E2
632 18749035, // F2
633 19864009, // F2s
634 21045125, // G2
635 22296464, // G2s
636
637 23622320, // A3
638 25026989, // A3s
639 26515195, // B3
640 28091878, // C3
641 29762191, // C3s
642 31531932, // D3
643 33406900, // D3s
644 35393537, // E3
645 37498071, // F3
646 39727803, // F3s
647 42090250, // G3
648 44592927, // G3s
649
650 47244640, // A4
651 50053978, // A4s
652 53030391, // B4
653 56183756, // C4 (Middle C)
654 59524596, // C4s
655 63064079, // D4
656 66814014, // D4s
657 70787074, // E4
658 74996142, // F4
659 79455606, // F4s
660 84180285, // G4
661 89186069, // G4s
662
663 94489281, // A5
664 100107957, // A5s
665 106060567, // B5
666 112367297, // C5
667 119048977, // C5s
668 126128157, // D5
669 133628029, // D5s
670 141573933, // E5
671 149992288, // F5
672 158911428, // F5s
673 168360785, // G5
674 178371925, // G5s
675
676 188978561, // A6
677 200215913, // A6s
678 212121348, // B6
679 224734593, // C6
680 238098169, // C6s
681 252256099, // D6
682 267256058, // D6s
683 283147866, // E6
684 299984783, // F6
685 317822855, // F6s
686 336721571, // G6
687 356744064 // G6s
688 /*
689 377957122 // A7
690 400431612 // A7s
691 424242481 // B7
692 449469401 // C7
693 476196124 // C7s
694 504512198 // D7
695 534512116 // D7s
696 566295948 // E7
697 599969565 // F7
698 635645496 // F7s
699 673443141 // G7
700 713488128 // G7s
701
702 755914244 // A8
703 800863224 // A8s
704 848484963 // B8
705 898938588 // C8
706 952392248 // C8s
707 1009024398 // D8
708 1069024232 // D8s
709 1132591895 // E8
710 1199939130 // F8
711 1271290992 // F8s
712 1346886282 // G8
713 1426976255 // G8s
714
715 1511828488 // A9
716 1601726449 // A9s
717 1696969925 // B9
718 1797877176 // C9
719 1904784495 // C9s
720 2018048796 // D9
721 2138048463 // D9s
722 */
723};
724