nes.c
1 /* 2 ** Nofrendo (c) 1998-2000 Matthew Conte (matt@conte.com) 3 ** 4 ** 5 ** This program is free software; you can redistribute it and/or 6 ** modify it under the terms of version 2 of the GNU Library General 7 ** Public License as published by the Free Software Foundation. 8 ** 9 ** This program is distributed in the hope that it will be useful, 10 ** but WITHOUT ANY WARRANTY; without even the implied warranty of 11 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 12 ** Library General Public License for more details. To obtain a 13 ** copy of the GNU Library General Public License, write to the Free 14 ** Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 15 ** 16 ** Any permitted reproduction of these routines, in whole or in part, 17 ** must bear this legend. 18 ** 19 ** 20 ** nes.c 21 ** 22 ** NES hardware related routines 23 ** $Id: nes.c,v 1.2 2001/04/27 14:37:11 neil Exp $ 24 */ 25 26 #include <stdio.h> 27 #include <string.h> 28 #include <stdlib.h> 29 #include "noftypes.h" 30 #include "nes6502.h" 31 #include "log.h" 32 #include "osd.h" 33 #include "nes.h" 34 #include "nes_apu.h" 35 #include "nes_ppu.h" 36 #include "nes_rom.h" 37 #include "nes_mmc.h" 38 #include "vid_drv.h" 39 #include "nofrendo.h" 40 41 42 #define NES_CLOCK_DIVIDER 12 43 //#define NES_MASTER_CLOCK 21477272.727272727272 44 #define NES_MASTER_CLOCK (236250000 / 11) 45 #define NES_SCANLINE_CYCLES (1364.0 / NES_CLOCK_DIVIDER) 46 #define NES_FIQ_PERIOD (NES_MASTER_CLOCK / NES_CLOCK_DIVIDER / 60) 47 48 #define NES_RAMSIZE 0x800 49 50 #define NES_SKIP_LIMIT (NES_REFRESH_RATE / 5) /* 12 or 10, depending on PAL/NTSC */ 51 52 static nes_t nes; 53 54 /* find out if a file is ours */ 55 int nes_isourfile(const char *filename) 56 { 57 return rom_checkmagic(filename); 58 } 59 60 /* TODO: just asking for problems -- please remove */ 61 nes_t *nes_getcontextptr(void) 62 { 63 return &nes; 64 } 65 66 void nes_getcontext(nes_t *machine) 67 { 68 apu_getcontext(nes.apu); 69 ppu_getcontext(nes.ppu); 70 nes6502_getcontext(nes.cpu); 71 mmc_getcontext(nes.mmc); 72 73 *machine = nes; 74 } 75 76 void nes_setcontext(nes_t *machine) 77 { 78 ASSERT(machine); 79 80 apu_setcontext(machine->apu); 81 ppu_setcontext(machine->ppu); 82 nes6502_setcontext(machine->cpu); 83 mmc_setcontext(machine->mmc); 84 85 nes = *machine; 86 } 87 88 static uint8 ram_read(uint32 address) 89 { 90 return nes.cpu->mem_page[0][address & (NES_RAMSIZE - 1)]; 91 } 92 93 static void ram_write(uint32 address, uint8 value) 94 { 95 nes.cpu->mem_page[0][address & (NES_RAMSIZE - 1)] = value; 96 } 97 98 static void write_protect(uint32 address, uint8 value) 99 { 100 /* don't allow write to go through */ 101 UNUSED(address); 102 UNUSED(value); 103 } 104 105 static uint8 read_protect(uint32 address) 106 { 107 /* don't allow read to go through */ 108 UNUSED(address); 109 110 return 0xFF; 111 } 112 113 #define LAST_MEMORY_HANDLER { -1, -1, NULL } 114 /* read/write handlers for standard NES */ 115 static const nes6502_memread default_readhandler[] = 116 { 117 { 0x0800, 0x1FFF, ram_read }, 118 { 0x2000, 0x3FFF, ppu_read }, 119 { 0x4000, 0x4015, apu_read }, 120 { 0x4016, 0x4017, ppu_readhigh }, 121 LAST_MEMORY_HANDLER 122 }; 123 124 static const nes6502_memwrite default_writehandler[] = 125 { 126 { 0x0800, 0x1FFF, ram_write }, 127 { 0x2000, 0x3FFF, ppu_write }, 128 { 0x4000, 0x4013, apu_write }, 129 { 0x4015, 0x4015, apu_write }, 130 { 0x4014, 0x4017, ppu_writehigh }, 131 LAST_MEMORY_HANDLER 132 }; 133 134 /* this big nasty boy sets up the address handlers that the CPU uses */ 135 static void build_address_handlers(nes_t *machine) 136 { 137 int count, num_handlers = 0; 138 mapintf_t *intf; 139 140 ASSERT(machine); 141 intf = machine->mmc->intf; 142 143 memset(machine->readhandler, 0, sizeof(machine->readhandler)); 144 memset(machine->writehandler, 0, sizeof(machine->writehandler)); 145 146 for (count = 0; num_handlers < MAX_MEM_HANDLERS; count++, num_handlers++) 147 { 148 if (NULL == default_readhandler[count].read_func) 149 break; 150 151 memcpy(&machine->readhandler[num_handlers], &default_readhandler[count], 152 sizeof(nes6502_memread)); 153 } 154 155 if (intf->sound_ext) 156 { 157 if (NULL != intf->sound_ext->mem_read) 158 { 159 for (count = 0; num_handlers < MAX_MEM_HANDLERS; count++, num_handlers++) 160 { 161 if (NULL == intf->sound_ext->mem_read[count].read_func) 162 break; 163 164 memcpy(&machine->readhandler[num_handlers], &intf->sound_ext->mem_read[count], 165 sizeof(nes6502_memread)); 166 } 167 } 168 } 169 170 if (NULL != intf->mem_read) 171 { 172 for (count = 0; num_handlers < MAX_MEM_HANDLERS; count++, num_handlers++) 173 { 174 if (NULL == intf->mem_read[count].read_func) 175 break; 176 177 memcpy(&machine->readhandler[num_handlers], &intf->mem_read[count], 178 sizeof(nes6502_memread)); 179 } 180 } 181 182 /* TODO: poof! numbers */ 183 machine->readhandler[num_handlers].min_range = 0x4018; 184 machine->readhandler[num_handlers].max_range = 0x5FFF; 185 machine->readhandler[num_handlers].read_func = read_protect; 186 num_handlers++; 187 machine->readhandler[num_handlers].min_range = -1; 188 machine->readhandler[num_handlers].max_range = -1; 189 machine->readhandler[num_handlers].read_func = NULL; 190 num_handlers++; 191 ASSERT(num_handlers <= MAX_MEM_HANDLERS); 192 193 num_handlers = 0; 194 195 for (count = 0; num_handlers < MAX_MEM_HANDLERS; count++, num_handlers++) 196 { 197 if (NULL == default_writehandler[count].write_func) 198 break; 199 200 memcpy(&machine->writehandler[num_handlers], &default_writehandler[count], 201 sizeof(nes6502_memwrite)); 202 } 203 204 if (intf->sound_ext) 205 { 206 if (NULL != intf->sound_ext->mem_write) 207 { 208 for (count = 0; num_handlers < MAX_MEM_HANDLERS; count++, num_handlers++) 209 { 210 if (NULL == intf->sound_ext->mem_write[count].write_func) 211 break; 212 213 memcpy(&machine->writehandler[num_handlers], &intf->sound_ext->mem_write[count], 214 sizeof(nes6502_memwrite)); 215 } 216 } 217 } 218 219 if (NULL != intf->mem_write) 220 { 221 for (count = 0; num_handlers < MAX_MEM_HANDLERS; count++, num_handlers++) 222 { 223 if (NULL == intf->mem_write[count].write_func) 224 break; 225 226 memcpy(&machine->writehandler[num_handlers], &intf->mem_write[count], 227 sizeof(nes6502_memwrite)); 228 } 229 } 230 231 /* catch-all for bad writes */ 232 /* TODO: poof! numbers */ 233 machine->writehandler[num_handlers].min_range = 0x4018; 234 machine->writehandler[num_handlers].max_range = 0x5FFF; 235 machine->writehandler[num_handlers].write_func = write_protect; 236 num_handlers++; 237 machine->writehandler[num_handlers].min_range = 0x8000; 238 machine->writehandler[num_handlers].max_range = 0xFFFF; 239 machine->writehandler[num_handlers].write_func = write_protect; 240 num_handlers++; 241 machine->writehandler[num_handlers].min_range = -1; 242 machine->writehandler[num_handlers].max_range = -1; 243 machine->writehandler[num_handlers].write_func = NULL; 244 num_handlers++; 245 ASSERT(num_handlers <= MAX_MEM_HANDLERS); 246 } 247 248 /* raise an IRQ */ 249 void nes_irq(void) 250 { 251 nes6502_irq(); 252 } 253 254 static uint8 nes_clearfiq(void) 255 { 256 if (nes.fiq_occurred) 257 { 258 nes.fiq_occurred = false; 259 return 0x40; 260 } 261 262 return 0; 263 } 264 265 void nes_setfiq(uint8 value) 266 { 267 nes.fiq_state = value; 268 nes.fiq_cycles = (int) NES_FIQ_PERIOD; 269 } 270 271 static void nes_checkfiq(int cycles) 272 { 273 nes.fiq_cycles -= cycles; 274 if (nes.fiq_cycles <= 0) 275 { 276 nes.fiq_cycles += (int) NES_FIQ_PERIOD; 277 if (0 == (nes.fiq_state & 0xC0)) 278 { 279 nes.fiq_occurred = true; 280 nes6502_irq(); 281 } 282 } 283 } 284 285 void nes_nmi(void) 286 { 287 nes6502_nmi(); 288 } 289 290 static void nes_renderframe(bool draw_flag) 291 { 292 int elapsed_cycles; 293 mapintf_t *mapintf = nes.mmc->intf; 294 int in_vblank = 0; 295 296 while (262 != nes.scanline) 297 { 298 // ppu_scanline(nes.vidbuf, nes.scanline, draw_flag); 299 ppu_scanline(vid_getbuffer(), nes.scanline, draw_flag); 300 301 if (241 == nes.scanline) 302 { 303 /* 7-9 cycle delay between when VINT flag goes up and NMI is taken */ 304 elapsed_cycles = nes6502_execute(7); 305 nes.scanline_cycles -= elapsed_cycles; 306 nes_checkfiq(elapsed_cycles); 307 308 ppu_checknmi(); 309 310 if (mapintf->vblank) 311 mapintf->vblank(); 312 in_vblank = 1; 313 } 314 315 if (mapintf->hblank) 316 mapintf->hblank(in_vblank); 317 318 nes.scanline_cycles += (float) NES_SCANLINE_CYCLES; 319 elapsed_cycles = nes6502_execute((int) nes.scanline_cycles); 320 nes.scanline_cycles -= (float) elapsed_cycles; 321 nes_checkfiq(elapsed_cycles); 322 323 ppu_endscanline(nes.scanline); 324 nes.scanline++; 325 } 326 327 nes.scanline = 0; 328 } 329 330 static void system_video(bool draw) 331 { 332 333 #ifdef NOLOOP 334 #else 335 #endif 336 /* blit to screen */ 337 vid_flush(); 338 339 /* grab input */ 340 osd_getinput(); 341 } 342 343 /* main emulation loop */ 344 static int last_ticks, frames_to_render; 345 void nes_emulate(void) 346 { 347 osd_setsound(nes.apu->process); 348 349 last_ticks = nofrendo_ticks; 350 frames_to_render = 0; 351 nes.scanline_cycles = 0; 352 nes.fiq_cycles = (int) NES_FIQ_PERIOD; 353 #ifdef NOLOOP 354 #else 355 while (false == nes.poweroff) 356 { 357 if (nofrendo_ticks != last_ticks) 358 { 359 int tick_diff = nofrendo_ticks - last_ticks; 360 361 frames_to_render += tick_diff; 362 last_ticks = nofrendo_ticks; 363 } 364 365 if (true == nes.pause) 366 { 367 /* TODO: dim the screen, and pause/silence the apu */ 368 system_video(true); 369 frames_to_render = 0; 370 } 371 else if (frames_to_render > 1) 372 { 373 frames_to_render--; 374 nes_renderframe(false); 375 system_video(false); 376 } 377 else if ((1 == frames_to_render && true == nes.autoframeskip) 378 || false == nes.autoframeskip) 379 { 380 frames_to_render = 0; 381 nes_renderframe(true); 382 system_video(true); 383 } 384 } 385 #endif 386 } 387 388 //ADD ON 389 #ifdef NOLOOP 390 void nes_step(int skip) 391 { 392 if (skip) { 393 nes_renderframe(false); 394 system_video(false); 395 } 396 else { 397 nes_renderframe(true); 398 system_video(true); 399 } 400 401 } 402 #endif 403 404 static void mem_trash(uint8 *buffer, int length) 405 { 406 int i; 407 408 for (i = 0; i < length; i++) 409 buffer[i] = (uint8) rand(); 410 } 411 412 /* Reset NES hardware */ 413 void nes_reset(int reset_type) 414 { 415 if (HARD_RESET == reset_type) 416 { 417 memset(nes.cpu->mem_page[0], 0, NES_RAMSIZE); 418 if (nes.rominfo->vram) 419 mem_trash(nes.rominfo->vram, 0x2000 * nes.rominfo->vram_banks); 420 } 421 422 apu_reset(); 423 ppu_reset(reset_type); 424 mmc_reset(); 425 nes6502_reset(); 426 427 nes.scanline = 241; 428 } 429 430 void nes_destroy(nes_t **machine) 431 { 432 if (*machine) 433 { 434 rom_free(&(*machine)->rominfo); 435 mmc_destroy(&(*machine)->mmc); 436 ppu_destroy(&(*machine)->ppu); 437 apu_destroy(&(*machine)->apu); 438 // bmp_destroy(&(*machine)->vidbuf); 439 if ((*machine)->cpu) 440 { 441 if ((*machine)->cpu->mem_page[0]) 442 free((*machine)->cpu->mem_page[0]); 443 free((*machine)->cpu); 444 } 445 446 free(*machine); 447 *machine = NULL; 448 } 449 } 450 451 void nes_poweroff(void) 452 { 453 nes.poweroff = true; 454 } 455 456 void nes_togglepause(void) 457 { 458 nes.pause ^= true; 459 } 460 461 /* insert a cart into the NES */ 462 int nes_insertcart(const char *filename, nes_t *machine) 463 { 464 nes6502_setcontext(machine->cpu); 465 /* rom file */ 466 machine->rominfo = rom_load(filename); 467 if (NULL == machine->rominfo) 468 goto _fail; 469 /* map cart's SRAM to CPU $6000-$7FFF */ 470 if (machine->rominfo->sram) 471 { 472 machine->cpu->mem_page[6] = machine->rominfo->sram; 473 machine->cpu->mem_page[7] = machine->rominfo->sram + 0x1000; 474 } 475 /* mapper */ 476 machine->mmc = mmc_create(machine->rominfo); 477 if (NULL == machine->mmc) 478 goto _fail; 479 480 /* if there's VRAM, let the PPU know */ 481 if (NULL != machine->rominfo->vram) 482 machine->ppu->vram_present = true; 483 apu_setext(machine->apu, machine->mmc->intf->sound_ext); 484 build_address_handlers(machine); 485 nes_setcontext(machine); 486 nes_reset(HARD_RESET); 487 488 return 0; 489 490 _fail: 491 nes_destroy(&machine); 492 return -1; 493 } 494 495 496 /* Initialize NES CPU, hardware, etc. */ 497 nes_t *nes_create(void) 498 { 499 nes_t *machine; 500 sndinfo_t osd_sound; 501 int i; 502 503 machine = malloc(sizeof(nes_t)); 504 if (NULL == machine) 505 return NULL; 506 507 memset(machine, 0, sizeof(nes_t)); 508 509 /* bitmap */ 510 /* 8 pixel overdraw */ 511 // machine->vidbuf = bmp_create(NES_SCREEN_WIDTH, NES_SCREEN_HEIGHT, 8); 512 // if (NULL == machine->vidbuf) 513 // goto _fail; 514 515 machine->autoframeskip = true; 516 517 /* cpu */ 518 machine->cpu = malloc(sizeof(nes6502_context)); 519 if (NULL == machine->cpu) 520 goto _fail; 521 522 memset(machine->cpu, 0, sizeof(nes6502_context)); 523 524 /* allocate 2kB RAM */ 525 machine->cpu->mem_page[0] = malloc(NES_RAMSIZE); 526 if (NULL == machine->cpu->mem_page[0]) 527 goto _fail; 528 529 /* point all pages at NULL for now */ 530 for (i = 1; i < NES6502_NUMBANKS; i++) 531 machine->cpu->mem_page[i] = NULL; 532 533 machine->cpu->read_handler = machine->readhandler; 534 machine->cpu->write_handler = machine->writehandler; 535 536 /* apu */ 537 osd_getsoundinfo(&osd_sound); 538 machine->apu = apu_create(0, osd_sound.sample_rate, NES_REFRESH_RATE, osd_sound.bps); 539 540 if (NULL == machine->apu) 541 goto _fail; 542 543 /* set the IRQ routines */ 544 machine->apu->irq_callback = nes_irq; 545 machine->apu->irqclear_callback = nes_clearfiq; 546 547 /* ppu */ 548 machine->ppu = ppu_create(); 549 if (NULL == machine->ppu) 550 goto _fail; 551 552 machine->poweroff = false; 553 machine->pause = false; 554 555 return machine; 556 557 _fail: 558 nes_destroy(&machine); 559 return NULL; 560 } 561 562 /* 563 ** $Log: nes.c,v $ 564 ** Revision 1.2 2001/04/27 14:37:11 neil 565 ** wheeee 566 ** 567 ** Revision 1.1.1.1 2001/04/27 07:03:54 neil 568 ** initial 569 ** 570 ** Revision 1.18 2000/11/29 12:58:23 matt 571 ** timing/fiq fixes 572 ** 573 ** Revision 1.17 2000/11/27 19:36:15 matt 574 ** more timing fixes 575 ** 576 ** Revision 1.16 2000/11/26 16:13:13 matt 577 ** slight fix (?) to nes_fiq 578 ** 579 ** Revision 1.15 2000/11/26 15:51:13 matt 580 ** frame IRQ emulation 581 ** 582 ** Revision 1.14 2000/11/25 20:30:39 matt 583 ** scanline emulation simplifications/timing fixes 584 ** 585 ** Revision 1.13 2000/11/25 01:53:42 matt 586 ** bool stinks sometimes 587 ** 588 ** Revision 1.12 2000/11/21 13:28:40 matt 589 ** take care to zero allocated mem 590 ** 591 ** Revision 1.11 2000/11/20 13:23:32 matt 592 ** nofrendo.c now handles timer 593 ** 594 ** Revision 1.10 2000/11/09 14:07:27 matt 595 ** state load fixed, state save mostly fixed 596 ** 597 ** Revision 1.9 2000/11/05 22:19:37 matt 598 ** pause buglet fixed 599 ** 600 ** Revision 1.8 2000/11/05 06:27:09 matt 601 ** thinlib spawns changes 602 ** 603 ** Revision 1.7 2000/10/29 14:36:45 matt 604 ** nes_clearframeirq is static 605 ** 606 ** Revision 1.6 2000/10/28 15:20:41 matt 607 ** irq callbacks in nes_apu 608 ** 609 ** Revision 1.5 2000/10/27 12:55:58 matt 610 ** nes6502 now uses 4kB banks across the boards 611 ** 612 ** Revision 1.4 2000/10/25 13:44:02 matt 613 ** no more silly define names 614 ** 615 ** Revision 1.3 2000/10/25 01:23:08 matt 616 ** basic system autodetection 617 ** 618 ** Revision 1.2 2000/10/25 00:23:16 matt 619 ** makefiles updated for new directory structure 620 ** 621 ** Revision 1.1 2000/10/24 12:20:28 matt 622 ** changed directory structure 623 ** 624 ** Revision 1.50 2000/10/23 17:51:09 matt 625 ** adding fds support 626 ** 627 ** Revision 1.49 2000/10/23 15:53:08 matt 628 ** better system handling 629 ** 630 ** Revision 1.48 2000/10/22 20:02:29 matt 631 ** autoframeskip bugfix 632 ** 633 ** Revision 1.47 2000/10/22 19:16:15 matt 634 ** more sane timer ISR / autoframeskip 635 ** 636 ** Revision 1.46 2000/10/21 19:26:59 matt 637 ** many more cleanups 638 ** 639 ** Revision 1.45 2000/10/17 12:00:56 matt 640 ** selectable apu base frequency 641 ** 642 ** Revision 1.44 2000/10/10 13:58:14 matt 643 ** stroustrup squeezing his way in the door 644 ** 645 ** Revision 1.43 2000/10/10 13:05:30 matt 646 ** Mr. Clean makes a guest appearance 647 ** 648 ** Revision 1.42 2000/10/08 17:53:37 matt 649 ** minor accuracy changes 650 ** 651 ** Revision 1.41 2000/09/18 02:09:12 matt 652 ** -pedantic is your friend 653 ** 654 ** Revision 1.40 2000/09/15 13:38:39 matt 655 ** changes for optimized apu core 656 ** 657 ** Revision 1.39 2000/09/15 04:58:07 matt 658 ** simplifying and optimizing APU core 659 ** 660 ** Revision 1.38 2000/09/08 11:57:29 matt 661 ** no more nes_fiq 662 ** 663 ** Revision 1.37 2000/08/31 02:39:01 matt 664 ** moved dos stuff in here (temp) 665 ** 666 ** Revision 1.36 2000/08/16 02:51:55 matt 667 ** random cleanups 668 ** 669 ** Revision 1.35 2000/08/11 02:43:50 matt 670 ** moved frame irq stuff out of APU into here 671 ** 672 ** Revision 1.34 2000/08/11 01:42:43 matt 673 ** change to OSD sound info interface 674 ** 675 ** Revision 1.33 2000/07/31 04:27:59 matt 676 ** one million cleanups 677 ** 678 ** Revision 1.32 2000/07/30 04:32:32 matt 679 ** emulation of the NES frame IRQ 680 ** 681 ** Revision 1.31 2000/07/27 04:07:14 matt 682 ** cleaned up the neighborhood lawns 683 ** 684 ** Revision 1.30 2000/07/27 03:59:52 neil 685 ** pausing tweaks, during fullscreen toggles 686 ** 687 ** Revision 1.29 2000/07/27 03:19:22 matt 688 ** just a little cleaner, that's all 689 ** 690 ** Revision 1.28 2000/07/27 02:55:23 matt 691 ** nes_emulate went through detox 692 ** 693 ** Revision 1.27 2000/07/27 02:49:18 matt 694 ** cleaner flow in nes_emulate 695 ** 696 ** Revision 1.26 2000/07/27 01:17:09 matt 697 ** nes_insertrom -> nes_insertcart 698 ** 699 ** Revision 1.25 2000/07/26 21:36:14 neil 700 ** Big honkin' change -- see the mailing list 701 ** 702 ** Revision 1.24 2000/07/25 02:25:53 matt 703 ** safer xxx_destroy calls 704 ** 705 ** Revision 1.23 2000/07/24 04:32:40 matt 706 ** autoframeskip bugfix 707 ** 708 ** Revision 1.22 2000/07/23 15:13:48 matt 709 ** apu API change, autoframeskip part of nes_t struct 710 ** 711 ** Revision 1.21 2000/07/21 02:44:41 matt 712 ** merged osd_getinput and osd_gethostinput 713 ** 714 ** Revision 1.20 2000/07/17 05:12:55 matt 715 ** nes_ppu.c is no longer a scary place to be-- cleaner & faster 716 ** 717 ** Revision 1.19 2000/07/17 01:52:28 matt 718 ** made sure last line of all source files is a newline 719 ** 720 ** Revision 1.18 2000/07/15 23:51:23 matt 721 ** hack for certain filthy NES titles 722 ** 723 ** Revision 1.17 2000/07/11 04:31:54 matt 724 ** less magic number nastiness for screen dimensions 725 ** 726 ** Revision 1.16 2000/07/11 02:38:25 matt 727 ** encapsulated memory address handlers into nes/nsf 728 ** 729 ** Revision 1.15 2000/07/10 13:50:49 matt 730 ** added function nes_irq() 731 ** 732 ** Revision 1.14 2000/07/10 05:27:55 matt 733 ** cleaned up mapper-specific callbacks 734 ** 735 ** Revision 1.13 2000/07/09 03:43:26 matt 736 ** minor changes to gui handling 737 ** 738 ** Revision 1.12 2000/07/06 16:42:23 matt 739 ** updated for new video driver 740 ** 741 ** Revision 1.11 2000/07/05 19:57:36 neil 742 ** __GNUC -> __DJGPP in nes.c 743 ** 744 ** Revision 1.10 2000/07/05 12:23:03 matt 745 ** removed unnecessary references 746 ** 747 ** Revision 1.9 2000/07/04 23:12:34 matt 748 ** memory protection handlers 749 ** 750 ** Revision 1.8 2000/07/04 04:58:29 matt 751 ** dynamic memory range handlers 752 ** 753 ** Revision 1.7 2000/06/26 04:58:51 matt 754 ** minor bugfix 755 ** 756 ** Revision 1.6 2000/06/20 20:42:12 matt 757 ** fixed some NULL pointer problems 758 ** 759 ** Revision 1.5 2000/06/09 15:12:26 matt 760 ** initial revision 761 ** 762 */