neshawk - rough sketch of NSF player. basically functional for some games

This commit is contained in:
zeromus 2015-03-11 09:46:27 +00:00
parent e485882aed
commit db9fbced86
9 changed files with 520 additions and 11 deletions

View File

@ -1855,9 +1855,9 @@ namespace BizHawk.Client.EmuHawk
{ {
ofd.Filter = FormatFilter( ofd.Filter = FormatFilter(
"Rom Files", "*.nes;*.fds;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.col;.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;%ARCH%", "Rom Files", "*.nes;*.fds;*.sms;*.gg;*.sg;*.pce;*.sgx;*.bin;*.smd;*.rom;*.a26;*.a78;*.lnx;*.m3u;*.cue;*.ccd;*.exe;*.gb;*.gbc;*.gba;*.gen;*.md;*.col;.int;*.smc;*.sfc;*.prg;*.d64;*.g64;*.crt;*.sgb;*.xml;*.z64;*.v64;*.n64;*.ws;*.wsc;%ARCH%",
"Music Files", "*.psf;*.sid", "Music Files", "*.psf;*.sid;*.nsf",
"Disc Images", "*.cue;*.ccd;*.m3u", "Disc Images", "*.cue;*.ccd;*.m3u",
"NES", "*.nes;*.fds;%ARCH%", "NES", "*.nes;*.fds;*.nsf;%ARCH%",
"Super NES", "*.smc;*.sfc;*.xml;%ARCH%", "Super NES", "*.smc;*.sfc;*.xml;%ARCH%",
"Master System", "*.sms;*.gg;*.sg;%ARCH%", "Master System", "*.sms;*.gg;*.sg;%ARCH%",
"PC Engine", "*.pce;*.sgx;*.cue;*.ccd;%ARCH%", "PC Engine", "*.pce;*.sgx;*.cue;*.ccd;%ARCH%",
@ -1885,7 +1885,7 @@ namespace BizHawk.Client.EmuHawk
ofd.Filter = FormatFilter( ofd.Filter = FormatFilter(
"Rom Files", "*.nes;*.fds;*.sms;*.gg;*.sg;*.gb;*.gbc;*.gba;*.pce;*.sgx;*.bin;*.smd;*.gen;*.md;*.smc;*.sfc;*.a26;*.a78;*.lnx;*.col;*.rom;*.cue;*.ccd;*.sgb;*.z64;*.v64;*.n64;*.ws;*.wsc;*.xml;%ARCH%", "Rom Files", "*.nes;*.fds;*.sms;*.gg;*.sg;*.gb;*.gbc;*.gba;*.pce;*.sgx;*.bin;*.smd;*.gen;*.md;*.smc;*.sfc;*.a26;*.a78;*.lnx;*.col;*.rom;*.cue;*.ccd;*.sgb;*.z64;*.v64;*.n64;*.ws;*.wsc;*.xml;%ARCH%",
"Disc Images", "*.cue;*.ccd;*.m3u", "Disc Images", "*.cue;*.ccd;*.m3u",
"NES", "*.nes;*.fds;%ARCH%", "NES", "*.nes;*.fds;*.nsf;%ARCH%",
"Super NES", "*.smc;*.sfc;*.xml;%ARCH%", "Super NES", "*.smc;*.sfc;*.xml;%ARCH%",
"Nintendo 64", "*.z64;*.v64;*.n64", "Nintendo 64", "*.z64;*.v64;*.n64",
"Gameboy", "*.gb;*.gbc;*.sgb;%ARCH%", "Gameboy", "*.gb;*.gbc;*.sgb;%ARCH%",

View File

@ -602,6 +602,7 @@
<Compile Include="Consoles\Nintendo\NES\Boards\NROM.cs"> <Compile Include="Consoles\Nintendo\NES\Boards\NROM.cs">
<SubType>Code</SubType> <SubType>Code</SubType>
</Compile> </Compile>
<Compile Include="Consoles\Nintendo\NES\Boards\NSFBoard.cs" />
<Compile Include="Consoles\Nintendo\NES\Boards\PxROM_FxROM.cs" /> <Compile Include="Consoles\Nintendo\NES\Boards\PxROM_FxROM.cs" />
<Compile Include="Consoles\Nintendo\NES\Boards\Sachen8259.cs" /> <Compile Include="Consoles\Nintendo\NES\Boards\Sachen8259.cs" />
<Compile Include="Consoles\Nintendo\NES\Boards\SachenSimple.cs" /> <Compile Include="Consoles\Nintendo\NES\Boards\SachenSimple.cs" />
@ -664,6 +665,7 @@
<DependentUpon>NES.cs</DependentUpon> <DependentUpon>NES.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Consoles\Nintendo\NES\NESControllers.cs" /> <Compile Include="Consoles\Nintendo\NES\NESControllers.cs" />
<Compile Include="Consoles\Nintendo\NES\NSFFormat.cs" />
<Compile Include="Consoles\Nintendo\NES\Palettes.cs" /> <Compile Include="Consoles\Nintendo\NES\Palettes.cs" />
<Compile Include="Consoles\Nintendo\NES\PPU.cs" /> <Compile Include="Consoles\Nintendo\NES\PPU.cs" />
<Compile Include="Consoles\Nintendo\NES\PPU.regs.cs" /> <Compile Include="Consoles\Nintendo\NES\PPU.regs.cs" />

View File

@ -0,0 +1,369 @@

using System;
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.
//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?
//some NSF players know when a song ends.
namespace BizHawk.Emulation.Cores.Nintendo.NES
{
[NES.INESBoardImplCancel]
public sealed class NSFBoard : NES.NESBoardBase
{
//configuration
internal NSFFormat nsf;
byte[] InitBankSwitches = new byte[8];
byte[] FakePRG = new byte[32768];
bool BankSwitched;
//------------------------------
//state
IntBuffer prg_banks_4k = new IntBuffer(8);
/// <summary>
/// whether vectors are currently patched. they should not be patched when running init/play routines because data from the ends of banks might get used
/// </summary>
bool Patch_Vectors;
int CurrentSong;
bool ResetSignal;
int ButtonState;
public override bool Configure(NES.EDetectionOrigin origin)
{
Cart.wram_size = 8;
return true;
}
public override void Dispose()
{
prg_banks_4k.Dispose();
base.Dispose();
}
public void InitNSF(NSFFormat nsf)
{
this.nsf = nsf;
//patch the NSF rom with the init and play addresses
NSFROM[0x12] = (byte)(nsf.InitAddress);
NSFROM[0x13] = (byte)(nsf.InitAddress >> 8);
NSFROM[0x19] = (byte)(nsf.PlayAddress);
NSFROM[0x1A] = (byte)(nsf.PlayAddress >> 8);
//complicated anlysis straight from FCEUX
//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;
for (int i = 0; i < 8; i++)
{
InitBankSwitches[i] = nsf.BankswitchInitValues[i];
if (InitBankSwitches[i] != 0)
BankSwitched = true;
}
if (!BankSwitched)
{
if ((nsf.LoadAddress & 0x7000) >= 0x7000)
{
//"Ice Climber, and other F000 base address tunes need this"
BankSwitched = true;
}
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();
CurrentSong = nsf.StartingSong;
}
void ReplayInit()
{
ResetSignal = true;
Patch_Vectors = true;
}
public override void NESSoftReset()
{
ReplayInit();
}
public override void SyncState(Serializer ser)
{
base.SyncState(ser);
}
public override void WriteEXP(int addr, byte value)
{
switch (addr)
{
case 0x1FF6:
case 0x1FF7:
//if (!(NSFHeader.SoundChip & 4)) return; //FDS
break;
case 0x1FF8:
case 0x1FF9:
case 0x1FFA:
case 0x1FFB:
case 0x1FFC:
case 0x1FFD:
case 0x1FFE:
case 0x1FFF:
if (!BankSwitched) break;
addr -= 0x1FF8;
prg_banks_4k[addr] = value;
break;
}
}
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)
{
if (addr < 0x3FF0)
return NSFROM[addr - 0x3800];
else return base.PeekReg2xxx(addr);
}
public override byte ReadReg2xxx(int addr)
{
if (addr < 0x3800)
return base.ReadReg2xxx(addr);
else if (addr >= 0x3FF0)
{
if (addr == 0x3FF0)
{
byte ret = 0;
if (ResetSignal) ret = 1;
ResetSignal = false;
return ret;
}
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 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 RESET_VECTOR = 0x3820;
//for reasons unknown, this is a little extra long
byte[] NSFROM = new byte[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
//0x11
0x20,0x00,0x00, //JSR to init routine (WILL BE PATCHED)
0xA9,0x00,
0xAA,
0xA8,
//0x18
0x20,0x00,0x00, //JSR to play routine (WILL BE PATCHED)
0x8D,0xF5,0x3F, //Start play routine NMIs.
0x90,0xFE, //Loopie time.
// 0x20
0x8D,0xF3,0x3F, //Init init NMIs
0x18,
0x90,0xFE, //Loopie time.
//0x26
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
//0x30
0x00,0x00,0x00,0x00,0x00,0x00
};
public override void AtVsyncNMI()
{
if(Patch_Vectors)
NES.cpu.NMI = true;
//strobe pad
NES.WriteMemory(0x4016, 1);
NES.WriteMemory(0x4016, 0);
//read pad and create rising edge button signals so we dont trigger events as quickly as we hold the button down
int currButtons = 0;
for (int i = 0; i < 8; i++)
{
currButtons <<= 1;
currButtons |= (NES.ReadMemory(0x4016) & 1);
}
int justDown = (~ButtonState) & currButtons;
Bit a = (justDown >> 7) & 1;
Bit b = (justDown >> 6) & 1;
Bit sel = (justDown >> 5) & 1;
Bit start = (justDown >> 4) & 1;
Bit up = (justDown >> 3) & 1;
Bit down = (justDown >> 2) & 1;
Bit left = (justDown >> 1) & 1;
Bit right = (justDown >> 0) & 1;
ButtonState = currButtons;
//RIGHT: next song
//LEFT: prev song
//A: restart song
bool reset = false;
if (right)
{
CurrentSong++;
reset = true;
}
if (left)
{
CurrentSong--;
reset = true;
}
if (a)
reset = true;
if (reset)
{
ReplayInit();
}
}
public override byte ReadPPU(int addr)
{
return 0;
}
public override byte ReadWRAM(int addr)
{
return base.ReadWRAM(addr);
}
public override byte ReadPRG(int addr)
{
//patch in vector reading
if (Patch_Vectors)
{
if (addr == 0x7FFA) return (byte)(NMI_VECTOR & 0xFF);
else if (addr == 0x7FFB) return (byte)((NMI_VECTOR >> 8) & 0xFF);
else if (addr == 0x7FFC) return (byte)(RESET_VECTOR & 0xFF);
else if (addr == 0x7FFD) { return (byte)((RESET_VECTOR >> 8) & 0xFF); }
return NES.DB;
}
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)
{
//rom data began at 0x80 of the NSF file
addr += 0x80;
return ROM[addr];
}
else return NES.DB;
}
}
}
}

View File

@ -35,11 +35,15 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
void AddressPPU(int addr); void AddressPPU(int addr);
byte ReadWRAM(int addr); byte ReadWRAM(int addr);
byte ReadEXP(int addr); byte ReadEXP(int addr);
byte ReadReg2xxx(int addr);
byte PeekReg2xxx(int addr);
void WritePRG(int addr, byte value); void WritePRG(int addr, byte value);
void WritePPU(int addr, byte value); void WritePPU(int addr, byte value);
void WriteWRAM(int addr, byte value); void WriteWRAM(int addr, byte value);
void WriteEXP(int addr, byte value); void WriteEXP(int addr, byte value);
void WriteReg2xxx(int addr, byte value);
void NESSoftReset(); void NESSoftReset();
void AtVsyncNMI();
byte[] SaveRam { get; } byte[] SaveRam { get; }
byte[] WRAM { get; set; } byte[] WRAM { get; set; }
byte[] VRAM { get; set; } byte[] VRAM { get; set; }
@ -83,6 +87,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
public abstract bool Configure(NES.EDetectionOrigin origin); public abstract bool Configure(NES.EDetectionOrigin origin);
public virtual void ClockPPU() { } public virtual void ClockPPU() { }
public virtual void ClockCPU() { } public virtual void ClockCPU() { }
public virtual void AtVsyncNMI() { }
public CartInfo Cart { get { return NES.cart; } } public CartInfo Cart { get { return NES.cart; } }
public NES NES { get; set; } public NES NES { get; set; }
@ -207,6 +212,21 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
return NES.DB; return NES.DB;
} }
public virtual byte ReadReg2xxx(int addr)
{
return NES.ppu.ReadReg(addr & 7);
}
public virtual byte PeekReg2xxx(int addr)
{
return NES.ppu.PeekReg(addr & 7);
}
public virtual void WriteReg2xxx(int addr, byte value)
{
NES.ppu.WriteReg(addr & 7, value);
}
public virtual void WritePPU(int addr, byte value) public virtual void WritePPU(int addr, byte value)
{ {
if (addr < 0x2000) if (addr < 0x2000)
@ -338,7 +358,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
void BoardSystemHardReset() void BoardSystemHardReset()
{ {
INESBoard newboard; INESBoard newboard;
// fds has a unique activation setup // FDS and NSF have a unique activation setup
if (Board is FDS) if (Board is FDS)
{ {
var newfds = new FDS(); var newfds = new FDS();
@ -347,6 +367,13 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
newfds.SetDiskImage(oldfds.GetDiskImage()); newfds.SetDiskImage(oldfds.GetDiskImage());
newboard = newfds; newboard = newfds;
} }
else if (Board is NSFBoard)
{
var newnsf = new NSFBoard();
var oldnsf = Board as NSFBoard;
newnsf.InitNSF(oldnsf.nsf);
newboard = newnsf;
}
else else
{ {
newboard = CreateBoardInstance(Board.GetType()); newboard = CreateBoardInstance(Board.GetType());

View File

@ -17,7 +17,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
int cpu_accumulate; //cpu timekeeper int cpu_accumulate; //cpu timekeeper
public PPU ppu; public PPU ppu;
public APU apu; public APU apu;
byte[] ram; public byte[] ram;
NESWatch[] sysbus_watch = new NESWatch[65536]; NESWatch[] sysbus_watch = new NESWatch[65536];
public byte[] CIRAM; //AKA nametables public byte[] CIRAM; //AKA nametables
string game_name = string.Empty; //friendly name exposed to user and used as filename base string game_name = string.Empty; //friendly name exposed to user and used as filename base
@ -505,7 +505,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
} }
else if (addr < 0x4000) else if (addr < 0x4000)
{ {
ret = ppu.PeekReg(addr & 7); ret = Board.PeekReg2xxx(addr);
} }
else if (addr < 0x4020) else if (addr < 0x4020)
{ {
@ -546,7 +546,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
} }
else if (addr < 0x4000) else if (addr < 0x4000)
{ {
ret = ppu.ReadReg(addr & 7); ret = Board.ReadReg2xxx(addr);
} }
else if (addr < 0x4020) else if (addr < 0x4020)
{ {
@ -605,7 +605,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
} }
else if (addr < 0x4000) else if (addr < 0x4000)
{ {
ppu.WriteReg(addr & 7, value); Board.WriteReg2xxx(addr,value);
} }
else if (addr < 0x4020) else if (addr < 0x4020)
{ {

View File

@ -346,7 +346,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
public enum EDetectionOrigin public enum EDetectionOrigin
{ {
None, BootGodDB, GameDB, INES, UNIF, FDS None, BootGodDB, GameDB, INES, UNIF, FDS, NSF
} }
StringWriter LoadReport; StringWriter LoadReport;
@ -406,6 +406,31 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
hash_sha1_several.Add(hash_sha1); hash_sha1_several.Add(hash_sha1);
LoadWriteLine("headerless rom hash: {0}", hash_sha1); LoadWriteLine("headerless rom hash: {0}", hash_sha1);
} }
else if(file.Take(5).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("NESM\x1A")))
{
origin = EDetectionOrigin.NSF;
LoadWriteLine("Loading as NSF");
var nsf = new NSFFormat();
nsf.WrapByteArray(file);
cart = new CartInfo();
var nsfboard = new NSFBoard();
nsfboard.Create(this);
nsfboard.ROM = rom;
nsfboard.InitNSF( nsf);
nsfboard.InitialRegisterValues = InitialMapperRegisterValues;
nsfboard.Configure(origin);
nsfboard.WRAM = new byte[cart.wram_size * 1024];
Board = nsfboard;
Board.PostConfigure();
Console.WriteLine("Using NTSC display type for NSF for now");
_display_type = Common.DisplayType.NTSC;
HardReset();
return;
}
else if (file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("FDS\x1A")) else if (file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("FDS\x1A"))
|| file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("\x01*NI"))) || file.Take(4).SequenceEqual(System.Text.Encoding.ASCII.GetBytes("\x01*NI")))
{ {

View File

@ -0,0 +1,84 @@
using System;
using System.Linq;
using System.IO;
using System.Collections.Generic;
using BizHawk.Common;
using BizHawk.Common.BufferExtensions;
using BizHawk.Common.IOExtensions;
using BizHawk.Emulation.Common;
namespace BizHawk.Emulation.Cores.Nintendo.NES
{
//http://kevtris.org/nes/nsfspec.txt
//http://en.wikipedia.org/wiki/NES_Sound_Format
public class NSFFormat
{
public byte[] NSFData;
public byte Version;
public byte TotalSongs;
/// <summary>
/// 1-indexed. 0 is an invalid value, I guess
/// </summary>
public byte StartingSong;
public ushort LoadAddress;
public ushort InitAddress;
public ushort PlayAddress;
public string SongName;
public string ArtistName;
public string CopyrightHolder;
public ushort SpeedNTSC;
public byte[] BankswitchInitValues = new byte[8];
public ushort SpeedPAL;
public bool IsNTSC;
public bool IsPAL;
[Flags]
public enum eExtraChips
{
None = 0, VRC6 = 1, VRC7 = 2, FDS = 4, MMC5 = 8, Namco106 = 16, FME7 = 32
}
public eExtraChips ExtraChips;
public void WrapByteArray(byte[] data)
{
NSFData = data;
var ms = new MemoryStream(data);
var br = new BinaryReader(ms);
br.BaseStream.Position += 5;
Version = br.ReadByte();
TotalSongs = br.ReadByte();
StartingSong = br.ReadByte();
LoadAddress = br.ReadUInt16();
InitAddress = br.ReadUInt16();
PlayAddress = br.ReadUInt16();
SongName = br.ReadStringFixedAscii(32);
ArtistName = br.ReadStringFixedAscii(32);
CopyrightHolder = br.ReadStringFixedAscii(32);
SpeedNTSC = br.ReadUInt16();
br.Read(BankswitchInitValues, 0, 8);
SpeedPAL = br.ReadUInt16();
byte temp = br.ReadByte();
if ((temp & 2) != 0) IsNTSC = IsPAL = true;
else if ((temp & 1) != 0) IsPAL = true; else IsNTSC = true;
ExtraChips = (eExtraChips)br.ReadByte();
}
}
}

View File

@ -124,6 +124,7 @@ namespace BizHawk.Emulation.Cores.Nintendo.NES
bool nmi_destiny = reg_2000.vblank_nmi_gen && Reg2002_vblank_active; bool nmi_destiny = reg_2000.vblank_nmi_gen && Reg2002_vblank_active;
runppu(3); runppu(3);
if (nmi_destiny) TriggerNMI(); if (nmi_destiny) TriggerNMI();
nes.Board.AtVsyncNMI();
runppu(postNMIlines * kLineTime - delay); runppu(postNMIlines * kLineTime - delay);
//this seems to run just before the dummy scanline begins //this seems to run just before the dummy scanline begins

View File

@ -32,7 +32,7 @@ namespace BizHawk.Emulation.Cores
Saturn, MegaCD, Saturn, MegaCD,
PCE, SGX, TurboCD, PCE, SGX, TurboCD,
INES, FDS, UNIF, INES, FDS, UNIF, NSF,
SFC, N64, SFC, N64,
GB, GBC, GBA, NDS, GB, GBC, GBA, NDS,
COL, COL,
@ -257,6 +257,7 @@ namespace BizHawk.Emulation.Cores
{ {
public static SimpleMagicRecord INES = new SimpleMagicRecord { Offset = 0, Key = "NES" }; public static SimpleMagicRecord INES = new SimpleMagicRecord { Offset = 0, Key = "NES" };
public static SimpleMagicRecord UNIF = new SimpleMagicRecord { Offset = 0, Key = "UNIF" }; public static SimpleMagicRecord UNIF = new SimpleMagicRecord { Offset = 0, Key = "UNIF" };
public static SimpleMagicRecord NSF = new SimpleMagicRecord { Offset = 0, Key = "NESM\x1A" };
public static SimpleMagicRecord FDS_HEADERLESS = new SimpleMagicRecord { Offset = 0, Key = "\x01*NINTENDO-HVC*" }; public static SimpleMagicRecord FDS_HEADERLESS = new SimpleMagicRecord { Offset = 0, Key = "\x01*NINTENDO-HVC*" };
public static SimpleMagicRecord FDS_HEADER = new SimpleMagicRecord { Offset = 0, Key = "FDS\x1A" }; public static SimpleMagicRecord FDS_HEADER = new SimpleMagicRecord { Offset = 0, Key = "FDS\x1A" };