From c5febf8e20deec501e6f2f4a60ccd041e5037d22 Mon Sep 17 00:00:00 2001 From: zeromus Date: Sun, 27 Feb 2011 09:45:50 +0000 Subject: [PATCH] neshawk!! emulate attractmode without bugs = initial checkin --- BizHawk.Emulation/BizHawk.Emulation.csproj | 6 + BizHawk.Emulation/CPUs/MOS 6502/Execute.cs | 22 +- BizHawk.Emulation/CPUs/MOS 6502/MOS6502.cs | 8 +- .../Consoles/Nintendo/NES/Boards/NROM.cs | 12 + .../Consoles/Nintendo/NES/NES.cs | 299 +++++++++-- .../Consoles/Nintendo/NES/PPU.cs | 74 +++ .../Consoles/Nintendo/NES/PPU.regs.cs | 493 ++++++++++++++++++ .../Consoles/Nintendo/NES/PPU.run.cs | 451 ++++++++++++++++ .../Consoles/Nintendo/NES/Palettes.cs | 87 ++++ BizHawk.Emulation/Util.cs | 105 +++- .../BizHawk.MultiClient.csproj | 2 + BizHawk.MultiClient/MainForm.cs | 11 - BizHawk.MultiClient/RenderPanel.cs | 2 +- BizHawk.MultiClient/RomGame.cs | 23 +- BizHawk.MultiClient/Throttle.cs | 9 +- CpuCoreGenerator/MOS 6502/CoreGenerator.cs | 2 +- 16 files changed, 1545 insertions(+), 61 deletions(-) create mode 100644 BizHawk.Emulation/Consoles/Nintendo/NES/Boards/NROM.cs create mode 100644 BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs create mode 100644 BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs create mode 100644 BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs create mode 100644 BizHawk.Emulation/Consoles/Nintendo/NES/Palettes.cs diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj index d863c333de..ae5f69aeb0 100644 --- a/BizHawk.Emulation/BizHawk.Emulation.csproj +++ b/BizHawk.Emulation/BizHawk.Emulation.csproj @@ -22,6 +22,7 @@ prompt 4 x86 + true pdbonly @@ -52,7 +53,12 @@ + + + + + diff --git a/BizHawk.Emulation/CPUs/MOS 6502/Execute.cs b/BizHawk.Emulation/CPUs/MOS 6502/Execute.cs index 851d7ce3e4..8fd4129744 100644 --- a/BizHawk.Emulation/CPUs/MOS 6502/Execute.cs +++ b/BizHawk.Emulation/CPUs/MOS 6502/Execute.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; diff --git a/BizHawk.Emulation/CPUs/MOS 6502/MOS6502.cs b/BizHawk.Emulation/CPUs/MOS 6502/MOS6502.cs index 8ddca04242..a5eaf6e0c9 100644 --- a/BizHawk.Emulation/CPUs/MOS 6502/MOS6502.cs +++ b/BizHawk.Emulation/CPUs/MOS 6502/MOS6502.cs @@ -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 ==== /// Carry Flag diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/NROM.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/NROM.cs new file mode 100644 index 0000000000..6756244cb1 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Boards/NROM.cs @@ -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]; + } + } +} \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs index 2fbede1e0d..8216d2501d 100644 --- a/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/NES.cs @@ -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 MemoryDomains { get { throw new NotImplementedException(); } } - public MemoryDomain MainMemory { get { throw new NotImplementedException(); } } + public IList MemoryDomains { get { return new List(); } } + 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(); + } } } \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs new file mode 100644 index 0000000000..392d44d2bf --- /dev/null +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.cs @@ -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; + + + } + } +} diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs new file mode 100644 index 0000000000..c9fdcec532 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.regs.cs @@ -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. \ No newline at end of file diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs new file mode 100644 index 0000000000..064aa1e917 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/PPU.run.cs @@ -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; + } + } + } +} + diff --git a/BizHawk.Emulation/Consoles/Nintendo/NES/Palettes.cs b/BizHawk.Emulation/Consoles/Nintendo/NES/Palettes.cs new file mode 100644 index 0000000000..c5ba6ed75c --- /dev/null +++ b/BizHawk.Emulation/Consoles/Nintendo/NES/Palettes.cs @@ -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 diff --git a/BizHawk.Emulation/Util.cs b/BizHawk.Emulation/Util.cs index 6531b244d1..7f11d5575a 100644 --- a/BizHawk.Emulation/Util.cs +++ b/BizHawk.Emulation/Util.cs @@ -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); + } + } + } } diff --git a/BizHawk.MultiClient/BizHawk.MultiClient.csproj b/BizHawk.MultiClient/BizHawk.MultiClient.csproj index 750fec93ac..068b4f8d7a 100644 --- a/BizHawk.MultiClient/BizHawk.MultiClient.csproj +++ b/BizHawk.MultiClient/BizHawk.MultiClient.csproj @@ -25,6 +25,7 @@ 4 x86 false + true pdbonly @@ -316,6 +317,7 @@ + diff --git a/BizHawk.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs index 267843c0dc..71259f5894 100644 --- a/BizHawk.MultiClient/MainForm.cs +++ b/BizHawk.MultiClient/MainForm.cs @@ -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); - /// - /// This functions calls Emulator.FrameAdvance(true) and handles any updates that need to happen on a per frame basis - /// - 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"]) diff --git a/BizHawk.MultiClient/RenderPanel.cs b/BizHawk.MultiClient/RenderPanel.cs index d293facddc..1df1c1e769 100644 --- a/BizHawk.MultiClient/RenderPanel.cs +++ b/BizHawk.MultiClient/RenderPanel.cs @@ -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) diff --git a/BizHawk.MultiClient/RomGame.cs b/BizHawk.MultiClient/RomGame.cs index f44e784fe0..4ce5f2468f 100644 --- a/BizHawk.MultiClient/RomGame.cs +++ b/BizHawk.MultiClient/RomGame.cs @@ -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; diff --git a/BizHawk.MultiClient/Throttle.cs b/BizHawk.MultiClient/Throttle.cs index 93ca65d996..035376074b 100644 --- a/BizHawk.MultiClient/Throttle.cs +++ b/BizHawk.MultiClient/Throttle.cs @@ -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)) diff --git a/CpuCoreGenerator/MOS 6502/CoreGenerator.cs b/CpuCoreGenerator/MOS 6502/CoreGenerator.cs index 4e1a398f83..bdbeb087e3 100644 --- a/CpuCoreGenerator/MOS 6502/CoreGenerator.cs +++ b/CpuCoreGenerator/MOS 6502/CoreGenerator.cs @@ -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