/* FCE Ultra - NES/Famicom Emulator * * Copyright notice for this file: * Copyright (C) 2002 Xodnizel * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include "fceu-types.h" #include "x6502.h" #include "fceu.h" #include "video.h" #include "sound.h" #include "nsf.h" #include "general.h" #include "fceu-memory.h" #include "file.h" #include "fds.h" #include "fds_apu.h" #include "cart.h" #include "input.h" #ifndef M_PI #define M_PI 3.14159265358979323846 #endif static uint8 SongReload; static int CurrentSong; static DECLFW(NSF_write); static DECLFR(NSF_read); static int vismode = 1; static uint8 NSFROM[0x30 + 6] = { /* 0x00 - NMI */ 0x8D, 0xF4, 0x3F, /* Stop play routine NMIs. */ 0xA2, 0xFF, 0x9A, /* Initialize the stack pointer. */ 0xAD, 0xF0, 0x3F, /* See if we need to init. */ 0xF0, 0x09, /* If 0, go to play routine playing. */ 0xAD, 0xF1, 0x3F, /* Confirm and load A */ 0xAE, 0xF3, 0x3F, /* Load X with PAL/NTSC byte */ 0x20, 0x00, 0x00, /* JSR to init routine */ 0xA9, 0x00, 0xAA, 0xA8, 0x20, 0x00, 0x00, /* JSR to play routine */ 0x8D, 0xF5, 0x3F, /* Start play routine NMIs. */ 0x90, 0xFE, /* Loopie time. */ /* 0x20 */ 0x8D, 0xF3, 0x3F, /* Init init NMIs */ 0x18, 0x90, 0xFE /* Loopie time. */ }; static DECLFR(NSFROMRead) { return (NSFROM - 0x3800)[A]; } static int doreset = 0; static int NSFNMIFlags; static uint8 *NSFDATA = 0; static int NSFMaxBank; static int NSFSize; static uint8 BSon; static uint16 PlayAddr; static uint16 InitAddr; static uint16 LoadAddr; static NSF_HEADER NSFHeader; void NSFMMC5_Close(void); static uint8 *ExWRAM = 0; void NSFGI(int h) { switch (h) { case GI_CLOSE: if (NSFDATA) { free(NSFDATA); NSFDATA = 0; } if (ExWRAM) { free(ExWRAM); ExWRAM = 0; } if (NSFHeader.SoundChip & 1) { /* NSFVRC6_Init(); */ } else if (NSFHeader.SoundChip & 2) { /* NSFVRC7_Init(); */ } else if (NSFHeader.SoundChip & 4) { /* FDSSoundReset(); */ } else if (NSFHeader.SoundChip & 8) { NSFMMC5_Close(); } else if (NSFHeader.SoundChip & 0x10) { /* NSFN106_Init(); */ } else if (NSFHeader.SoundChip & 0x20) { /* NSFAY_Init(); */ } break; case GI_RESETM2: case GI_POWER: NSF_init(); break; } } /* First 32KB is reserved for sound chip emulation in the iNES mapper code. */ static INLINE void BANKSET(uint32 A, uint32 bank) { bank &= NSFMaxBank; if (NSFHeader.SoundChip & 4) memcpy(ExWRAM + (A - 0x6000), NSFDATA + (bank << 12), 4096); else setprg4(A, bank); } int NSFLoad(FCEUFILE *fp) { int x; FCEU_fseek(fp, 0, SEEK_SET); FCEU_fread(&NSFHeader, 1, 0x80, fp); if (memcmp(NSFHeader.ID, "NESM\x1a", 5)) return 0; NSFHeader.SongName[31] = NSFHeader.Artist[31] = NSFHeader.Copyright[31] = 0; LoadAddr = NSFHeader.LoadAddressLow; LoadAddr |= NSFHeader.LoadAddressHigh << 8; if (LoadAddr < 0x6000) { FCEUD_PrintError("Invalid load address."); return(0); } InitAddr = NSFHeader.InitAddressLow; InitAddr |= NSFHeader.InitAddressHigh << 8; PlayAddr = NSFHeader.PlayAddressLow; PlayAddr |= NSFHeader.PlayAddressHigh << 8; NSFSize = FCEU_fgetsize(fp) - 0x80; NSFMaxBank = ((NSFSize + (LoadAddr & 0xfff) + 4095) / 4096); NSFMaxBank = uppow2(NSFMaxBank); if (!(NSFDATA = (uint8*)FCEU_malloc(NSFMaxBank * 4096))) return 0; FCEU_fseek(fp, 0x80, SEEK_SET); memset(NSFDATA, 0x00, NSFMaxBank * 4096); FCEU_fread(NSFDATA + (LoadAddr & 0xfff), 1, NSFSize, fp); NSFMaxBank--; BSon = 0; for (x = 0; x < 8; x++) BSon |= NSFHeader.BankSwitch[x]; GameInfo->type = GIT_NSF; GameInfo->input[0] = GameInfo->input[1] = SI_GAMEPAD; GameInfo->cspecial = SIS_NSF; for (x = 0;; x++) { if (NSFROM[x] == 0x20) { NSFROM[x + 1] = InitAddr & 0xFF; NSFROM[x + 2] = InitAddr >> 8; NSFROM[x + 8] = PlayAddr & 0xFF; NSFROM[x + 9] = PlayAddr >> 8; break; } } if (NSFHeader.VideoSystem == 0) GameInfo->vidsys = GIV_NTSC; else if (NSFHeader.VideoSystem == 1) GameInfo->vidsys = GIV_PAL; GameInterface = NSFGI; FCEU_printf("NSF Loaded. File information:\n\n"); FCEU_printf(" Name: %s\n Artist: %s\n Copyright: %s\n\n", NSFHeader.SongName, NSFHeader.Artist, NSFHeader.Copyright); if (NSFHeader.SoundChip) { static char *tab[6] = { "Konami VRCVI", "Konami VRCVII", "Nintendo FDS", "Nintendo MMC5", "Namco 106", "Sunsoft FME-07" }; for (x = 0; x < 6; x++) if (NSFHeader.SoundChip & (1 << x)) { FCEU_printf(" Expansion hardware: %s\n", tab[x]); NSFHeader.SoundChip = 1 << x; /* Prevent confusing weirdness if more than one bit is set. */ break; } } if (BSon) FCEU_printf(" Bank-switched.\n"); FCEU_printf(" Load address: $%04x\n Init address: $%04x\n Play address: $%04x\n", LoadAddr, InitAddr, PlayAddr); FCEU_printf(" %s\n", (NSFHeader.VideoSystem & 1) ? "PAL" : "NTSC"); FCEU_printf(" Starting song: %d / %d\n\n", NSFHeader.StartingSong, NSFHeader.TotalSongs); if (NSFHeader.SoundChip & 4) ExWRAM = FCEU_gmalloc(32768 + 8192); else ExWRAM = FCEU_gmalloc(8192); return 1; } static DECLFR(NSFVectorRead) { if (((NSFNMIFlags & 1) && SongReload) || (NSFNMIFlags & 2) || doreset) { if (A == 0xFFFA) return(0x00); else if (A == 0xFFFB) return(0x38); else if (A == 0xFFFC) return(0x20); else if (A == 0xFFFD) { doreset = 0; return(0x38); } return(X.DB); } else return(CartBR(A)); } void NSFVRC6_Init(void); void NSFVRC7_Init(void); void NSFMMC5_Init(void); void NSFN106_Init(void); void NSFAY_Init(void); void NSF_init(void) { doreset = 1; ResetCartMapping(); if (NSFHeader.SoundChip & 4) { SetupCartPRGMapping(0, ExWRAM, 32768 + 8192, 1); setprg32(0x6000, 0); setprg8(0xE000, 4); memset(ExWRAM, 0x00, 32768 + 8192); SetWriteHandler(0x6000, 0xDFFF, CartBW); SetReadHandler(0x6000, 0xFFFF, CartBR); } else { memset(ExWRAM, 0x00, 8192); SetReadHandler(0x6000, 0x7FFF, CartBR); SetWriteHandler(0x6000, 0x7FFF, CartBW); SetupCartPRGMapping(0, NSFDATA, ((NSFMaxBank + 1) * 4096), 0); SetupCartPRGMapping(1, ExWRAM, 8192, 1); setprg8r(1, 0x6000, 0); SetReadHandler(0x8000, 0xFFFF, CartBR); } if (BSon) { int32 x; for (x = 0; x < 8; x++) { if (NSFHeader.SoundChip & 4 && x >= 6) BANKSET(0x6000 + (x - 6) * 4096, NSFHeader.BankSwitch[x]); BANKSET(0x8000 + x * 4096, NSFHeader.BankSwitch[x]); } } else { int32 x; for (x = (LoadAddr & 0xF000); x < 0x10000; x += 0x1000) BANKSET(x, ((x - (LoadAddr & 0x7000)) >> 12)); } SetReadHandler(0xFFFA, 0xFFFD, NSFVectorRead); SetWriteHandler(0x2000, 0x3fff, 0); SetReadHandler(0x2000, 0x37ff, 0); SetReadHandler(0x3836, 0x3FFF, 0); SetReadHandler(0x3800, 0x3835, NSFROMRead); SetWriteHandler(0x5ff6, 0x5fff, NSF_write); SetWriteHandler(0x3ff0, 0x3fff, NSF_write); SetReadHandler(0x3ff0, 0x3fff, NSF_read); if (NSFHeader.SoundChip & 1) { NSFVRC6_Init(); } else if (NSFHeader.SoundChip & 2) { NSFVRC7_Init(); } else if (NSFHeader.SoundChip & 4) { FDSSoundReset(); } else if (NSFHeader.SoundChip & 8) { NSFMMC5_Init(); } else if (NSFHeader.SoundChip & 0x10) { NSFN106_Init(); } else if (NSFHeader.SoundChip & 0x20) { NSFAY_Init(); } CurrentSong = NSFHeader.StartingSong; SongReload = 0xFF; NSFNMIFlags = 0; } static DECLFW(NSF_write) { switch (A) { case 0x3FF3: NSFNMIFlags |= 1; break; case 0x3FF4: NSFNMIFlags &= ~2; break; case 0x3FF5: NSFNMIFlags |= 2; break; case 0x5FF6: case 0x5FF7: if (!(NSFHeader.SoundChip & 4)) return; case 0x5FF8: case 0x5FF9: case 0x5FFA: case 0x5FFB: case 0x5FFC: case 0x5FFD: case 0x5FFE: case 0x5FFF: if (!BSon) return; A &= 0xF; BANKSET((A * 4096), V); break; } } static DECLFR(NSF_read) { int x; switch (A) { case 0x3ff0: x = SongReload; #ifdef FCEUDEF_DEBUGGER if (!fceuindbg) #endif SongReload = 0; return x; case 0x3ff1: #ifdef FCEUDEF_DEBUGGER if (!fceuindbg) #endif { memset(RAM, 0x00, 0x800); BWrite[0x4015](0x4015, 0x0); for (x = 0; x < 0x14; x++) BWrite[0x4000 + x](0x4000 + x, 0); BWrite[0x4015](0x4015, 0xF); if (NSFHeader.SoundChip & 4) { BWrite[0x4017](0x4017, 0xC0); /* FDS BIOS writes $C0 */ BWrite[0x4089](0x4089, 0x80); BWrite[0x408A](0x408A, 0xE8); } else { memset(ExWRAM, 0x00, 8192); BWrite[0x4017](0x4017, 0xC0); BWrite[0x4017](0x4017, 0xC0); BWrite[0x4017](0x4017, 0x40); } if (BSon) { for (x = 0; x < 8; x++) BANKSET(0x8000 + x * 4096, NSFHeader.BankSwitch[x]); } return(CurrentSong - 1); } case 0x3FF3: return PAL; } return 0; } uint8 FCEU_GetJoyJoy(void); static int special = 0; void DrawNSF(uint8 *XBuf) { char snbuf[16]; int x; if (vismode == 0) return; memset(XBuf, 0, 256 * 240); { int32 *Bufpl; int32 mul = 0; int l; l = GetSoundBuffer(&Bufpl); if (special == 0) { if (FSettings.SoundVolume) mul = 8192 * 240 / (16384 * FSettings.SoundVolume / 50); for (x = 0; x < 256; x++) { uint32 y; y = 142 + ((Bufpl[(x * l) >> 8] * mul) >> 14); if (y < 240) XBuf[x + y * 256] = 3; } } else if (special == 1) { if (FSettings.SoundVolume) mul = 8192 * 240 / (8192 * FSettings.SoundVolume / 50); for (x = 0; x < 256; x++) { double r; uint32 xp, yp; r = (Bufpl[(x * l) >> 8] * mul) >> 14; xp = 128 + r*cos(x*M_PI*2 / 256); yp = 120 + r*sin(x*M_PI*2 / 256); xp &= 255; yp %= 240; XBuf[xp + yp * 256] = 3; } } else if (special == 2) { static double theta = 0; if (FSettings.SoundVolume) mul = 8192 * 240 / (16384 * FSettings.SoundVolume / 50); for (x = 0; x < 128; x++) { double xc, yc; double r, t; uint32 m, n; xc = (double)128 - x; yc = 0 - ((double)(((Bufpl[(x * l) >> 8]) * mul) >> 14)); t = M_PI + atan(yc / xc); r = sqrt(xc * xc + yc * yc); t += theta; m = 128 + r*cos(t); n = 120 + r*sin(t); if (m < 256 && n < 240) XBuf[m + n * 256] = 3; } for (x = 128; x < 256; x++) { double xc, yc; double r, t; uint32 m, n; xc = (double)x - 128; yc = (double)((Bufpl[(x * l) >> 8] * mul) >> 14); t = atan(yc / xc); r = sqrt(xc * xc + yc * yc); t += theta; m = 128 + r*cos(t); n = 120 + r*sin(t); if (m < 256 && n < 240) XBuf[m + n * 256] = 3; } theta += (double)M_PI / 256; } } DrawTextTrans(XBuf + 10 * 256 + 4 + (((31 - strlen((char*)NSFHeader.SongName)) << 2)), 256, NSFHeader.SongName, 6); DrawTextTrans(XBuf + 26 * 256 + 4 + (((31 - strlen((char*)NSFHeader.Artist)) << 2)), 256, NSFHeader.Artist, 6); DrawTextTrans(XBuf + 42 * 256 + 4 + (((31 - strlen((char*)NSFHeader.Copyright)) << 2)), 256, NSFHeader.Copyright, 6); DrawTextTrans(XBuf + 70 * 256 + 4 + (((31 - strlen("Song:")) << 2)), 256, (uint8*)"Song:", 6); sprintf(snbuf, "<%d/%d>", CurrentSong, NSFHeader.TotalSongs); DrawTextTrans(XBuf + 82 * 256 + 4 + (((31 - strlen(snbuf)) << 2)), 256, (uint8*)snbuf, 6); { static uint8 last = 0; uint8 tmp; tmp = FCEU_GetJoyJoy(); if ((tmp & JOY_RIGHT) && !(last & JOY_RIGHT)) { if (CurrentSong < NSFHeader.TotalSongs) { CurrentSong++; SongReload = 0xFF; } } else if ((tmp & JOY_LEFT) && !(last & JOY_LEFT)) { if (CurrentSong > 1) { CurrentSong--; SongReload = 0xFF; } } else if ((tmp & JOY_UP) && !(last & JOY_UP)) { CurrentSong += 10; if (CurrentSong > NSFHeader.TotalSongs) CurrentSong = NSFHeader.TotalSongs; SongReload = 0xFF; } else if ((tmp & JOY_DOWN) && !(last & JOY_DOWN)) { CurrentSong -= 10; if (CurrentSong < 1) CurrentSong = 1; SongReload = 0xFF; } else if ((tmp & JOY_START) && !(last & JOY_START)) SongReload = 0xFF; else if ((tmp & JOY_A) && !(last & JOY_A)) { special = (special + 1) % 3; } last = tmp; } } void DoNSFFrame(void) { if (((NSFNMIFlags & 1) && SongReload) || (NSFNMIFlags & 2)) TriggerNMI(); } void FCEUI_NSFSetVis(int mode) { vismode = mode; } int FCEUI_NSFChange(int amount) { CurrentSong += amount; if (CurrentSong < 1) CurrentSong = 1; else if (CurrentSong > NSFHeader.TotalSongs) CurrentSong = NSFHeader.TotalSongs; SongReload = 0xFF; return(CurrentSong); } /* Returns total songs */ int FCEUI_NSFGetInfo(uint8 *name, uint8 *artist, uint8 *copyright, int maxlen) { strncpy((char*)name, (const char*)NSFHeader.SongName, (size_t)maxlen); strncpy((char*)artist, (const char*)NSFHeader.Artist, (size_t)maxlen); strncpy((char*)copyright, (const char*)NSFHeader.Copyright, (size_t)maxlen); return(NSFHeader.TotalSongs); }