nsf - big cleanup and fix a lot of games and add savestates
This commit is contained in:
parent
ab0547ddea
commit
10dbd9bafb
|
@ -1,27 +1,49 @@
|
||||||
|
using System;
|
||||||
using System;
|
|
||||||
using BizHawk.Common;
|
using BizHawk.Common;
|
||||||
|
|
||||||
//NSF ROM and general approaches are taken from FCEUX. however, i've improvised/simplified/broken things so the rom is doing some pointless stuff now.
|
//NSF ROM and general approaches are heavily derived from FCEUX. the general ideas:
|
||||||
|
//1. Have a hardcoded NSF driver rom loaded to 0x3800
|
||||||
|
//2. Have fake registers at $3FFx for the NSF driver to use
|
||||||
|
//3. These addresses are chosen because no known NSF could possibly use them for anything.
|
||||||
|
|
||||||
|
//NSF:
|
||||||
//check nsfspec.txt for more on why FDS is weird. lets try not following FCEUX too much there.
|
//check nsfspec.txt for more on why FDS is weird. lets try not following FCEUX too much there.
|
||||||
|
|
||||||
//TODO - add a sleep mode to the cpu and patch the rom program to use it?
|
//TODO - add a sleep mode to the cpu and patch the rom program to use it?
|
||||||
//some NSF players know when a song ends.
|
//TODO - some NSF players know when a song ends and skip to the next one.. how do they know?
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Cores.Nintendo.NES
|
namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
{
|
{
|
||||||
[NES.INESBoardImplCancel]
|
[NES.INESBoardImplCancel]
|
||||||
public sealed class NSFBoard : NES.NESBoardBase
|
public sealed class NSFBoard : NES.NESBoardBase
|
||||||
{
|
{
|
||||||
|
//------------------------------
|
||||||
//configuration
|
//configuration
|
||||||
|
|
||||||
internal NSFFormat nsf;
|
internal NSFFormat nsf;
|
||||||
byte[] InitBankSwitches = new byte[8];
|
|
||||||
byte[] FakePRG = new byte[32768];
|
/// <summary>
|
||||||
|
/// Whether the NSF is bankswitched
|
||||||
|
/// </summary>
|
||||||
bool BankSwitched;
|
bool BankSwitched;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the bankswitch values to be used before the INIT routine is called
|
||||||
|
/// </summary>
|
||||||
|
byte[] InitBankSwitches = new byte[8];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// An image of the entire PRG space where the unmapped files are located
|
||||||
|
/// </summary>
|
||||||
|
byte[] FakePRG = new byte[32768];
|
||||||
|
|
||||||
//------------------------------
|
//------------------------------
|
||||||
//state
|
//state
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// PRG bankswitching
|
||||||
|
/// </summary>
|
||||||
IntBuffer prg_banks_4k = new IntBuffer(8);
|
IntBuffer prg_banks_4k = new IntBuffer(8);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -29,8 +51,19 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool Patch_Vectors;
|
bool Patch_Vectors;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current 1-indexed song number (1 is the first song)
|
||||||
|
/// </summary>
|
||||||
int CurrentSong;
|
int CurrentSong;
|
||||||
bool ResetSignal;
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the INIT routine needs to be called
|
||||||
|
/// </summary>
|
||||||
|
bool InitPending;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Previous button state for button press handling
|
||||||
|
/// </summary>
|
||||||
int ButtonState;
|
int ButtonState;
|
||||||
|
|
||||||
public override bool Configure(NES.EDetectionOrigin origin)
|
public override bool Configure(NES.EDetectionOrigin origin)
|
||||||
|
@ -46,6 +79,16 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
base.Dispose();
|
base.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void SyncState(Serializer ser)
|
||||||
|
{
|
||||||
|
base.SyncState(ser);
|
||||||
|
ser.Sync("prg_banks_4k", ref prg_banks_4k);
|
||||||
|
ser.Sync("Patch_Vectors", ref Patch_Vectors);
|
||||||
|
ser.Sync("CurrentSong", ref CurrentSong);
|
||||||
|
ser.Sync("InitPending", ref InitPending);
|
||||||
|
ser.Sync("ButtonState", ref ButtonState);
|
||||||
|
}
|
||||||
|
|
||||||
public void InitNSF(NSFFormat nsf)
|
public void InitNSF(NSFFormat nsf)
|
||||||
{
|
{
|
||||||
this.nsf = nsf;
|
this.nsf = nsf;
|
||||||
|
@ -56,46 +99,28 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
NSFROM[0x19] = (byte)(nsf.PlayAddress);
|
NSFROM[0x19] = (byte)(nsf.PlayAddress);
|
||||||
NSFROM[0x1A] = (byte)(nsf.PlayAddress >> 8);
|
NSFROM[0x1A] = (byte)(nsf.PlayAddress >> 8);
|
||||||
|
|
||||||
//complicated anlysis straight from FCEUX
|
//analyze bankswitch configuration. fix broken configurations
|
||||||
//apparently, it converts a non-bankswitched configuration into a bankswitched configuration
|
|
||||||
//since the non-bankswitched configuration is seemingly almost pointless.
|
|
||||||
//I'm not too sure how we would really get a non-bankswitched file, using the code below.
|
|
||||||
//It would need to be loaded below 0x8000 I think?
|
|
||||||
BankSwitched = false;
|
BankSwitched = false;
|
||||||
for (int i = 0; i < 8; i++)
|
for (int i = 0; i < 8; i++)
|
||||||
{
|
{
|
||||||
InitBankSwitches[i] = nsf.BankswitchInitValues[i];
|
int bank = nsf.BankswitchInitValues[i];
|
||||||
if (InitBankSwitches[i] != 0)
|
|
||||||
|
//discard out of range bankswitches.. for example, Balloon Fight is 3120B but has initial bank settings set to 0,0,0,0,0,1,0
|
||||||
|
if (bank * 4096 > nsf.NSFData.Length - 0x80)
|
||||||
|
bank = 0;
|
||||||
|
|
||||||
|
InitBankSwitches[i] = (byte)bank;
|
||||||
|
if (bank != 0)
|
||||||
BankSwitched = true;
|
BankSwitched = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//if bit bankswitched, set up the fake PRG with the NSF data at the correct load address
|
||||||
if (!BankSwitched)
|
if (!BankSwitched)
|
||||||
{
|
{
|
||||||
if ((nsf.LoadAddress & 0x7000) >= 0x7000)
|
//copy to load address
|
||||||
{
|
int load_start = nsf.LoadAddress - 0x8000;
|
||||||
//"Ice Climber, and other F000 base address tunes need this"
|
int load_size = nsf.NSFData.Length - 0x80;
|
||||||
BankSwitched = true;
|
Buffer.BlockCopy(nsf.NSFData, 0x80, FakePRG, load_start, load_size);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
byte bankCounter = 0;
|
|
||||||
for (int x = (nsf.LoadAddress >> 12) & 0x7; x < 8; x++)
|
|
||||||
{
|
|
||||||
InitBankSwitches[x] = bankCounter;
|
|
||||||
bankCounter++;
|
|
||||||
}
|
|
||||||
BankSwitched = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
if (InitBankSwitches[i] != 0)
|
|
||||||
BankSwitched = true;
|
|
||||||
|
|
||||||
if (!BankSwitched)
|
|
||||||
{
|
|
||||||
throw new Exception("Test");
|
|
||||||
//setup FakePRG by copying in
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReplayInit();
|
ReplayInit();
|
||||||
|
@ -104,7 +129,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
|
|
||||||
void ReplayInit()
|
void ReplayInit()
|
||||||
{
|
{
|
||||||
ResetSignal = true;
|
InitPending = true;
|
||||||
Patch_Vectors = true;
|
Patch_Vectors = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,11 +138,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
ReplayInit();
|
ReplayInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void SyncState(Serializer ser)
|
|
||||||
{
|
|
||||||
base.SyncState(ser);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void WriteEXP(int addr, byte value)
|
public override void WriteEXP(int addr, byte value)
|
||||||
{
|
{
|
||||||
switch (addr)
|
switch (addr)
|
||||||
|
@ -141,20 +161,6 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public override void WriteReg2xxx(int addr, byte value)
|
|
||||||
{
|
|
||||||
switch (addr)
|
|
||||||
{
|
|
||||||
case 0x3FF3: Patch_Vectors = true; break;
|
|
||||||
case 0x3FF4: Patch_Vectors = false; break;
|
|
||||||
case 0x3FF5: Patch_Vectors = true; break;
|
|
||||||
default:
|
|
||||||
base.WriteReg2xxx(addr, value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override byte PeekReg2xxx(int addr)
|
public override byte PeekReg2xxx(int addr)
|
||||||
{
|
{
|
||||||
if (addr < 0x3FF0)
|
if (addr < 0x3FF0)
|
||||||
|
@ -168,111 +174,124 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
return base.ReadReg2xxx(addr);
|
return base.ReadReg2xxx(addr);
|
||||||
else if (addr >= 0x3FF0)
|
else if (addr >= 0x3FF0)
|
||||||
{
|
{
|
||||||
if (addr == 0x3FF0)
|
switch (addr)
|
||||||
{
|
{
|
||||||
byte ret = 0;
|
case 0x3FF0:
|
||||||
if (ResetSignal) ret = 1;
|
{
|
||||||
ResetSignal = false;
|
byte ret = 0;
|
||||||
return ret;
|
if (InitPending) ret = 1;
|
||||||
|
InitPending = false;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
case 0x3FF1:
|
||||||
|
{
|
||||||
|
//kevtris's reset process seems not to work. dunno what all is going on in there
|
||||||
|
|
||||||
|
//our own innovation, should work OK..
|
||||||
|
NES.apu.NESSoftReset();
|
||||||
|
|
||||||
|
//mostly fceux's guidance
|
||||||
|
NES.WriteMemory(0x4015, 0);
|
||||||
|
for (int i = 0; i < 14; i++)
|
||||||
|
NES.WriteMemory((ushort)(0x4000 + i), 0);
|
||||||
|
NES.WriteMemory(0x4015, 0x0F);
|
||||||
|
|
||||||
|
//clearing APU misc stuff, maybe not needed with soft reset above
|
||||||
|
//NES.WriteMemory(0x4017, 0xC0);
|
||||||
|
//NES.WriteMemory(0x4017, 0xC0);
|
||||||
|
//NES.WriteMemory(0x4017, 0x40);
|
||||||
|
|
||||||
|
//important to NSF standard for ram to be cleared, otherwise replayers are confused on account of not initializing memory themselves
|
||||||
|
var ram = NES.ram;
|
||||||
|
var wram = this.WRAM;
|
||||||
|
int wram_size = wram.Length;
|
||||||
|
for (int i = 0; i < 0x800; i++)
|
||||||
|
ram[i] = 0;
|
||||||
|
for (int i = 0; i < wram_size; i++)
|
||||||
|
wram[i] = 0;
|
||||||
|
|
||||||
|
//store specified initial bank state
|
||||||
|
if (BankSwitched)
|
||||||
|
for (int i = 0; i < 8; i++)
|
||||||
|
WriteEXP(0x5FF8 + i - 0x4000, InitBankSwitches[i]);
|
||||||
|
|
||||||
|
return (byte)(CurrentSong - 1);
|
||||||
|
}
|
||||||
|
case 0x3FF2:
|
||||||
|
return 0; //always return NTSC for now
|
||||||
|
case 0x3FF3:
|
||||||
|
Patch_Vectors = false;
|
||||||
|
return 0;
|
||||||
|
case 0x3FF4:
|
||||||
|
Patch_Vectors = true;
|
||||||
|
return 0;
|
||||||
|
default:
|
||||||
|
return base.ReadReg2xxx(addr);
|
||||||
}
|
}
|
||||||
else if (addr == 0x3FF1)
|
|
||||||
{
|
|
||||||
//kevtris's reset process seems not to work. dunno what all is going on in there
|
|
||||||
|
|
||||||
//our own innovation, should work OK..
|
|
||||||
NES.apu.NESSoftReset();
|
|
||||||
|
|
||||||
//mostly fceux's guidance
|
|
||||||
NES.WriteMemory(0x4015, 0);
|
|
||||||
for (int i = 0; i < 14; i++)
|
|
||||||
NES.WriteMemory((ushort)(0x4000 + i), 0);
|
|
||||||
NES.WriteMemory(0x4015, 0x0F);
|
|
||||||
|
|
||||||
//clearing APU misc stuff, maybe not needed with soft reset above
|
|
||||||
//NES.WriteMemory(0x4017, 0xC0);
|
|
||||||
//NES.WriteMemory(0x4017, 0xC0);
|
|
||||||
//NES.WriteMemory(0x4017, 0x40);
|
|
||||||
|
|
||||||
//important to NSF standard for ram to be cleared, otherwise replayers are confused on account of not initializing memory themselves
|
|
||||||
var ram = NES.ram;
|
|
||||||
var wram = this.WRAM;
|
|
||||||
int wram_size = wram.Length;
|
|
||||||
for (int i = 0; i < 0x800; i++)
|
|
||||||
ram[i] = 0;
|
|
||||||
for (int i = 0; i < wram_size; i++)
|
|
||||||
wram[i] = 0;
|
|
||||||
|
|
||||||
//store specified initial bank state
|
|
||||||
if (BankSwitched)
|
|
||||||
for (int i = 0; i < 8; i++)
|
|
||||||
WriteEXP(0x5FF8 + i - 0x4000, InitBankSwitches[i]);
|
|
||||||
|
|
||||||
return (byte)(CurrentSong - 1);
|
|
||||||
}
|
|
||||||
else if (addr == 0x3FF3) return 0; //always return NTSC I guess
|
|
||||||
else return base.ReadReg2xxx(addr);
|
|
||||||
}
|
}
|
||||||
else if (addr - 0x3800 < NSFROM.Length) return NSFROM[addr - 0x3800];
|
else if (addr - 0x3800 < NSFROM.Length) return NSFROM[addr - 0x3800];
|
||||||
else return base.ReadReg2xxx(addr);
|
else return base.ReadReg2xxx(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
//; @NMIVector
|
|
||||||
//00:XX00:8D F4 3F STA $3FF4 = #$00 ; clear NMI_2 (the value of A is unimportant)
|
|
||||||
//00:XX03:A2 FF LDX #$FF
|
|
||||||
//00:XX05:9A TXS ; stack pointer is initialized
|
|
||||||
//00:XX06:AD F0 3F LDA $3FF0 = #$00 ; read a flag that says whether we need to run init
|
|
||||||
//00:XX09:F0 09 BEQ $8014 ; If we dont need init, go to @PastInit
|
|
||||||
//00:XX0B:AD F1 3F LDA $3FF1 = #$00 ; reading this value causes a reset
|
|
||||||
//00:XX0E:AE F3 3F LDX $3FF3 = #$00 ; reads the PAL flag
|
|
||||||
//00:XX11:20 00 00 JSR $0000 ; JSR to INIT routine
|
|
||||||
//; @PastInit
|
|
||||||
//00:XX14:A9 00 LDA #$00
|
|
||||||
//00:XX16:AA TAX
|
|
||||||
//00:XX17:A8 TAY ; X and Y are cleared
|
|
||||||
//00:XX18:20 00 00 JSR $0000 ; JSR to PLAY routine
|
|
||||||
//00:XX1B:8D F5 3F STA $3FF5 = #$FF ; set NMI_2 flag
|
|
||||||
//00:XX1E:90 FE BCC $XX1E ; infinite loop.. when the song is over?
|
|
||||||
//; @ResetVector
|
|
||||||
//00:XX20:8D F3 3F STA $3FF3 = #$00 ; set NMI_1 flag (the value of A is unimportant); since the rom boots here, this was needed for the initial NMI. but we also get it from having the reset signal set, so..
|
|
||||||
//00:XX23:18 CLC
|
|
||||||
//00:XX24:90 FE BCC $XX24 ;infinite loop to wait for first NMI
|
|
||||||
|
|
||||||
const ushort NMI_VECTOR = 0x3800;
|
const ushort NMI_VECTOR = 0x3800;
|
||||||
const ushort RESET_VECTOR = 0x3820;
|
const ushort RESET_VECTOR = 0x3820;
|
||||||
|
|
||||||
//for reasons unknown, this is a little extra long
|
//readable registers
|
||||||
byte[] NSFROM = new byte[0x30 + 6]
|
//3FF0 - InitPending (cleared on read)
|
||||||
|
//3FF1 - NextSong (also performs reset process - clears APU, RAM, etc)
|
||||||
|
//3FF2 - PAL flag
|
||||||
|
//3FF3 - PatchVectors=false
|
||||||
|
//3FF4 - PatchVectors=true
|
||||||
|
|
||||||
|
byte[] NSFROM = new byte[0x23]
|
||||||
{
|
{
|
||||||
//0x00 - NMI
|
//@NMIVector
|
||||||
0x8D,0xF4,0x3F, //Stop play routine NMIs.
|
//Suspend vector patching
|
||||||
0xA2,0xFF,0x9A, //Initialize the stack pointer.
|
//3800:LDA $3FF3
|
||||||
0xAD,0xF0,0x3F, //See if we need to init.
|
0xAD,0xF3,0x3F,
|
||||||
0xF0,0x09, //If 0, go to play routine playing.
|
|
||||||
|
//Initialize stack pointer
|
||||||
|
//3803:LDX #$FF
|
||||||
|
0xA2,0xFF,
|
||||||
|
//3805:TXS
|
||||||
|
0x9A,
|
||||||
|
|
||||||
0xAD,0xF1,0x3F, //Confirm and load A
|
//Check (and clear) InitPending flag
|
||||||
0xAE,0xF3,0x3F, //Load X with PAL/NTSC byte
|
//3806:LDA $3FF0
|
||||||
|
0xAD,0xF0,0x3F,
|
||||||
|
//3809:BEQ $8014
|
||||||
|
0xF0,0x09,
|
||||||
|
|
||||||
//0x11
|
//Read the next song (resetting the player) and PAL flag into A and X and then call the INIT routine
|
||||||
0x20,0x00,0x00, //JSR to init routine (WILL BE PATCHED)
|
//380B:LDA $3FF1
|
||||||
|
0xAD,0xF1,0x3F,
|
||||||
|
//380E:LDX $3FF2
|
||||||
|
0xAE,0xF2,0x3F,
|
||||||
|
//3811:JSR INIT
|
||||||
|
0x20,0x00,0x00,
|
||||||
|
|
||||||
|
//Fall through to:
|
||||||
|
//@Play - call PLAY routine with X and Y cleared (this is not supposed to be required, but fceux did it)
|
||||||
|
//3814:LDA #$00
|
||||||
0xA9,0x00,
|
0xA9,0x00,
|
||||||
|
//3816:TAX
|
||||||
0xAA,
|
0xAA,
|
||||||
|
//3817:TAY
|
||||||
0xA8,
|
0xA8,
|
||||||
|
//3818:JSR PLAY
|
||||||
|
0x20,0x00,0x00,
|
||||||
|
|
||||||
|
//Resume vector patching and infinite loop waiting for next NMI
|
||||||
|
//381B:LDA $3FF4
|
||||||
|
0xAD,0xF4,0x3F,
|
||||||
|
//381E:BCC $XX1E
|
||||||
|
0x90,0xFE,
|
||||||
|
|
||||||
//0x18
|
//@ResetVector - just set up an infinite loop waiting for the first NMI
|
||||||
0x20,0x00,0x00, //JSR to play routine (WILL BE PATCHED)
|
//3820:CLC
|
||||||
0x8D,0xF5,0x3F, //Start play routine NMIs.
|
|
||||||
0x90,0xFE, //Loopie time.
|
|
||||||
|
|
||||||
// 0x20
|
|
||||||
0x8D,0xF3,0x3F, //Init init NMIs
|
|
||||||
0x18,
|
0x18,
|
||||||
0x90,0xFE, //Loopie time.
|
//3821:BCC $XX24
|
||||||
|
0x90,0xFE,
|
||||||
//0x26
|
|
||||||
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
|
||||||
//0x30
|
|
||||||
0x00,0x00,0x00,0x00,0x00,0x00
|
|
||||||
};
|
};
|
||||||
|
|
||||||
public override void AtVsyncNMI()
|
public override void AtVsyncNMI()
|
||||||
|
@ -350,19 +369,22 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int bank_4k = addr >> 12;
|
|
||||||
int ofs = addr & ((1 << 12) - 1);
|
|
||||||
bank_4k = prg_banks_4k[bank_4k];
|
|
||||||
addr = (bank_4k << 12) | ofs;
|
|
||||||
|
|
||||||
if (BankSwitched)
|
if (BankSwitched)
|
||||||
{
|
{
|
||||||
|
int bank_4k = addr >> 12;
|
||||||
|
int ofs = addr & ((1 << 12) - 1);
|
||||||
|
bank_4k = prg_banks_4k[bank_4k];
|
||||||
|
addr = (bank_4k << 12) | ofs;
|
||||||
|
|
||||||
//rom data began at 0x80 of the NSF file
|
//rom data began at 0x80 of the NSF file
|
||||||
addr += 0x80;
|
addr += 0x80;
|
||||||
|
|
||||||
return ROM[addr];
|
return ROM[addr];
|
||||||
}
|
}
|
||||||
else return NES.DB;
|
else
|
||||||
|
{
|
||||||
|
return FakePRG[addr];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue