neshawk!! emulate attractmode without bugs = initial checkin

This commit is contained in:
zeromus 2011-02-27 09:45:50 +00:00
parent 303d276ac7
commit c5febf8e20
16 changed files with 1545 additions and 61 deletions

View File

@ -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" />

View File

@ -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;

View File

@ -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>

View File

@ -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];
}
}
}

View File

@ -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();
}
}
}

View File

@ -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;
}
}
}

View File

@ -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.

View File

@ -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;
}
}
}
}

View File

@ -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

View File

@ -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);
}
}
}
}

View File

@ -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" />

View File

@ -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"])

View File

@ -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)

View File

@ -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;

View File

@ -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))

View File

@ -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