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