neshawk!! emulate attractmode without bugs = initial checkin
This commit is contained in:
parent
303d276ac7
commit
c5febf8e20
|
@ -22,6 +22,7 @@
|
|||
<ErrorReport>prompt</ErrorReport>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
|
@ -52,7 +53,12 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<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\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.Sega.cs" />
|
||||
<Compile Include="Consoles\Sega\SMS\VDP.ModeTMS.cs" />
|
||||
|
|
|
@ -17,7 +17,23 @@ namespace BizHawk.Emulation.CPUs.M6502
|
|||
PendingCycles += cycles;
|
||||
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++);
|
||||
switch (opcode)
|
||||
{
|
||||
|
@ -732,6 +748,10 @@ FlagT = true;// this seems wrong
|
|||
PendingCycles -= 4; TotalExecutedCycles += 4;
|
||||
break;
|
||||
case 0xAD: // LDA addr
|
||||
if (this_pc == 0x800A)
|
||||
{
|
||||
int zzz = 9;
|
||||
}
|
||||
A = ReadMemory(ReadWord(PC)); PC += 2;
|
||||
P = (byte)((P & 0x7D) | TableNZ[A]);
|
||||
PendingCycles -= 4; TotalExecutedCycles += 4;
|
||||
|
|
|
@ -23,6 +23,8 @@ namespace BizHawk.Emulation.CPUs.M6502
|
|||
}
|
||||
}*/
|
||||
|
||||
public bool debug;
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
A = 0;
|
||||
|
@ -43,7 +45,7 @@ namespace BizHawk.Emulation.CPUs.M6502
|
|||
public string State()
|
||||
{
|
||||
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 val = a + b + " ";
|
||||
if (FlagN) val = val + "N";
|
||||
|
@ -70,6 +72,10 @@ namespace BizHawk.Emulation.CPUs.M6502
|
|||
public bool Interrupt;
|
||||
public bool NMI;//
|
||||
|
||||
private const ushort NMIVector = 0xFFFA;
|
||||
private const ushort ResetVector = 0xFFFC;
|
||||
private const ushort BRKVector = 0xFFFE;
|
||||
|
||||
// ==== End State ====
|
||||
|
||||
/// <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
|
||||
{
|
||||
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
|
||||
MOS6502 cpu = new MOS6502();
|
||||
byte[] rom;
|
||||
protected MOS6502 cpu;
|
||||
INESBoard board;
|
||||
PPU ppu;
|
||||
RomInfo romInfo;
|
||||
byte[] ram;
|
||||
|
||||
//user configuration
|
||||
int[,] palette; //TBD!!
|
||||
|
||||
public byte ReadPPUReg(int addr)
|
||||
{
|
||||
return 0xFF;
|
||||
return ppu.ReadReg(addr);
|
||||
}
|
||||
|
||||
public byte ReadReg(int addr)
|
||||
|
@ -23,32 +97,66 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
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)
|
||||
{
|
||||
if (addr < 0x0800) return ram[addr];
|
||||
if (addr < 0x1000) return ram[addr - 0x0800];
|
||||
if (addr < 0x1800) return ram[addr - 0x1000];
|
||||
if (addr < 0x2000) return ram[addr - 0x1800];
|
||||
if (addr < 0x4000) return ReadPPUReg(addr & 7);
|
||||
if (addr < 0x4020) return ReadReg(addr - 0x4020);
|
||||
if (addr < 0x6000) return 0xFF; //exp rom
|
||||
if (addr < 0x8000) return 0xFF; //sram
|
||||
return 0xFF; //got tired of doing this
|
||||
else if (addr < 0x1000) return ram[addr - 0x0800];
|
||||
else if (addr < 0x1800) return ram[addr - 0x1000];
|
||||
else if (addr < 0x2000) return ram[addr - 0x1800];
|
||||
else if (addr < 0x4000) return ReadPPUReg(addr & 7);
|
||||
else if (addr < 0x4020) return ReadReg(addr); //we're not rebasing the register just to keep register names canonical
|
||||
else if (addr < 0x6000) return 0xFF; //exp rom
|
||||
else if (addr < 0x8000) return 0xFF; //sram
|
||||
else return board.ReadPRG(addr - 0x8000);
|
||||
}
|
||||
|
||||
public void WriteMemory(ushort addr, byte value)
|
||||
{
|
||||
if (addr < 0x0800) ram[addr] = value;
|
||||
if (addr < 0x1000) ram[addr - 0x0800] = value;
|
||||
if (addr < 0x1800) ram[addr - 0x1000] = value;
|
||||
if (addr < 0x2000) ram[addr - 0x1800] = value;
|
||||
else if (addr < 0x1000) ram[addr - 0x0800] = value;
|
||||
else if (addr < 0x1800) ram[addr - 0x1000] = 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()
|
||||
{
|
||||
cpu.ReadMemory = ReadMemory;
|
||||
cpu.WriteMemory = WriteMemory;
|
||||
palette = Palettes.FCEUX_Standard;
|
||||
}
|
||||
|
||||
class MyVideoProvider : IVideoProvider
|
||||
|
@ -61,15 +169,19 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
|
||||
public int[] GetVideoBuffer()
|
||||
{
|
||||
int testval = 0;
|
||||
if (emu.Controller.IsPressed("DOWN")) testval = 0xFF;
|
||||
|
||||
int[] pixels = new int[256 * 256];
|
||||
int i = 0;
|
||||
for (int y = 0; y < 256; y++)
|
||||
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;
|
||||
}
|
||||
|
@ -98,28 +210,36 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
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)
|
||||
{
|
||||
//TODO!
|
||||
//cpu.Execute(10000);
|
||||
ppu.FrameAdvance();
|
||||
}
|
||||
|
||||
protected void RunCpu(int cycles)
|
||||
{
|
||||
cpu.Execute(cycles);
|
||||
}
|
||||
|
||||
public void HardReset()
|
||||
{
|
||||
cpu.Reset();
|
||||
cpu = new MOS6502();
|
||||
cpu.ReadMemory = ReadMemory;
|
||||
cpu.WriteMemory = WriteMemory;
|
||||
ppu = new PPU(this);
|
||||
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++)
|
||||
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
|
||||
|
@ -157,8 +277,16 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
}
|
||||
|
||||
public string SystemId { get { return "NES"; } }
|
||||
public IList<MemoryDomain> MemoryDomains { get { throw new NotImplementedException(); } }
|
||||
public MemoryDomain MainMemory { get { throw new NotImplementedException(); } }
|
||||
public IList<MemoryDomain> MemoryDomains { get { return new List<MemoryDomain>(); } }
|
||||
public MemoryDomain MainMemory
|
||||
{
|
||||
get
|
||||
{
|
||||
return new MemoryDomain("x", 8, Endian.Little,
|
||||
addr => 0,
|
||||
(addr, value) => { });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public object Query(EmulatorQuery query)
|
||||
|
@ -170,5 +298,106 @@ namespace BizHawk.Emulation.Consoles.Nintendo
|
|||
{
|
||||
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.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
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 int SaveRamBytesUsed(byte[] SaveRAM)
|
||||
|
@ -272,7 +301,81 @@ namespace BizHawk
|
|||
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>
|
||||
<PlatformTarget>x86</PlatformTarget>
|
||||
<UseVSHostingProcess>false</UseVSHostingProcess>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
|
@ -316,6 +317,7 @@
|
|||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="config\ControllerImages\NESController.PNG" />
|
||||
<Content Include="gamedb.txt" />
|
||||
<None Include="images\Refresh.bmp" />
|
||||
<None Include="images\TruncateFromRW.png" />
|
||||
<None Include="images\TruncateFromFile.png" />
|
||||
|
|
|
@ -467,17 +467,6 @@ namespace BizHawk.MultiClient
|
|||
[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);
|
||||
|
||||
/// <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()
|
||||
{
|
||||
if (Global.ClientControls["Quick Save State"])
|
||||
|
|
|
@ -254,7 +254,7 @@ namespace BizHawk.MultiClient
|
|||
public void DrawScreenInfo()
|
||||
{
|
||||
//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?
|
||||
|
||||
if (Global.Config.DisplayInput)
|
||||
|
|
|
@ -23,16 +23,23 @@ namespace BizHawk.MultiClient
|
|||
throw new Exception("The file needs to exist, yo.");
|
||||
|
||||
var stream = file.GetStream();
|
||||
|
||||
int header = (int) (stream.Length%BankSize);
|
||||
stream.Position = header;
|
||||
int length = (int) stream.Length - header;
|
||||
|
||||
RomData = new byte[length];
|
||||
stream.Read(RomData, 0, length);
|
||||
if (file.Extension == "NES")
|
||||
{
|
||||
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 = DeInterleaveSMD(RomData);
|
||||
RomData = new byte[length];
|
||||
stream.Read(RomData, 0, length);
|
||||
|
||||
if (file.Extension == "SMD")
|
||||
RomData = DeInterleaveSMD(RomData);
|
||||
}
|
||||
|
||||
var info = Database.GetGameInfo(RomData, file.FullName);
|
||||
name = info.Name;
|
||||
|
|
|
@ -301,10 +301,15 @@ namespace BizHawk.MultiClient
|
|||
else
|
||||
sleepy = 0;
|
||||
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
|
||||
{
|
||||
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;
|
||||
}
|
||||
if ((ttime - ltime) >= (tfreq * 4 / desiredfps))
|
||||
|
|
|
@ -329,7 +329,7 @@ namespace M6502
|
|||
w.WriteLine(" while (PendingCycles > 0)");
|
||||
w.WriteLine(" {");
|
||||
|
||||
w.WriteLine("Console.WriteLine(State());");
|
||||
w.WriteLine("if(debug) Console.WriteLine(State());");
|
||||
|
||||
// TODO interrupts, halt state, shit like that
|
||||
|
||||
|
|
Loading…
Reference in New Issue