neshawk!! emulate attractmode without bugs = initial checkin
This commit is contained in:
parent
303d276ac7
commit
c5febf8e20
|
@ -22,6 +22,7 @@
|
||||||
<ErrorReport>prompt</ErrorReport>
|
<ErrorReport>prompt</ErrorReport>
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
|
@ -52,7 +53,12 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Compile Include="Consoles\Calculator\TI83.cs" />
|
<Compile Include="Consoles\Calculator\TI83.cs" />
|
||||||
|
<Compile Include="Consoles\Nintendo\NES\Boards\NROM.cs" />
|
||||||
<Compile Include="Consoles\Nintendo\NES\NES.cs" />
|
<Compile Include="Consoles\Nintendo\NES\NES.cs" />
|
||||||
|
<Compile Include="Consoles\Nintendo\NES\Palettes.cs" />
|
||||||
|
<Compile Include="Consoles\Nintendo\NES\PPU.cs" />
|
||||||
|
<Compile Include="Consoles\Nintendo\NES\PPU.regs.cs" />
|
||||||
|
<Compile Include="Consoles\Nintendo\NES\PPU.run.cs" />
|
||||||
<Compile Include="Consoles\Sega\SMS\MemoryMap.CodeMasters.cs" />
|
<Compile Include="Consoles\Sega\SMS\MemoryMap.CodeMasters.cs" />
|
||||||
<Compile Include="Consoles\Sega\SMS\MemoryMap.Sega.cs" />
|
<Compile Include="Consoles\Sega\SMS\MemoryMap.Sega.cs" />
|
||||||
<Compile Include="Consoles\Sega\SMS\VDP.ModeTMS.cs" />
|
<Compile Include="Consoles\Sega\SMS\VDP.ModeTMS.cs" />
|
||||||
|
|
|
@ -17,7 +17,23 @@ namespace BizHawk.Emulation.CPUs.M6502
|
||||||
PendingCycles += cycles;
|
PendingCycles += cycles;
|
||||||
while (PendingCycles > 0)
|
while (PendingCycles > 0)
|
||||||
{
|
{
|
||||||
Console.WriteLine(State());
|
if (NMI)
|
||||||
|
{
|
||||||
|
WriteMemory((ushort)(S-- + 0x100), (byte)(PC >> 8));
|
||||||
|
WriteMemory((ushort)(S-- + 0x100), (byte)PC);
|
||||||
|
byte oldP = P;
|
||||||
|
FlagB = false;
|
||||||
|
FlagT = true;
|
||||||
|
WriteMemory((ushort)(S-- + 0x100), P);
|
||||||
|
P = oldP;
|
||||||
|
FlagI = true;
|
||||||
|
PC = ReadWord(NMIVector);
|
||||||
|
PendingCycles -= 7;
|
||||||
|
NMI = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(debug) Console.WriteLine(State());
|
||||||
|
ushort this_pc = PC;
|
||||||
byte opcode = ReadMemory(PC++);
|
byte opcode = ReadMemory(PC++);
|
||||||
switch (opcode)
|
switch (opcode)
|
||||||
{
|
{
|
||||||
|
@ -732,6 +748,10 @@ FlagT = true;// this seems wrong
|
||||||
PendingCycles -= 4; TotalExecutedCycles += 4;
|
PendingCycles -= 4; TotalExecutedCycles += 4;
|
||||||
break;
|
break;
|
||||||
case 0xAD: // LDA addr
|
case 0xAD: // LDA addr
|
||||||
|
if (this_pc == 0x800A)
|
||||||
|
{
|
||||||
|
int zzz = 9;
|
||||||
|
}
|
||||||
A = ReadMemory(ReadWord(PC)); PC += 2;
|
A = ReadMemory(ReadWord(PC)); PC += 2;
|
||||||
P = (byte)((P & 0x7D) | TableNZ[A]);
|
P = (byte)((P & 0x7D) | TableNZ[A]);
|
||||||
PendingCycles -= 4; TotalExecutedCycles += 4;
|
PendingCycles -= 4; TotalExecutedCycles += 4;
|
||||||
|
|
|
@ -23,6 +23,8 @@ namespace BizHawk.Emulation.CPUs.M6502
|
||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
public bool debug;
|
||||||
|
|
||||||
public void Reset()
|
public void Reset()
|
||||||
{
|
{
|
||||||
A = 0;
|
A = 0;
|
||||||
|
@ -43,7 +45,7 @@ namespace BizHawk.Emulation.CPUs.M6502
|
||||||
public string State()
|
public string State()
|
||||||
{
|
{
|
||||||
int notused;
|
int notused;
|
||||||
string a = string.Format("{0:X4} {1:X2} {2} ", PC, ReadMemory(PC), Disassemble(PC, out notused)).PadRight(41);
|
string a = string.Format("{0:X4} {1:X2} {2} ", PC, ReadMemory(PC), Disassemble(PC, out notused)).PadRight(30);
|
||||||
string b = string.Format("A:{0:X2} X:{1:X2} Y:{2:X2} P:{3:X2} SP:{4:X2} Cy:{5}", A, X, Y, P, S, TotalExecutedCycles);
|
string b = string.Format("A:{0:X2} X:{1:X2} Y:{2:X2} P:{3:X2} SP:{4:X2} Cy:{5}", A, X, Y, P, S, TotalExecutedCycles);
|
||||||
string val = a + b + " ";
|
string val = a + b + " ";
|
||||||
if (FlagN) val = val + "N";
|
if (FlagN) val = val + "N";
|
||||||
|
@ -70,6 +72,10 @@ namespace BizHawk.Emulation.CPUs.M6502
|
||||||
public bool Interrupt;
|
public bool Interrupt;
|
||||||
public bool NMI;//
|
public bool NMI;//
|
||||||
|
|
||||||
|
private const ushort NMIVector = 0xFFFA;
|
||||||
|
private const ushort ResetVector = 0xFFFC;
|
||||||
|
private const ushort BRKVector = 0xFFFE;
|
||||||
|
|
||||||
// ==== End State ====
|
// ==== End State ====
|
||||||
|
|
||||||
/// <summary>Carry Flag</summary>
|
/// <summary>Carry Flag</summary>
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Consoles.Nintendo.Boards
|
||||||
|
{
|
||||||
|
public class NROM : NES.NESBoardBase
|
||||||
|
{
|
||||||
|
public override byte ReadPRG(int addr)
|
||||||
|
{
|
||||||
|
return RomInfo.ROM[addr];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,16 +6,90 @@ using BizHawk.Emulation.CPUs.M6502;
|
||||||
|
|
||||||
namespace BizHawk.Emulation.Consoles.Nintendo
|
namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
{
|
{
|
||||||
public class NES : IEmulator
|
public partial class NES : IEmulator
|
||||||
{
|
{
|
||||||
|
public interface INESBoard
|
||||||
|
{
|
||||||
|
byte ReadPRG(int addr);
|
||||||
|
byte ReadPPU(int addr);
|
||||||
|
void WritePRG(int addr, byte value);
|
||||||
|
void WritePPU(int addr, byte value);
|
||||||
|
void Initialize(RomInfo romInfo, NES nes);
|
||||||
|
};
|
||||||
|
|
||||||
|
public abstract class NESBoardBase : INESBoard
|
||||||
|
{
|
||||||
|
public void Initialize(RomInfo romInfo, NES nes)
|
||||||
|
{
|
||||||
|
this.RomInfo = romInfo;
|
||||||
|
this.NES = nes;
|
||||||
|
switch (romInfo.Mirroring)
|
||||||
|
{
|
||||||
|
case 0: SetMirroring(0, 0, 1, 1); break;
|
||||||
|
case 1: SetMirroring(0, 1, 0, 1); break;
|
||||||
|
default: SetMirroring(-1, -1, -1, -1); break; //crash!
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public RomInfo RomInfo { get; set; }
|
||||||
|
public NES NES { get; set; }
|
||||||
|
|
||||||
|
int[] mirroring = new int[4];
|
||||||
|
protected void SetMirroring(int a, int b, int c, int d)
|
||||||
|
{
|
||||||
|
mirroring[0] = a;
|
||||||
|
mirroring[1] = b;
|
||||||
|
mirroring[2] = c;
|
||||||
|
mirroring[3] = d;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public virtual byte ReadPRG(int addr) { return RomInfo.ROM[addr];}
|
||||||
|
public virtual void WritePRG(int addr, byte value) { }
|
||||||
|
|
||||||
|
|
||||||
|
public virtual void WritePPU(int addr, byte value)
|
||||||
|
{
|
||||||
|
if (addr < 0x2000)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int block = (addr >> 10) & 3;
|
||||||
|
block = mirroring[block];
|
||||||
|
int ofs = addr & 0x3FF;
|
||||||
|
NES.ppu.NTARAM[(block << 10) | ofs] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual byte ReadPPU(int addr)
|
||||||
|
{
|
||||||
|
if (addr < 0x2000)
|
||||||
|
{
|
||||||
|
return RomInfo.VROM[addr];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int block = (addr >> 10)&3;
|
||||||
|
block = mirroring[block];
|
||||||
|
int ofs = addr & 0x3FF;
|
||||||
|
return NES.ppu.NTARAM[(block << 10) | ofs];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//hardware
|
//hardware
|
||||||
MOS6502 cpu = new MOS6502();
|
protected MOS6502 cpu;
|
||||||
byte[] rom;
|
INESBoard board;
|
||||||
|
PPU ppu;
|
||||||
|
RomInfo romInfo;
|
||||||
byte[] ram;
|
byte[] ram;
|
||||||
|
|
||||||
|
//user configuration
|
||||||
|
int[,] palette; //TBD!!
|
||||||
|
|
||||||
public byte ReadPPUReg(int addr)
|
public byte ReadPPUReg(int addr)
|
||||||
{
|
{
|
||||||
return 0xFF;
|
return ppu.ReadReg(addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte ReadReg(int addr)
|
public byte ReadReg(int addr)
|
||||||
|
@ -23,32 +97,66 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
return 0xFF;
|
return 0xFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void WritePPUReg(int addr, byte val)
|
||||||
|
{
|
||||||
|
ppu.WriteReg(addr,val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WriteReg(int addr, byte val)
|
||||||
|
{
|
||||||
|
//Console.WriteLine("wrote register: {0:x4} = {1:x2}", addr,val);
|
||||||
|
switch (addr)
|
||||||
|
{
|
||||||
|
case 0x4014: Exec_OAMDma(val); break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Exec_OAMDma(byte val)
|
||||||
|
{
|
||||||
|
ushort addr = (ushort)(val << 8);
|
||||||
|
for (int i = 0; i < 256; i++)
|
||||||
|
{
|
||||||
|
byte db = ReadMemory((ushort)addr);
|
||||||
|
if (i == 1 && db != 0)
|
||||||
|
{
|
||||||
|
int zzz = 9;
|
||||||
|
}
|
||||||
|
WriteMemory(0x2004, db);
|
||||||
|
addr++;
|
||||||
|
}
|
||||||
|
cpu.PendingCycles-=512;
|
||||||
|
}
|
||||||
|
|
||||||
public byte ReadMemory(ushort addr)
|
public byte ReadMemory(ushort addr)
|
||||||
{
|
{
|
||||||
if (addr < 0x0800) return ram[addr];
|
if (addr < 0x0800) return ram[addr];
|
||||||
if (addr < 0x1000) return ram[addr - 0x0800];
|
else if (addr < 0x1000) return ram[addr - 0x0800];
|
||||||
if (addr < 0x1800) return ram[addr - 0x1000];
|
else if (addr < 0x1800) return ram[addr - 0x1000];
|
||||||
if (addr < 0x2000) return ram[addr - 0x1800];
|
else if (addr < 0x2000) return ram[addr - 0x1800];
|
||||||
if (addr < 0x4000) return ReadPPUReg(addr & 7);
|
else if (addr < 0x4000) return ReadPPUReg(addr & 7);
|
||||||
if (addr < 0x4020) return ReadReg(addr - 0x4020);
|
else if (addr < 0x4020) return ReadReg(addr); //we're not rebasing the register just to keep register names canonical
|
||||||
if (addr < 0x6000) return 0xFF; //exp rom
|
else if (addr < 0x6000) return 0xFF; //exp rom
|
||||||
if (addr < 0x8000) return 0xFF; //sram
|
else if (addr < 0x8000) return 0xFF; //sram
|
||||||
return 0xFF; //got tired of doing this
|
else return board.ReadPRG(addr - 0x8000);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void WriteMemory(ushort addr, byte value)
|
public void WriteMemory(ushort addr, byte value)
|
||||||
{
|
{
|
||||||
if (addr < 0x0800) ram[addr] = value;
|
if (addr < 0x0800) ram[addr] = value;
|
||||||
if (addr < 0x1000) ram[addr - 0x0800] = value;
|
else if (addr < 0x1000) ram[addr - 0x0800] = value;
|
||||||
if (addr < 0x1800) ram[addr - 0x1000] = value;
|
else if (addr < 0x1800) ram[addr - 0x1000] = value;
|
||||||
if (addr < 0x2000) ram[addr - 0x1800] = value;
|
else if (addr < 0x2000) ram[addr - 0x1800] = value;
|
||||||
|
else if (addr < 0x4000) WritePPUReg(addr & 7,value);
|
||||||
|
else if (addr < 0x4020) WriteReg(addr, value); //we're not rebasing the register just to keep register names canonical
|
||||||
|
else if (addr < 0x6000) { } //exp rom
|
||||||
|
else if (addr < 0x8000) { } //sram
|
||||||
|
else board.WritePRG(addr - 0x8000, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public NES()
|
public NES()
|
||||||
{
|
{
|
||||||
cpu.ReadMemory = ReadMemory;
|
palette = Palettes.FCEUX_Standard;
|
||||||
cpu.WriteMemory = WriteMemory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class MyVideoProvider : IVideoProvider
|
class MyVideoProvider : IVideoProvider
|
||||||
|
@ -61,15 +169,19 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
|
|
||||||
public int[] GetVideoBuffer()
|
public int[] GetVideoBuffer()
|
||||||
{
|
{
|
||||||
int testval = 0;
|
|
||||||
if (emu.Controller.IsPressed("DOWN")) testval = 0xFF;
|
|
||||||
|
|
||||||
int[] pixels = new int[256 * 256];
|
int[] pixels = new int[256 * 256];
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (int y = 0; y < 256; y++)
|
for (int y = 0; y < 256; y++)
|
||||||
for (int x = 0; x < 256; x++)
|
for (int x = 0; x < 256; x++)
|
||||||
{
|
{
|
||||||
pixels[i++] = testval;
|
int pixel = emu.ppu.xbuf[i];
|
||||||
|
int deemph = pixel >> 8;
|
||||||
|
int palentry = pixel & 0xFF;
|
||||||
|
int r = emu.palette[pixel, 0];
|
||||||
|
int g = emu.palette[pixel, 1];
|
||||||
|
int b = emu.palette[pixel, 2];
|
||||||
|
pixels[i] = (r<<16)|(g<<8)|b;
|
||||||
|
i++;
|
||||||
}
|
}
|
||||||
return pixels;
|
return pixels;
|
||||||
}
|
}
|
||||||
|
@ -98,28 +210,36 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
set { controller = value; }
|
set { controller = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void LoadGame(IGame game)
|
|
||||||
{
|
|
||||||
rom = game.GetRomData();
|
|
||||||
|
|
||||||
//parse iNes and UNIF!
|
|
||||||
//setup banks and stuff! oh crap what a pain! lets start with non-mapper games and add mappers later.
|
|
||||||
|
|
||||||
HardReset();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void FrameAdvance(bool render)
|
public void FrameAdvance(bool render)
|
||||||
{
|
{
|
||||||
//TODO!
|
//TODO!
|
||||||
//cpu.Execute(10000);
|
//cpu.Execute(10000);
|
||||||
|
ppu.FrameAdvance();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void RunCpu(int cycles)
|
||||||
|
{
|
||||||
|
cpu.Execute(cycles);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HardReset()
|
public void HardReset()
|
||||||
{
|
{
|
||||||
cpu.Reset();
|
cpu = new MOS6502();
|
||||||
|
cpu.ReadMemory = ReadMemory;
|
||||||
|
cpu.WriteMemory = WriteMemory;
|
||||||
|
ppu = new PPU(this);
|
||||||
ram = new byte[0x800];
|
ram = new byte[0x800];
|
||||||
|
|
||||||
|
//fceux uses this technique, which presumably tricks some games into thinking the memory is randomized
|
||||||
for (int i = 0; i < 0x800; i++)
|
for (int i = 0; i < 0x800; i++)
|
||||||
ram[i] = 0xFF;
|
{
|
||||||
|
if ((i & 4) != 0) ram[i] = 0xFF; else ram[i] = 0x00;
|
||||||
|
}
|
||||||
|
|
||||||
|
//in this emulator, reset takes place instantaneously
|
||||||
|
cpu.PC = (ushort)(ReadMemory(0xFFFC) | (ReadMemory(0xFFFD) << 8));
|
||||||
|
|
||||||
|
//cpu.debug = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Frame
|
public int Frame
|
||||||
|
@ -157,8 +277,16 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
}
|
}
|
||||||
|
|
||||||
public string SystemId { get { return "NES"; } }
|
public string SystemId { get { return "NES"; } }
|
||||||
public IList<MemoryDomain> MemoryDomains { get { throw new NotImplementedException(); } }
|
public IList<MemoryDomain> MemoryDomains { get { return new List<MemoryDomain>(); } }
|
||||||
public MemoryDomain MainMemory { get { throw new NotImplementedException(); } }
|
public MemoryDomain MainMemory
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return new MemoryDomain("x", 8, Endian.Little,
|
||||||
|
addr => 0,
|
||||||
|
(addr, value) => { });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public object Query(EmulatorQuery query)
|
public object Query(EmulatorQuery query)
|
||||||
|
@ -170,5 +298,106 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
{
|
{
|
||||||
return "|........|........|0|"; //TODO: implement
|
return "|........|........|0|"; //TODO: implement
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class RomInfo
|
||||||
|
{
|
||||||
|
public int MapperNo, Mirroring, Num_PRG_Banks, Num_CHR_Banks;
|
||||||
|
public byte[] ROM, VROM;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe struct iNES_HEADER {
|
||||||
|
public fixed byte ID[4]; /*NES^Z*/
|
||||||
|
public byte ROM_size;
|
||||||
|
public byte VROM_size;
|
||||||
|
public byte ROM_type;
|
||||||
|
public byte ROM_type2;
|
||||||
|
public fixed byte reserve[8];
|
||||||
|
|
||||||
|
public bool CheckID()
|
||||||
|
{
|
||||||
|
fixed (iNES_HEADER* self = &this)
|
||||||
|
return 0==Util.memcmp(self, "NES\x1A", 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
//some cleanup code recommended by fceux
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
fixed (iNES_HEADER* self = &this)
|
||||||
|
{
|
||||||
|
if (0==Util.memcmp((char*)(self) + 0x7, "DiskDude", 8))
|
||||||
|
{
|
||||||
|
Util.memset((char*)(self) + 0x7, 0, 0x9);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == Util.memcmp((char*)(self) + 0x7, "demiforce", 9))
|
||||||
|
{
|
||||||
|
Util.memset((char*)(self) + 0x7, 0, 0x9);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (0 == Util.memcmp((char*)(self) + 0xA, "Ni03", 4))
|
||||||
|
{
|
||||||
|
if (0 == Util.memcmp((char*)(self) + 0x7, "Dis", 3))
|
||||||
|
Util.memset((char*)(self) + 0x7, 0, 0x9);
|
||||||
|
else
|
||||||
|
Util.memset((char*)(self) + 0xA, 0, 0x6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public RomInfo Analyze()
|
||||||
|
{
|
||||||
|
var ret = new RomInfo();
|
||||||
|
ret.MapperNo = (ROM_type>>4);
|
||||||
|
ret.MapperNo|=(ROM_type2&0xF0);
|
||||||
|
ret.Mirroring = (ROM_type&1);
|
||||||
|
if((ROM_type&8)!=0) ret.Mirroring=2;
|
||||||
|
ret.Num_PRG_Banks = ROM_size;
|
||||||
|
if (ret.Num_PRG_Banks == 0)
|
||||||
|
ret.Num_PRG_Banks = 256;
|
||||||
|
ret.Num_CHR_Banks = VROM_size;
|
||||||
|
|
||||||
|
//fceux calls uppow2(PRG_Banks) here, and also ups the chr size as well
|
||||||
|
//then it does something complicated that i don't understand with making sure it doesnt read too much data
|
||||||
|
//fceux only allows this condition for mappers in the list "not_power2" which is only 228
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
INESBoard Classify(RomInfo info)
|
||||||
|
{
|
||||||
|
//you may think that this should be table driven.. but im not so sure.
|
||||||
|
//i think this should be a backstop eventually, with other classification happening from the game database.
|
||||||
|
//if the gamedatabase has an exact answer for a game then the board can be determined..
|
||||||
|
//otherwise we might try to find a general case handler below.
|
||||||
|
|
||||||
|
if (info.MapperNo == 0 && info.Num_CHR_Banks == 1 && info.Num_PRG_Banks == 2 && info.Mirroring == 1) return new Boards.NROM();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe void LoadGame(IGame game)
|
||||||
|
{
|
||||||
|
byte[] file = game.GetRomData();
|
||||||
|
if (file.Length < 16) throw new InvalidOperationException("Alleged NES rom too small to be anything useful");
|
||||||
|
fixed (byte* bfile = &file[0])
|
||||||
|
{
|
||||||
|
var header = (iNES_HEADER*)bfile;
|
||||||
|
if (!header->CheckID()) throw new InvalidOperationException("iNES header not found");
|
||||||
|
header->Cleanup();
|
||||||
|
|
||||||
|
romInfo = header->Analyze();
|
||||||
|
board = Classify(romInfo);
|
||||||
|
if (board == null) throw new InvalidOperationException("Couldn't classify NES rom");
|
||||||
|
board.Initialize(romInfo, this);
|
||||||
|
|
||||||
|
//we're going to go ahead and copy these out, just in case we need to pad them alter
|
||||||
|
romInfo.ROM = new byte[romInfo.Num_PRG_Banks * 16 * 1024];
|
||||||
|
romInfo.VROM = new byte[romInfo.Num_CHR_Banks * 8 * 1024];
|
||||||
|
Array.Copy(file, 16, romInfo.ROM, 0, romInfo.ROM.Length);
|
||||||
|
Array.Copy(file, 16 + romInfo.ROM.Length, romInfo.VROM, 0, romInfo.VROM.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
HardReset();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,74 @@
|
||||||
|
//http://nesdev.parodius.com/bbs/viewtopic.php?p=4571&sid=db4c7e35316cc5d734606dd02f11dccb
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using BizHawk.Emulation.CPUs.M6502;
|
||||||
|
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
|
{
|
||||||
|
partial class NES
|
||||||
|
{
|
||||||
|
partial class PPU
|
||||||
|
{
|
||||||
|
void ppubus_write(int addr, byte value)
|
||||||
|
{
|
||||||
|
nes.board.WritePPU(addr, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte ppubus_read(int addr)
|
||||||
|
{
|
||||||
|
return nes.board.ReadPPU(addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum PPUPHASE {
|
||||||
|
VBL, BG, OBJ
|
||||||
|
};
|
||||||
|
PPUPHASE ppuphase;
|
||||||
|
|
||||||
|
NES nes;
|
||||||
|
public PPU(NES nes)
|
||||||
|
{
|
||||||
|
this.nes = nes;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ppudead; //measured in frames
|
||||||
|
bool idleSynch;
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
regs_reset();
|
||||||
|
ppudead = 2;
|
||||||
|
idleSynch = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TriggerNMI()
|
||||||
|
{
|
||||||
|
nes.cpu.NMI = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void runppu(int x)
|
||||||
|
{
|
||||||
|
//pputime+=x;
|
||||||
|
//if(cputodo<200) return;
|
||||||
|
|
||||||
|
//DON'T LIKE THIS....
|
||||||
|
ppur.status.cycle = (ppur.status.cycle + x) %
|
||||||
|
ppur.status.end_cycle;
|
||||||
|
|
||||||
|
nes.RunCpu(x);
|
||||||
|
//pputime -= cputodo<<2;
|
||||||
|
}
|
||||||
|
|
||||||
|
//hack
|
||||||
|
bool PAL = false;
|
||||||
|
bool SPRITELIMIT = true;
|
||||||
|
const int MAXSPRITES = 8;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,493 @@
|
||||||
|
//blargg: Reading from $2007 when the VRAM address is $3fxx will fill the internal read buffer with the contents at VRAM address $3fxx, in addition to reading the palette RAM.
|
||||||
|
|
||||||
|
//static const byte powerUpPalette[] =
|
||||||
|
//{
|
||||||
|
// 0x3F,0x01,0x00,0x01, 0x00,0x02,0x02,0x0D, 0x08,0x10,0x08,0x24, 0x00,0x00,0x04,0x2C,
|
||||||
|
// 0x09,0x01,0x34,0x03, 0x00,0x04,0x00,0x14, 0x08,0x3A,0x00,0x02, 0x00,0x20,0x2C,0x08
|
||||||
|
//};
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using BizHawk.Emulation.CPUs.M6502;
|
||||||
|
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
|
{
|
||||||
|
partial class NES
|
||||||
|
{
|
||||||
|
partial class PPU
|
||||||
|
{
|
||||||
|
class Reg_2001
|
||||||
|
{
|
||||||
|
public Bit color_disable; //Color disable (0: normal color; 1: AND all palette entries with 110000, effectively producing a monochrome display)
|
||||||
|
public Bit show_bg_leftmost; //Show leftmost 8 pixels of background
|
||||||
|
public Bit show_obj_leftmost; //Show sprites in leftmost 8 pixels
|
||||||
|
public Bit show_bg; //Show background
|
||||||
|
public Bit show_obj; //Show sprites
|
||||||
|
public Bit intense_green; //Intensify greens (and darken other colors)
|
||||||
|
public Bit intense_blue; //Intensify blues (and darken other colors)
|
||||||
|
public Bit intense_red; //Intensify reds (and darken other colors)
|
||||||
|
|
||||||
|
public bool PPUON { get { return show_bg || show_obj; } }
|
||||||
|
|
||||||
|
public byte Value
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return (byte)(color_disable | (show_bg_leftmost << 1) | (show_obj_leftmost << 2) | (show_bg << 3) | (show_obj << 4) | (intense_green << 5) | (intense_blue << 6) | (intense_red << 7));
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
color_disable = (value & 1);
|
||||||
|
show_bg_leftmost = (value >> 1) & 1;
|
||||||
|
show_obj_leftmost = (value >> 2) & 1;
|
||||||
|
show_bg = (value >> 3) & 1;
|
||||||
|
show_obj = (value >> 4) & 1;
|
||||||
|
intense_green = (value >> 5) & 1;
|
||||||
|
intense_blue = (value >> 6) & 1;
|
||||||
|
intense_red = (value >> 7) & 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct PPUSTATUS
|
||||||
|
{
|
||||||
|
public int sl;
|
||||||
|
public int cycle, end_cycle;
|
||||||
|
}
|
||||||
|
|
||||||
|
//uses the internal counters concept at http://nesdev.icequake.net/PPU%20addressing.txt
|
||||||
|
//TODO - this should be turned into a state machine
|
||||||
|
class PPUREGS
|
||||||
|
{
|
||||||
|
PPU ppu;
|
||||||
|
public PPUREGS(PPU ppu)
|
||||||
|
{
|
||||||
|
this.ppu = ppu;
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
//normal clocked regs. as the game can interfere with these at any time, they need to be savestated
|
||||||
|
public int fv;//3
|
||||||
|
public int v;//1
|
||||||
|
public int h;//1
|
||||||
|
public int vt;//5
|
||||||
|
public int ht;//5
|
||||||
|
|
||||||
|
//temp unlatched regs (need savestating, can be written to at any time)
|
||||||
|
public int _fv, _vt, _v, _h, _ht;
|
||||||
|
|
||||||
|
//other regs that need savestating
|
||||||
|
public int fh;//3 (horz scroll)
|
||||||
|
|
||||||
|
//other regs that don't need saving
|
||||||
|
public int par;//8 (sort of a hack, just stored in here, but not managed by this system)
|
||||||
|
|
||||||
|
//cached state data. these are always reset at the beginning of a frame and don't need saving
|
||||||
|
//but just to be safe, we're gonna save it
|
||||||
|
public PPUSTATUS status = new PPUSTATUS();
|
||||||
|
|
||||||
|
public void reset()
|
||||||
|
{
|
||||||
|
fv = v = h = vt = ht = 0;
|
||||||
|
fh = par = 0;
|
||||||
|
_fv = _v = _h = _vt = _ht = 0;
|
||||||
|
status.cycle = 0;
|
||||||
|
status.end_cycle = 341;
|
||||||
|
status.sl = 241;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void install_latches()
|
||||||
|
{
|
||||||
|
fv = _fv;
|
||||||
|
v = _v;
|
||||||
|
h = _h;
|
||||||
|
vt = _vt;
|
||||||
|
ht = _ht;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void install_h_latches()
|
||||||
|
{
|
||||||
|
ht = _ht;
|
||||||
|
h = _h;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear_latches()
|
||||||
|
{
|
||||||
|
_fv = _v = _h = _vt = _ht = 0;
|
||||||
|
fh = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void increment_hsc()
|
||||||
|
{
|
||||||
|
//The first one, the horizontal scroll counter, consists of 6 bits, and is
|
||||||
|
//made up by daisy-chaining the HT counter to the H counter. The HT counter is
|
||||||
|
//then clocked every 8 pixel dot clocks (or every 8/3 CPU clock cycles).
|
||||||
|
ht++;
|
||||||
|
h += (ht >> 5);
|
||||||
|
ht &= 31;
|
||||||
|
h &= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void increment_vs()
|
||||||
|
{
|
||||||
|
fv++;
|
||||||
|
vt += (fv >> 3);
|
||||||
|
vt &= 31; //fixed tecmo super bowl
|
||||||
|
v += (vt == 30) ? 1 : 0;
|
||||||
|
fv &= 7;
|
||||||
|
if (vt == 30) vt = 0;
|
||||||
|
v &= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get_ntread()
|
||||||
|
{
|
||||||
|
return 0x2000 | (v << 0xB) | (h << 0xA) | (vt << 5) | ht;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int get_2007access()
|
||||||
|
{
|
||||||
|
return ((fv & 3) << 0xC) | (v << 0xB) | (h << 0xA) | (vt << 5) | ht;
|
||||||
|
}
|
||||||
|
|
||||||
|
//The PPU has an internal 4-position, 2-bit shifter, which it uses for
|
||||||
|
//obtaining the 2-bit palette select data during an attribute table byte
|
||||||
|
//fetch. To represent how this data is shifted in the diagram, letters a..c
|
||||||
|
//are used in the diagram to represent the right-shift position amount to
|
||||||
|
//apply to the data read from the attribute data (a is always 0). This is why
|
||||||
|
//you only see bits 0 and 1 used off the read attribute data in the diagram.
|
||||||
|
public int get_atread()
|
||||||
|
{
|
||||||
|
return 0x2000 | (v << 0xB) | (h << 0xA) | 0x3C0 | ((vt & 0x1C) << 1) | ((ht & 0x1C) >> 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
//address line 3 relates to the pattern table fetch occuring (the PPU always makes them in pairs).
|
||||||
|
public int get_ptread()
|
||||||
|
{
|
||||||
|
int s = ppu.reg_2000.bg_pattern_hi;
|
||||||
|
return (s << 0xC) | (par << 0x4) | fv;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void increment2007(bool by32)
|
||||||
|
{
|
||||||
|
|
||||||
|
//If the VRAM address increment bit (2000.2) is clear (inc. amt. = 1), all the
|
||||||
|
//scroll counters are daisy-chained (in the order of HT, VT, H, V, FV) so that
|
||||||
|
//the carry out of each counter controls the next counter's clock rate. The
|
||||||
|
//result is that all 5 counters function as a single 15-bit one. Any access to
|
||||||
|
//2007 clocks the HT counter here.
|
||||||
|
//
|
||||||
|
//If the VRAM address increment bit is set (inc. amt. = 32), the only
|
||||||
|
//difference is that the HT counter is no longer being clocked, and the VT
|
||||||
|
//counter is now being clocked by access to 2007.
|
||||||
|
if (by32)
|
||||||
|
{
|
||||||
|
vt++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ht++;
|
||||||
|
vt += (ht >> 5) & 1;
|
||||||
|
}
|
||||||
|
h += (vt >> 5);
|
||||||
|
v += (h >> 1);
|
||||||
|
fv += (v >> 1);
|
||||||
|
ht &= 31;
|
||||||
|
vt &= 31;
|
||||||
|
h &= 1;
|
||||||
|
v &= 1;
|
||||||
|
fv &= 7;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class Reg_2000
|
||||||
|
{
|
||||||
|
PPU ppu;
|
||||||
|
public Reg_2000(PPU ppu)
|
||||||
|
{
|
||||||
|
this.ppu = ppu;
|
||||||
|
}
|
||||||
|
//these bits go straight into PPUR
|
||||||
|
//(00 = $2000; 01 = $2400; 02 = $2800; 03 = $2c00)
|
||||||
|
|
||||||
|
public Bit vram_incr32; //(0: increment by 1, going across; 1: increment by 32, going down)
|
||||||
|
public Bit obj_pattern_hi; //Sprite pattern table address for 8x8 sprites (0: $0000; 1: $1000)
|
||||||
|
public Bit bg_pattern_hi; //Background pattern table address (0: $0000; 1: $1000)
|
||||||
|
public Bit obj_size_16; //Sprite size (0: 8x8 sprites; 1: 8x16 sprites)
|
||||||
|
public Bit ppu_layer; //PPU layer select (should always be 0 in the NES; some Nintendo arcade boards presumably had two PPUs)
|
||||||
|
public Bit vblank_nmi_gen; //Vertical blank NMI generation (0: off; 1: on)
|
||||||
|
|
||||||
|
public byte Value
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
ppu.ppur._h = value & 1;
|
||||||
|
ppu.ppur._v = (value >> 1) & 1;
|
||||||
|
vram_incr32 = (value >> 2) & 1;
|
||||||
|
obj_pattern_hi = (value >> 3) & 1;
|
||||||
|
bg_pattern_hi = (value >> 4) & 1;
|
||||||
|
obj_size_16 = (value >> 5) & 1;
|
||||||
|
ppu_layer = (value >> 6) & 1;
|
||||||
|
vblank_nmi_gen = (value >> 7) & 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Bit Reg2002_objoverflow; //Sprite overflow. The PPU can handle only eight sprites on one scanline and sets this bit if it starts drawing sprites.
|
||||||
|
Bit Reg2002_objhit; //Sprite 0 overlap. Set when a nonzero pixel of sprite 0 is drawn overlapping a nonzero background pixel. Used for raster timing.
|
||||||
|
Bit Reg2002_vblank_active; //Vertical blank start (0: has not started; 1: has started)
|
||||||
|
byte PPUGenLatch;
|
||||||
|
PPUREGS ppur;
|
||||||
|
Reg_2000 reg_2000;
|
||||||
|
Reg_2001 reg_2001;
|
||||||
|
byte reg_2003;
|
||||||
|
byte[] OAM;
|
||||||
|
byte[] PALRAM;
|
||||||
|
public byte[] NTARAM;
|
||||||
|
bool vtoggle;
|
||||||
|
byte VRAMBuffer;
|
||||||
|
void regs_reset()
|
||||||
|
{
|
||||||
|
//TODO - would like to reconstitute the entire PPU instead of all this..
|
||||||
|
reg_2000 = new Reg_2000(this);
|
||||||
|
reg_2001 = new Reg_2001();
|
||||||
|
ppur = new PPUREGS(this);
|
||||||
|
Reg2002_objoverflow = false;
|
||||||
|
Reg2002_objhit = false;
|
||||||
|
Reg2002_vblank_active = false;
|
||||||
|
PPUGenLatch = 0;
|
||||||
|
reg_2003 = 0;
|
||||||
|
OAM = new byte[0x100];
|
||||||
|
PALRAM = new byte[0x20];
|
||||||
|
NTARAM = new byte[0x800];
|
||||||
|
vtoggle = false;
|
||||||
|
VRAMBuffer = 0;
|
||||||
|
}
|
||||||
|
//---------------------
|
||||||
|
|
||||||
|
//PPU CONTROL (write)
|
||||||
|
void write_2000(byte value)
|
||||||
|
{
|
||||||
|
if (!reg_2000.vblank_nmi_gen & ((value & 0x80) != 0) && (Reg2002_vblank_active))
|
||||||
|
{
|
||||||
|
//if we just unleashed the vblank interrupt then activate it now
|
||||||
|
//TriggerNMI2();
|
||||||
|
Debug.Assert(false);
|
||||||
|
}
|
||||||
|
reg_2000.Value = value;
|
||||||
|
}
|
||||||
|
byte read_2000() { return PPUGenLatch; }
|
||||||
|
|
||||||
|
//PPU MASK (write)
|
||||||
|
void write_2001(byte value)
|
||||||
|
{
|
||||||
|
//printf("%04x:$%02x, %d\n",A,V,scanline);
|
||||||
|
reg_2001.Value = value;
|
||||||
|
}
|
||||||
|
byte read_2001() { return PPUGenLatch; }
|
||||||
|
|
||||||
|
//PPU STATUS (read)
|
||||||
|
void write_2002(byte value) { }
|
||||||
|
byte read_2002()
|
||||||
|
{
|
||||||
|
//once we thought we clear latches here, but that caused midframe glitches.
|
||||||
|
//i think we should only reset the state machine for 2005/2006
|
||||||
|
//ppur.clear_latches();
|
||||||
|
|
||||||
|
vtoggle = false;
|
||||||
|
int ret = (Reg2002_vblank_active << 7) | (Reg2002_objhit << 6) | (Reg2002_objoverflow << 5) | (PPUGenLatch & 0x1F);
|
||||||
|
|
||||||
|
Reg2002_vblank_active = 0;
|
||||||
|
|
||||||
|
return (byte)ret;
|
||||||
|
}
|
||||||
|
void clear_2002()
|
||||||
|
{
|
||||||
|
Reg2002_vblank_active = Reg2002_objhit = Reg2002_objoverflow = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//OAM ADDRESS (write)
|
||||||
|
void write_2003(byte value)
|
||||||
|
{
|
||||||
|
//just record the oam buffer write target
|
||||||
|
reg_2003 = value;
|
||||||
|
}
|
||||||
|
byte read_2003() { return PPUGenLatch; }
|
||||||
|
|
||||||
|
//OAM DATA (write)
|
||||||
|
void write_2004(byte value)
|
||||||
|
{
|
||||||
|
if ((reg_2003 & 3) == 2) value &= 0xE3; //some of the OAM bits are unwired so we mask them out here
|
||||||
|
//otherwise we just write this value and move on to the next oam byte
|
||||||
|
OAM[reg_2003] = value;
|
||||||
|
reg_2003++;
|
||||||
|
}
|
||||||
|
byte read_2004() { return 0xFF; /* TODO !!!!!! THIS IS UGLY. WE SHOULD PASTE IT IN OR REWRITE IT BUT WE NEED TO ASK QEED FOR TEST CASES*/ }
|
||||||
|
|
||||||
|
//SCROLL (write)
|
||||||
|
void write_2005(byte value)
|
||||||
|
{
|
||||||
|
if (!vtoggle)
|
||||||
|
{
|
||||||
|
ppur._ht= value >> 3;
|
||||||
|
ppur.fh = value & 7;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ppur._vt = value >> 3;
|
||||||
|
ppur._fv = value & 7;
|
||||||
|
}
|
||||||
|
vtoggle ^= true;
|
||||||
|
}
|
||||||
|
byte read_2005() { return PPUGenLatch; }
|
||||||
|
|
||||||
|
//VRAM address register (write)
|
||||||
|
void write_2006(byte value)
|
||||||
|
{
|
||||||
|
if (!vtoggle)
|
||||||
|
{
|
||||||
|
ppur._vt &= 0x07;
|
||||||
|
ppur._vt |= (value & 0x3) << 3;
|
||||||
|
ppur._h = (value >> 2) & 1;
|
||||||
|
ppur._v = (value >> 3) & 1;
|
||||||
|
ppur._fv = (value >> 4) & 3;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ppur._vt &= 0x18;
|
||||||
|
ppur._vt |= (value >> 5);
|
||||||
|
ppur._ht = value & 31;
|
||||||
|
ppur.install_latches();
|
||||||
|
}
|
||||||
|
vtoggle ^= true;
|
||||||
|
}
|
||||||
|
byte read_2006() { return PPUGenLatch; }
|
||||||
|
|
||||||
|
//VRAM data register (r/w)
|
||||||
|
void write_2007(byte value)
|
||||||
|
{
|
||||||
|
//does this take 4x longer? nestopia indicates so perhaps...
|
||||||
|
|
||||||
|
int addr = ppur.get_2007access() & 0x3FFF;
|
||||||
|
if ((addr & 0x3F00) == 0x3F00)
|
||||||
|
{
|
||||||
|
//handle palette. this is being done nestopia style, because i found some documentation for it (appendix 1)
|
||||||
|
addr &= 0x1F;
|
||||||
|
byte color = (byte)(value & 0x3F); //are these bits really unwired? can they be read back somehow?
|
||||||
|
|
||||||
|
PALRAM[addr] = color;
|
||||||
|
if ((addr & 3) == 0)
|
||||||
|
{
|
||||||
|
PALRAM[addr ^ 0x10] = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ppubus_write(addr, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ppur.increment2007(reg_2000.vram_incr32 != 0);
|
||||||
|
}
|
||||||
|
byte read_2007()
|
||||||
|
{
|
||||||
|
int addr = ppur.get_2007access() & 0x3FFF;
|
||||||
|
|
||||||
|
//ordinarily we return the buffered values
|
||||||
|
byte ret = VRAMBuffer;
|
||||||
|
|
||||||
|
//in any case, we read from the ppu bus
|
||||||
|
VRAMBuffer = ppubus_read(addr);
|
||||||
|
|
||||||
|
//but reads from the palette are implemented in the PPU and return immediately
|
||||||
|
if ((addr & 0x3F00) == 0x3F00)
|
||||||
|
{
|
||||||
|
//TODO apply greyscale shit?
|
||||||
|
ret = PALRAM[addr & 0x1F];
|
||||||
|
}
|
||||||
|
|
||||||
|
ppur.increment2007(reg_2000.vram_incr32 != 0);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
//--------
|
||||||
|
|
||||||
|
public byte ReadReg(int addr)
|
||||||
|
{
|
||||||
|
switch (addr)
|
||||||
|
{
|
||||||
|
case 0: return read_2000(); case 1: return read_2001(); case 2: return read_2002(); case 3: return read_2003();
|
||||||
|
case 4: return read_2004(); case 5: return read_2005(); case 6: return read_2006(); case 7: return read_2007();
|
||||||
|
default: throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void WriteReg(int addr, byte value)
|
||||||
|
{
|
||||||
|
PPUGenLatch = value;
|
||||||
|
switch (addr)
|
||||||
|
{
|
||||||
|
case 0: write_2000(value); break; case 1: write_2001(value); break; case 2: write_2002(value); break; case 3: write_2003(value); break;
|
||||||
|
case 4: write_2004(value); break; case 5: write_2005(value); break; case 6: write_2006(value); break; case 7: write_2007(value); break;
|
||||||
|
default: throw new InvalidOperationException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//ARead[x]=A200x;
|
||||||
|
//BWrite[x]=B2000;
|
||||||
|
//ARead[x+1]=A200x;
|
||||||
|
//BWrite[x+1]=B2001;
|
||||||
|
//ARead[x+2]=A2002;
|
||||||
|
//BWrite[x+2]=B2002;
|
||||||
|
//ARead[x+3]=A200x;
|
||||||
|
//BWrite[x+3]=B2003;
|
||||||
|
//ARead[x+4]=A2004; //A2004;
|
||||||
|
//BWrite[x+4]=B2004;
|
||||||
|
//ARead[x+5]=A200x;
|
||||||
|
//BWrite[x+5]=B2005;
|
||||||
|
//ARead[x+6]=A200x;
|
||||||
|
//BWrite[x+6]=B2006;
|
||||||
|
//ARead[x+7]=A2007;
|
||||||
|
//BWrite[x+7]=B2007;
|
||||||
|
|
||||||
|
|
||||||
|
//Address Size Description
|
||||||
|
//$0000 $1000 Pattern Table 0
|
||||||
|
//$1000 $1000 Pattern Table 1
|
||||||
|
//$2000 $3C0 Name Table 0
|
||||||
|
//$23C0 $40 Attribute Table 0
|
||||||
|
//$2400 $3C0 Name Table 1
|
||||||
|
//$27C0 $40 Attribute Table 1
|
||||||
|
//$2800 $3C0 Name Table 2
|
||||||
|
//$2BC0 $40 Attribute Table 2
|
||||||
|
//$2C00 $3C0 Name Table 3
|
||||||
|
//$2FC0 $40 Attribute Table 3
|
||||||
|
//$3000 $F00 Mirror of 2000h-2EFFh
|
||||||
|
//$3F00 $10 BG Palette
|
||||||
|
//$3F10 $10 Sprite Palette
|
||||||
|
//$3F20 $E0 Mirror of 3F00h-3F1Fh
|
||||||
|
|
||||||
|
|
||||||
|
//appendix 1
|
||||||
|
//http://nocash.emubase.de/everynes.htm#ppupalettes
|
||||||
|
//Palette Memory (25 entries used)
|
||||||
|
// 3F00h Background Color (Color 0)
|
||||||
|
// 3F01h-3F03h Background Palette 0 (Color 1-3)
|
||||||
|
// 3F05h-3F07h Background Palette 1 (Color 1-3)
|
||||||
|
// 3F09h-3F0Bh Background Palette 2 (Color 1-3)
|
||||||
|
// 3F0Dh-3F0Fh Background Palette 3 (Color 1-3)
|
||||||
|
// 3F11h-3F13h Sprite Palette 0 (Color 1-3)
|
||||||
|
// 3F15h-3F17h Sprite Palette 1 (Color 1-3)
|
||||||
|
// 3F19h-3F1Bh Sprite Palette 2 (Color 1-3)
|
||||||
|
// 3F1Dh-3F1Fh Sprite Palette 3 (Color 1-3)
|
||||||
|
//Palette Gaps and Mirrors
|
||||||
|
// 3F04h,3F08h,3F0Ch - Three general purpose 6bit data registers.
|
||||||
|
// 3F10h,3F14h,3F18h,3F1Ch - Mirrors of 3F00h,3F04h,3F08h,3F0Ch.
|
||||||
|
// 3F20h-3FFFh - Mirrors of 3F00h-3F1Fh.
|
|
@ -0,0 +1,451 @@
|
||||||
|
//http://nesdev.parodius.com/bbs/viewtopic.php?p=4571&sid=db4c7e35316cc5d734606dd02f11dccb
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using BizHawk.Emulation.CPUs.M6502;
|
||||||
|
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
|
{
|
||||||
|
partial class NES
|
||||||
|
{
|
||||||
|
partial class PPU
|
||||||
|
{
|
||||||
|
const int kFetchTime = 2;
|
||||||
|
|
||||||
|
struct BGDataRecord {
|
||||||
|
public byte nt, at;
|
||||||
|
public byte pt_0, pt_1;
|
||||||
|
};
|
||||||
|
|
||||||
|
public int[] xbuf = new int[256*256];
|
||||||
|
|
||||||
|
void Read_bgdata(ref BGDataRecord bgdata) {
|
||||||
|
int addr = ppur.get_ntread();
|
||||||
|
if (addr == 0x2043)
|
||||||
|
{
|
||||||
|
int zzz = 9;
|
||||||
|
}
|
||||||
|
bgdata.nt = ppubus_read(addr);
|
||||||
|
runppu(kFetchTime);
|
||||||
|
|
||||||
|
addr = ppur.get_atread();
|
||||||
|
byte at = ppubus_read(addr);
|
||||||
|
|
||||||
|
//modify at to get appropriate palette shift
|
||||||
|
if((ppur.vt&2)!=0) at >>= 4;
|
||||||
|
if((ppur.ht&2)!=0) at >>= 2;
|
||||||
|
at &= 0x03;
|
||||||
|
at <<= 2;
|
||||||
|
|
||||||
|
bgdata.at = at;
|
||||||
|
|
||||||
|
//horizontal scroll clocked at cycle 3 and then
|
||||||
|
//vertical scroll at 251
|
||||||
|
runppu(1);
|
||||||
|
if (reg_2001.PPUON)
|
||||||
|
{
|
||||||
|
ppur.increment_hsc();
|
||||||
|
if (ppur.status.cycle == 251)
|
||||||
|
ppur.increment_vs();
|
||||||
|
}
|
||||||
|
runppu(1);
|
||||||
|
|
||||||
|
ppur.par = bgdata.nt;
|
||||||
|
addr = ppur.get_ptread();
|
||||||
|
bgdata.pt_0 = ppubus_read(addr);
|
||||||
|
runppu(kFetchTime);
|
||||||
|
addr |= 8;
|
||||||
|
bgdata.pt_1 = ppubus_read(addr);
|
||||||
|
runppu(kFetchTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe struct TempOAM
|
||||||
|
{
|
||||||
|
public fixed byte oam[4];
|
||||||
|
public fixed byte patterns[2];
|
||||||
|
public byte index;
|
||||||
|
byte pad;
|
||||||
|
}
|
||||||
|
|
||||||
|
int PaletteAdjustPixel(int pixel)
|
||||||
|
{
|
||||||
|
//tack on the deemph bits
|
||||||
|
pixel |= (reg_2001.intense_red<<8)|(reg_2001.intense_green<<9)|(reg_2001.intense_blue<<10);
|
||||||
|
return pixel;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int kLineTime = 341;
|
||||||
|
public unsafe void FrameAdvance()
|
||||||
|
{
|
||||||
|
BGDataRecord[] bgdata = new BGDataRecord[34]; //one at the end is junk, it can never be rendered
|
||||||
|
|
||||||
|
//262 scanlines
|
||||||
|
if (ppudead != 0)
|
||||||
|
{
|
||||||
|
FrameAdvance_ppudead();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reg2002_vblank_active = 1;
|
||||||
|
ppuphase = PPUPHASE.VBL;
|
||||||
|
|
||||||
|
//Not sure if this is correct. According to Matt Conte and my own tests, it is.
|
||||||
|
//Timing is probably off, though.
|
||||||
|
//NOTE: Not having this here breaks a Super Donkey Kong game.
|
||||||
|
reg_2003 = 0;
|
||||||
|
const int delay = 20; //fceu used 12 here but I couldnt get it to work in marble madness and pirates.
|
||||||
|
|
||||||
|
runppu(delay); //X6502_Run(12);
|
||||||
|
if (reg_2000.vblank_nmi_gen) TriggerNMI();
|
||||||
|
if (PAL)
|
||||||
|
runppu(70 * (kLineTime) - delay);
|
||||||
|
else
|
||||||
|
runppu(20 * (kLineTime) - delay);
|
||||||
|
|
||||||
|
//this seems to run just before the dummy scanline begins
|
||||||
|
clear_2002();
|
||||||
|
//this early out caused metroid to fail to boot. I am leaving it here as a reminder of what not to do
|
||||||
|
//if(!PPUON) { runppu(kLineTime*242); goto finish; }
|
||||||
|
|
||||||
|
//There are 2 conditions that update all 5 PPU scroll counters with the
|
||||||
|
//contents of the latches adjacent to them. The first is after a write to
|
||||||
|
//2006/2. The second, is at the beginning of scanline 20, when the PPU starts
|
||||||
|
//rendering data for the first time in a frame (this update won't happen if
|
||||||
|
//all rendering is disabled via 2001.3 and 2001.4).
|
||||||
|
|
||||||
|
//if(PPUON)
|
||||||
|
// ppur.install_latches();
|
||||||
|
|
||||||
|
TempOAM[,] oams = new TempOAM[2,64]; //[7] turned to [8] for faster indexing
|
||||||
|
int[] oamcounts = new int[2];
|
||||||
|
int oamslot=0;
|
||||||
|
int oamcount=0;
|
||||||
|
|
||||||
|
//capture the initial xscroll
|
||||||
|
//int xscroll = ppur.fh;
|
||||||
|
//render 241 scanlines (including 1 dummy at beginning)
|
||||||
|
for (int sl = 0; sl < 241; sl++)
|
||||||
|
{
|
||||||
|
ppur.status.sl = sl;
|
||||||
|
|
||||||
|
int yp = sl - 1;
|
||||||
|
ppuphase = PPUPHASE.BG;
|
||||||
|
|
||||||
|
//twiddle the oam buffers
|
||||||
|
int scanslot = oamslot ^ 1;
|
||||||
|
int renderslot = oamslot;
|
||||||
|
oamslot ^= 1;
|
||||||
|
|
||||||
|
oamcount = oamcounts[renderslot];
|
||||||
|
|
||||||
|
//the main scanline rendering loop:
|
||||||
|
//32 times, we will fetch a tile and then render 8 pixels.
|
||||||
|
//two of those tiles were read in the last scanline.
|
||||||
|
for (int xt = 0; xt < 32; xt++)
|
||||||
|
{
|
||||||
|
Read_bgdata(ref bgdata[xt + 2]);
|
||||||
|
|
||||||
|
//ok, we're also going to draw here.
|
||||||
|
//unless we're on the first dummy scanline
|
||||||
|
if (sl != 0)
|
||||||
|
{
|
||||||
|
int xstart = xt << 3;
|
||||||
|
oamcount = oamcounts[renderslot];
|
||||||
|
int target = (yp << 8) + xstart;
|
||||||
|
int rasterpos = xstart;
|
||||||
|
|
||||||
|
//check all the conditions that can cause things to render in these 8px
|
||||||
|
bool renderspritenow = reg_2001.show_obj && (xt > 0 || reg_2001.show_obj_leftmost);
|
||||||
|
bool renderbgnow = reg_2001.show_bg && (xt > 0 || reg_2001.show_bg_leftmost);
|
||||||
|
|
||||||
|
for (int xp = 0; xp < 8; xp++, rasterpos++)
|
||||||
|
{
|
||||||
|
|
||||||
|
//bg pos is different from raster pos due to its offsetability.
|
||||||
|
//so adjust for that here
|
||||||
|
int bgpos = rasterpos + ppur.fh;
|
||||||
|
int bgpx = bgpos & 7;
|
||||||
|
int bgtile = bgpos >> 3;
|
||||||
|
|
||||||
|
int pixel = 0, pixelcolor;
|
||||||
|
|
||||||
|
//generate the BG data
|
||||||
|
if (renderbgnow)
|
||||||
|
{
|
||||||
|
byte pt_0 = bgdata[bgtile].pt_0;
|
||||||
|
byte pt_1 = bgdata[bgtile].pt_1;
|
||||||
|
pixel = ((pt_0 >> (7 - bgpx)) & 1) | (((pt_1 >> (7 - bgpx)) & 1) << 1);
|
||||||
|
if(pixel != 0)
|
||||||
|
pixel |= bgdata[bgtile].at;
|
||||||
|
}
|
||||||
|
pixelcolor = PALRAM[pixel];
|
||||||
|
|
||||||
|
//look for a sprite to be drawn
|
||||||
|
bool havepixel = false;
|
||||||
|
for (int s = 0; s < oamcount; s++)
|
||||||
|
{
|
||||||
|
fixed (TempOAM* oam = &oams[renderslot, s])
|
||||||
|
{
|
||||||
|
int x = oam->oam[3];
|
||||||
|
if (rasterpos >= x && rasterpos < x + 8)
|
||||||
|
{
|
||||||
|
//build the pixel.
|
||||||
|
//fetch the LSB of the patterns
|
||||||
|
int spixel = oam->patterns[0] & 1;
|
||||||
|
spixel |= (oam->patterns[1] & 1) << 1;
|
||||||
|
|
||||||
|
//shift down the patterns so the next pixel is in the LSB
|
||||||
|
oam->patterns[0] >>= 1;
|
||||||
|
oam->patterns[1] >>= 1;
|
||||||
|
|
||||||
|
if (!renderspritenow) continue;
|
||||||
|
|
||||||
|
//bail out if we already have a pixel from a higher priority sprite
|
||||||
|
if (havepixel) continue;
|
||||||
|
|
||||||
|
//transparent pixel bailout
|
||||||
|
if (spixel == 0) continue;
|
||||||
|
|
||||||
|
//spritehit:
|
||||||
|
//1. is it sprite#0?
|
||||||
|
//2. is the bg pixel nonzero?
|
||||||
|
//then, it is spritehit.
|
||||||
|
if (oam->index == 0 && (pixel & 3) != 0 && rasterpos < 255)
|
||||||
|
{
|
||||||
|
Reg2002_objhit = true;
|
||||||
|
}
|
||||||
|
havepixel = true;
|
||||||
|
|
||||||
|
|
||||||
|
//priority handling
|
||||||
|
if ((oam->oam[2] & 0x20) != 0)
|
||||||
|
{
|
||||||
|
//behind background:
|
||||||
|
if ((pixel & 3) != 0) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//bring in the palette bits and palettize
|
||||||
|
spixel |= (oam->oam[2] & 3) << 2;
|
||||||
|
pixelcolor = PALRAM[0x10 + spixel];
|
||||||
|
} //rasterpos in sprite range
|
||||||
|
|
||||||
|
} //c# fixed oam ptr
|
||||||
|
|
||||||
|
}//oamcount loop
|
||||||
|
|
||||||
|
xbuf[target] = PaletteAdjustPixel(pixelcolor);
|
||||||
|
target++;
|
||||||
|
|
||||||
|
} //loop across 8 pixels
|
||||||
|
} //scanline != 0
|
||||||
|
} //loop across 32 tiles
|
||||||
|
|
||||||
|
|
||||||
|
//look for sprites (was supposed to run concurrent with bg rendering)
|
||||||
|
oamcounts[scanslot] = 0;
|
||||||
|
oamcount = 0;
|
||||||
|
if (sl == 0xb1)
|
||||||
|
{
|
||||||
|
int zzz = 9;
|
||||||
|
}
|
||||||
|
int spriteHeight = reg_2000.obj_size_16 ? 16 : 8;
|
||||||
|
for (int i = 0; i < 64; i++)
|
||||||
|
{
|
||||||
|
int spr = i * 4;
|
||||||
|
if (yp >= OAM[spr] && yp < OAM[spr] + spriteHeight)
|
||||||
|
{
|
||||||
|
//if we already have maxsprites, then this new one causes an overflow,
|
||||||
|
//set the flag and bail out.
|
||||||
|
if (oamcount >= 8 && reg_2001.PPUON)
|
||||||
|
{
|
||||||
|
Reg2002_objoverflow = true;
|
||||||
|
if (SPRITELIMIT)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
//just copy some bytes into the internal sprite buffer
|
||||||
|
for (int j = 0; j < 4; j++)
|
||||||
|
fixed (TempOAM* oam = &oams[scanslot, oamcount])
|
||||||
|
oam->oam[j] = OAM[spr + j];
|
||||||
|
|
||||||
|
//note that we stuff the oam index into [6].
|
||||||
|
//i need to turn this into a struct so we can have fewer magic numbers
|
||||||
|
oams[scanslot, oamcount].index = (byte)i;
|
||||||
|
oamcount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
oamcounts[scanslot] = oamcount;
|
||||||
|
|
||||||
|
//FV is clocked by the PPU's horizontal blanking impulse, and therefore will increment every scanline.
|
||||||
|
//well, according to (which?) tests, maybe at the end of hblank.
|
||||||
|
//but, according to what it took to get crystalis working, it is at the beginning of hblank.
|
||||||
|
|
||||||
|
//this is done at cycle 251
|
||||||
|
//rendering scanline, it doesn't need to be scanline 0,
|
||||||
|
//because on the first scanline when the increment is 0, the vs_scroll is reloaded.
|
||||||
|
//if(PPUON && sl != 0)
|
||||||
|
// ppur.increment_vs();
|
||||||
|
|
||||||
|
//todo - think about clearing oams to a predefined value to force deterministic behavior
|
||||||
|
|
||||||
|
//so.. this is the end of hblank. latch horizontal scroll values
|
||||||
|
//do it cycle at 251
|
||||||
|
if (reg_2001.PPUON && sl != 0)
|
||||||
|
ppur.install_h_latches();
|
||||||
|
|
||||||
|
ppuphase = PPUPHASE.OBJ;
|
||||||
|
|
||||||
|
//fetch sprite patterns
|
||||||
|
for (int s = 0; s < MAXSPRITES; s++)
|
||||||
|
{
|
||||||
|
if (sl == 0x9E && s == 1)
|
||||||
|
{
|
||||||
|
int zzz = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
//if we have hit our eight sprite pattern and we dont have any more sprites, then bail
|
||||||
|
if (s == oamcount && s >= 8)
|
||||||
|
break;
|
||||||
|
|
||||||
|
//if this is a real sprite sprite, then it is not above the 8 sprite limit.
|
||||||
|
//this is how we support the no 8 sprite limit feature.
|
||||||
|
//not that at some point we may need a virtual CALL_PPUREAD which just peeks and doesnt increment any counters
|
||||||
|
//this could be handy for the debugging tools also
|
||||||
|
bool realSprite = (s < 8);
|
||||||
|
|
||||||
|
fixed (TempOAM* oam = &oams[scanslot, s])
|
||||||
|
{
|
||||||
|
int line = yp - oam->oam[0];
|
||||||
|
if ((oam->oam[2] & 0x80) != 0) //vflip
|
||||||
|
line = spriteHeight - line - 1;
|
||||||
|
|
||||||
|
int patternNumber = oam->oam[1];
|
||||||
|
int patternAddress;
|
||||||
|
|
||||||
|
//8x16 sprite handling:
|
||||||
|
if (reg_2000.obj_size_16)
|
||||||
|
{
|
||||||
|
int bank = (patternNumber & 1) << 12;
|
||||||
|
patternNumber = patternNumber & ~1;
|
||||||
|
patternNumber |= (line >> 3);
|
||||||
|
patternAddress = (patternNumber << 4) | bank;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
patternAddress = (patternNumber << 4) | (reg_2000.obj_pattern_hi << 9);
|
||||||
|
}
|
||||||
|
|
||||||
|
//offset into the pattern for the current line.
|
||||||
|
//tricky: tall sprites have already had lines>8 taken care of by getting a new pattern number above.
|
||||||
|
//so we just need the line offset for the second pattern
|
||||||
|
patternAddress += line & 7;
|
||||||
|
|
||||||
|
//garbage nametable fetches
|
||||||
|
//reset the scroll counter, happens at cycle 304
|
||||||
|
if (realSprite)
|
||||||
|
{
|
||||||
|
if ((sl == 0) && reg_2001.PPUON)
|
||||||
|
{
|
||||||
|
if (ppur.status.cycle == 304)
|
||||||
|
{
|
||||||
|
runppu(1);
|
||||||
|
ppur.install_latches();
|
||||||
|
runppu(1);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
runppu(kFetchTime);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
runppu(kFetchTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
//..etc.. hacks about dragon's lair, MMC3, crystalis and SMB3. this should be implemented through the board
|
||||||
|
|
||||||
|
if (realSprite) runppu(kFetchTime);
|
||||||
|
|
||||||
|
//pattern table fetches
|
||||||
|
int addr = patternAddress;
|
||||||
|
oam->patterns[0] = ppubus_read(addr);
|
||||||
|
if (realSprite) runppu(kFetchTime);
|
||||||
|
|
||||||
|
addr += 8;
|
||||||
|
oam->patterns[1] = ppubus_read(addr);
|
||||||
|
if (realSprite) runppu(kFetchTime);
|
||||||
|
|
||||||
|
//hflip
|
||||||
|
if ((oam->oam[2] & 0x40) == 0)
|
||||||
|
{
|
||||||
|
oam->patterns[0] = BITREV.byte_8[oam->patterns[0]];
|
||||||
|
oam->patterns[1] = BITREV.byte_8[oam->patterns[1]];
|
||||||
|
}
|
||||||
|
} //c# fixed oam
|
||||||
|
|
||||||
|
} //sprite pattern fetch loop
|
||||||
|
|
||||||
|
ppuphase = PPUPHASE.BG;
|
||||||
|
|
||||||
|
//fetch BG: two tiles for next line
|
||||||
|
for (int xt = 0; xt < 2; xt++)
|
||||||
|
Read_bgdata(ref bgdata[xt]);
|
||||||
|
|
||||||
|
//I'm unclear of the reason why this particular access to memory is made.
|
||||||
|
//The nametable address that is accessed 2 times in a row here, is also the
|
||||||
|
//same nametable address that points to the 3rd tile to be rendered on the
|
||||||
|
//screen (or basically, the first nametable address that will be accessed when
|
||||||
|
//the PPU is fetching background data on the next scanline).
|
||||||
|
//(not implemented yet)
|
||||||
|
runppu(kFetchTime);
|
||||||
|
if (sl == 0)
|
||||||
|
{
|
||||||
|
if (idleSynch && reg_2001.PPUON && !PAL)
|
||||||
|
ppur.status.end_cycle = 340;
|
||||||
|
else
|
||||||
|
ppur.status.end_cycle = 341;
|
||||||
|
idleSynch ^= true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
ppur.status.end_cycle = 341;
|
||||||
|
runppu(kFetchTime);
|
||||||
|
|
||||||
|
//After memory access 170, the PPU simply rests for 4 cycles (or the
|
||||||
|
//equivelant of half a memory access cycle) before repeating the whole
|
||||||
|
//pixel/scanline rendering process. If the scanline being rendered is the very
|
||||||
|
//first one on every second frame, then this delay simply doesn't exist.
|
||||||
|
if (ppur.status.end_cycle == 341)
|
||||||
|
runppu(1);
|
||||||
|
|
||||||
|
} //scanline loop
|
||||||
|
|
||||||
|
//hacks...
|
||||||
|
//if (MMC5Hack && PPUON) MMC5_hb(240);
|
||||||
|
|
||||||
|
//idle for one line
|
||||||
|
runppu(kLineTime);
|
||||||
|
|
||||||
|
} //FrameAdvance
|
||||||
|
|
||||||
|
|
||||||
|
void FrameAdvance_ppudead()
|
||||||
|
{
|
||||||
|
//not quite emulating all the NES power up behavior
|
||||||
|
//since it is known that the NES ignores writes to some
|
||||||
|
//register before around a full frame, but no games
|
||||||
|
//should write to those regs during that time, it needs
|
||||||
|
//to wait for vblank
|
||||||
|
ppur.status.sl = 241;
|
||||||
|
if (PAL)
|
||||||
|
runppu(70 * kLineTime);
|
||||||
|
else
|
||||||
|
runppu(20 * kLineTime);
|
||||||
|
ppur.status.sl = 0;
|
||||||
|
runppu(242 * kLineTime);
|
||||||
|
--ppudead;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using BizHawk.Emulation.CPUs.M6502;
|
||||||
|
|
||||||
|
|
||||||
|
namespace BizHawk.Emulation.Consoles.Nintendo
|
||||||
|
{
|
||||||
|
partial class NES
|
||||||
|
{
|
||||||
|
static class Palettes
|
||||||
|
{
|
||||||
|
public static int[,] FCEUX_Standard = new int[,]
|
||||||
|
{
|
||||||
|
{ 0x1D<<2, 0x1D<<2, 0x1D<<2 }, /* Value 0 */
|
||||||
|
{ 0x09<<2, 0x06<<2, 0x23<<2 }, /* Value 1 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x2A<<2 }, /* Value 2 */
|
||||||
|
{ 0x11<<2, 0x00<<2, 0x27<<2 }, /* Value 3 */
|
||||||
|
{ 0x23<<2, 0x00<<2, 0x1D<<2 }, /* Value 4 */
|
||||||
|
{ 0x2A<<2, 0x00<<2, 0x04<<2 }, /* Value 5 */
|
||||||
|
{ 0x29<<2, 0x00<<2, 0x00<<2 }, /* Value 6 */
|
||||||
|
{ 0x1F<<2, 0x02<<2, 0x00<<2 }, /* Value 7 */
|
||||||
|
{ 0x10<<2, 0x0B<<2, 0x00<<2 }, /* Value 8 */
|
||||||
|
{ 0x00<<2, 0x11<<2, 0x00<<2 }, /* Value 9 */
|
||||||
|
{ 0x00<<2, 0x14<<2, 0x00<<2 }, /* Value 10 */
|
||||||
|
{ 0x00<<2, 0x0F<<2, 0x05<<2 }, /* Value 11 */
|
||||||
|
{ 0x06<<2, 0x0F<<2, 0x17<<2 }, /* Value 12 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x00<<2 }, /* Value 13 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x00<<2 }, /* Value 14 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x00<<2 }, /* Value 15 */
|
||||||
|
{ 0x2F<<2, 0x2F<<2, 0x2F<<2 }, /* Value 16 */
|
||||||
|
{ 0x00<<2, 0x1C<<2, 0x3B<<2 }, /* Value 17 */
|
||||||
|
{ 0x08<<2, 0x0E<<2, 0x3B<<2 }, /* Value 18 */
|
||||||
|
{ 0x20<<2, 0x00<<2, 0x3C<<2 }, /* Value 19 */
|
||||||
|
{ 0x2F<<2, 0x00<<2, 0x2F<<2 }, /* Value 20 */
|
||||||
|
{ 0x39<<2, 0x00<<2, 0x16<<2 }, /* Value 21 */
|
||||||
|
{ 0x36<<2, 0x0A<<2, 0x00<<2 }, /* Value 22 */
|
||||||
|
{ 0x32<<2, 0x13<<2, 0x03<<2 }, /* Value 23 */
|
||||||
|
{ 0x22<<2, 0x1C<<2, 0x00<<2 }, /* Value 24 */
|
||||||
|
{ 0x00<<2, 0x25<<2, 0x00<<2 }, /* Value 25 */
|
||||||
|
{ 0x00<<2, 0x2A<<2, 0x00<<2 }, /* Value 26 */
|
||||||
|
{ 0x00<<2, 0x24<<2, 0x0E<<2 }, /* Value 27 */
|
||||||
|
{ 0x00<<2, 0x20<<2, 0x22<<2 }, /* Value 28 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x00<<2 }, /* Value 29 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x00<<2 }, /* Value 30 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x00<<2 }, /* Value 31 */
|
||||||
|
{ 0x3F<<2, 0x3F<<2, 0x3F<<2 }, /* Value 32 */
|
||||||
|
{ 0x0F<<2, 0x2F<<2, 0x3F<<2 }, /* Value 33 */
|
||||||
|
{ 0x17<<2, 0x25<<2, 0x3F<<2 }, /* Value 34 */
|
||||||
|
{ 0x10<<2, 0x22<<2, 0x3F<<2 }, /* Value 35 */
|
||||||
|
{ 0x3D<<2, 0x1E<<2, 0x3F<<2 }, /* Value 36 */
|
||||||
|
{ 0x3F<<2, 0x1D<<2, 0x2D<<2 }, /* Value 37 */
|
||||||
|
{ 0x3F<<2, 0x1D<<2, 0x18<<2 }, /* Value 38 */
|
||||||
|
{ 0x3F<<2, 0x26<<2, 0x0E<<2 }, /* Value 39 */
|
||||||
|
{ 0x3C<<2, 0x2F<<2, 0x0F<<2 }, /* Value 40 */
|
||||||
|
{ 0x20<<2, 0x34<<2, 0x04<<2 }, /* Value 41 */
|
||||||
|
{ 0x13<<2, 0x37<<2, 0x12<<2 }, /* Value 42 */
|
||||||
|
{ 0x16<<2, 0x3E<<2, 0x26<<2 }, /* Value 43 */
|
||||||
|
{ 0x00<<2, 0x3A<<2, 0x36<<2 }, /* Value 44 */
|
||||||
|
{ 0x1E<<2, 0x1E<<2, 0x1E<<2 }, /* Value 45 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x00<<2 }, /* Value 46 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x00<<2 }, /* Value 47 */
|
||||||
|
{ 0x3F<<2, 0x3F<<2, 0x3F<<2 }, /* Value 48 */
|
||||||
|
{ 0x2A<<2, 0x39<<2, 0x3F<<2 }, /* Value 49 */
|
||||||
|
{ 0x31<<2, 0x35<<2, 0x3F<<2 }, /* Value 50 */
|
||||||
|
{ 0x35<<2, 0x32<<2, 0x3F<<2 }, /* Value 51 */
|
||||||
|
{ 0x3F<<2, 0x31<<2, 0x3F<<2 }, /* Value 52 */
|
||||||
|
{ 0x3F<<2, 0x31<<2, 0x36<<2 }, /* Value 53 */
|
||||||
|
{ 0x3F<<2, 0x2F<<2, 0x2C<<2 }, /* Value 54 */
|
||||||
|
{ 0x3F<<2, 0x36<<2, 0x2A<<2 }, /* Value 55 */
|
||||||
|
{ 0x3F<<2, 0x39<<2, 0x28<<2 }, /* Value 56 */
|
||||||
|
{ 0x38<<2, 0x3F<<2, 0x28<<2 }, /* Value 57 */
|
||||||
|
{ 0x2A<<2, 0x3C<<2, 0x2F<<2 }, /* Value 58 */
|
||||||
|
{ 0x2C<<2, 0x3F<<2, 0x33<<2 }, /* Value 59 */
|
||||||
|
{ 0x27<<2, 0x3F<<2, 0x3C<<2 }, /* Value 60 */
|
||||||
|
{ 0x31<<2, 0x31<<2, 0x31<<2 }, /* Value 61 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x00<<2 }, /* Value 62 */
|
||||||
|
{ 0x00<<2, 0x00<<2, 0x00<<2 }, /* Value 63 */
|
||||||
|
};
|
||||||
|
|
||||||
|
} //class palettes
|
||||||
|
|
||||||
|
} //partial class NES
|
||||||
|
|
||||||
|
} //namespace
|
|
@ -1,4 +1,5 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
@ -251,6 +252,34 @@ namespace BizHawk
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
//I think this is a little faster with uint than with byte
|
||||||
|
struct Bit
|
||||||
|
{
|
||||||
|
Bit(uint val) { this.val = val; }
|
||||||
|
uint val;
|
||||||
|
public static implicit operator Bit(int rhs) { Debug.Assert((rhs & ~1) == 0); return new Bit((uint)(rhs)); }
|
||||||
|
public static implicit operator Bit(uint rhs) { Debug.Assert((rhs & ~1) == 0); return new Bit((uint)(rhs)); }
|
||||||
|
public static implicit operator Bit(byte rhs) { Debug.Assert((rhs & ~1) == 0); return new Bit((uint)(rhs)); }
|
||||||
|
public static implicit operator Bit(bool rhs) { return new Bit(rhs ? (byte)1 : (byte)0); }
|
||||||
|
public static implicit operator long(Bit rhs) { return (long)rhs.val; }
|
||||||
|
public static implicit operator int(Bit rhs) { return (int)rhs.val; }
|
||||||
|
public static implicit operator uint(Bit rhs) { return (uint)rhs.val; }
|
||||||
|
public static implicit operator byte(Bit rhs) { return (byte)rhs.val; }
|
||||||
|
public static implicit operator bool(Bit rhs) { return rhs.val != 0; }
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return val.ToString();
|
||||||
|
}
|
||||||
|
public static bool operator ==(Bit lhs, Bit rhs) { return lhs.val == rhs.val; }
|
||||||
|
public static bool operator !=(Bit lhs, Bit rhs) { return lhs.val != rhs.val; }
|
||||||
|
public override int GetHashCode() { return val.GetHashCode(); }
|
||||||
|
public override bool Equals(object obj) { return this == (Bit)obj; } //this is probably wrong
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static class Util
|
public static class Util
|
||||||
{
|
{
|
||||||
public static int SaveRamBytesUsed(byte[] SaveRAM)
|
public static int SaveRamBytesUsed(byte[] SaveRAM)
|
||||||
|
@ -272,7 +301,81 @@ namespace BizHawk
|
||||||
return sb.ToString();
|
return sb.ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
public static unsafe int memcmp(void* a, string b, int len)
|
||||||
|
{
|
||||||
|
fixed (byte* bp = System.Text.Encoding.ASCII.GetBytes(b))
|
||||||
|
return memcmp(a, bp, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe int memcmp(void* a, void* b, int len)
|
||||||
|
{
|
||||||
|
byte* ba = (byte*)a;
|
||||||
|
byte* bb = (byte*)b;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
{
|
||||||
|
byte _a = ba[i];
|
||||||
|
byte _b = bb[i];
|
||||||
|
int c = _a - _b;
|
||||||
|
if (c != 0) return c;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe void memset(void* ptr, int val, int len)
|
||||||
|
{
|
||||||
|
byte* bptr = (byte*)ptr;
|
||||||
|
for (int i = 0; i < len; i++)
|
||||||
|
bptr[i] = (byte)val;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] ReadAllBytes(Stream stream)
|
||||||
|
{
|
||||||
|
const int BUFF_SIZE = 4096;
|
||||||
|
byte[] buffer = new byte[BUFF_SIZE];
|
||||||
|
|
||||||
|
int bytesRead = 0;
|
||||||
|
var inStream = new BufferedStream(stream);
|
||||||
|
var outStream = new MemoryStream();
|
||||||
|
|
||||||
|
while ((bytesRead = inStream.Read(buffer, 0, BUFF_SIZE)) > 0)
|
||||||
|
{
|
||||||
|
outStream.Write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
|
||||||
|
return outStream.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class BITREV
|
||||||
|
{
|
||||||
|
public static byte[] byte_8;
|
||||||
|
static BITREV()
|
||||||
|
{
|
||||||
|
make_byte_8();
|
||||||
|
}
|
||||||
|
static void make_byte_8()
|
||||||
|
{
|
||||||
|
int bits = 8;
|
||||||
|
int n = 1 << 8;
|
||||||
|
byte_8 = new byte[n];
|
||||||
|
|
||||||
|
int m = 1;
|
||||||
|
int a = n >> 1;
|
||||||
|
int j = 2;
|
||||||
|
|
||||||
|
byte_8[0] = 0;
|
||||||
|
byte_8[1] = (byte)a;
|
||||||
|
|
||||||
|
while ((--bits) != 0)
|
||||||
|
{
|
||||||
|
m <<= 1;
|
||||||
|
a >>= 1;
|
||||||
|
for (int i = 0; i < m; i++)
|
||||||
|
byte_8[j++] = (byte)(byte_8[i] + a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
<WarningLevel>4</WarningLevel>
|
<WarningLevel>4</WarningLevel>
|
||||||
<PlatformTarget>x86</PlatformTarget>
|
<PlatformTarget>x86</PlatformTarget>
|
||||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
<DebugType>pdbonly</DebugType>
|
<DebugType>pdbonly</DebugType>
|
||||||
|
@ -316,6 +317,7 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Include="config\ControllerImages\NESController.PNG" />
|
<Content Include="config\ControllerImages\NESController.PNG" />
|
||||||
|
<Content Include="gamedb.txt" />
|
||||||
<None Include="images\Refresh.bmp" />
|
<None Include="images\Refresh.bmp" />
|
||||||
<None Include="images\TruncateFromRW.png" />
|
<None Include="images\TruncateFromRW.png" />
|
||||||
<None Include="images\TruncateFromFile.png" />
|
<None Include="images\TruncateFromFile.png" />
|
||||||
|
|
|
@ -467,17 +467,6 @@ namespace BizHawk.MultiClient
|
||||||
[System.Security.SuppressUnmanagedCodeSecurity, DllImport("User32.dll", CharSet = CharSet.Auto)]
|
[System.Security.SuppressUnmanagedCodeSecurity, DllImport("User32.dll", CharSet = CharSet.Auto)]
|
||||||
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, UInt32 msgFilterMin, UInt32 msgFilterMax, UInt32 flags);
|
public static extern bool PeekMessage(out Message msg, IntPtr hWnd, UInt32 msgFilterMin, UInt32 msgFilterMax, UInt32 flags);
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This functions calls Emulator.FrameAdvance(true) and handles any updates that need to happen on a per frame basis
|
|
||||||
/// </summary>
|
|
||||||
public void DoFrameAdvance() //TODO: rename this and run it once per frame
|
|
||||||
{
|
|
||||||
Global.Emulator.FrameAdvance(true); //TODO: Do these things need to happen on (false) as well? Think about it
|
|
||||||
RamWatch1.UpdateValues();
|
|
||||||
RamSearch1.UpdateValues();
|
|
||||||
InputLog.GetMnemonic(); //TODO: log to input log or user choice, if user choice & playback don't log!
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CheckHotkeys()
|
public void CheckHotkeys()
|
||||||
{
|
{
|
||||||
if (Global.ClientControls["Quick Save State"])
|
if (Global.ClientControls["Quick Save State"])
|
||||||
|
|
|
@ -254,7 +254,7 @@ namespace BizHawk.MultiClient
|
||||||
public void DrawScreenInfo()
|
public void DrawScreenInfo()
|
||||||
{
|
{
|
||||||
//TODO: If movie loaded use that frame counter, and also display total movie frame count if read-only
|
//TODO: If movie loaded use that frame counter, and also display total movie frame count if read-only
|
||||||
if (Global.Config.DisplayFrameCounter)
|
//if (Global.Config.DisplayFrameCounter)
|
||||||
MessageFont.DrawString(null, Global.Emulator.Frame.ToString(), 1, 1, new Color4(Color.White)); //TODO: Allow user to set screen coordinates?
|
MessageFont.DrawString(null, Global.Emulator.Frame.ToString(), 1, 1, new Color4(Color.White)); //TODO: Allow user to set screen coordinates?
|
||||||
|
|
||||||
if (Global.Config.DisplayInput)
|
if (Global.Config.DisplayInput)
|
||||||
|
|
|
@ -23,16 +23,23 @@ namespace BizHawk.MultiClient
|
||||||
throw new Exception("The file needs to exist, yo.");
|
throw new Exception("The file needs to exist, yo.");
|
||||||
|
|
||||||
var stream = file.GetStream();
|
var stream = file.GetStream();
|
||||||
|
|
||||||
int header = (int) (stream.Length%BankSize);
|
|
||||||
stream.Position = header;
|
|
||||||
int length = (int) stream.Length - header;
|
|
||||||
|
|
||||||
RomData = new byte[length];
|
if (file.Extension == "NES")
|
||||||
stream.Read(RomData, 0, length);
|
{
|
||||||
|
RomData = Util.ReadAllBytes(stream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int header = (int)(stream.Length % BankSize);
|
||||||
|
stream.Position = header;
|
||||||
|
int length = (int)stream.Length - header;
|
||||||
|
|
||||||
if (file.Extension == "SMD")
|
RomData = new byte[length];
|
||||||
RomData = DeInterleaveSMD(RomData);
|
stream.Read(RomData, 0, length);
|
||||||
|
|
||||||
|
if (file.Extension == "SMD")
|
||||||
|
RomData = DeInterleaveSMD(RomData);
|
||||||
|
}
|
||||||
|
|
||||||
var info = Database.GetGameInfo(RomData, file.FullName);
|
var info = Database.GetGameInfo(RomData, file.FullName);
|
||||||
name = info.Name;
|
name = info.Name;
|
||||||
|
|
|
@ -301,10 +301,15 @@ namespace BizHawk.MultiClient
|
||||||
else
|
else
|
||||||
sleepy = 0;
|
sleepy = 0;
|
||||||
if (sleepy >= 10)
|
if (sleepy >= 10)
|
||||||
Thread.Sleep((int)(sleepy / 2)); // reduce it further beacuse Sleep usually sleeps for more than the amount we tell it to
|
{
|
||||||
|
Thread.Sleep((int) (sleepy/2));
|
||||||
|
// reduce it further beacuse Sleep usually sleeps for more than the amount we tell it to
|
||||||
|
}
|
||||||
else if (sleepy > 0) // spin for <1 millisecond waits
|
else if (sleepy > 0) // spin for <1 millisecond waits
|
||||||
|
{
|
||||||
Thread.Sleep(0);
|
Thread.Sleep(0);
|
||||||
//SwitchToThread(); // limit to other threads on the same CPU core for other short waits
|
}
|
||||||
|
//SwitchToThread(); // limit to other threads on the same CPU core for other short waits
|
||||||
goto waiter;
|
goto waiter;
|
||||||
}
|
}
|
||||||
if ((ttime - ltime) >= (tfreq * 4 / desiredfps))
|
if ((ttime - ltime) >= (tfreq * 4 / desiredfps))
|
||||||
|
|
|
@ -329,7 +329,7 @@ namespace M6502
|
||||||
w.WriteLine(" while (PendingCycles > 0)");
|
w.WriteLine(" while (PendingCycles > 0)");
|
||||||
w.WriteLine(" {");
|
w.WriteLine(" {");
|
||||||
|
|
||||||
w.WriteLine("Console.WriteLine(State());");
|
w.WriteLine("if(debug) Console.WriteLine(State());");
|
||||||
|
|
||||||
// TODO interrupts, halt state, shit like that
|
// TODO interrupts, halt state, shit like that
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue