| 1 | /* |
| 2 | * SMS emulation |
| 3 | * (C) notaz, 2009-2010 |
| 4 | * (C) irixxxx, 2021-2025 |
| 5 | * |
| 6 | * This work is licensed under the terms of MAME license. |
| 7 | * See COPYING file in the top-level directory. |
| 8 | */ |
| 9 | /* |
| 10 | * TODO: |
| 11 | * - start in a state as if BIOS ran (partly done for VDP registers, RAM) |
| 12 | * - region support (currently only very limited PAL and Mark-III support) |
| 13 | * - mapper for EEPROM support |
| 14 | */ |
| 15 | #include "pico_int.h" |
| 16 | #include "memory.h" |
| 17 | #include "sound/sn76496.h" |
| 18 | #include "sound/emu2413/emu2413.h" |
| 19 | |
| 20 | #include <platform/common/input_pico.h> // for keyboard handling |
| 21 | |
| 22 | |
| 23 | extern void YM2413_regWrite(unsigned reg); |
| 24 | extern void YM2413_dataWrite(unsigned data); |
| 25 | |
| 26 | extern unsigned sprites_status; // TODO put in some hdr file! |
| 27 | extern int sprites_zoom, xscroll; |
| 28 | |
| 29 | static unsigned char vdp_data_read(void) |
| 30 | { |
| 31 | struct PicoVideo *pv = &Pico.video; |
| 32 | unsigned char d; |
| 33 | |
| 34 | d = Pico.ms.vdp_buffer; |
| 35 | Pico.ms.vdp_buffer = PicoMem.vramb[MEM_LE2(pv->addr)]; |
| 36 | pv->addr = (pv->addr + 1) & 0x3fff; |
| 37 | pv->pending = 0; |
| 38 | return d; |
| 39 | } |
| 40 | |
| 41 | static unsigned char vdp_ctl_read(void) |
| 42 | { |
| 43 | struct PicoVideo *pv = &Pico.video; |
| 44 | unsigned char d; |
| 45 | |
| 46 | z80_int_assert(0); |
| 47 | d = pv->status | (pv->pending_ints << 7); |
| 48 | pv->pending = pv->pending_ints = 0; |
| 49 | pv->status = 0; |
| 50 | |
| 51 | if (pv->reg[0] & 0x04) |
| 52 | d |= 0x1f; // unused bits in mode 4 read as 1 |
| 53 | |
| 54 | elprintf(EL_SR, "VDP sr: %02x", d); |
| 55 | return d; |
| 56 | } |
| 57 | |
| 58 | static void vdp_data_write(unsigned char d) |
| 59 | { |
| 60 | struct PicoVideo *pv = &Pico.video; |
| 61 | |
| 62 | if (pv->type == 3) { |
| 63 | // cram. 32 on SMS, but 64 on MD. Fill 2nd half of cram for prio bit mirror |
| 64 | if (PicoIn.AHW & PAHW_GG) { // GG, same layout as MD |
| 65 | unsigned a = pv->addr & 0x3f; |
| 66 | if (a & 0x1) { // write complete color on high byte write |
| 67 | u16 c = ((d&0x0f) << 8) | Pico.ms.vdp_buffer; |
| 68 | if (PicoMem.cram[a >> 1] != c) Pico.m.dirtyPal = 1; |
| 69 | PicoMem.cram[a >> 1] = PicoMem.cram[(a >> 1)+0x20] = c; |
| 70 | } |
| 71 | } else { // SMS, convert to MD layout (00BbGgRr to 0000BbBbGgGgRrRr) |
| 72 | unsigned a = pv->addr & 0x1f; |
| 73 | u16 c = ((d&0x30)<<6) + ((d&0x0c)<<4) + ((d&0x03)<<2); |
| 74 | if (PicoMem.cram[a] != (c | (c>>2))) Pico.m.dirtyPal = 1; |
| 75 | PicoMem.cram[a] = PicoMem.cram[a+0x20] = c | (c>>2); |
| 76 | } |
| 77 | } else { |
| 78 | PicoMem.vramb[MEM_LE2(pv->addr)] = d; |
| 79 | } |
| 80 | pv->addr = (pv->addr + 1) & 0x3fff; |
| 81 | |
| 82 | Pico.ms.vdp_buffer = d; |
| 83 | pv->pending = 0; |
| 84 | } |
| 85 | |
| 86 | // VDP horizontal timing, total 342 px: |
| 87 | // 256 px active display, |
| 88 | // 23 px right border+blanking, |
| 89 | // 26 px hsync, |
| 90 | // 37 px left blanking+border |
| 91 | // VINT is at the beginning of hsync, and HINT is one px later. Relative TO V/HINT: |
| 92 | // -18..-2 px 1st half of sprite attribute table (r5) scan |
| 93 | // -10 px sprite mode latching (r1,r0) |
| 94 | // -2 px hscroll latching (r8) |
| 95 | // hscroll is probably latched internally due to it depending on the horizontal |
| 96 | // scroll lock, which has this at 0 for the top 16 lines. |
| 97 | // I don't think the sprite mode is really latched. The SAT scan determines the |
| 98 | // relative y position within the sprite pattern, which will break since SAT |
| 99 | // scanning is done in one go here, while in reality it is distributed over |
| 100 | // several slots. Cache it here to avoid backward effects of later changes to r1 |
| 101 | // TODO: off by 2 CPU cycles according to VDPTEST? |
| 102 | |
| 103 | static NOINLINE void vdp_reg_write(struct PicoVideo *pv, u8 a, u8 d) |
| 104 | { |
| 105 | int l; |
| 106 | |
| 107 | pv->reg[a] = d; |
| 108 | switch (a) { |
| 109 | case 0: // mode control 1 |
| 110 | l = pv->pending_ints & (d >> 3) & 2; |
| 111 | elprintf(EL_INTS, "hint %d", l); |
| 112 | z80_int_assert(l); |
| 113 | if (z80_cyclesDone() - Pico.t.z80c_line_start < 228 - (int)(10*1.5)+2) |
| 114 | sprites_zoom = (pv->reg[1] & 0x3) | (pv->reg[0] & 0x8); |
| 115 | break; |
| 116 | case 1: // mode control 2 |
| 117 | l = pv->pending_ints & (d >> 5) & 1; |
| 118 | elprintf(EL_INTS, "vint %d", l); |
| 119 | z80_int_assert(l); |
| 120 | if (z80_cyclesDone() - Pico.t.z80c_line_start < 228 - (int)(10*1.5)+2) |
| 121 | sprites_zoom = (pv->reg[1] & 0x3) | (pv->reg[0] & 0x8); |
| 122 | break; |
| 123 | case 8: // horizontal scroll |
| 124 | if (z80_cyclesDone() - Pico.t.z80c_line_start < 228 - (int)(2*1.5)+2) |
| 125 | xscroll = d; |
| 126 | break; |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | static void vdp_ctl_write(u8 d) |
| 131 | { |
| 132 | struct PicoVideo *pv = &Pico.video; |
| 133 | |
| 134 | if (pv->pending) { |
| 135 | pv->type = d >> 6; |
| 136 | if (pv->type == 2) { |
| 137 | elprintf(EL_IO, " VDP r%02x=%02x", d & 0x0f, pv->addr & 0xff); |
| 138 | if (pv->reg[d & 0x0f] != (u8)pv->addr) |
| 139 | vdp_reg_write(pv, d & 0x0f, pv->addr); |
| 140 | } |
| 141 | pv->addr &= 0x00ff; |
| 142 | pv->addr |= (d & 0x3f) << 8; |
| 143 | if (pv->type == 0) { |
| 144 | Pico.ms.vdp_buffer = PicoMem.vramb[MEM_LE2(pv->addr)]; |
| 145 | pv->addr = (pv->addr + 1) & 0x3fff; |
| 146 | } |
| 147 | } else { |
| 148 | pv->addr &= 0x3f00; |
| 149 | pv->addr |= d; |
| 150 | } |
| 151 | pv->pending ^= 1; |
| 152 | } |
| 153 | |
| 154 | static u8 vdp_hcounter(int cycles) |
| 155 | { |
| 156 | // 171 slots per scanline of 228 clocks, counted 0xf4-0x93, 0xe9-0xf3 |
| 157 | // this matches h counter tables in SMSVDPTest: |
| 158 | // hc = (cycles+2) * 171 /228 -1 + 0xf4; |
| 159 | int hc = (((cycles+2) * ((171<<8)/228))>>8)-1 + 0xf4; // Q8 to avoid dividing |
| 160 | if (hc > 0x193) hc += 0xe9-0x93-1; |
| 161 | return hc; |
| 162 | } |
| 163 | |
| 164 | // keyboard matrix: |
| 165 | // port A @0xdc: |
| 166 | // row 0: 1 2 3 4 5 6 7 |
| 167 | // row 1: q w e r t y u |
| 168 | // row 2: a s d f g h j |
| 169 | // row 3: z x c v b n m |
| 170 | // row 4: ED SP CL DL kana space clear/home del/ins |
| 171 | // row 5: , . / PI v < > pi |
| 172 | // row 6: k l ; : ] CR ^ enter |
| 173 | // row 7: i o p @ [ |
| 174 | // port B @0xdd: |
| 175 | // row 8: 8 9 0 - ^ YN BR yen break |
| 176 | // row 9: GR graph |
| 177 | // row 10: CTL control |
| 178 | // row 11: FN SFT func shift |
| 179 | static unsigned char kbd_matrix[12]; |
| 180 | |
| 181 | // row | col |
| 182 | static unsigned char kbd_map[] = { |
| 183 | [PEVB_KBD_1] = 0x00, |
| 184 | [PEVB_KBD_2] = 0x01, |
| 185 | [PEVB_KBD_3] = 0x02, |
| 186 | [PEVB_KBD_4] = 0x03, |
| 187 | [PEVB_KBD_5] = 0x04, |
| 188 | [PEVB_KBD_6] = 0x05, |
| 189 | [PEVB_KBD_7] = 0x06, |
| 190 | [PEVB_KBD_8] = 0x80, |
| 191 | [PEVB_KBD_9] = 0x81, |
| 192 | [PEVB_KBD_0] = 0x82, |
| 193 | [PEVB_KBD_MINUS] = 0x83, |
| 194 | [PEVB_KBD_CARET] = 0x84, |
| 195 | [PEVB_KBD_YEN] = 0x85, |
| 196 | [PEVB_KBD_ESCAPE] = 0x86, // break |
| 197 | |
| 198 | [PEVB_KBD_q] = 0x10, |
| 199 | [PEVB_KBD_w] = 0x11, |
| 200 | [PEVB_KBD_e] = 0x12, |
| 201 | [PEVB_KBD_r] = 0x13, |
| 202 | [PEVB_KBD_t] = 0x14, |
| 203 | [PEVB_KBD_y] = 0x15, |
| 204 | [PEVB_KBD_u] = 0x16, |
| 205 | [PEVB_KBD_i] = 0x70, |
| 206 | [PEVB_KBD_o] = 0x71, |
| 207 | [PEVB_KBD_p] = 0x72, |
| 208 | [PEVB_KBD_AT] = 0x73, |
| 209 | [PEVB_KBD_LEFTBRACKET] = 0x74, |
| 210 | |
| 211 | [PEVB_KBD_a] = 0x20, |
| 212 | [PEVB_KBD_s] = 0x21, |
| 213 | [PEVB_KBD_d] = 0x22, |
| 214 | [PEVB_KBD_f] = 0x23, |
| 215 | [PEVB_KBD_g] = 0x24, |
| 216 | [PEVB_KBD_h] = 0x25, |
| 217 | [PEVB_KBD_j] = 0x26, |
| 218 | [PEVB_KBD_k] = 0x60, |
| 219 | [PEVB_KBD_l] = 0x61, |
| 220 | [PEVB_KBD_SEMICOLON] = 0x62, |
| 221 | [PEVB_KBD_COLON] = 0x63, |
| 222 | [PEVB_KBD_RIGHTBRACKET] = 0x64, |
| 223 | [PEVB_KBD_RETURN] = 0x65, |
| 224 | [PEVB_KBD_UP] = 0x66, // up |
| 225 | |
| 226 | [PEVB_KBD_z] = 0x30, |
| 227 | [PEVB_KBD_x] = 0x31, |
| 228 | [PEVB_KBD_c] = 0x32, |
| 229 | [PEVB_KBD_v] = 0x33, |
| 230 | [PEVB_KBD_b] = 0x34, |
| 231 | [PEVB_KBD_n] = 0x35, |
| 232 | [PEVB_KBD_m] = 0x36, |
| 233 | [PEVB_KBD_COMMA] = 0x50, |
| 234 | [PEVB_KBD_PERIOD] = 0x51, |
| 235 | [PEVB_KBD_SLASH] = 0x52, |
| 236 | [PEVB_KBD_RO] = 0x53, // pi |
| 237 | [PEVB_KBD_DOWN] = 0x54, // down |
| 238 | [PEVB_KBD_LEFT] = 0x55, // left |
| 239 | [PEVB_KBD_RIGHT] = 0x56, // right |
| 240 | |
| 241 | [PEVB_KBD_CJK] = 0x40, // kana |
| 242 | [PEVB_KBD_SPACE] = 0x41, // space |
| 243 | [PEVB_KBD_HOME] = 0x42, // clear/home |
| 244 | [PEVB_KBD_BACKSPACE] = 0x43, // del/ins |
| 245 | |
| 246 | [PEVB_KBD_SOUND] = 0x96, // graph |
| 247 | [PEVB_KBD_CAPSLOCK] = 0xa6, // ctrl |
| 248 | [PEVB_KBD_FUNC] = 0xb5, // func |
| 249 | [PEVB_KBD_LSHIFT] = 0xb6, // shift (both keys on the same column) |
| 250 | [PEVB_KBD_RSHIFT] = 0xb6, |
| 251 | }; |
| 252 | |
| 253 | static void kbd_update(void) |
| 254 | { |
| 255 | u32 key = (PicoIn.kbd & 0x00ff); |
| 256 | u32 sft = (PicoIn.kbd & 0xff00) >> 8; |
| 257 | |
| 258 | memset(kbd_matrix, 0, sizeof(kbd_matrix)); |
| 259 | if (sft) { |
| 260 | int rc = kbd_map[sft]; |
| 261 | kbd_matrix[rc>>4] = (1 << (rc&0x7)); |
| 262 | } |
| 263 | if (key) { |
| 264 | int rc = kbd_map[key]; |
| 265 | kbd_matrix[rc>>4] = (1 << (rc&0x7)); |
| 266 | } |
| 267 | } |
| 268 | |
| 269 | // tape handling |
| 270 | |
| 271 | static struct tape { |
| 272 | FILE *ftape; |
| 273 | int fsize; // size of sample in bytes |
| 274 | int mode; // "w", "r" |
| 275 | |
| 276 | int cycle; // latest polling cycle |
| 277 | int pause; // pause tape playing |
| 278 | int poll_cycles; // for auto play/pause detection |
| 279 | int poll_count; |
| 280 | |
| 281 | int phase; // start cycle of current sample |
| 282 | int cycles_sample; // cycles per sample |
| 283 | u32 cycles_mult; // Q32, inverse of cycles per sample |
| 284 | |
| 285 | int isbit; // is bitstream format? |
| 286 | u8 bitsample; // bitstream sample |
| 287 | s16 wavsample; // wave file sample |
| 288 | } tape; |
| 289 | |
| 290 | static u8 tape_update(int cycle) |
| 291 | { |
| 292 | struct tape *pt = &tape; |
| 293 | u8 ret = 0; |
| 294 | int phase = cycle - pt->phase; |
| 295 | int count = ((u64)phase * pt->cycles_mult) >> 32; |
| 296 | int cycles = cycle - pt->cycle; |
| 297 | |
| 298 | if (cycles < 0 || pt->ftape == NULL || pt->mode != 'r') return 0; |
| 299 | pt->cycle = cycle; |
| 300 | |
| 301 | // auto play/pause detection: |
| 302 | pt->poll_cycles += cycles; |
| 303 | if (pt->pause) { |
| 304 | // if in pause and poll cycles are short for 1/4s, play |
| 305 | if (cycles < OSC_NTSC/15/1000) { |
| 306 | if (pt->poll_cycles > OSC_NTSC/15/4) { |
| 307 | pt->pause = pt->poll_cycles = pt->poll_count = 0; |
| 308 | } |
| 309 | } else { |
| 310 | // long poll cycles reset the logic |
| 311 | pt->poll_cycles = 0; |
| 312 | } |
| 313 | } else { |
| 314 | // if in play and poll cycles are long for 1/4s, pause |
| 315 | if (cycles >= OSC_NTSC/15/1000) { |
| 316 | if (pt->poll_cycles > OSC_NTSC/15/4) { |
| 317 | pt->pause = 1; pt->poll_cycles = 0; |
| 318 | } |
| 319 | pt->poll_count = 0; |
| 320 | } else if (++pt->poll_count > 10) { |
| 321 | // >10 short poll cycles reset the logic. This is to cover for software |
| 322 | // polling the keyboard matrix, which is partly on PB too. |
| 323 | pt->poll_cycles = pt->poll_count = 0; |
| 324 | } |
| 325 | } |
| 326 | |
| 327 | if (pt->pause) { |
| 328 | pt->phase = cycle; |
| 329 | return 0; |
| 330 | } |
| 331 | |
| 332 | // skip samples if necessary |
| 333 | if (count > 1) { |
| 334 | fseek(pt->ftape, (count-1) * pt->fsize, SEEK_CUR); |
| 335 | pt->phase += (count-1) * pt->cycles_sample; |
| 336 | } |
| 337 | |
| 338 | // read a new sample from file if needed |
| 339 | if (count) { |
| 340 | if (pt->isbit) |
| 341 | fread(&pt->bitsample, 1, sizeof(u8), pt->ftape); |
| 342 | else { |
| 343 | // read sample only from 1st channel |
| 344 | fread(&pt->wavsample, 1, sizeof(u16), pt->ftape); |
| 345 | if (pt->fsize > sizeof(u16)) // skip other channels |
| 346 | fseek(pt->ftape, pt->fsize - sizeof(u16), SEEK_CUR); |
| 347 | #if ! CPU_IS_LE |
| 348 | pt->wavsample = (u8)(pt->wavsample >> 8) | (pt->wavsample << 8); |
| 349 | #endif |
| 350 | } |
| 351 | // catch EOF and reading errors |
| 352 | if (feof(pt->ftape) || ferror(pt->ftape)) { |
| 353 | fclose(pt->ftape); |
| 354 | pt->ftape = NULL; |
| 355 | } |
| 356 | pt->phase += pt->cycles_sample; |
| 357 | } |
| 358 | |
| 359 | // compute result from sample |
| 360 | if (pt->isbit) { |
| 361 | phase = cycle - pt->phase; // recompute as phase might have changed above. |
| 362 | if (pt->bitsample == '0') ret = ((phase * pt->cycles_mult)>>31) & 1; |
| 363 | if (pt->bitsample == '1') ret = ((phase * pt->cycles_mult)>>30) & 1; |
| 364 | } else |
| 365 | ret = pt->wavsample >= 0x0800; // 1/16th of the max volume |
| 366 | |
| 367 | return ret; |
| 368 | } |
| 369 | |
| 370 | static void tape_write(int cycle, int data) |
| 371 | { |
| 372 | struct tape *pt = &tape; |
| 373 | int cycles = cycle - pt->cycle; // cycles since last write |
| 374 | int samples; |
| 375 | |
| 376 | if (cycles < 0 || pt->ftape == NULL || pt->mode != 'w') return; |
| 377 | pt->cycle = cycle; |
| 378 | pt->poll_cycles += cycles; |
| 379 | |
| 380 | // write samples to file. Stop if the sample doesn't change for more than 2s |
| 381 | if (pt->isbit) { |
| 382 | pt->poll_count += (data >= 0); |
| 383 | if (data >= 0 && pt->poll_cycles >= pt->cycles_sample*15/16) { |
| 384 | // determine bit, duration ~1/1200s, either 2400Hz, or 1200Hz, or bust |
| 385 | switch (pt->poll_count) { |
| 386 | case 4: pt->bitsample = '1'; break; // 2*2400Hz |
| 387 | case 2: pt->bitsample = '0'; break; // 1*1200Hz |
| 388 | default: pt->bitsample = ' '; break; // ignore everything else |
| 389 | } |
| 390 | if (pt->poll_cycles >= pt->cycles_sample*17/16) pt->bitsample = ' '; |
| 391 | |
| 392 | if (pt->poll_cycles < OSC_NTSC/15*2) { |
| 393 | samples = ((u64)pt->poll_cycles * pt->cycles_mult + 0x80000000LL) >> 32; |
| 394 | while (samples-- > 0 && !ferror(pt->ftape)) |
| 395 | fwrite(&pt->bitsample, 1, sizeof(u8), pt->ftape); |
| 396 | } |
| 397 | |
| 398 | pt->poll_count = pt->poll_cycles = 0; |
| 399 | } |
| 400 | } else { |
| 401 | if (pt->wavsample && pt->poll_cycles < OSC_NTSC/15*2) { |
| 402 | samples = ((u64)cycles * pt->cycles_mult) >> 32; |
| 403 | while (samples-- > 0 && !ferror(pt->ftape)) |
| 404 | fwrite(&pt->wavsample, 1, sizeof(s16), pt->ftape); |
| 405 | } |
| 406 | |
| 407 | // current sample value in little endian, for writing next time |
| 408 | if (data != -1) { |
| 409 | pt->wavsample = (data ? 0x7ff8 : 0x8008); |
| 410 | #if ! CPU_IS_LE |
| 411 | pt->wavsample = (u8)(pt->wavsample >> 8) | (pt->wavsample << 8); |
| 412 | #endif |
| 413 | pt->poll_cycles = 0; |
| 414 | } |
| 415 | } |
| 416 | |
| 417 | // catch write errors |
| 418 | if (ferror(pt->ftape)) { |
| 419 | fclose(pt->ftape); |
| 420 | pt->ftape = NULL; |
| 421 | } |
| 422 | } |
| 423 | |
| 424 | // NB: SC-3000 has a 8255 chip, mapped to 0xdc-0xdf. Not fully emulated. |
| 425 | |
| 426 | static unsigned char z80_sms_in(unsigned short a) |
| 427 | { |
| 428 | unsigned char d = 0xff; |
| 429 | |
| 430 | a &= 0xff; |
| 431 | elprintf(EL_IO, "z80 port %04x read", a); |
| 432 | if(a >= 0xf0){ |
| 433 | if (Pico.m.hardware & PMS_HW_FM) { |
| 434 | switch(a) |
| 435 | { |
| 436 | case 0xf0: |
| 437 | // FM reg port |
| 438 | break; |
| 439 | case 0xf1: |
| 440 | // FM data port |
| 441 | break; |
| 442 | case 0xf2: |
| 443 | // bit 0 = 1 active FM Pac |
| 444 | d = 0xf8 | Pico.ms.fm_ctl; |
| 445 | break; |
| 446 | } |
| 447 | } |
| 448 | } |
| 449 | else{ |
| 450 | switch (a & 0xc1) |
| 451 | { |
| 452 | case 0x00: |
| 453 | case 0x01: |
| 454 | if ((PicoIn.AHW & PAHW_GG) && a < 0x8) { // GG I/O area |
| 455 | switch (a) { |
| 456 | case 0: d = (~PicoIn.pad[0] & 0x80) | |
| 457 | (!(Pico.m.hardware & PMS_HW_JAP) << 6); break; |
| 458 | case 1: d = Pico.ms.io_gg[1] | (Pico.ms.io_gg[2] & 0x7f); break; |
| 459 | case 5: d = Pico.ms.io_gg[5] & 0xf8; break; |
| 460 | default: d = Pico.ms.io_gg[a]; break; |
| 461 | } |
| 462 | } |
| 463 | break; |
| 464 | |
| 465 | case 0x40: /* V counter */ |
| 466 | d = Pico.video.v_counter; |
| 467 | elprintf(EL_HVCNT, "V counter read: %02x", d); |
| 468 | break; |
| 469 | |
| 470 | case 0x41: /* H counter */ |
| 471 | d = Pico.ms.vdp_hlatch; |
| 472 | elprintf(EL_HVCNT, "H counter read: %02x", d); |
| 473 | break; |
| 474 | |
| 475 | case 0x80: |
| 476 | d = vdp_data_read(); |
| 477 | break; |
| 478 | |
| 479 | case 0x81: |
| 480 | d = vdp_ctl_read(); |
| 481 | break; |
| 482 | |
| 483 | case 0xc0: /* I/O port A and B */ |
| 484 | // For SC-3000: 8255 port A, assume always configured for input |
| 485 | if (! (PicoIn.AHW & PAHW_SC) || (Pico.ms.io_sg & 7) == 7) { |
| 486 | d = ~((PicoIn.pad[0] & 0x3f) | (PicoIn.pad[1] << 6)); |
| 487 | if (!(Pico.ms.io_ctl & 0x01)) // TR as output |
| 488 | d = (d & ~0x20) | ((Pico.ms.io_ctl << 1) & 0x20); |
| 489 | } else { |
| 490 | int i; // read kbd 8 bits |
| 491 | kbd_update(); |
| 492 | for (i = 7; i >= 0; i--) |
| 493 | d = (d<<1) | !(kbd_matrix[i] & (1<<(Pico.ms.io_sg&7))); |
| 494 | } |
| 495 | break; |
| 496 | |
| 497 | case 0xc1: /* I/O port B and miscellaneous */ |
| 498 | // For SC-3000: 8255 port B, assume always configured for input |
| 499 | if (! (PicoIn.AHW & PAHW_SC) || (Pico.ms.io_sg & 7) == 7) { |
| 500 | d = (Pico.ms.io_ctl & 0x80) | ((Pico.ms.io_ctl << 1) & 0x40) | 0x30; |
| 501 | d |= ~(PicoIn.pad[1] >> 2) & 0x0f; |
| 502 | if (!(Pico.ms.io_ctl & 0x04)) // TR as output |
| 503 | d = (d & ~0x08) | ((Pico.ms.io_ctl >> 3) & 0x08); |
| 504 | if (Pico.ms.io_ctl & 0x08) d |= 0x80; // TH as input is unconnected |
| 505 | if (Pico.ms.io_ctl & 0x02) d |= 0x40; |
| 506 | } else { |
| 507 | int i; // read kbd 4 bits |
| 508 | kbd_update(); |
| 509 | for (i = 11; i >= 8; i--) |
| 510 | d = (d<<1) | !(kbd_matrix[i] & (1<<(Pico.ms.io_sg&7))); |
| 511 | // bit 5 = printer fault |
| 512 | // bit 6 = printer busy |
| 513 | d &= ~0x60; // set so that BASIC thinks printer is connected |
| 514 | } |
| 515 | if (PicoIn.AHW & PAHW_SC) { |
| 516 | // bit 7 = tape input |
| 517 | d &= ~0x80; |
| 518 | d |= tape_update(z80_cyclesDone()) ? 0x80 : 0; |
| 519 | } |
| 520 | break; |
| 521 | } |
| 522 | } |
| 523 | elprintf(EL_IO, "ret = %02x", d); |
| 524 | return d; |
| 525 | } |
| 526 | |
| 527 | static void z80_sms_out(unsigned short a, unsigned char d) |
| 528 | { |
| 529 | elprintf(EL_IO, "z80 port %04x write %02x", a, d); |
| 530 | |
| 531 | a &= 0xff; |
| 532 | if (a >= 0xf0){ |
| 533 | if (Pico.m.hardware & PMS_HW_FM) { |
| 534 | switch(a) |
| 535 | { |
| 536 | case 0xf0: |
| 537 | // FM reg port |
| 538 | Pico.m.hardware |= PMS_HW_FMUSED; |
| 539 | YM2413_regWrite(d); |
| 540 | break; |
| 541 | case 0xf1: |
| 542 | // FM data port |
| 543 | YM2413_dataWrite(d); |
| 544 | break; |
| 545 | case 0xf2: |
| 546 | // bit 0 = 1 active FM Pac |
| 547 | Pico.ms.fm_ctl = d & 0x1; |
| 548 | break; |
| 549 | } |
| 550 | } |
| 551 | } |
| 552 | else { |
| 553 | switch (a & 0xc1) |
| 554 | { |
| 555 | case 0x00: |
| 556 | if ((PicoIn.AHW & PAHW_GG) && a < 0x8) // GG I/O area |
| 557 | Pico.ms.io_gg[a] = d; |
| 558 | if ((PicoIn.AHW & PAHW_GG) && a == 0x6) |
| 559 | SN76496Config(d); |
| 560 | break; |
| 561 | case 0x01: |
| 562 | if ((PicoIn.AHW & PAHW_GG) && a < 0x8) { // GG I/O area |
| 563 | Pico.ms.io_gg[a] = d; |
| 564 | } else { |
| 565 | // pad. latch hcounter if one of the TH lines is switched to 1 |
| 566 | if ((Pico.ms.io_ctl ^ d) & d & 0xa0) |
| 567 | Pico.ms.vdp_hlatch = vdp_hcounter(z80_cyclesDone() - Pico.t.z80c_line_start); |
| 568 | Pico.ms.io_ctl = d; |
| 569 | } |
| 570 | break; |
| 571 | |
| 572 | case 0x40: |
| 573 | case 0x41: |
| 574 | PsndDoPSG(z80_cyclesDone()); |
| 575 | SN76496Write(d); |
| 576 | break; |
| 577 | |
| 578 | case 0x80: |
| 579 | vdp_data_write(d); |
| 580 | break; |
| 581 | |
| 582 | case 0x81: |
| 583 | vdp_ctl_write(d); |
| 584 | break; |
| 585 | |
| 586 | case 0xc0: |
| 587 | if ((PicoIn.AHW & PAHW_SC) && (a & 0x2)) { |
| 588 | // For SC-3000: 8255 port C, assume always configured for output |
| 589 | Pico.ms.io_sg = d; // 0xc2 = kbd/pad matrix column select |
| 590 | // bit 4 = tape output |
| 591 | // bit 5 = printer data |
| 592 | // bit 6 = printer reset |
| 593 | // bit 7 = printer feed |
| 594 | } |
| 595 | break; |
| 596 | case 0xc1: |
| 597 | if ((PicoIn.AHW & PAHW_SC) && (a & 0x2) && !(d & 0x80)) { |
| 598 | // For SC-3000: 8255 control port. BSR mode used for printer and tape. |
| 599 | int b = (d>>1) & 0x7; |
| 600 | Pico.ms.io_sg &= ~(1<<b); |
| 601 | Pico.ms.io_sg |= ((d&1)<<b); |
| 602 | |
| 603 | // debug hack to copy printer data to stdout. |
| 604 | // Printer data is sent at about 4.7 KBaud, 10 bits per character: |
| 605 | // start=0, 8 data bits (LSB first), stop=1. data line is inverted. |
| 606 | // no Baud tracking needed as all bits are sent through here. |
| 607 | static int chr, bit; |
| 608 | if (b == 4) { // tape out |
| 609 | tape_write(z80_cyclesDone(), d&1); |
| 610 | } else if (b == 5) { // !data |
| 611 | if (!bit) { |
| 612 | if (d&1) // start bit |
| 613 | bit = 8; |
| 614 | } else { |
| 615 | chr = (chr>>1) | (d&1 ? 0 : 0x80); |
| 616 | if (!--bit) { |
| 617 | printf("%c",chr); |
| 618 | if (chr == 0xd) printf("\n"); |
| 619 | } |
| 620 | } |
| 621 | } else if (b == 6 && !(d&1)) // !reset |
| 622 | bit = 0; |
| 623 | } |
| 624 | break; |
| 625 | } |
| 626 | } |
| 627 | } |
| 628 | |
| 629 | static void z80_exec(int aim) |
| 630 | { |
| 631 | Pico.t.z80c_aim = aim; |
| 632 | Pico.t.z80c_cnt += z80_run(Pico.t.z80c_aim - Pico.t.z80c_cnt); |
| 633 | } |
| 634 | |
| 635 | |
| 636 | // ROM/SRAM bank mapping, see https://www.smspower.org/Development/Mappers |
| 637 | |
| 638 | static int bank_mask; |
| 639 | |
| 640 | static void xwrite(unsigned int a, unsigned char d); |
| 641 | |
| 642 | |
| 643 | // Sega mapper. Maps 3 banks 16KB each, with SRAM support |
| 644 | static void write_sram_sega(unsigned short a, unsigned char d) |
| 645 | { |
| 646 | // SRAM is mapped in 2 16KB banks, selected by bit 2 in control reg |
| 647 | a &= 0x3fff; |
| 648 | a += ((Pico.ms.carthw[0x0c] & 0x04) >> 2) * 0x4000; |
| 649 | |
| 650 | Pico.sv.changed |= (Pico.sv.data[a] != d); |
| 651 | Pico.sv.data[a] = d; |
| 652 | } |
| 653 | |
| 654 | static void write_bank_sega(unsigned short a, unsigned char d) |
| 655 | { |
| 656 | if (a < 0xfff8) return; |
| 657 | // avoid mapper detection for RAM fill with 0 |
| 658 | if (Pico.ms.mapper != PMS_MAP_SEGA && (Pico.ms.mapper || d == 0)) return; |
| 659 | |
| 660 | elprintf(EL_Z80BNK, "bank sega %04x %02x @ %04x", a, d, z80_pc()); |
| 661 | Pico.ms.mapper = PMS_MAP_SEGA; |
| 662 | if (d == Pico.ms.carthw[a & 0x0f]) return; |
| 663 | Pico.ms.carthw[a & 0x0f] = d; |
| 664 | |
| 665 | switch (a & 0x0f) |
| 666 | { |
| 667 | case 0x0d: |
| 668 | d &= bank_mask; |
| 669 | z80_map_set(z80_read_map, 0x0400, 0x3fff, Pico.rom+0x400 + (d << 14), 0); |
| 670 | break; |
| 671 | case 0x0e: |
| 672 | d &= bank_mask; |
| 673 | z80_map_set(z80_read_map, 0x4000, 0x7fff, Pico.rom + (d << 14), 0); |
| 674 | break; |
| 675 | |
| 676 | case 0x0c: |
| 677 | if (d & ~0x8c) |
| 678 | elprintf(EL_STATUS|EL_ANOMALY, "%02x written to control reg!", d); |
| 679 | /*FALLTHROUGH*/ |
| 680 | case 0x0f: |
| 681 | if (Pico.ms.carthw[0xc] & 0x08) { |
| 682 | d = (Pico.ms.carthw[0xc] & 0x04) >> 2; |
| 683 | z80_map_set(z80_read_map, 0x8000, 0xbfff, Pico.sv.data + d*0x4000, 0); |
| 684 | z80_map_set(z80_write_map, 0x8000, 0xbfff, write_sram_sega, 1); |
| 685 | } else { |
| 686 | d = Pico.ms.carthw[0xf] & bank_mask; |
| 687 | z80_map_set(z80_read_map, 0x8000, 0xbfff, Pico.rom + (d << 14), 0); |
| 688 | z80_map_set(z80_write_map, 0x8000, 0xbfff, xwrite, 1); |
| 689 | } |
| 690 | break; |
| 691 | } |
| 692 | } |
| 693 | |
| 694 | // Codemasters mapper. Similar to Sega, but different addresses |
| 695 | static void write_bank_codem(unsigned short a, unsigned char d) |
| 696 | { |
| 697 | if (a >= 0xc000 || (a & 0x3fff)) return; // address is 0x0000, 0x4000, 0x8000? |
| 698 | // don't detect linear mapping to avoid confusing with MSX |
| 699 | if (Pico.ms.mapper != PMS_MAP_CODEM && (Pico.ms.mapper || (a>>14) == d)) return; |
| 700 | elprintf(EL_Z80BNK, "bank codem %04x %02x @ %04x", a, d, z80_pc()); |
| 701 | Pico.ms.mapper = PMS_MAP_CODEM; |
| 702 | if (Pico.ms.carthw[a>>14] == d) return; |
| 703 | Pico.ms.carthw[a>>14] = d; |
| 704 | |
| 705 | d &= bank_mask; |
| 706 | z80_map_set(z80_read_map, a, a+0x3fff, Pico.rom + (d << 14), 0); |
| 707 | if (Pico.ms.carthw[1] & 0x80) { |
| 708 | z80_map_set(z80_read_map, 0xa000, 0xbfff, PicoMem.vram+0x4000, 0); |
| 709 | z80_map_set(z80_write_map, 0xa000, 0xbfff, PicoMem.vram+0x4000, 0); |
| 710 | } else { |
| 711 | d = Pico.ms.carthw[2] & bank_mask; |
| 712 | z80_map_set(z80_read_map, 0xa000, 0xbfff, Pico.rom + (d << 14)+0x2000, 0); |
| 713 | z80_map_set(z80_write_map, 0xa000, 0xbfff, xwrite, 1); |
| 714 | } |
| 715 | } |
| 716 | |
| 717 | // MSX mapper. 4 selectable 8KB banks at the top |
| 718 | static void write_bank_msx(unsigned short a, unsigned char d) |
| 719 | { |
| 720 | if (a > 0x0003) return; |
| 721 | // don't detect linear mapping to avoid confusing with Codemasters |
| 722 | if (Pico.ms.mapper != PMS_MAP_MSX && (Pico.ms.mapper || (a|d) == 0 || d >= 0x80)) return; |
| 723 | elprintf(EL_Z80BNK, "bank msx %04x %02x @ %04x", a, d, z80_pc()); |
| 724 | Pico.ms.mapper = PMS_MAP_MSX; |
| 725 | Pico.ms.carthw[a] = d; |
| 726 | |
| 727 | a = (a^2)*0x2000 + 0x4000; |
| 728 | d &= 2*bank_mask + 1; |
| 729 | z80_map_set(z80_read_map, a, a+0x1fff, Pico.rom + (d << 13), 0); |
| 730 | } |
| 731 | |
| 732 | // Korea mapping, 1 selectable 16KB bank at the top |
| 733 | static void write_bank_korea(unsigned short a, unsigned char d) |
| 734 | { |
| 735 | if (a != 0xa000) return; |
| 736 | if (Pico.ms.mapper != PMS_MAP_KOREA && (Pico.ms.mapper)) return; |
| 737 | elprintf(EL_Z80BNK, "bank korea %04x %02x @ %04x", a, d, z80_pc()); |
| 738 | Pico.ms.mapper = PMS_MAP_KOREA; |
| 739 | Pico.ms.carthw[0xf] = d; |
| 740 | |
| 741 | d &= bank_mask; |
| 742 | z80_map_set(z80_read_map, 0x8000, 0xbfff, Pico.rom + (d << 14), 0); |
| 743 | } |
| 744 | |
| 745 | // Korean n-in-1 mapping. 1 selectable 32KB bank at the bottom |
| 746 | static void write_bank_n32k(unsigned short a, unsigned char d) |
| 747 | { |
| 748 | if (a != 0xffff) return; |
| 749 | // code must be in RAM since all visible ROM space is swapped |
| 750 | if (Pico.ms.mapper != PMS_MAP_N32K && (Pico.ms.mapper || z80_pc() < 0xc000)) return; |
| 751 | elprintf(EL_Z80BNK, "bank 32k %04x %02x @ %04x", a, d, z80_pc()); |
| 752 | Pico.ms.mapper = PMS_MAP_N32K; |
| 753 | Pico.ms.carthw[0xf] = d; |
| 754 | |
| 755 | d &= bank_mask >> 1; |
| 756 | z80_map_set(z80_read_map, 0, 0x7fff, Pico.rom + (d << 15), 0); |
| 757 | } |
| 758 | |
| 759 | // Korean 4-in-1. 2 selectable 16KB banks, top bank is shifted by bottom one |
| 760 | static void write_bank_n16k(unsigned short a, unsigned char d) |
| 761 | { |
| 762 | if (a != 0x3ffe && a != 0x7fff && a != 0xbfff) return; |
| 763 | // code must be in RAM since all visible ROM space is swapped |
| 764 | if (Pico.ms.mapper != PMS_MAP_N16K && (Pico.ms.mapper || z80_pc() < 0xc000)) return; |
| 765 | elprintf(EL_Z80BNK, "bank 16k %04x %02x @ %04x", a, d, z80_pc()); |
| 766 | Pico.ms.mapper = PMS_MAP_N16K; |
| 767 | Pico.ms.carthw[a>>14] = d; |
| 768 | |
| 769 | d &= bank_mask; |
| 770 | a = a & 0xc000; |
| 771 | // the top bank shifts with the bottom bank. |
| 772 | if (a == 0x8000) d += Pico.ms.carthw[0] & 0x30; |
| 773 | z80_map_set(z80_read_map, a, a+0x3fff, Pico.rom + (d << 14), 0); |
| 774 | } |
| 775 | |
| 776 | // MSX-Nemesis mapper. 4 selectable 8KB banks at the top |
| 777 | static void write_bank_msxn(unsigned short a, unsigned char d) |
| 778 | { |
| 779 | if (a > 0x0003) return; |
| 780 | // never autodetected, selectable only via config |
| 781 | if (Pico.ms.mapper != PMS_MAP_NEMESIS) return; |
| 782 | elprintf(EL_Z80BNK, "bank nems %04x %02x @ %04x", a, d, z80_pc()); |
| 783 | Pico.ms.carthw[a] = d; |
| 784 | |
| 785 | a = (a^2)*0x2000 + 0x4000; |
| 786 | d &= 2*bank_mask + 1; |
| 787 | z80_map_set(z80_read_map, a, a+0x1fff, Pico.rom + (d << 13), 0); |
| 788 | } |
| 789 | |
| 790 | // Korean Janggun mapper. 4 selectable 8KB banks at the top, hardware byte flip |
| 791 | static unsigned char read_flipped_jang(unsigned a) |
| 792 | { |
| 793 | static unsigned char flipper[16] = // reversed nibble bit order |
| 794 | { 0x0,0x8,0x4,0xc,0x2,0xa,0x6,0xe,0x1,0x9,0x5,0xd,0x3,0xb,0x7,0xf }; |
| 795 | unsigned char c; |
| 796 | |
| 797 | // return value at address a in reversed bit order |
| 798 | c = Pico.rom[(Pico.ms.carthw[a>>13] << 13) + (a & 0x1fff)]; |
| 799 | return (flipper[c&0xf]<<4) | flipper[c>>4]; |
| 800 | } |
| 801 | |
| 802 | static void write_bank_jang(unsigned short a, unsigned char d) |
| 803 | { |
| 804 | // address is 0xfffe, 0xffff, 0x4000, 0x6000, 0x8000, 0xa000 |
| 805 | if ((a|1) != 0xffff && (a < 0x4000 || a > 0xa000 || (a & 0x1fff))) return; |
| 806 | // never autodetected, selectable only via config |
| 807 | if (Pico.ms.mapper != PMS_MAP_JANGGUN) return; |
| 808 | elprintf(EL_Z80BNK, "bank jang %04x %02x @ %04x", a, d, z80_pc()); |
| 809 | |
| 810 | if ((a|1) == 0xffff) { |
| 811 | int x = a & 1, f = d & 0x40; |
| 812 | Pico.ms.carthw[x] = d; |
| 813 | d &= bank_mask; |
| 814 | Pico.ms.carthw[2*x + 2] = 2*d, Pico.ms.carthw[2*x + 3] = 2*d+1; |
| 815 | a = (x+1) * 0x4000; |
| 816 | if (!f) |
| 817 | z80_map_set(z80_read_map, a, a+0x3fff, Pico.rom + (d << 14), 0); |
| 818 | else |
| 819 | z80_map_set(z80_read_map, a, a+0x3fff, read_flipped_jang, 1); |
| 820 | } else { |
| 821 | d &= 2*bank_mask + 1; |
| 822 | Pico.ms.carthw[a>>13] = d; |
| 823 | if (!(Pico.ms.carthw[(a>>15)&1] & 0x40)) |
| 824 | z80_map_set(z80_read_map, a, a+0x1fff, Pico.rom + (d << 13), 0); |
| 825 | else |
| 826 | z80_map_set(z80_read_map, a, a+0x1fff, read_flipped_jang, 1); |
| 827 | } |
| 828 | } |
| 829 | |
| 830 | // Korean 188-in-1. 4 8KB banks from 0x4000, selected by xor'd bank index |
| 831 | static void write_bank_xor(unsigned short a, unsigned char d) |
| 832 | { |
| 833 | // 4x8KB bank select @0x2000 |
| 834 | if ((a&0xff00) != 0x2000) return; |
| 835 | if (Pico.ms.mapper != PMS_MAP_XOR && Pico.ms.mapper) return; |
| 836 | |
| 837 | elprintf(EL_Z80BNK, "bank xor %04x %02x @ %04x", a, d, z80_pc()); |
| 838 | Pico.ms.mapper = PMS_MAP_XOR; |
| 839 | |
| 840 | Pico.ms.carthw[0] = d; |
| 841 | z80_map_set(z80_read_map, 0x4000, 0x5fff, Pico.rom + ((d^0x1f) << 13), 0); |
| 842 | z80_map_set(z80_read_map, 0x6000, 0x7fff, Pico.rom + ((d^0x1e) << 13), 0); |
| 843 | z80_map_set(z80_read_map, 0x8000, 0x9fff, Pico.rom + ((d^0x1d) << 13), 0); |
| 844 | z80_map_set(z80_read_map, 0xa000, 0xbfff, Pico.rom + ((d^0x1c) << 13), 0); |
| 845 | } |
| 846 | |
| 847 | // SG-1000 8KB RAM Adaptor mapper. 8KB RAM at address 0x2000 |
| 848 | static void write_bank_x8k(unsigned short a, unsigned char d) |
| 849 | { |
| 850 | // 8KB address range @ 0x2000 (adaptor) or @ 0x8000 (cartridge) |
| 851 | if (((a&0xe000) != 0x2000 && (a&0xe000) != 0x8000) || (a & 0x0f) == 5) return; |
| 852 | if (Pico.ms.mapper != PMS_MAP_8KBRAM && Pico.ms.mapper) return; |
| 853 | |
| 854 | elprintf(EL_Z80BNK, "bank x8k %04x %02x @ %04x", a, d, z80_pc()); |
| 855 | ((unsigned char *)(PicoMem.vram+0x4000))[a&0x1fff] = d; |
| 856 | Pico.ms.mapper = PMS_MAP_8KBRAM; |
| 857 | |
| 858 | a &= 0xe000; |
| 859 | Pico.ms.carthw[0] = a >> 12; |
| 860 | z80_map_set(z80_read_map, a, a+0x1fff, PicoMem.vram+0x4000, 0); |
| 861 | z80_map_set(z80_write_map, a, a+0x1fff, PicoMem.vram+0x4000, 0); |
| 862 | } |
| 863 | |
| 864 | // SC-3000 32KB RAM mapper for BASIC level IIIB. 32KB RAM at address 0x8000 |
| 865 | static void write_bank_x32k(unsigned short a, unsigned char d) |
| 866 | { |
| 867 | // 32KB address range @ 0x8000 |
| 868 | if ((a&0xc000) != 0x8000) return; |
| 869 | if (Pico.ms.mapper != PMS_MAP_32KBRAM && |
| 870 | (Pico.ms.mapper || Pico.romsize > 0x8000)) return; |
| 871 | |
| 872 | elprintf(EL_Z80BNK, "bank x32k %04x %02x @ %04x", a, d, z80_pc()); |
| 873 | ((unsigned char *)(PicoMem.vram+0x4000))[a&0x7fff] = d; |
| 874 | Pico.ms.mapper = PMS_MAP_32KBRAM; |
| 875 | |
| 876 | a &= 0xc000; |
| 877 | Pico.ms.carthw[0] = a >> 12; |
| 878 | // NB this deactivates internal RAM and all mapper detection |
| 879 | memcpy(PicoMem.vram+0x4000+(0x8000-0x2000)/2, PicoMem.zram, 0x2000); |
| 880 | z80_map_set(z80_read_map, a, a+0x7fff, PicoMem.vram+0x4000, 0); |
| 881 | z80_map_set(z80_write_map, a, a+0x7fff, PicoMem.vram+0x4000, 0); |
| 882 | } |
| 883 | |
| 884 | char *mappers[] = { |
| 885 | [PMS_MAP_SEGA] = "Sega", |
| 886 | [PMS_MAP_CODEM] = "Codemasters", |
| 887 | [PMS_MAP_KOREA] = "Korea", |
| 888 | [PMS_MAP_MSX] = "Korea MSX", |
| 889 | [PMS_MAP_N32K] = "Korea X-in-1", |
| 890 | [PMS_MAP_N16K] = "Korea 4-Pak", |
| 891 | [PMS_MAP_JANGGUN] = "Korea Janggun", |
| 892 | [PMS_MAP_NEMESIS] = "Korea Nemesis", |
| 893 | [PMS_MAP_8KBRAM] = "Taiwan 8K RAM", |
| 894 | [PMS_MAP_XOR] = "Korea XOR", |
| 895 | [PMS_MAP_32KBRAM] = "Sega 32K RAM", |
| 896 | }; |
| 897 | |
| 898 | // TODO auto-selecting is not really reliable. |
| 899 | // Before adding more mappers this should be revised. |
| 900 | static void xwrite(unsigned int a, unsigned char d) |
| 901 | { |
| 902 | int sz = (/*PicoIn.AHW & (PAHW_SG|PAHW_SC) ? 2 :*/ 8) * 1024; |
| 903 | |
| 904 | elprintf(EL_IO, "z80 write [%04x] %02x", a, d); |
| 905 | if (a >= 0xc000) |
| 906 | PicoMem.zram[a & (sz-1)] = d; |
| 907 | |
| 908 | switch (Pico.ms.mapper) { // via config, or auto detected |
| 909 | case PMS_MAP_SEGA: write_bank_sega(a, d); break; |
| 910 | case PMS_MAP_CODEM: write_bank_codem(a, d); break; |
| 911 | case PMS_MAP_MSX: write_bank_msx(a, d); break; |
| 912 | case PMS_MAP_KOREA: write_bank_korea(a, d); break; |
| 913 | case PMS_MAP_N32K: write_bank_n32k(a, d); break; |
| 914 | case PMS_MAP_N16K: write_bank_n16k(a, d); break; |
| 915 | case PMS_MAP_JANGGUN: write_bank_jang(a, d); break; |
| 916 | case PMS_MAP_NEMESIS: write_bank_msxn(a, d); break; |
| 917 | case PMS_MAP_8KBRAM: write_bank_x8k(a, d); break; |
| 918 | case PMS_MAP_32KBRAM: write_bank_x32k(a, d); break; |
| 919 | case PMS_MAP_XOR: write_bank_xor(a, d); break; |
| 920 | |
| 921 | case PMS_MAP_AUTO: |
| 922 | // disable autodetection after some time |
| 923 | if ((a >= 0xc000 && a < 0xfff8) || Pico.ms.mapcnt > 50) break; |
| 924 | // NB the sequence of mappers is crucial for the auto detection |
| 925 | if (PicoIn.AHW & PAHW_SC) { |
| 926 | write_bank_x32k(a,d); |
| 927 | } else if (PicoIn.AHW & PAHW_SG) { |
| 928 | write_bank_x8k(a, d); |
| 929 | } else { |
| 930 | write_bank_n32k(a, d); |
| 931 | write_bank_sega(a, d); |
| 932 | write_bank_msx(a, d); |
| 933 | write_bank_codem(a, d); |
| 934 | write_bank_korea(a, d); |
| 935 | write_bank_n16k(a, d); |
| 936 | write_bank_xor(a, d); |
| 937 | } |
| 938 | |
| 939 | Pico.ms.mapcnt ++; |
| 940 | if (Pico.ms.mapper) |
| 941 | elprintf(EL_STATUS, "autodetected %s mapper",mappers[Pico.ms.mapper]); |
| 942 | break; |
| 943 | } |
| 944 | } |
| 945 | |
| 946 | // Try to detect some tricky cases by their TMR header |
| 947 | // NB Codemasters, some Betas, most unlicensed games have no or invalid TMRs. |
| 948 | // if the cksum header is valid mark this by 0x.fff.... and use that instead |
| 949 | |
| 950 | // TMR product codes and hardware type for known 50Hz-only games |
| 951 | static u32 region_pal[] = { // cf Meka, meka/meka.nam |
| 952 | 0x40207067 /* Addams Family */, 0x40207020 /* Back.Future 3 */, |
| 953 | 0x40207058 /* Battlemaniacs */, 0x40007105 /* Cal.Games 2 */, |
| 954 | 0x402f7065 /* Dracula */ , 0x40007109 /* Home Alone */, |
| 955 | 0x40009024 /* Pwr.Strike 2 */ , 0x40207047 /* Predator 2 EU */, |
| 956 | 0x40002519 /* Quest.Yak */ , 0x40207064 /* Robocop 3 */, |
| 957 | 0x4f205014 /* Sens.Soccer */ , 0x40002573 /* Sonic Blast */, |
| 958 | 0x40007080 /* S.Harrier EU */ , 0x40007038 /* Taito Chase */, |
| 959 | 0x40009015 /* Sonic 2 EU */ , /* NBA Jam: no valid id/cksum */ |
| 960 | 0x4fff8872 /* Excell.Dizzy */ , 0x4ffffac4 /* Fantast.Dizzy */, |
| 961 | 0x4fff4a89 /* Csm.Spacehead */, 0x4fffe352 /* Micr.Machines */, |
| 962 | 0x4fffa203 /* Bad Apple */ |
| 963 | }; |
| 964 | |
| 965 | // TMR product codes and hardware type for known non-FM games |
| 966 | static u32 no_fmsound[] = { // cf Meka, meka/meka.pat |
| 967 | 0x40002070 /* Walter Payton */, 0x40017020 /* American Pro */, |
| 968 | 0x4fffe890 /* Wanted */ |
| 969 | }; |
| 970 | |
| 971 | // TMR product codes and hardware type for known GG carts running in SMS mode |
| 972 | // NB GG carts having the system type set to 4 (eg. HTH games) run as SMS anyway |
| 973 | static u32 gg_smsmode[] = { // cf https://www.smspower.org/Tags/SMS-GG |
| 974 | 0x60002401 /* Castl.Ilusion */, 0x6f101018 /* Taito Chase */, |
| 975 | 0x70709018 /* Olympic Gold */ , 0x70709038 /* Outrun EU */, |
| 976 | 0x60801068 /* Predator 2 */ , 0x70408098 /* Prince.Persia */, |
| 977 | 0x50101037 /* Rastan Saga */ , 0x7f086018 /* RC Grandprix */, |
| 978 | 0x60002415 /* Super Kickoff */, 0x60801108 /* WWF.Steelcage */, |
| 979 | /* Excell.Dizzy, Fantast.Dizzy, Super Tetris: no valid id/cksum in TMR */ |
| 980 | 0x4f813028 /* Tesserae */ |
| 981 | }; |
| 982 | |
| 983 | // TMR product codes and hardware type for known games using 3-D glasses |
| 984 | static u32 three_dee[] = { |
| 985 | 0x4f008001 /* Missile Def. */ , 0x40008007 /* Out Run 3-D */, |
| 986 | 0x40008006 /* Poseidon Wars */, 0x40008004 /* Space Harrier */, |
| 987 | 0x40008002 /* Zaxxon 3-D */ , 0x4fff8793 /* Maze Hunter */ |
| 988 | }; |
| 989 | |
| 990 | void PicoResetMS(void) |
| 991 | { |
| 992 | unsigned tmr; |
| 993 | u32 id, hw, ck, i; |
| 994 | |
| 995 | // set preselected hw/mapper from config |
| 996 | if (PicoIn.hwSelect) { |
| 997 | PicoIn.AHW &= ~(PAHW_GG|PAHW_SG|PAHW_SC); |
| 998 | switch (PicoIn.hwSelect) { |
| 999 | case PHWS_GG: PicoIn.AHW |= PAHW_GG; break; |
| 1000 | case PHWS_SG: PicoIn.AHW |= PAHW_SG; break; |
| 1001 | case PHWS_SC: PicoIn.AHW |= PAHW_SC; break; |
| 1002 | } |
| 1003 | } |
| 1004 | Pico.ms.mapcnt = Pico.ms.mapper = 0; |
| 1005 | if (PicoIn.mapper) |
| 1006 | Pico.ms.mapper = PicoIn.mapper; |
| 1007 | Pico.m.hardware |= PMS_HW_JAP; // default region Japan if no TMR header |
| 1008 | if (PicoIn.regionOverride > 2) |
| 1009 | Pico.m.hardware &= ~PMS_HW_JAP; |
| 1010 | Pico.m.hardware |= PMS_HW_FM; |
| 1011 | if (!(PicoIn.opt & POPT_EN_YM2413)) |
| 1012 | Pico.m.hardware &= ~PMS_HW_FM; |
| 1013 | |
| 1014 | // check if the ROM header contains more system information |
| 1015 | for (tmr = 0x2000; tmr < 0xbfff && tmr <= Pico.romsize; tmr *= 2) { |
| 1016 | if (!memcmp(Pico.rom + tmr-16, "TMR SEGA", 8)) { |
| 1017 | hw = Pico.rom[tmr-1] >> 4; |
| 1018 | id = CPU_LE4(*(u32 *)&Pico.rom[tmr-4]); |
| 1019 | ck = (CPU_LE4(*(u32 *)&Pico.rom[tmr-8])>>16) | (id&0xf0000000) | 0xfff0000; |
| 1020 | |
| 1021 | if (!PicoIn.hwSelect && !PicoIn.AHW && hw && ((id+1)&0xfffe) != 0) { |
| 1022 | if (hw >= 0x5 && hw < 0x8) |
| 1023 | PicoIn.AHW |= PAHW_GG; // GG cartridge detected |
| 1024 | } |
| 1025 | if (!PicoIn.regionOverride) { |
| 1026 | Pico.m.hardware &= ~PMS_HW_JAP; |
| 1027 | if (hw == 0x5 || hw == 0x3) |
| 1028 | Pico.m.hardware |= PMS_HW_JAP; // region Japan |
| 1029 | } |
| 1030 | for (i = 0; i < sizeof(region_pal)/sizeof(*region_pal); i++) |
| 1031 | if ((id == region_pal[i] || ck == region_pal[i]) && !PicoIn.regionOverride) |
| 1032 | { |
| 1033 | Pico.m.pal = 1; // requires 50Hz timing |
| 1034 | break; |
| 1035 | } |
| 1036 | for (i = 0; i < sizeof(gg_smsmode)/sizeof(*gg_smsmode); i++) |
| 1037 | if ((id == gg_smsmode[i] || ck == gg_smsmode[i]) && !PicoIn.hwSelect) { |
| 1038 | PicoIn.AHW &= ~PAHW_GG; // requires SMS mode |
| 1039 | if (hw < 0x5) PicoIn.AHW |= PAHW_GG; |
| 1040 | break; |
| 1041 | } |
| 1042 | for (i = 0; i < sizeof(no_fmsound)/sizeof(*no_fmsound); i++) |
| 1043 | if ((id == no_fmsound[i] || ck == no_fmsound[i])) { |
| 1044 | Pico.m.hardware &= ~PMS_HW_FM; // incompatible with FM |
| 1045 | break; |
| 1046 | } |
| 1047 | for (i = 0; i < sizeof(three_dee)/sizeof(*three_dee); i++) |
| 1048 | if ((id == three_dee[i] || ck == three_dee[i])) { |
| 1049 | Pico.m.hardware |= PMS_HW_3D; // uses 3-D glasses |
| 1050 | break; |
| 1051 | } |
| 1052 | break; |
| 1053 | } |
| 1054 | } |
| 1055 | |
| 1056 | z80_reset(); |
| 1057 | PsndReset(); // pal must be known here |
| 1058 | PicoCloseTape(); |
| 1059 | |
| 1060 | Pico.ms.io_ctl = (PicoIn.AHW & (PAHW_SG|PAHW_SC)) ? 0xf5 : 0xff; |
| 1061 | Pico.ms.fm_ctl = 0xff; |
| 1062 | |
| 1063 | // reset memory mapping |
| 1064 | PicoMemSetupMS(); |
| 1065 | |
| 1066 | // BIOS, VDP intialisation |
| 1067 | Pico.video.reg[0] = 0x36; |
| 1068 | Pico.video.reg[1] = 0xa0; |
| 1069 | Pico.video.reg[2] = 0xff; |
| 1070 | Pico.video.reg[3] = 0xff; |
| 1071 | Pico.video.reg[4] = 0xff; |
| 1072 | Pico.video.reg[5] = 0xff; |
| 1073 | Pico.video.reg[6] = 0xfb; |
| 1074 | Pico.video.reg[7] = 0x00; |
| 1075 | Pico.video.reg[8] = 0x00; |
| 1076 | Pico.video.reg[9] = 0x00; |
| 1077 | Pico.video.reg[10] = 0xff; |
| 1078 | Pico.m.dirtyPal = 1; |
| 1079 | |
| 1080 | // BIOS, clear zram (unitialized on Mark-III, cf src/mame/drivers/sms.cpp) |
| 1081 | i = !(PicoIn.AHW & PAHW_GG) && (Pico.m.hardware & PMS_HW_JAP) ? 0xf0 : 0x00; |
| 1082 | memset(PicoMem.zram, i, sizeof(PicoMem.zram)); |
| 1083 | } |
| 1084 | |
| 1085 | void PicoPowerMS(void) |
| 1086 | { |
| 1087 | int s, tmp; |
| 1088 | |
| 1089 | memset(&PicoMem,0,sizeof(PicoMem)); |
| 1090 | memset(&Pico.video,0,sizeof(Pico.video)); |
| 1091 | memset(&Pico.m,0,sizeof(Pico.m)); |
| 1092 | |
| 1093 | // calculate a mask for bank writes. |
| 1094 | // ROM loader has aligned the size for us, so this is safe. |
| 1095 | s = 0; tmp = Pico.romsize; |
| 1096 | while ((tmp >>= 1) != 0) |
| 1097 | s++; |
| 1098 | if (Pico.romsize > (1 << s)) |
| 1099 | s++; |
| 1100 | tmp = 1 << s; |
| 1101 | bank_mask = (tmp - 1) >> 14; |
| 1102 | |
| 1103 | PicoMem.ioports[0] = 0xc3; // hack to jump @0 at end of RAM to wrap around |
| 1104 | Pico.ms.mapper = PicoIn.mapper; |
| 1105 | PicoReset(); |
| 1106 | } |
| 1107 | |
| 1108 | void PicoMemSetupMS(void) |
| 1109 | { |
| 1110 | u8 mapper = Pico.ms.mapper; |
| 1111 | int sz = (/*PicoIn.AHW & (PAHW_SG|PAHW_SC) ? 2 :*/ 8) * 1024; |
| 1112 | u32 a; |
| 1113 | |
| 1114 | // RAM and its mirrors |
| 1115 | for (a = 0xc000; a < 0x10000; a += sz) { |
| 1116 | z80_map_set(z80_read_map, a, a + sz-1, PicoMem.zram, 0); |
| 1117 | z80_map_set(z80_write_map, a, a + sz-1, PicoMem.zram, 0); |
| 1118 | } |
| 1119 | a = 0xffff - (1<<Z80_MEM_SHIFT); |
| 1120 | z80_map_set(z80_write_map, a+1, 0xffff, xwrite, 1); // mapper detection |
| 1121 | |
| 1122 | // ROM |
| 1123 | z80_map_set(z80_read_map, 0x0000, 0xbfff, Pico.rom, 0); |
| 1124 | z80_map_set(z80_write_map, 0x0000, 0xbfff, xwrite, 1); // mapper detection |
| 1125 | |
| 1126 | // SC-3000 has 2KB, but no harm in mapping the 32KB for BASIC here |
| 1127 | if ((PicoIn.AHW & PAHW_SC) && mapper == PMS_MAP_AUTO) |
| 1128 | mapper = PMS_MAP_32KBRAM; |
| 1129 | // Nemesis mapper maps last 8KB rom bank #15 to adress 0 |
| 1130 | if (mapper == PMS_MAP_NEMESIS && Pico.romsize > 0x1e000) |
| 1131 | z80_map_set(z80_read_map, 0x0000, 0x1fff, Pico.rom + 0x1e000, 0); |
| 1132 | |
| 1133 | #ifdef _USE_DRZ80 |
| 1134 | drZ80.z80_in = z80_sms_in; |
| 1135 | drZ80.z80_out = z80_sms_out; |
| 1136 | #endif |
| 1137 | #ifdef _USE_CZ80 |
| 1138 | Cz80_Set_INPort(&CZ80, z80_sms_in); |
| 1139 | Cz80_Set_OUTPort(&CZ80, z80_sms_out); |
| 1140 | #endif |
| 1141 | |
| 1142 | // memory mapper setup, linear mapping of 1st 48KB |
| 1143 | memset(Pico.ms.carthw, 0, sizeof(Pico.ms.carthw)); |
| 1144 | if (mapper == PMS_MAP_MSX || mapper == PMS_MAP_NEMESIS) { |
| 1145 | xwrite(0x0000, 4); |
| 1146 | xwrite(0x0001, 5); |
| 1147 | xwrite(0x0002, 2); |
| 1148 | xwrite(0x0003, 3); |
| 1149 | } else if (mapper == PMS_MAP_KOREA) { |
| 1150 | xwrite(0xa000, 2); |
| 1151 | } else if (mapper == PMS_MAP_N32K) { |
| 1152 | xwrite(0xffff, 0); |
| 1153 | } else if (mapper == PMS_MAP_N16K) { |
| 1154 | xwrite(0x3ffe, 0); |
| 1155 | xwrite(0x7fff, 1); |
| 1156 | xwrite(0xbfff, 2); |
| 1157 | } else if (mapper == PMS_MAP_JANGGUN) { |
| 1158 | xwrite(0xfffe, 1); |
| 1159 | xwrite(0xffff, 2); |
| 1160 | } else if (mapper == PMS_MAP_XOR) { |
| 1161 | xwrite(0x2000, 0); |
| 1162 | } else if (mapper == PMS_MAP_CODEM) { |
| 1163 | xwrite(0x0000, 0); |
| 1164 | xwrite(0x4000, 1); |
| 1165 | xwrite(0x8000, 2); |
| 1166 | } else if (mapper == PMS_MAP_SEGA) { |
| 1167 | xwrite(0xfffc, 0); |
| 1168 | xwrite(0xfffd, 0); |
| 1169 | xwrite(0xfffe, 1); |
| 1170 | xwrite(0xffff, 2); |
| 1171 | } else if (mapper == PMS_MAP_32KBRAM) { |
| 1172 | xwrite(0x8000, 0); |
| 1173 | } else if (mapper == PMS_MAP_AUTO) { |
| 1174 | // pre-initialize Sega mapper to linear mapping (else state load may fail) |
| 1175 | Pico.ms.carthw[0xe] = 0x1; |
| 1176 | Pico.ms.carthw[0xf] = 0x2; |
| 1177 | } |
| 1178 | } |
| 1179 | |
| 1180 | void PicoStateLoadedMS(void) |
| 1181 | { |
| 1182 | u8 mapper = Pico.ms.mapper; |
| 1183 | u8 zram_dff0[16]; // TODO xwrite also writes to zram :-/ |
| 1184 | u8 carthw[16]; |
| 1185 | |
| 1186 | memcpy(zram_dff0, PicoMem.zram+0x1ff0, 16); |
| 1187 | memcpy(carthw, Pico.ms.carthw, 16); |
| 1188 | memset(Pico.ms.carthw, -1, 16); |
| 1189 | if (mapper == PMS_MAP_8KBRAM || mapper == PMS_MAP_32KBRAM) { |
| 1190 | u16 a = carthw[0] << 12; |
| 1191 | xwrite(a, *(unsigned char *)(PicoMem.vram+0x4000)); |
| 1192 | } else if (mapper == PMS_MAP_MSX || mapper == PMS_MAP_NEMESIS) { |
| 1193 | xwrite(0x0000, carthw[0]); |
| 1194 | xwrite(0x0001, carthw[1]); |
| 1195 | xwrite(0x0002, carthw[2]); |
| 1196 | xwrite(0x0003, carthw[3]); |
| 1197 | } else if (mapper == PMS_MAP_KOREA) { |
| 1198 | xwrite(0xa000, carthw[0x0f]); |
| 1199 | } else if (mapper == PMS_MAP_N32K) { |
| 1200 | xwrite(0xffff, carthw[0x0f]); |
| 1201 | } else if (mapper == PMS_MAP_N16K) { |
| 1202 | xwrite(0x3ffe, carthw[0]); |
| 1203 | xwrite(0x7fff, carthw[1]); |
| 1204 | xwrite(0xbfff, carthw[2]); |
| 1205 | } else if (mapper == PMS_MAP_JANGGUN) { |
| 1206 | xwrite(0x4000, carthw[2]); |
| 1207 | xwrite(0x6000, carthw[3]); |
| 1208 | xwrite(0x8000, carthw[4]); |
| 1209 | xwrite(0xa000, carthw[5]); |
| 1210 | } else if (mapper == PMS_MAP_XOR) { |
| 1211 | xwrite(0x2000, carthw[0]); |
| 1212 | } else if (mapper == PMS_MAP_CODEM) { |
| 1213 | xwrite(0x0000, carthw[0]); |
| 1214 | xwrite(0x4000, carthw[1]); |
| 1215 | xwrite(0x8000, carthw[2]); |
| 1216 | } else if (mapper == PMS_MAP_SEGA) { |
| 1217 | xwrite(0xfffc, carthw[0x0c]); |
| 1218 | xwrite(0xfffd, carthw[0x0d]); |
| 1219 | xwrite(0xfffe, carthw[0x0e]); |
| 1220 | xwrite(0xffff, carthw[0x0f]); |
| 1221 | } |
| 1222 | memcpy(PicoMem.zram+0x1ff0, zram_dff0, 16); |
| 1223 | memcpy(Pico.ms.carthw, carthw, 16); |
| 1224 | } |
| 1225 | |
| 1226 | void PicoFrameMS(void) |
| 1227 | { |
| 1228 | struct PicoVideo *pv = &Pico.video; |
| 1229 | int is_pal = Pico.m.pal; |
| 1230 | int lines = is_pal ? 313 : 262; |
| 1231 | int cycles_line = 228; |
| 1232 | int skip = PicoIn.skipFrame; |
| 1233 | int lines_vis = 192; |
| 1234 | int hint; // Hint counter |
| 1235 | int nmi; |
| 1236 | int y; |
| 1237 | |
| 1238 | PsndStartFrame(); |
| 1239 | |
| 1240 | // for SMS the pause button generates an NMI, for GG ths is not the case |
| 1241 | nmi = (PicoIn.pad[0] >> 7) & 1; |
| 1242 | if ((PicoIn.AHW & PAHW_8BIT) == PAHW_SMS && !Pico.ms.nmi_state && nmi) |
| 1243 | z80_nmi(); |
| 1244 | Pico.ms.nmi_state = nmi; |
| 1245 | |
| 1246 | if ((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18)) |
| 1247 | lines_vis = (pv->reg[1] & 0x08) ? 240 : 224; |
| 1248 | PicoFrameStartSMS(); |
| 1249 | hint = pv->reg[0x0a]; |
| 1250 | |
| 1251 | // SMS: xscroll:f3 sprovr,vint, vcount:fc, hint:fd |
| 1252 | // GG: xscroll:f5 sprovr,vint:fd vcount:fe, hint:ff |
| 1253 | for (y = 0; y < lines; y++) |
| 1254 | { |
| 1255 | Pico.t.z80c_line_start = Pico.t.z80c_aim; |
| 1256 | |
| 1257 | // advance the line counter. It is set back at some point in the VBLANK so |
| 1258 | // that the line count in the active area (-32..lines+1) is contiguous. |
| 1259 | pv->v_counter = Pico.m.scanline = (u8)y; |
| 1260 | switch (is_pal ? -lines_vis : lines_vis) { |
| 1261 | case 192: if (y > 218) pv->v_counter = y - (lines-256); break; |
| 1262 | case 224: if (y > 234) pv->v_counter = y - (lines-256); break; |
| 1263 | /* case 240: if (y > 242) pv->v_counter = y - (lines-256); break; ? */ |
| 1264 | case -192: if (y > 242) pv->v_counter = y - (lines-256); break; |
| 1265 | case -224: if (y > 258) pv->v_counter = y - (lines-256); break; |
| 1266 | case -240: if (y > 266) pv->v_counter = y - (lines-256); break; |
| 1267 | } |
| 1268 | |
| 1269 | // Parse sprites for the next line |
| 1270 | if (y < lines_vis) |
| 1271 | PicoParseSATSMS(y-1); |
| 1272 | else if (y > lines-32) |
| 1273 | PicoParseSATSMS(y-1-lines); |
| 1274 | |
| 1275 | // take over status bits from previously rendered line TODO: cycle exact? |
| 1276 | pv->status |= sprites_status; |
| 1277 | sprites_status = 0; |
| 1278 | |
| 1279 | // Interrupt handling. Simulate interrupt flagged and immediately reset in |
| 1280 | // same insn by flagging the irq, execute for 1 insn, then checking if the |
| 1281 | // irq is still pending. (GG Chicago, SMS Back to the Future III) |
| 1282 | pv->pending_ints &= ~2; // lost if not caught in the same line |
| 1283 | if (y <= lines_vis) |
| 1284 | { |
| 1285 | if (--hint < 0) |
| 1286 | { |
| 1287 | hint = pv->reg[0x0a]; |
| 1288 | pv->pending_ints |= 2; |
| 1289 | z80_exec(Pico.t.z80c_cnt + 1); |
| 1290 | |
| 1291 | if ((pv->reg[0] & 0x10) && (pv->pending_ints & 2)) { |
| 1292 | elprintf(EL_INTS, "hint"); |
| 1293 | z80_int_assert(1); |
| 1294 | } |
| 1295 | } |
| 1296 | } |
| 1297 | else if (y == lines_vis + 1) { |
| 1298 | pv->pending_ints |= 1; |
| 1299 | z80_exec(Pico.t.z80c_cnt + 1); |
| 1300 | |
| 1301 | if ((pv->reg[1] & 0x20) && (pv->pending_ints & 1)) { |
| 1302 | elprintf(EL_INTS, "vint"); |
| 1303 | z80_int_assert(1); |
| 1304 | } |
| 1305 | } |
| 1306 | z80_exec(Pico.t.z80c_line_start + 12); // GG Madou 1, display off after line start |
| 1307 | |
| 1308 | // render next line |
| 1309 | if (y < lines_vis && !skip) |
| 1310 | PicoLineSMS(y); |
| 1311 | |
| 1312 | z80_exec(Pico.t.z80c_line_start + cycles_line); |
| 1313 | } |
| 1314 | |
| 1315 | // end of frame updates |
| 1316 | tape_update(Pico.t.z80c_aim); |
| 1317 | tape_write(Pico.t.z80c_aim, -1); |
| 1318 | tape.cycle -= Pico.t.z80c_aim; |
| 1319 | tape.phase -= Pico.t.z80c_aim; |
| 1320 | |
| 1321 | z80_resetCycles(); |
| 1322 | PsndGetSamplesMS(lines); |
| 1323 | } |
| 1324 | |
| 1325 | void PicoFrameDrawOnlyMS(void) |
| 1326 | { |
| 1327 | struct PicoVideo *pv = &Pico.video; |
| 1328 | int lines_vis = 192; |
| 1329 | int y; |
| 1330 | |
| 1331 | if ((pv->reg[0] & 6) == 6 && (pv->reg[1] & 0x18)) |
| 1332 | lines_vis = (pv->reg[1] & 0x08) ? 240 : 224; |
| 1333 | PicoFrameStartSMS(); |
| 1334 | |
| 1335 | for (y = 0; y < lines_vis; y++) { |
| 1336 | PicoParseSATSMS(y-1); |
| 1337 | PicoLineSMS(y); |
| 1338 | } |
| 1339 | } |
| 1340 | |
| 1341 | // open tape file for reading (WAV and bitstream files) |
| 1342 | int PicoPlayTape(const char *fname) |
| 1343 | { |
| 1344 | struct tape *pt = &tape; |
| 1345 | const char *ext = strrchr(fname, '.'); |
| 1346 | int rate; |
| 1347 | |
| 1348 | if (pt->ftape) PicoCloseTape(); |
| 1349 | pt->ftape = fopen(fname, "rb"); |
| 1350 | if (pt->ftape == NULL) return 1; |
| 1351 | pt->mode = 'r'; |
| 1352 | |
| 1353 | pt->isbit = ext && ! memcmp(ext, ".bit", 4); |
| 1354 | if (! pt->isbit) { |
| 1355 | u8 hdr[44]; |
| 1356 | int chans; |
| 1357 | fread(hdr, 1, sizeof(hdr), pt->ftape); |
| 1358 | // TODO add checks for WAV header... |
| 1359 | chans = hdr[22] | (hdr[23]<<8); |
| 1360 | rate = hdr[24] | (hdr[25]<<8) | (hdr[26]<<16) | (hdr[27]<<24); |
| 1361 | pt->wavsample = 0; |
| 1362 | pt->fsize = chans*sizeof(s16); |
| 1363 | } else { |
| 1364 | rate = 1200; |
| 1365 | pt->bitsample = ' '; |
| 1366 | pt->fsize = 1; |
| 1367 | } |
| 1368 | |
| 1369 | pt->cycles_sample = (Pico.m.pal ? OSC_PAL/15 : OSC_NTSC/15) / rate; |
| 1370 | pt->cycles_mult = (1LL<<32) / pt->cycles_sample; |
| 1371 | pt->cycle = Pico.t.z80c_aim; |
| 1372 | pt->phase = Pico.t.z80c_aim; |
| 1373 | pt->pause = 0; |
| 1374 | return 0; |
| 1375 | } |
| 1376 | |
| 1377 | // open tape file for writing, and write WAV hdr (44KHz, mono, 16 bit samples) |
| 1378 | int PicoRecordTape(const char *fname) |
| 1379 | { |
| 1380 | const char *ext = strrchr(fname, '.'); |
| 1381 | struct tape *pt = &tape; |
| 1382 | int rate, i; |
| 1383 | |
| 1384 | if (pt->ftape) PicoCloseTape(); |
| 1385 | pt->ftape = fopen(fname, "wb"); |
| 1386 | if (pt->ftape == NULL) return 1; |
| 1387 | pt->mode = 'w'; |
| 1388 | |
| 1389 | pt->isbit = ext && ! memcmp(ext, ".bit", 4); |
| 1390 | if (! pt->isbit) { |
| 1391 | // WAV header "riffraff" for PCM 44KHz mono, 16 bit samples. |
| 1392 | u8 hdr[44] = { // file and data size updated on file close |
| 1393 | 'R','I','F','F', 0,0,0,0, 'W','A','V','E', // "RIFF", file size, "WAVE" |
| 1394 | // "fmt ", hdr size, type, chans, rate, bytes/sec,bytes/sample,bits/sample |
| 1395 | 'f','m','t',' ', 16,0,0,0, 1,0, 1,0, 68,172,0,0, 136,88,1,0, 2,0, 16,0, |
| 1396 | 'd','a','t','a', 0,0,0,0 }; // "data", data size |
| 1397 | |
| 1398 | rate = 44100; |
| 1399 | pt->wavsample = 0; // Marker for "don't write yet" |
| 1400 | pt->fsize = sizeof(s16); |
| 1401 | |
| 1402 | fwrite(hdr, 1, sizeof(hdr), pt->ftape); |
| 1403 | for (i = 0; i < 44100; i++) |
| 1404 | fwrite(&pt->wavsample, 1, sizeof(s16), pt->ftape); |
| 1405 | } else { |
| 1406 | rate = 1200; |
| 1407 | pt->bitsample = ' '; // Marker for "don't write yet" |
| 1408 | for (i = 0; i < 1200; i++) |
| 1409 | fwrite(&pt->bitsample, 1, sizeof(u8), pt->ftape); |
| 1410 | } |
| 1411 | |
| 1412 | pt->cycles_sample = (Pico.m.pal ? OSC_PAL/15 : OSC_NTSC/15) / rate; |
| 1413 | pt->cycles_mult = (1LL<<32) / pt->cycles_sample; |
| 1414 | pt->cycle = Pico.t.z80c_aim; |
| 1415 | pt->phase = Pico.t.z80c_aim; |
| 1416 | pt->pause = 0; |
| 1417 | return 0; |
| 1418 | } |
| 1419 | |
| 1420 | void PicoCloseTape(void) |
| 1421 | { |
| 1422 | struct tape *pt = &tape; |
| 1423 | int i, le; |
| 1424 | |
| 1425 | // if recording, write last data, and update length in header |
| 1426 | if (pt->mode == 'w') { |
| 1427 | if (! pt->isbit) { |
| 1428 | for (i = 0; i < 44100; i++) |
| 1429 | fwrite(&pt->wavsample, 1, sizeof(s16), pt->ftape); |
| 1430 | le = i = ftell(pt->ftape); |
| 1431 | #if ! CPU_IS_LE |
| 1432 | le = (u8)(le>>24) | ((u8)le<<24) | ((u8)(le>>16)<<8) | ((u8)(le>>8)<<16); |
| 1433 | #endif |
| 1434 | fseek(pt->ftape, 4, SEEK_SET); |
| 1435 | fwrite(&le, 1, 4, pt->ftape); |
| 1436 | le = i-44; |
| 1437 | #if ! CPU_IS_LE |
| 1438 | le = (u8)(le>>24) | ((u8)le<<24) | ((u8)(le>>16)<<8) | ((u8)(le>>8)<<16); |
| 1439 | #endif |
| 1440 | fseek(pt->ftape, 40, SEEK_SET); |
| 1441 | fwrite(&le, 1, 4, pt->ftape); |
| 1442 | } else { |
| 1443 | pt->bitsample = ' '; |
| 1444 | for (i = 0; i < 1200; i++) |
| 1445 | fwrite(&pt->bitsample, 1, sizeof(u8), pt->ftape); |
| 1446 | } |
| 1447 | } |
| 1448 | |
| 1449 | if (pt->ftape) fclose(pt->ftape); |
| 1450 | pt->ftape = NULL; |
| 1451 | } |
| 1452 | // vim:ts=2:sw=2:expandtab |