1 // ======================================================================== //
2 //  LOCUTUS -- aka. LTO Flash!                                              //
3 //                                                                          //
4 //  This class models Locutus, and serves as a structured container for     //
5 //  a Locutus compatible cartridge.  This may be initialized by or          //
6 //  serialized to a LUIGI file.                                             //
7 //                                                                          //
8 //  Note:  This is not tightly integrated into jzIntv at all.  Rather,      //
9 //  some adaptor C code will bridge between this C++ and jzIntv.            //
10 // ======================================================================== //
11 
12 #include "locutus.hpp"
13 #include <cstring>
14 #include <algorithm>
15 #include <iterator>
16 
17 #define LOC_JLP_SG_OFS (224)    // Arbitrary; however must be mult of 8
18 
19 using namespace std;
20 extern "C"
21 {
22     extern int jlp_accel_on;
23     extern int lto_isa_enabled;
24 }
25 
26 // ------------------------------------------------------------------------ //
27 //  Constructor                                                             //
28 // ------------------------------------------------------------------------ //
t_locutus()29 t_locutus::t_locutus()
30 :   uid          ( 0          )
31 ,   cpu_cache    ( nullptr    )
32 ,   jlp_savegame (            )
33 ,   jlp_sg_start ( 0          )
34 ,   jlp_sg_end   ( 0          )
35 ,   jlp_sg_addr  ( 0          )
36 ,   jlp_sg_row   ( 0          )
37 ,   jlp_sg_file  ( nullptr    )
38 ,   jlp_crc16    ( 0          )
39 ,   jlp_xregs    ( nullptr    )
40 ,   jlp_sleep    ( 0          )
41 ,   jlp_rand     ( 0x4A5A6A7A )
42 ,   is_scrambled ( false      )
43 {
44     memset( static_cast<void *>(&ram     [0]   ), 0, sizeof( ram      ) );
45     memset( static_cast<void *>(&mem_map [0][0]), 0, sizeof( mem_map  ) );
46     memset( static_cast<void *>(&pfl_map [0][0]), 0, sizeof( pfl_map  ) );
47     memset( static_cast<void *>(druid          ), 0, sizeof( druid    ) );
48 
49     for (int i = 0; i < 256; i++)
50         mem_perm[i][0].reset();
51 
52     for (int i = 0; i < 256; i++)
53         mem_perm[i][1].reset();
54 
55     for (int i = 0; i < 16; i++)
56         for (int j = 0; j < 16; j++)
57             pfl_perm[i][j].reset();
58 
59     for (int i = 0; i < 16; i++)
60         bsel[i] = 0;
61 
62     for (int i = 0; i < 16; i++)
63         jlp_regs[i] = 0;
64 
65     initialized.reset();
66 }
67 
68 // ------------------------------------------------------------------------ //
69 //  GET_INITIALIZED_SPAN_LIST  Get the spans of initialized memory          //
70 // ------------------------------------------------------------------------ //
get_initialized_span_list(void) const71 t_addr_list t_locutus::get_initialized_span_list( void ) const
72 {
73     uint32_t    s_addr  = 0, addr;
74     bool        in_span = false;
75     t_addr_list init_spans;
76 
77     for (addr = 0; addr < 512 * 1024; addr++)
78     {
79         if ( !in_span && initialized[addr] )
80         {
81             s_addr  = addr;
82             in_span = true;
83 
84         } else if ( in_span && !initialized[addr] )
85         {
86             init_spans.push_back( t_addr_span( s_addr, addr - 1 ) );
87             in_span = false;
88         }
89     }
90 
91     if (in_span)
92         init_spans.push_back( t_addr_span( s_addr, addr - 1 ) );
93 
94     return init_spans;
95 }
96 
97 
98 // ======================================================================== //
99 //  Intellivision Interface!                                                //
100 // ======================================================================== //
101 
102 
103 // ------------------------------------------------------------------------ //
104 //  INTV_READ                                                               //
105 // ------------------------------------------------------------------------ //
intv_read(const uint16_t intv_addr,const bool force) const106 uint16_t t_locutus::intv_read( const uint16_t intv_addr,
107                                const bool force ) const
108 {
109     jlp_rand.update( intv_addr, 16 );
110 
111     if ( jlp_sleep > 0 ) { jlp_sleep--; return 0xFFFF; }
112 
113     if ( ( intv_addr <= 0x04FF                        ) ||
114          ( intv_addr >= 0x1000 && intv_addr <= 0x1FFF ) ||
115          ( intv_addr >= 0x3000 && intv_addr <= 0x47FF ) )
116         return 0xFFFF;
117 
118     const uint8_t para = intv_addr >> 8;
119     const t_perm  perm = get_mem_perm( para, false );
120 
121     if ( jlp_accel_on &&
122          ( ( intv_addr >= 0x8000 && intv_addr <= 0x803F ) ||
123            ( intv_addr >= 0x9F80 && intv_addr <= 0x9FFF ) ) )
124         return do_jlp_accel_read( intv_addr );
125 
126     if ( force || perm[LOCUTUS_PERM_READ] )
127     {
128         const uint32_t map       = get_mem_map( para, false );
129         const uint32_t locu_addr = ( map << 8 ) | ( intv_addr & 0xFF );
130 
131         return read( locu_addr );
132     } else
133     {
134         return 0xFFFF;
135     }
136 }
137 
138 // ------------------------------------------------------------------------ //
139 //  DO_BANKSW_WRITE                                                         //
140 // ------------------------------------------------------------------------ //
do_banksw_write(const uint16_t addr,const uint16_t data)141 void    t_locutus::do_banksw_write( const uint16_t addr,
142                                     const uint16_t data )
143 {
144     const uint16_t hchap_addr = ((addr & 0xF) << 12) | ((addr & 0x10) << 7);
145     const uint8_t  para       = hchap_addr >> 8;
146 
147     if ( !get_mem_perm( para, false )[LOCUTUS_PERM_BANKSW] )
148         return;
149 
150     // Intellicart bank switching:
151     //
152     //  Each register accepts an 8-bit value.  (The upper 8 bits of the
153     //  value written are ignored.)  The lower 8 bits of the value written
154     //  specify the upper 8 bits of the target address for that 2K range.
155     //  The target address is combined with the CPU address in this manner:
156     //
157     //      target_addr = bank_select << 8;
158     //      icart_addr  = (intv_addr & 0x07FF) + target_addr;
159 
160     for (int i = 0; i < 8; i++)
161     {
162         set_mem_map( para + i, false, (data + i) & 0xFF );
163     }
164 
165     if (cpu_cache)
166     {
167         const uint16_t cpu_addr = para << 8;
168         cpu_cache->invalidate( cpu_addr, cpu_addr + 2047 );
169     }
170 }
171 
172 // ------------------------------------------------------------------------ //
173 //  DO_PAGEFLIP_WRITE                                                       //
174 // ------------------------------------------------------------------------ //
do_pageflip_write(const uint16_t intv_addr,const uint16_t data)175 void    t_locutus::do_pageflip_write( const uint16_t intv_addr,
176                                       const uint16_t data )
177 {
178     const uint8_t chap = intv_addr >> 12;
179     const uint8_t page = data & 0xF;
180 
181     if ( chap == 0 )
182         return;
183 
184     if ( !get_pageflip_perm( chap, page )[LOCUTUS_PERM_BANKSW] )
185         return;
186 
187     t_perm         perm = get_pageflip_perm( chap, page );
188     const uint16_t map  = get_pageflip_map ( chap, page ) << 4;
189     const uint8_t  para = chap << 4;
190 
191     perm[LOCUTUS_PERM_BANKSW] = false;
192     bsel[chap] = page;
193 
194     if ( chap != 4 )
195     {
196         for (int i = 0; i < 16; i++)
197         {
198             set_mem_map ( para + i, false, map + i );
199             set_mem_perm( para + i, false, perm    );
200         }
201 
202         if (cpu_cache)
203             cpu_cache->invalidate( para << 8, (para << 8) + 4095 );
204 
205     } else
206     {
207         // on $4xxx, only flip $4800 - $4FFF
208         for (int i = 8; i < 16; i++)
209         {
210             set_mem_map ( para + i, false, map + i );
211             set_mem_perm( para + i, false, perm    );
212         }
213 
214         if (cpu_cache)
215             cpu_cache->invalidate( 0x4800, 0x4FFF );
216     }
217 }
218 
219 
220 // ------------------------------------------------------------------------ //
221 //  INTV_WRITE                                                              //
222 // ------------------------------------------------------------------------ //
intv_write(const uint16_t intv_addr,const uint16_t data)223 void    t_locutus::intv_write( const uint16_t intv_addr,
224                                const uint16_t data )
225 {
226     jlp_rand.update( data,      16 );
227     jlp_rand.update( intv_addr, 16 );
228 
229     if ( jlp_sleep > 0 ) { jlp_sleep--; return; }
230 
231     if ( intv_addr >= 0x40 && intv_addr <= 0x5F)
232     {
233         do_banksw_write( intv_addr, data );
234         return;
235     }
236 
237     const uint8_t our_para = intv_addr >> 8;
238     const t_perm  our_perm = get_mem_perm( our_para, false );
239 
240     // Handle PV register updates
241     if ( intv_addr != 0x9F8D &&
242             (our_perm[LOCUTUS_PERM_READ] || our_perm[LOCUTUS_PERM_WRITE]) )
243     {
244         do_jlp_accel_write( 0x9F8D, intv_read( intv_addr, true ) );
245     }
246 
247     // Intercept JLP accelerator writes even before page-flip writes
248     if ( jlp_accel_on &&
249          ( ( intv_addr >= 0x8000 && intv_addr < 0x8040 ) ||
250            ( intv_addr >= 0x9F80 && intv_addr < 0x9FFF ) ) )
251     {
252         do_jlp_accel_write( intv_addr, data );
253         return;
254     }
255 
256     // That includes the switch to turn the accelerators on
257     if ( !jlp_accel_on && intv_addr == 0x8033 && data == 0x4A5A &&
258         get_jlp_accel() != JLP_DISABLED )
259     {
260         flip_jlp_accel( true );
261         return;
262     }
263 
264     // Always handle page-flip writes before anything else (except JLP)
265     if ( ( intv_addr & 0x0FFF) == 0xFFF &&
266          ((intv_addr & 0xF000) | 0x0A50 ) == ( data & 0xFFF0 ) &&
267          (!jlp_accel_on || (intv_addr != 0x8FFF && intv_addr != 0x9FFF)) )
268     {
269         do_pageflip_write( intv_addr, data );
270         return;
271     }
272 
273     // Feature flag 32 controls whether we respond to Locutus mapper writes
274     // in the $1000 - $14FF address range.  If it's not set, we ignore the
275     // write.  This should be controlled by the lto_mapper variable in the
276     // .CFG file used to create the LUIGI.
277     //
278     // Also, ignore the write if the user marked the segment writeable.
279     if ( feature_flag[32] && !our_perm[LOCUTUS_PERM_WRITE] )
280     {
281         const bool reset_copy = intv_addr & 0x100;
282 
283         //  Locutus native memory mapping update
284         if ( intv_addr >= 0x1000 && intv_addr <= 0x11FF )
285         {
286             uint8_t upd_para = intv_addr & 0xFF;
287             set_mem_map( upd_para, reset_copy, data );
288             return;
289         }
290 
291         //  Locutus native memory permission update
292         if ( intv_addr >= 0x1200 && intv_addr <= 0x13FF )
293         {
294             uint8_t upd_para = intv_addr & 0xFF;
295             t_perm  new_perm;
296 
297             new_perm[0] = data & (1 << 0);
298             new_perm[1] = data & (1 << 1);
299             new_perm[2] = data & (1 << 2);
300             new_perm[3] = data & (1 << 3);
301 
302             set_mem_perm( upd_para, reset_copy, new_perm );
303             return;
304         }
305 
306         //  Locutus native page-flip table update
307         if ( intv_addr >= 0x1400 && intv_addr <= 0x14FF )
308         {
309             const int i = (intv_addr >> 4) & 0xF;
310             const int j = (intv_addr >> 0) & 0xF;
311 
312             const uint16_t m = (data & 0x07F0) >> 4; // memory map
313             t_perm new_perm;
314 
315             new_perm[0] = data & (1 << 0);
316             new_perm[1] = data & (1 << 1);
317             new_perm[2] = data & (1 << 2);
318             new_perm[3] = data & (1 << 3);
319 
320             set_pageflip_map ( i, j, m        );
321             set_pageflip_perm( i, j, new_perm );
322 
323             return;
324         }
325     }
326 
327     //  Normal data write!  Who'd have thunk it?
328     if ( our_perm[LOCUTUS_PERM_WRITE] )
329     {
330         const uint32_t map       = get_mem_map( our_para, false );
331         const uint32_t locu_addr = ( map << 8 ) | (intv_addr & 0xFF);
332 
333         write( locu_addr, data );
334     }
335 }
336 
337 // ------------------------------------------------------------------------ //
338 //  INTV_RESET                                                              //
339 // ------------------------------------------------------------------------ //
intv_reset(void)340 void    t_locutus::intv_reset( void )
341 {
342     for (int para = 0; para < 256; para++)
343         set_mem_map ( para, false, get_mem_map( para, true ) );
344 
345     for (int para = 0; para < 256; para++)
346         set_mem_perm( para, false, get_mem_perm( para, true ) );
347 
348     for (int chap = 0; chap < 16; chap++)
349         bsel[chap] = 0;
350 
351     const auto jlp_accel = get_jlp_accel();
352     jlp_accel_on = false;
353 
354     if ( jlp_accel == JLP_ACCEL_ON || jlp_accel == JLP_ACCEL_FLASH_ON )
355         flip_jlp_accel( true );
356     // Note: Do *nothing* if JLP_DISABLED or JLP_ACCEL_OFF
357 
358     const auto lto_mapper = get_enable_lto_mapper();
359     lto_isa_enabled = lto_mapper || jlp_accel != JLP_DISABLED;
360 }
361 
362 // ======================================================================== //
363 //  JLP Acceleration Support                                                //
364 // ======================================================================== //
365 
366 // ------------------------------------------------------------------------ //
367 //  INIT_JLP_FLASH      Initialize JLP Flash support.                       //
368 // ------------------------------------------------------------------------ //
init_jlp_flash(const char * jlp_savegame_)369 void t_locutus::init_jlp_flash( const char* jlp_savegame_ )
370 {
371     // Close any open save-game file image
372     if ( jlp_sg_file )
373     {
374         fclose( jlp_sg_file );
375         jlp_sg_file = nullptr;
376     }
377 
378     // Get JLP flash parameters
379     const auto jlp_accel = get_jlp_accel();
380     const auto jlp_flash = jlp_accel == JLP_DISABLED ? 0u : get_jlp_flash();
381 
382     // If flash is disabled, set it up that way.
383     if ( !jlp_flash )
384     {
385         jlp_sg_start = 0;
386         jlp_sg_end   = 0;
387         jlp_sg_bytes = 0;
388 
389         jlp_sg_img.resize(0);
390         return;
391     }
392 
393     // Otherwise, initialize the JLP flash image
394     jlp_sg_start = LOC_JLP_SG_OFS;
395     jlp_sg_end   = LOC_JLP_SG_OFS + jlp_flash * 8 - 1;
396     jlp_sg_bytes = 192 * 8 * jlp_flash;
397     jlp_sg_addr  = 0xBADDu;
398     jlp_sg_row   = 0xBADDu;
399 
400     jlp_sg_img.resize( jlp_sg_bytes, 0xFF );
401 
402     // If we were given a backing file, open it.
403     if ( jlp_savegame_ )
404         jlp_savegame = jlp_savegame_;
405     else
406         jlp_savegame.clear();
407 
408     const char *fname = jlp_savegame_;
409     if ( fname &&
410          ( (jlp_sg_file = fopen(fname, "r+b")) != nullptr ||
411            (jlp_sg_file = fopen(fname, "w+b")) != nullptr ) )
412     {
413         // Try to read in the file image
414         fseek( jlp_sg_file, 0, SEEK_SET );
415         if ( fread( &(jlp_sg_img[0]), 1, jlp_sg_bytes, jlp_sg_file) !=
416                 jlp_sg_bytes )
417         {
418             // ignore errors here for now; they're OK
419         }
420 
421         // Create a backup file if we can
422         std::string jlp_savegame_bak = jlp_savegame + "~";
423         const char *fname_bak = jlp_savegame_bak.c_str();
424 
425         FILE *f_bak = fopen( fname_bak, "wb" );
426 
427         if (f_bak)
428         {
429             fwrite( &(jlp_sg_img[0]), 1, jlp_sg_bytes, f_bak );
430             fclose( f_bak );
431         }
432 
433         // Write our initial image to the backing file as part of init
434         fseek( jlp_sg_file, 0, SEEK_SET );
435         if ( fwrite( &(jlp_sg_img[0]), 1, jlp_sg_bytes, jlp_sg_file )
436                                                             != jlp_sg_bytes )
437         {
438             throw std::string(
439                     "Locutus JLP: Unable to initialize backing file\n");
440         }
441         fflush( jlp_sg_file );
442     }
443 }
444 
445 // ------------------------------------------------------------------------ //
446 //  DO_JLP_ACCEL_READ                                                       //
447 // ------------------------------------------------------------------------ //
do_jlp_accel_read(const uint16_t addr) const448 uint16_t t_locutus::do_jlp_accel_read( const uint16_t addr ) const
449 {
450     if ( addr >= 0x8000 && addr < 0x8023 ) return 0;
451     if ( addr == 0x8023 )                  return jlp_sg_start;
452     if ( addr == 0x8024 )                  return jlp_sg_end;
453     if ( addr >= 0x8025 && addr < 0x8040 ) return 0;
454     if ( addr >= 0x9F80 && addr < 0x9F90 ) return jlp_regs[ addr & 0xF ];
455     if ( addr >= 0x9F90 && addr < 0x9FA0  && jlp_xregs )
456         return jlp_xregs[ addr & 0xF ];
457     if ( addr >= 0x9FA0 && addr < 0x9FFD ) return 0xFFFF;
458     if ( addr == 0x9FFD )                  return jlp_crc16.get();
459     if ( addr == 0x9FFE )                  return next_rand() & 0xFFFF;
460     if ( addr == 0x9FFF )                  return 0;
461 
462 
463     return 0xFFFF;
464 }
465 
466 // ------------------------------------------------------------------------ //
467 //  DO_JLP_ACCEL_WRITE                                                      //
468 // ------------------------------------------------------------------------ //
do_jlp_accel_write(const uint16_t addr,const uint16_t data)469 void t_locutus::do_jlp_accel_write( const uint16_t addr, const uint16_t data )
470 {
471     // -------------------------------------------------------------------- //
472     //  Check for mult/div writes                                           //
473     //      $9F8(0,1):  s16($9F80) x s16($9F81) -> s32($9F8F:$9F8E)         //
474     //      $9F8(2,3):  s16($9F82) x u16($9F83) -> s32($9F8F:$9F8E)         //
475     //      $9F8(4,5):  u16($9F84) x s16($9F85) -> s32($9F8F:$9F8E)         //
476     //      $9F8(6,7):  u16($9F86) x u16($9F87) -> u32($9F8F:$9F8E)         //
477     //      $9F8(8,9):  s16($9F88) / s16($9F89) -> quot($9F8E), rem($9F8F)  //
478     //      $9F8(A,B):  u16($9F8A) / u16($9F8B) -> quot($9F8E), rem($9F8F)  //
479     // -------------------------------------------------------------------- //
480     if ( addr >= 0x9F80 && addr <= 0x9F8F )
481     {
482         uint32_t prod, quot, rem;
483 
484         jlp_regs[ addr & 0xF ] = data;
485 
486         switch (addr & 0xF)
487         {
488             case 0: case 1:
489                 prod = uint32_t( int32_t(  int16_t( jlp_regs[0] ) ) *
490                                  int32_t(  int16_t( jlp_regs[1] ) ) );
491                 jlp_regs[0xE] = prod;
492                 jlp_regs[0xF] = prod >> 16;
493                 break;
494 
495             case 2: case 3:
496                 prod = uint32_t( int32_t(  int16_t( jlp_regs[2] ) ) *
497                                  int32_t( uint16_t( jlp_regs[3] ) ) );
498                 jlp_regs[0xE] = prod;
499                 jlp_regs[0xF] = prod >> 16;
500                 break;
501 
502             case 4: case 5:
503                 prod = uint32_t( int32_t( uint16_t( jlp_regs[4] ) ) *
504                                  int32_t(  int16_t( jlp_regs[5] ) ) );
505                 jlp_regs[0xE] = prod;
506                 jlp_regs[0xF] = prod >> 16;
507                 break;
508 
509             case 6: case 7:
510                 prod = uint32_t( uint32_t( uint16_t( jlp_regs[6] ) ) *
511                                  uint32_t( uint16_t( jlp_regs[7] ) ) );
512                 jlp_regs[0xE] = prod;
513                 jlp_regs[0xF] = prod >> 16;
514                 break;
515 
516             case 8: case 9:
517                 if ( jlp_regs[9] )
518                 {
519                     quot = uint32_t( int16_t( jlp_regs[8] ) /
520                                      int16_t( jlp_regs[9] ) );
521                     rem  = uint32_t( int16_t( jlp_regs[8] ) %
522                                      int16_t( jlp_regs[9] ) );
523                     jlp_regs[0xE] = quot;
524                     jlp_regs[0xF] = rem;
525                 }
526                 break;
527 
528             case 10: case 11:
529                 if ( jlp_regs[11] )
530                 {
531                     quot = uint32_t( uint16_t( jlp_regs[10] ) /
532                                      uint16_t( jlp_regs[11] ) );
533                     rem  = uint32_t( uint16_t( jlp_regs[10] ) %
534                                      uint16_t( jlp_regs[11] ) );
535                     jlp_regs[0xE] = quot;
536                     jlp_regs[0xF] = rem;
537                 }
538                 break;
539         }
540         return;
541     }
542 
543     // -------------------------------------------------------------------- //
544     //  Check for JLP X-Regs                                                //
545     // -------------------------------------------------------------------- //
546     if ( addr >= 0x9F90 && addr <= 0x9F9F && jlp_xregs )
547     {
548         jlp_xregs[ addr & 0xF ] = data;
549         return;
550     }
551 
552     // -------------------------------------------------------------------- //
553     //  Check for Save-Game Arch V2 writes                                  //
554     //                                                                      //
555     //      $8025   JLP RAM address to operate on                           //
556     //      $8026   Flash row to operate on                                 //
557     //                                                                      //
558     //      $802D   Copy JLP RAM to flash row (unlock: $C0DE)               //
559     //      $802E   Copy JLP RAM to flash row (unlock: $DEC0)               //
560     //      $802F   Copy JLP RAM to flash row (unlock: $BEEF)               //
561     // -------------------------------------------------------------------- //
562     if ( addr == 0x8025 ) { jlp_sg_addr = data; return; }
563     if ( addr == 0x8026 ) { jlp_sg_row  = data; return; }
564 
565     if ( addr == 0x802D && data == 0xC0DE ) { jlp_sg_ram_to_flash(); return; }
566     if ( addr == 0x802E && data == 0xDEC0 ) { jlp_sg_flash_to_ram(); return; }
567     if ( addr == 0x802F && data == 0xBEEF ) { jlp_sg_erase_sector(); return; }
568 
569     // -------------------------------------------------------------------- //
570     //  Check for CRC-16 accelerator writes                                 //
571     // -------------------------------------------------------------------- //
572     if ( addr == 0x9FFC ) jlp_crc16.update( data, 16 );
573     if ( addr == 0x9FFD ) jlp_crc16.set( data );
574 
575     // -------------------------------------------------------------------- //
576     //  Check for JLP Accelerator Disable                                   //
577     // -------------------------------------------------------------------- //
578     if ( addr == 0x8034 && data == 0x6A7A )
579         flip_jlp_accel( false );
580 }
581 
582 // ------------------------------------------------------------------------ //
583 //  JLP_SG_VALIDATE_ARGS    Ensure JLP SG command arguments are valid       //
584 // ------------------------------------------------------------------------ //
jlp_sg_validate_args(const bool chk_addr) const585 bool t_locutus::jlp_sg_validate_args( const bool chk_addr ) const
586 {
587     if ( jlp_sg_row < jlp_sg_start || jlp_sg_row > jlp_sg_end )
588         return false;
589 
590     if ( chk_addr && (jlp_sg_addr < 0x8040 || jlp_sg_addr + 95 > 0x9F7F) )
591         return false;
592 
593     return true;
594 }
595 
596 // ------------------------------------------------------------------------ //
597 //  JLP_SG_RAM_TO_FLASH     Copy RAM to a row in flash                      //
598 // ------------------------------------------------------------------------ //
jlp_sg_ram_to_flash(void)599 void t_locutus::jlp_sg_ram_to_flash( void )
600 {
601     jlp_sleep = 400 + next_rand() % 200;
602 
603     if ( !jlp_sg_validate_args() )
604         return;
605 
606     const uint32_t row_idx = (jlp_sg_row - jlp_sg_start) * 192;
607 
608     // Refuse to write if row isn't empty
609     for ( int i = 0; i < 192; i++ )
610         if ( jlp_sg_img[ row_idx + i ] != 0xFF )
611             return;
612 
613     // Copy the data from RAM to flash in little-endian
614     for ( int i = 0, a = 0; i < 192; i += 2, a++ )
615     {
616         const uint16_t intv_addr = jlp_sg_addr + a;
617         const uint16_t para      = intv_addr >> 8;
618         const uint32_t map       = get_mem_map( para, false );
619         const uint32_t locu_addr = ( map << 8 ) | ( intv_addr & 0xFF );
620         const uint16_t data      = read( locu_addr );
621 
622         jlp_sg_img[ row_idx + i + 0 ] = data & 0xFF;
623         jlp_sg_img[ row_idx + i + 1 ] = data >> 8;
624     }
625 
626     // Update backing file
627     if ( jlp_sg_file )
628     {
629         fseek( jlp_sg_file, row_idx, SEEK_SET );
630         fwrite( &(jlp_sg_img[row_idx]), 2, 96, jlp_sg_file );
631         fflush( jlp_sg_file );
632     }
633 }
634 
635 // ------------------------------------------------------------------------ //
636 //  JLP_SG_FLASH_TO_RAM     Copy a row in flash to RAM                      //
637 // ------------------------------------------------------------------------ //
jlp_sg_flash_to_ram(void)638 void t_locutus::jlp_sg_flash_to_ram( void )
639 {
640     jlp_sleep = 10 + next_rand() % 20;
641 
642     if ( !jlp_sg_validate_args() )
643         return;
644 
645     const uint32_t row_idx = (jlp_sg_row - jlp_sg_start) * 192;
646 
647     // Copy the data from flash to RAM in little-endian
648     for ( int i = 0, a = 0; i < 192; i += 2, a++ )
649     {
650         const uint16_t intv_addr = jlp_sg_addr + a;
651         const uint16_t para      = intv_addr >> 8;
652         const uint32_t map       = get_mem_map( para, false );
653         const uint32_t locu_addr = ( map << 8 ) | ( intv_addr & 0xFF );
654         const uint16_t data_lo   = jlp_sg_img[ row_idx + i + 0 ];
655         const uint16_t data_hi   = jlp_sg_img[ row_idx + i + 1 ];
656         const uint16_t data      = data_lo | (data_hi << 8);
657 
658         write( locu_addr, data );
659     }
660 }
661 
662 // ------------------------------------------------------------------------ //
663 //  JLP_SG_ERASE_SECTOR     Erase a sector (8 rows)                         //
664 // ------------------------------------------------------------------------ //
jlp_sg_erase_sector(void)665 void t_locutus::jlp_sg_erase_sector( void )
666 {
667     jlp_sleep = 800 + next_rand() % 200;
668 
669     if ( !jlp_sg_validate_args( false ) )
670         return;
671 
672     const uint32_t row_idx = ((jlp_sg_row - jlp_sg_start) & ~7) * 192;
673 
674     memset( &(jlp_sg_img[row_idx]), 0xFF, 192 * 8 );
675 
676     // Update backing file
677     if ( jlp_sg_file )
678     {
679         fseek( jlp_sg_file, row_idx, SEEK_SET );
680         fwrite( &(jlp_sg_img[row_idx]), 2, 96 * 8, jlp_sg_file );
681         fflush( jlp_sg_file );
682     }
683 }
684 
685 // ------------------------------------------------------------------------ //
686 //  FLIP_JLP_ACCEL      Flip the JLP Accelerators on/off.                   //
687 // ------------------------------------------------------------------------ //
flip_jlp_accel(const bool new_jlp_accel_on)688 void t_locutus::flip_jlp_accel( const bool new_jlp_accel_on )
689 {
690 //  printf("Locutus: flip_jlp_accel %s\n", new_jlp_accel_on ? "ON" : "off");
691 //  fflush(stdout);
692     // Locutus ignores attempts to switch into the already-active mode.
693     // This is important, as updates to the various memory mapper structures
694     // that happened since the last flip will be retained.
695     if ( new_jlp_accel_on == !!jlp_accel_on )
696         return;
697 
698     jlp_accel_on = new_jlp_accel_on;
699 
700     // Turning JLP Accelerators + RAM on:
701     //
702     //  -- Map $8040 - $9F7F through to Locutus $10040 - $11F7F
703     //  -- Mark $8040 - $9F7F +R, +W, 16-bit, no page flip, no bankswitch
704     //  -- JLP Accelerators become visible at $8000 - $803F, $9F80 - $9FFF
705     //
706     // Note that we don't actually modify the flip table, and we do remember
707     // which page was last flipped in on $8xxx and $9xxx.
708     if ( jlp_accel_on )
709     {
710         const t_perm perm = 0x3;  // +R, +W, 16-bit, no pageflip/bankswitch
711         uint16_t jlp_map = 0x0100;
712 
713         for ( uint8_t para = 0x80 ; para <= 0x9F ; para++ )
714         {
715             set_mem_perm( para, false, perm );
716             set_mem_map ( para, false, jlp_map++ );
717         }
718     } else
719     // Turning JLP Accelerators + RAM off:
720     //
721     //  -- $8000 - $9FFF get mapped to most recently flipped-in page
722     //  -- JLP Accelerators become invisible
723     {
724         for ( uint8_t chap = 8; chap <= 9; chap++ )
725         {
726             const uint8_t  page = bsel[chap];
727             t_perm         perm = get_pageflip_perm( chap, page );
728             const uint16_t map  = get_pageflip_map ( chap, page ) << 4;
729             const uint8_t  para = chap << 4;
730 
731             perm[LOCUTUS_PERM_BANKSW] = false;
732 
733             for (int i = 0; i < 16; i++)
734             {
735                 set_mem_map ( para + i, false, map + i );
736                 set_mem_perm( para + i, false, perm    );
737             }
738         }
739     }
740 
741     if (cpu_cache)
742         cpu_cache->invalidate( 0x8000, 0x9FFF );
743 }
744 
745 
746 // ------------------------------------------------------------------------ //
747 //  Feature Flags                                                           //
748 //                                                                          //
749 //  Bit(s)  Description                                                     //
750 //  ------- -----------------------------------------------------------     //
751 //  0..1    Intellivoice compatibility                                      //
752 //  2..3    ECS compatibility                                               //
753 //  4..5    Intellivision 2 compatibility                                   //
754 //  6..7    Keyboard Component compatibility                                //
755 //  8..15   Reserved                                                        //
756 //  16..17  JLP Accelerator Enable (MPY/DIV/etc at $9F80 - $9FFF)           //
757 //  18..21  Reserved                                                        //
758 //  22..31  JLP Flash Save-Game minimum required size in sectors            //
759 //  32      LTO Mapper                                                      //
760 //  33..62  Reserved                                                        //
761 //  63      Defaults flag:  0 = defaults, 1 = explicitly set by user        //
762 //  64..127 Reserved.                                                       //
763 //                                                                          //
764 //  Note: t_metadata and feature_flags must stay in sync.  If an outside    //
765 //  class directly modifies t_metadata or the feature flags, it must take   //
766 //  an additional step to bring them back in sync.                          //
767 //                                                                          //
768 //  The getters will assert() if they are out of sync at a get().           //
769 // ------------------------------------------------------------------------ //
set_compat_voice(const compat_level_t voice_compat)770 void t_locutus::set_compat_voice( const compat_level_t voice_compat )
771 {
772     const int voice = int( voice_compat );
773     set_feature_flag(  0, voice     & 1 );
774     set_feature_flag(  1, voice     & 2 );
775     metadata.voice_compat = voice_compat;
776 }
777 
set_compat_ecs(const compat_level_t ecs_compat)778 void t_locutus::set_compat_ecs  ( const compat_level_t ecs_compat )
779 {
780     const int ecs = int( ecs_compat );
781     set_feature_flag(  2, ecs       & 1 );
782     set_feature_flag(  3, ecs       & 2 );
783     metadata.ecs_compat = ecs_compat;
784 }
785 
set_compat_intv2(const compat_level_t intv2_compat)786 void t_locutus::set_compat_intv2( const compat_level_t intv2_compat )
787 {
788     const int intv2 = int( intv2_compat );
789     set_feature_flag(  4, intv2     & 1 );
790     set_feature_flag(  5, intv2     & 2 );
791     metadata.intv2_compat = intv2_compat;
792 }
793 
set_compat_kc(const compat_level_t kc_compat)794 void t_locutus::set_compat_kc( const compat_level_t kc_compat )
795 {
796     const int kc = int( kc_compat );
797     set_feature_flag(  6, kc        & 1 );
798     set_feature_flag(  7, kc        & 2 );
799     metadata.kc_compat = kc_compat;
800 }
801 
set_compat_tv(const compat_level_t tv_compat)802 void t_locutus::set_compat_tv( const compat_level_t tv_compat )
803 {
804     const int tv = int( tv_compat );
805     if ( tv_compat == CMP_TOLERATES ) {
806         set_feature_flag(  8, 0 );
807         set_feature_flag(  9, 0 );
808         set_feature_flag( 10, 0 );
809         set_feature_flag( 11, 0 );
810     } else
811     {
812         set_feature_flag(  8, 1 );
813         set_feature_flag(  9, 0 );
814         set_feature_flag( 10, tv & 1 );
815         set_feature_flag( 11, tv & 2 );
816     }
817     metadata.tv_compat = tv_compat;
818 }
819 
set_jlp_features(const jlp_accel_t jlp_accel,const unsigned jlp_flash)820 void t_locutus::set_jlp_features( const jlp_accel_t jlp_accel,
821                                   const unsigned jlp_flash )
822 {
823     const int jlp_flag = int( jlp_accel );
824 
825     set_feature_flag( 16, jlp_flag & 1 );
826     set_feature_flag( 17, jlp_flag & 2 );
827 
828     // Note:  jlp_flash ignored if (jlp_accel & 2) == 0
829     set_feature_flag( 22, ( jlp_flash >> 0 ) & 1 );
830     set_feature_flag( 23, ( jlp_flash >> 1 ) & 1 );
831     set_feature_flag( 24, ( jlp_flash >> 2 ) & 1 );
832     set_feature_flag( 25, ( jlp_flash >> 3 ) & 1 );
833     set_feature_flag( 26, ( jlp_flash >> 4 ) & 1 );
834     set_feature_flag( 27, ( jlp_flash >> 5 ) & 1 );
835     set_feature_flag( 28, ( jlp_flash >> 6 ) & 1 );
836     set_feature_flag( 29, ( jlp_flash >> 7 ) & 1 );
837     set_feature_flag( 30, ( jlp_flash >> 8 ) & 1 );
838     set_feature_flag( 31, ( jlp_flash >> 9 ) & 1 );
839 
840     metadata.jlp_accel = jlp_accel;
841     metadata.jlp_flash = jlp_flash;
842 }
843 
set_enable_lto_mapper(const bool lto_mapper)844 void t_locutus::set_enable_lto_mapper( const bool lto_mapper )
845 {
846     set_feature_flag( 32, lto_mapper ? 1 : 0 );
847     metadata.lto_mapper = lto_mapper;
848 }
849 
set_explicit_flags(const bool explicit_flags)850 void t_locutus::set_explicit_flags( const bool explicit_flags )
851 {
852     set_feature_flag( 63, explicit_flags ? 1 : 0 );
853     metadata.is_defaults = !explicit_flags;
854 }
855 
get_compat_voice(void) const856 compat_level_t t_locutus::get_compat_voice( void ) const
857 {
858     int voice = ( get_feature_flag( 0 ) ? 1 : 0 )
859               | ( get_feature_flag( 1 ) ? 2 : 0 );
860     compat_level_t voice_compat = compat_level_t( voice );
861     assert( voice_compat == metadata.voice_compat );
862     return voice_compat;
863 }
864 
get_compat_ecs(void) const865 compat_level_t t_locutus::get_compat_ecs  ( void ) const
866 {
867     int ecs = ( get_feature_flag( 2 ) ? 1 : 0 )
868             | ( get_feature_flag( 3 ) ? 2 : 0 );
869     compat_level_t ecs_compat = compat_level_t( ecs );
870     assert( ecs_compat == metadata.ecs_compat );
871     return ecs_compat;
872 }
873 
get_compat_intv2(void) const874 compat_level_t t_locutus::get_compat_intv2( void ) const
875 {
876     int intv2 = ( get_feature_flag( 4 ) ? 1 : 0 )
877               | ( get_feature_flag( 5 ) ? 2 : 0 );
878     compat_level_t intv2_compat = compat_level_t( intv2 );
879     assert( intv2_compat == metadata.intv2_compat );
880     return intv2_compat;
881 }
882 
get_compat_kc(void) const883 compat_level_t t_locutus::get_compat_kc   ( void ) const
884 {
885     int kc = ( get_feature_flag( 6 ) ? 1 : 0 )
886            | ( get_feature_flag( 7 ) ? 2 : 0 );
887     compat_level_t kc_compat = compat_level_t( kc );
888     assert( kc_compat == metadata.kc_compat );
889     return kc_compat;
890 }
891 
get_compat_tv(void) const892 compat_level_t t_locutus::get_compat_tv   ( void ) const
893 {
894     int ex = ( get_feature_flag( 8 ) ? 1 : 0 )
895            | ( get_feature_flag( 9 ) ? 2 : 0 );
896     int tv = ( get_feature_flag( 10 ) ? 1 : 0 )
897            | ( get_feature_flag( 11 ) ? 2 : 0 );
898 
899     compat_level_t tv_compat = ex ? compat_level_t( tv ) : CMP_TOLERATES;
900     assert( tv_compat == metadata.tv_compat );
901     return tv_compat;
902 }
903 
get_jlp_accel(void) const904 jlp_accel_t t_locutus::get_jlp_accel( void ) const
905 {
906     int jlp_accel_val = ( get_feature_flag( 16 ) ? 1 : 0 )
907                       | ( get_feature_flag( 17 ) ? 2 : 0 );
908     jlp_accel_t jlp_accel = jlp_accel_t( jlp_accel_val );
909     assert( jlp_accel == metadata.jlp_accel );
910     return jlp_accel;
911 }
912 
get_jlp_flash(void) const913 unsigned t_locutus::get_jlp_flash( void ) const
914 {
915     // Note:  jlp_flash ignored if (jlp_accel & 2) == 0
916     int jlp_flash = ( get_feature_flag( 22 ) ?   1 : 0 )
917                   | ( get_feature_flag( 23 ) ?   2 : 0 )
918                   | ( get_feature_flag( 24 ) ?   4 : 0 )
919                   | ( get_feature_flag( 25 ) ?   8 : 0 )
920                   | ( get_feature_flag( 26 ) ?  16 : 0 )
921                   | ( get_feature_flag( 27 ) ?  32 : 0 )
922                   | ( get_feature_flag( 28 ) ?  64 : 0 )
923                   | ( get_feature_flag( 29 ) ? 128 : 0 )
924                   | ( get_feature_flag( 30 ) ? 256 : 0 )
925                   | ( get_feature_flag( 31 ) ? 512 : 0 );
926     assert( jlp_flash == metadata.jlp_flash );
927     return jlp_flash;
928 }
929 
get_enable_lto_mapper(void) const930 bool t_locutus::get_enable_lto_mapper( void ) const
931 {
932     bool lto_mapper = get_feature_flag( 32 );
933     assert( lto_mapper == metadata.lto_mapper );
934     return lto_mapper;
935 }
936 
get_explicit_flags(void) const937 bool t_locutus::get_explicit_flags( void ) const
938 {
939     return get_feature_flag( 63 );
940 }
941 
copy_feature_flag_to_metadata()942 void t_locutus::copy_feature_flag_to_metadata( )
943 {
944     int voice = ( get_feature_flag( 0 ) ? 1 : 0 )
945               | ( get_feature_flag( 1 ) ? 2 : 0 );
946     metadata.voice_compat = compat_level_t( voice );
947 
948     int ecs = ( get_feature_flag( 2 ) ? 1 : 0 )
949             | ( get_feature_flag( 3 ) ? 2 : 0 );
950     metadata.ecs_compat = compat_level_t( ecs );
951 
952     int intv2 = ( get_feature_flag( 4 ) ? 1 : 0 )
953               | ( get_feature_flag( 5 ) ? 2 : 0 );
954     metadata.intv2_compat = compat_level_t( intv2 );
955 
956     int kc = ( get_feature_flag( 6 ) ? 1 : 0 )
957            | ( get_feature_flag( 7 ) ? 2 : 0 );
958     metadata.kc_compat = compat_level_t( kc );
959 
960     int ex = ( get_feature_flag( 8 ) ? 1 : 0 )
961            | ( get_feature_flag( 9 ) ? 2 : 0 );
962     int tv = ( get_feature_flag( 10 ) ? 1 : 0 )
963            | ( get_feature_flag( 11 ) ? 2 : 0 );
964     metadata.tv_compat = ex ? compat_level_t( tv ) : CMP_TOLERATES;
965 
966     int jlp_accel_val = ( get_feature_flag( 16 ) ? 1 : 0 )
967                       | ( get_feature_flag( 17 ) ? 2 : 0 );
968     metadata.jlp_accel = jlp_accel_t( jlp_accel_val );
969 
970     // Note:  jlp_flash ignored if (jlp_accel & 2) == 0
971     int jlp_flash = ( get_feature_flag( 22 ) ?   1 : 0 )
972                   | ( get_feature_flag( 23 ) ?   2 : 0 )
973                   | ( get_feature_flag( 24 ) ?   4 : 0 )
974                   | ( get_feature_flag( 25 ) ?   8 : 0 )
975                   | ( get_feature_flag( 26 ) ?  16 : 0 )
976                   | ( get_feature_flag( 27 ) ?  32 : 0 )
977                   | ( get_feature_flag( 28 ) ?  64 : 0 )
978                   | ( get_feature_flag( 29 ) ? 128 : 0 )
979                   | ( get_feature_flag( 30 ) ? 256 : 0 )
980                   | ( get_feature_flag( 31 ) ? 512 : 0 );
981     metadata.jlp_flash = jlp_flash;
982 
983     metadata.lto_mapper = get_feature_flag( 32 );
984     metadata.is_defaults = !get_explicit_flags();
985 }
986 
987 // ------------------------------------------------------------------------ //
988 //  T_CPU_CACHE_IF::~T_CPU_CACHE_IF                                         //
989 //  Anchor this class' VTable!                                              //
990 // ------------------------------------------------------------------------ //
~t_cpu_cache_if()991 t_cpu_cache_if::~t_cpu_cache_if()
992 {
993     // Nothing.
994 }
995