diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs index a22cab9..f06c119 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs @@ -10,7 +10,8 @@ namespace AxibugEmuOnline.Client private void Start() { - StartGame("Kirby.nes"); + + //StartGame("Kirby.nes"); } public void StartGame(string romName) diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs index e0a0488..429bc4c 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs @@ -1,11 +1,12 @@ -using Codice.CM.Common; -using System; -using System.Collections; +using System; +using VirtualNes.Core.Debug; namespace VirtualNes.Core { public class APU { + public const uint QUEUE_LENGTH = 8192; + private NES nes; private byte exsound_select; private APU_INTERNAL @internal = new APU_INTERNAL(); @@ -13,8 +14,8 @@ namespace VirtualNes.Core private int last_diff; protected short[] m_SoundBuffer = new short[256]; protected int[] lowpass_filter = new int[4]; - protected QUEUE queue; - protected QUEUE exqueue; + protected QUEUE queue = new QUEUE(); + protected QUEUE exqueue = new QUEUE(); protected bool[] m_bMute = new bool[16]; public APU(NES parent) @@ -28,8 +29,6 @@ namespace VirtualNes.Core Array.Clear(m_SoundBuffer, 0, m_SoundBuffer.Length); Array.Clear(lowpass_filter, 0, lowpass_filter.Length); - queue = QUEUE.GetDefault(); - exqueue = QUEUE.GetDefault(); for (int i = 0; i < m_bMute.Length; i++) m_bMute[i] = true; @@ -48,6 +47,49 @@ namespace VirtualNes.Core { return @internal.SyncRead(addr); } + + internal void Write(ushort addr, byte data) + { + // $4018偼VirtuaNES屌桳億乕僩 + if (addr >= 0x4000 && addr <= 0x401F) + { + @internal.SyncWrite(addr, data); + SetQueue(nes.cpu.GetTotalCycles(), addr, data); + } + } + + private void SetQueue(int writetime, ushort addr, byte data) + { + queue.data[queue.wrptr].time = writetime; + queue.data[queue.wrptr].addr = addr; + queue.data[queue.wrptr].data = data; + queue.wrptr++; + + var newwrptr = (int)(queue.wrptr & (QUEUE_LENGTH - 1)); + queue.wrptr = newwrptr; + + if (queue.wrptr == queue.rdptr) + { + Debuger.LogError("queue overflow."); + } + } + + private bool GetQueue(int writetime, ref QUEUEDATA ret) + { + if (queue.wrptr == queue.rdptr) + { + return false; + } + if (queue.data[queue.rdptr].time <= writetime) + { + ret = queue.data[queue.rdptr]; + queue.rdptr++; + var newrdptr = (int)(queue.rdptr & (QUEUE_LENGTH - 1)); + queue.rdptr = newrdptr; + return true; + } + return false; + } } public struct QUEUEDATA @@ -58,19 +100,10 @@ namespace VirtualNes.Core public byte reserved; } - public struct QUEUE + public class QUEUE { public int rdptr; public int wrptr; - QUEUEDATA[] data; - - public static QUEUE GetDefault() - { - var res = new QUEUE(); - res.rdptr = 0; - res.wrptr = 0; - res.data = new QUEUEDATA[8192]; - return res; - } + public QUEUEDATA[] data = new QUEUEDATA[8192]; } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs index 5315c25..033b9f8 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs @@ -4,6 +4,26 @@ namespace VirtualNes.Core { public class APU_INTERNAL : APU_INTERFACE { + private static int[] vbl_length = new int[32] + { + 5, 127, 10, 1, 19, 2, 40, 3, + 80, 4, 30, 5, 7, 6, 13, 7, + 6, 8, 12, 9, 24, 10, 48, 11, + 96, 12, 36, 13, 8, 14, 16, 15, + }; + + private static int[] dpcm_cycles_pal = new int[16] + { + 397, 353, 315, 297, 265, 235, 209, 198, + 176, 148, 131, 118, 98, 78, 66, 50, + }; + + private static int[] dpcm_cycles = new int[16] + { + 428, 380, 340, 320, 286, 254, 226, 214, + 190, 160, 142, 128, 106, 85, 72, 54, + }; + private NES nes; // Frame Counter private int FrameCycle; @@ -19,7 +39,8 @@ namespace VirtualNes.Core private NOISE ch3 = new NOISE(); private DPCM ch4 = new DPCM(); - + // $4015 Reg + private byte reg4015, sync_reg4015; public void SetParent(NES parent) { @@ -48,7 +69,27 @@ namespace VirtualNes.Core private void UpdateFrame() { - //TODO : ʵ + if (FrameCount == 0) + { + if ((FrameIRQ & 0xC0) == 0 && nes.GetFrameIRQmode()) + { + FrameIRQoccur = 0xFF; + nes.cpu.SetIRQ(CPU.IRQ_FRAMEIRQ); + } + } + + if (FrameCount == 3) + { + if ((FrameIRQ & 0x80) != 0) + { + FrameCycle += 14915; + } + } + + // Counters Update + nes.Write(0x4018, (byte)FrameCount); + + FrameCount = (FrameCount + 1) & 3; } public override void Reset(float fClock, int nRate) @@ -102,5 +143,267 @@ namespace VirtualNes.Core } return data; } + + internal void SyncWrite(ushort addr, byte data) + { + switch (addr) + { + // CH0,1 rectangle + case 0x4000: + case 0x4001: + case 0x4002: + case 0x4003: + case 0x4004: + case 0x4005: + case 0x4006: + case 0x4007: + SyncWriteRectangle((addr < 0x4004) ? 0 : 1, addr, data); + break; + // CH2 triangle + case 0x4008: + case 0x4009: + case 0x400A: + case 0x400B: + SyncWriteTriangle(addr, data); + break; + // CH3 noise + case 0x400C: + case 0x400D: + case 0x400E: + case 0x400F: + SyncWriteNoise(addr, data); + break; + // CH4 DPCM + case 0x4010: + case 0x4011: + case 0x4012: + case 0x4013: + SyncWriteDPCM(addr, data); + break; + + case 0x4015: + sync_reg4015 = data; + + if ((data & (1 << 0)) == 0) + { + ch0.sync_enable = 0; + ch0.sync_len_count = 0; + } + if ((data & (1 << 1)) == 0) + { + ch1.sync_enable = 0; + ch1.sync_len_count = 0; + } + if ((data & (1 << 2)) == 0) + { + ch2.sync_enable = 0; + ch2.sync_len_count = 0; + ch2.sync_lin_count = 0; + ch2.sync_counter_start = 0; + } + if ((data & (1 << 3)) == 0) + { + ch3.sync_enable = 0; + ch3.sync_len_count = 0; + } + if ((data & (1 << 4)) == 0) + { + ch4.sync_enable = 0; + ch4.sync_dmalength = 0; + ch4.sync_irq_enable = 0; + + nes.cpu.ClrIRQ(CPU.IRQ_DPCM); + } + else + { + ch4.sync_enable = 0xFF; + if (ch4.sync_dmalength == 0) + { + // ch4.sync_cycles = ch4.sync_cache_cycles; + ch4.sync_dmalength = ch4.sync_cache_dmalength; + ch4.sync_cycles = 0; + } + } + break; + + case 0x4017: + SyncWrite4017(data); + break; + + // VirtuaNESŗL|[g + case 0x4018: + SyncUpdateRectangle(ch0, data); + SyncUpdateRectangle(ch1, data); + SyncUpdateTriangle(data); + SyncUpdateNoise(data); + break; + default: + break; + } + } + + private void SyncUpdateNoise(int type) + { + if (ch3.sync_enable == 0 || ch3.sync_len_count <= 0) + return; + + // Update Length + if (ch3.sync_len_count != 0 && ch3.sync_holdnote == 0) + { + if ((type & 1) == 0 && ch3.sync_len_count != 0) + { + ch3.sync_len_count--; + } + } + } + + private void SyncUpdateTriangle(int type) + { + if (ch2.sync_enable == 0) + return; + + if ((type & 1) == 0 && ch2.sync_holdnote == 0) + { + if (ch2.sync_len_count != 0) + { + ch2.sync_len_count--; + } + } + + // Update Length/Linear + if (ch2.sync_counter_start != 0) + { + ch2.sync_lin_count = ch2.sync_reg[0] & 0x7F; + } + else if (ch2.sync_lin_count != 0) + { + ch2.sync_lin_count--; + } + if (ch2.sync_holdnote == 0 && ch2.sync_lin_count != 0) + { + ch2.sync_counter_start = 0; + } + } + + private void SyncUpdateRectangle(RECTANGLE ch, int type) + { + if (ch.sync_enable == 0 || ch.sync_len_count <= 0) + return; + + // Update Length + if (ch.sync_len_count != 0 && ch.sync_holdnote == 0) + { + if ((type & 1) == 0 && ch.sync_len_count != 0) + { + ch.sync_len_count--; + } + } + } + + private void SyncWrite4017(byte data) + { + FrameCycle = 0; + FrameIRQ = data; + FrameIRQoccur = 0; + + nes.cpu.ClrIRQ(CPU.IRQ_FRAMEIRQ); + + FrameType = (data & 0x80) != 0 ? 1 : 0; + FrameCount = 0; + if ((data & 0x80) > 0) + { + UpdateFrame(); + } + FrameCount = 1; + FrameCycle = 14915; + } + + private void SyncWriteDPCM(ushort addr, byte data) + { + ch4.reg[addr & 3] = data; + switch (addr & 3) + { + case 0: + ch4.sync_cache_cycles = nes.GetVideoMode() ? dpcm_cycles_pal[data & 0x0F] * 8 : dpcm_cycles[data & 0x0F] * 8; + ch4.sync_looping = (byte)(data & 0x40); + ch4.sync_irq_gen = (byte)(data & 0x80); + if (ch4.sync_irq_gen == 0) + { + ch4.sync_irq_enable = 0; + nes.cpu.ClrIRQ(CPU.IRQ_DPCM); + } + break; + case 1: + break; + case 2: + break; + case 3: + ch4.sync_cache_dmalength = (data << 4) + 1; + break; + } + } + + private void SyncWriteNoise(ushort addr, byte data) + { + ch3.sync_reg[addr & 3] = data; + switch (addr & 3) + { + case 0: + ch3.sync_holdnote = (byte)(data & 0x20); + break; + case 1: + break; + case 2: + break; + case 3: // Master + ch3.sync_len_count = vbl_length[data >> 3] * 2; + if ((sync_reg4015 & (1 << 3)) != 0) + ch3.sync_enable = 0xFF; + break; + } + } + + private void SyncWriteTriangle(ushort addr, byte data) + { + ch2.sync_reg[addr & 3] = data; + switch (addr & 3) + { + case 0: + ch2.sync_holdnote = (byte)(data & 0x80); + break; + case 1: + break; + case 2: + break; + case 3: // Master + ch2.sync_len_count = vbl_length[ch2.sync_reg[3] >> 3] * 2; + ch2.sync_counter_start = 0x80; + + if ((sync_reg4015 & (1 << 2)) != 0) + ch2.sync_enable = 0xFF; + break; + } + } + + private void SyncWriteRectangle(int no, ushort addr, byte data) + { + RECTANGLE ch = (no == 0) ? ch0 : ch1; + + ch.sync_reg[addr & 3] = data; + switch (addr & 3) + { + case 0: + ch.sync_holdnote = (byte)(data & 0x20); + break; + case 1: + case 2: + break; + case 3: // Master + ch.sync_len_count = vbl_length[data >> 3] * 2; + if ((sync_reg4015 & (1 << no)) != 0) + ch.sync_enable = 0xFF; + break; + } + } } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs index 833748e..0fca7d8 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs @@ -1,13 +1,14 @@ #undef DPCM_SYNCCLOCK -using Codice.CM.Client.Differences; using System; -using System.Runtime.CompilerServices; +using UnityEngine.UI; namespace VirtualNes.Core { public class CPU { + private static int nmicount; + // 6502 status flags public const byte C_FLAG = 0x01; // 1: Carry public const byte Z_FLAG = 0x02; // 1: Zero @@ -42,7 +43,7 @@ namespace VirtualNes.Core private int DMA_cycles; private Mapper mapper; private APU apu; - private R6502 R; + private R6502 R = new R6502(); private byte[] ZN_Table = new byte[256]; public CPU(NES parent) @@ -97,18 +98,18 @@ namespace VirtualNes.Core nmi_request = irq_request = 0; opcode = OP6502(R.PC++); - if (R.Int_Pending != 0) + if (R.INT_pending != 0) { - if ((R.Int_Pending & NMI_FLAG) != 0) + if ((R.INT_pending & NMI_FLAG) != 0) { nmi_request = 0xFF; byte temp = unchecked((byte)(~NMI_FLAG)); - R.Int_Pending &= temp; + R.INT_pending &= temp; } - else if ((R.Int_Pending & IRQ_MASK) != 0) + else if ((R.INT_pending & IRQ_MASK) != 0) { byte temp = unchecked((byte)(~IRQ_TRIGGER2)); - R.Int_Pending &= temp; + R.INT_pending &= temp; if ( ((R.P & I_FLAG) == 0) && @@ -117,7 +118,7 @@ namespace VirtualNes.Core { irq_request = 0xFF; temp = unchecked((byte)(~IRQ_TRIGGER)); - R.Int_Pending &= temp; + R.INT_pending &= temp; } } } @@ -125,23 +126,80 @@ namespace VirtualNes.Core switch (opcode) { case 0x69: - MR_IM(ref DT, ref R); ADC(ref WT, ref DT, ref R); + MR_IM(ref DT); ADC(ref WT, ref DT); ADD_CYCLE(2, ref exec_cycles); break; case 0x65: - MR_ZP(ref EA, ref DT, ref R); ADC(ref WT, ref DT, ref R); + MR_ZP(ref EA, ref DT); ADC(ref WT, ref DT); ADD_CYCLE(3, ref exec_cycles); break; case 0x75: - MR_ZX(ref DT, ref EA, ref R); ADC(ref WT, ref DT, ref R); + MR_ZX(ref DT, ref EA); ADC(ref WT, ref DT); ADD_CYCLE(4, ref exec_cycles); break; case 0x6D: - MR_AB(ref EA, ref DT, ref R);ADC(ref WT, ref DT, ref R); + MR_AB(ref EA, ref DT); ADC(ref WT, ref DT); ADD_CYCLE(4, ref exec_cycles); break; case 0x7D: - + MR_AX(ref ET, ref EA, ref DT); ADC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles); + ADD_CYCLE(4, ref exec_cycles); + break; + case 0x79: + MR_AY(ref ET, ref EA, ref DT); ADC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles); + ADD_CYCLE(4, ref exec_cycles); + break; + case 0x61: + MR_IX(ref DT, ref EA); ADC(ref WT, ref DT); + ADD_CYCLE(6, ref exec_cycles); + break; + case 0x71: + MR_IY(ref DT, ref ET, ref EA); ADC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles); + ADD_CYCLE(4, ref exec_cycles); + break; + case 0xE9: + MR_IM(ref DT); SBC(ref WT, ref DT); + ADD_CYCLE(2, ref exec_cycles); + break; + case 0xE5: + MR_ZP(ref EA, ref DT); SBC(ref WT, ref DT); + ADD_CYCLE(3, ref exec_cycles); + break; + case 0xF5: + MR_ZX(ref DT, ref EA); SBC(ref WT, ref DT); + ADD_CYCLE(4, ref exec_cycles); + break; + case 0xED: + MR_AB(ref EA, ref DT); SBC(ref WT, ref DT); + ADD_CYCLE(4, ref exec_cycles); + break; + case 0xFD: + MR_AX(ref ET, ref EA, ref DT); SBC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles); + ADD_CYCLE(4, ref exec_cycles); + break; + case 0xF9: // SBC $????,Y + MR_AY(ref ET, ref EA, ref DT); SBC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles); + ADD_CYCLE(4, ref exec_cycles); + break; + case 0xE1: // SBC ($??,X) + MR_IX(ref DT, ref EA); SBC(ref WT, ref DT); + ADD_CYCLE(6, ref exec_cycles); + break; + case 0xF1: // SBC ($??),Y + MR_IY(ref DT, ref ET, ref EA); SBC(ref WT, ref DT); CHECK_EA(ref EA, ref ET, ref exec_cycles); + ADD_CYCLE(5, ref exec_cycles); + break; + case 0xC6: // DEC $?? + MR_ZP(ref EA, ref DT); DEC(ref DT); MW_ZP(EA, DT); + ADD_CYCLE(5, ref exec_cycles); + break; + case 0xD6: // DEC $??,X + MR_ZX(ref DT, ref EA); DEC(ref DT); MW_ZP(EA, DT); + ADD_CYCLE(6, ref exec_cycles); + break; + case 0xCE: // DEC $???? + MR_AB(ref EA, ref DT); DEC(ref DT); MW_EA(EA, DT); + ADD_CYCLE(6, ref exec_cycles); break; } } @@ -152,8 +210,6 @@ namespace VirtualNes.Core return TOTAL_cycles - OLD_cycles; } - - internal void SetClockProcess(bool bEnable) { m_bClockProcess = bEnable; @@ -193,33 +249,41 @@ namespace VirtualNes.Core return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF]; } - private void MR_IM(ref byte DT, ref R6502 R) + private void MR_IM(ref byte DT) { DT = OP6502(R.PC++); } - private void MR_ZP(ref ushort EA, ref byte DT, ref R6502 R) + private void MR_ZP(ref ushort EA, ref byte DT) { EA = OP6502(R.PC++); - DT = ZPRD(ref EA); + DT = ZPRD(EA); } - private byte ZPRD(ref ushort A) + private byte ZPRD(ushort A) { return MMU.RAM[A]; } - private void ADC(ref ushort WT, ref byte DT, ref R6502 R) + private ushort ZPRDW(int A) { - WT = (ushort)(R.A + DT + (R.P & C_FLAG)); - TST_FLAG(WT > 0xFF, C_FLAG, ref R); - var temp = ((~(R.A ^ DT)) & (R.A ^ WT) & 0x80); - TST_FLAG(temp != 0, V_FLAG, ref R); - R.A = (byte)WT; - SET_ZN_FLAG(R.A, ref R); + ushort ram1 = MMU.RAM[A]; + ushort ram2 = MMU.RAM[A + 1]; + ram2 <<= 8; + return (ushort)(ram1 + ram2); } - private void TST_FLAG(bool F, byte V, ref R6502 R) + private void ADC(ref ushort WT, ref byte DT) + { + WT = (ushort)(R.A + DT + (R.P & C_FLAG)); + TST_FLAG(WT > 0xFF, C_FLAG); + var temp = ((~(R.A ^ DT)) & (R.A ^ WT) & 0x80); + TST_FLAG(temp != 0, V_FLAG); + R.A = (byte)WT; + SET_ZN_FLAG(R.A); + } + + private void TST_FLAG(bool F, byte V) { byte temp = (byte)~V; R.P &= temp; @@ -227,7 +291,7 @@ namespace VirtualNes.Core if (F) R.P |= V; } - private void SET_ZN_FLAG(byte A, ref R6502 R) + private void SET_ZN_FLAG(byte A) { byte temp = unchecked((byte)(~(Z_FLAG | N_FLAG))); R.P &= temp; @@ -239,24 +303,126 @@ namespace VirtualNes.Core exec_cycles += V; } - private void MR_ZX(ref byte DT, ref ushort EA, ref R6502 R) + private void MR_ZX(ref byte DT, ref ushort EA) { DT = OP6502(R.PC++); EA = (ushort)(DT + R.X); - DT = ZPRD(ref EA); + DT = ZPRD(EA); } - private void MR_AB(ref ushort EA, ref byte DT, ref R6502 R) + private void MR_AB(ref ushort EA, ref byte DT) { EA = OP6502W(R.PC); R.PC += 2; DT = RD6502(EA); } + private void MR_AX(ref ushort ET, ref ushort EA, ref byte DT) + { + ET = OP6502W(R.PC); + R.PC += 2; + EA = (byte)(ET + R.X); + DT = RD6502(EA); + } + + private void CHECK_EA(ref ushort EA, ref ushort ET, ref int exec_cycles) + { + if ((ET & 0xFF00) != (EA & 0xFF00)) ADD_CYCLE(1, ref exec_cycles); + } + + private void MR_AY(ref ushort ET, ref ushort EA, ref byte DT) + { + ET = OP6502W(R.PC); + R.PC += 2; + EA = (ushort)(ET + R.Y); + DT = RD6502(EA); + } + + private void MR_IX(ref byte DT, ref ushort EA) + { + DT = OP6502(R.PC++); + EA = ZPRDW(DT + R.X); + DT = RD6502(EA); + } + + private void MR_IY(ref byte DT, ref ushort ET, ref ushort EA) + { + DT = OP6502(R.PC++); + ET = ZPRDW(DT); + EA = (ushort)(ET + R.Y); + DT = RD6502(EA); + } + + private void SBC(ref ushort WT, ref byte DT) + { + WT = (ushort)(R.A - DT - (~R.P & C_FLAG)); + bool f = ((R.A ^ DT) & (R.A ^ WT) & (0x80)) != 0; + TST_FLAG(f, V_FLAG); + TST_FLAG(WT < 0x100, C_FLAG); + R.A = (byte)WT; + SET_ZN_FLAG(R.A); + } + + private void DEC(ref byte DT) + { + DT--; + SET_ZN_FLAG(DT); + } + + private void MW_ZP(ushort EA, byte DT) + { + ZPWR(EA, DT); + } + + private void ZPWR(ushort a, byte v) + { + MMU.RAM[a] = v; + } + + private void MW_EA(ushort EA, byte DT) + { + WR6502(EA, DT); + } + internal void ClrIRQ(byte mask) { byte temp = (byte)~mask; - R.Int_Pending &= temp; + R.INT_pending &= temp; + } + + internal void WR6502(ushort addr, byte data) + { + if (addr < 0x2000) + { + // RAM (Mirror $0800, $1000, $1800) + MMU.RAM[addr & 0x07FF] = data; + } + else + { + // Others + nes.Write(addr, data); + } + } + + internal void NMI() + { + R.INT_pending |= NMI_FLAG; + nmicount = 0; + } + + internal void SetIRQ(byte mask) + { + R.INT_pending |= mask; + } + + internal int GetTotalCycles() + { + return TOTAL_cycles; + } + + internal void DMA(int cycles) + { + DMA_cycles += cycles; } } @@ -292,7 +458,7 @@ namespace VirtualNes.Core IRQ_VECTOR = 0xFFFE } - public struct R6502 + public class R6502 { public ushort PC; public byte A; @@ -301,6 +467,6 @@ namespace VirtualNes.Core public byte Y; public byte S; - public byte Int_Pending; + public byte INT_pending; } } \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs index fad8a8f..b8cf455 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs @@ -40,6 +40,25 @@ namespace VirtualNes public static byte PPU7_Temp; // $2007 read buffer public static ushort loopy_t; // same as $2005/$2006 public static ushort loopy_v; // same as $2005/$2006 - public static ushort loopy_x; // tile x offset + public static ushort loopy_x; // tile x offset + + // 儊儌儕僞僀僾 + // For PROM (CPU) + public const byte BANKTYPE_ROM = 0x00; + public const byte BANKTYPE_RAM = 0xFF; + public const byte BANKTYPE_DRAM = 0x01; + public const byte BANKTYPE_MAPPER = 0x80; + // For VROM/VRAM=/CRAM (PPU) + public const byte BANKTYPE_VROM = 0x00; + public const byte BANKTYPE_CRAM = 0x01; + public const byte BANKTYPE_VRAM = 0x80; + + // 儈儔乕僞僀僾 + public const byte VRAM_HMIRROR = 0x00; // Horizontal + public const byte VRAM_VMIRROR = 0x01; // Virtical + public const byte VRAM_MIRROR4 = 0x02; // All screen + public const byte VRAM_MIRROR4L = 0x03; // PA10 L屌掕 $2000-$23FF偺儈儔乕 + public const byte VRAM_MIRROR4H = 0x04; // PA10 H屌掕 $2400-$27FF偺儈儔乕 + } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs index 9e569ab..b1a30f9 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs @@ -487,6 +487,111 @@ namespace VirtualNes.Core return ret; } - } + internal void Write(ushort addr, byte data) + { + switch (addr >> 13) + { + case 0x00: // $0000-$1FFF + MMU.RAM[addr & 0x07FF] = data; + break; + case 0x01: // $2000-$3FFF + if (!rom.IsNSF()) + { + ppu.Write((ushort)(addr & 0xE007), data); + } + break; + case 0x02: // $4000-$5FFF + if (addr < 0x4100) + { + WriteReg(addr, data); + } + else + { + mapper->WriteLow(addr, data); + } + break; + case 0x03: // $6000-$7FFF + mapper->WriteLow(addr, data); + break; + case 0x04: // $8000-$9FFF + case 0x05: // $A000-$BFFF + case 0x06: // $C000-$DFFF + case 0x07: // $E000-$FFFF + mapper->Write(addr, data); + + GenieCodeProcess(); + break; + } + } + + private void WriteReg(ushort addr, byte data) + { + switch (addr & 0xFF) + { + case 0x00: + case 0x01: + case 0x02: + case 0x03: + case 0x04: + case 0x05: + case 0x06: + case 0x07: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + case 0x0E: + case 0x0F: + case 0x10: + case 0x11: + case 0x12: + case 0x13: + case 0x15: + apu.Write(addr, data); + MMU.CPUREG[addr & 0xFF] = data; + break; + case 0x14: + ppu.DMA(data); + cpu.DMA(514); // DMA Pending cycle + MMU.CPUREG[addr & 0xFF] = data; + break; + case 0x16: + mapper.ExWrite(addr, data); // For VS-Unisystem + pad.Write(addr, data); + CPUREG[addr & 0xFF] = data; + m_TapeIn = data; + break; + case 0x17: + CPUREG[addr & 0xFF] = data; + pad->Write(addr, data); + apu->Write(addr, data); + break; + // VirtuaNESŗL|[g + case 0x18: + apu->Write(addr, data); + break; + default: + mapper->ExWrite(addr, data); + break; + } + } + + internal bool GetVideoMode() + { + return bVideoMode; + } + + internal void SetFrameIRQmode(bool bMode) + { + bFrameIRQ = bMode; + } + + internal bool GetFrameIRQmode() + { + return bFrameIRQ; + } + } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs index 0d5aea4..4f608b3 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs @@ -5,6 +5,66 @@ namespace VirtualNes.Core { public class PPU { + private static byte[][] CreateCOLORMAP() + { + byte[][] res = new byte[5][]; + res[0] = new byte[64] + { 0x35, 0xFF, 0x16, 0x22, 0x1C, 0xFF, 0xFF, 0x15, + 0xFF, 0x00, 0x27, 0x05, 0x04, 0x27, 0x08, 0x30, + 0x21, 0xFF, 0xFF, 0x29, 0x3C, 0xFF, 0x36, 0x12, + 0xFF, 0x2B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, + 0xFF, 0x31, 0xFF, 0x2A, 0x2C, 0x0C, 0xFF, 0xFF, + 0xFF, 0x07, 0x34, 0x06, 0x13, 0xFF, 0x26, 0x0F, + 0xFF, 0x19, 0x10, 0x0A, 0xFF, 0xFF, 0xFF, 0x17, + 0xFF, 0x11, 0x09, 0xFF, 0xFF, 0x25, 0x18, 0xFF + }; + res[1] = new byte[64] + { 0xFF, 0x27, 0x18, 0xFF, 0x3A, 0x25, 0xFF, 0x31, + 0x16, 0x13, 0x38, 0x34, 0x20, 0x23, 0x31, 0x1A, + 0xFF, 0x21, 0x06, 0xFF, 0x1B, 0x29, 0xFF, 0x22, + 0xFF, 0x24, 0xFF, 0xFF, 0xFF, 0x08, 0xFF, 0x03, + 0xFF, 0x36, 0x26, 0x33, 0x11, 0xFF, 0x10, 0x02, + 0x14, 0xFF, 0x00, 0x09, 0x12, 0x0F, 0xFF, 0x30, + 0xFF, 0xFF, 0x2A, 0x17, 0x0C, 0x01, 0x15, 0x19, + 0xFF, 0x2C, 0x07, 0x37, 0xFF, 0x05, 0xFF, 0xFF + }; + res[2] = new byte[64] + { 0xFF, 0xFF, 0xFF, 0x10, 0x1A, 0x30, 0x31, 0x09, + 0x01, 0x0F, 0x36, 0x08, 0x15, 0xFF, 0xFF, 0xF0, + 0x22, 0x1C, 0xFF, 0x12, 0x19, 0x18, 0x17, 0xFF, + 0x00, 0xFF, 0xFF, 0x02, 0x16, 0x06, 0xFF, 0x35, + 0x23, 0xFF, 0x8B, 0xF7, 0xFF, 0x27, 0x26, 0x20, + 0x29, 0xFF, 0x21, 0x24, 0x11, 0xFF, 0xEF, 0xFF, + 0x2C, 0xFF, 0xFF, 0xFF, 0x07, 0xF9, 0x28, 0xFF, + 0x0A, 0xFF, 0x32, 0x37, 0x13, 0xFF, 0xFF, 0x0C + }; + res[3] = new byte[64] + { 0x18, 0xFF, 0x1C, 0x89, 0x0F, 0xFF, 0x01, 0x17, // 00-07 + 0x10, 0x0F, 0x2A, 0xFF, 0x36, 0x37, 0x1A, 0xFF, // 08-0F + 0x25, 0xFF, 0x12, 0xFF, 0x0F, 0xFF, 0xFF, 0x26, // 10-17 + 0xFF, 0xFF, 0x22, 0xFF, 0xFF, 0x0F, 0x3A, 0x21, // 18-1F + 0x05, 0x0A, 0x07, 0xC2, 0x13, 0xFF, 0x00, 0x15, // 20-27 + 0x0C, 0xFF, 0x11, 0xFF, 0xFF, 0x38, 0xFF, 0xFF, // 28-2F + 0xFF, 0xFF, 0x08, 0x16, 0xFF, 0xFF, 0x30, 0x3C, // 30-37 + 0x0F, 0x27, 0xFF, 0x60, 0x29, 0xFF, 0x30, 0x09 // 38-3F + }; + res[4] = new byte[64] + { + // Super Xevious/Gradius + 0x35, 0xFF, 0x16, 0x22, 0x1C, 0x09, 0xFF, 0x15, // 00-07 + 0x20, 0x00, 0x27, 0x05, 0x04, 0x28, 0x08, 0x30, // 08-0F + 0x21, 0xFF, 0xFF, 0x29, 0x3C, 0xFF, 0x36, 0x12, // 10-17 + 0xFF, 0x2B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, // 18-1F + 0xFF, 0x31, 0xFF, 0x2A, 0x2C, 0x0C, 0x1B, 0xFF, // 20-27 + 0xFF, 0x07, 0x34, 0x06, 0xFF, 0x25, 0x26, 0x0F, // 28-2F + 0xFF, 0x19, 0x10, 0x0A, 0xFF, 0xFF, 0xFF, 0x17, // 30-37 + 0xFF, 0x11, 0x1A, 0xFF, 0x38, 0xFF, 0x18, 0x3A, // 38-3F + }; + + return res; + } + private static byte[][] VSColorMap = CreateCOLORMAP(); + // PPU Control Register #1 PPU #0 public const byte PPU_VBLANK_BIT = 0x80; public const byte PPU_SPHIT_BIT = 0x40; // 堘偆丠 @@ -34,7 +94,7 @@ namespace VirtualNes.Core public const byte SP_PRIORITY_BIT = 0x20; public const byte SP_COLOR_BIT = 0x03; - private NES m_nes; + private NES nes; private byte[] lpScreen; private byte[] lpColormode; private bool bVSMode; @@ -47,7 +107,7 @@ namespace VirtualNes.Core public PPU(NES nes) { - m_nes = nes; + this.nes = nes; lpScreen = null; lpColormode = null; @@ -134,6 +194,142 @@ namespace VirtualNes.Core } } + internal void Write(ushort addr, byte data) + { + if (bVSMode && VSSecurityData != 0) + { + if (addr == 0x2000) + { + addr = 0x2001; + } + else if (addr == 0x2001) + { + addr = 0x2000; + } + } + + switch (addr) + { + // Read only Register + case 0x2002: // PPU Status register(R) + break; + // Write Register + case 0x2000: // PPU Control Register #1(W) + // NameTable select + // t:0000110000000000=d:00000011 + MMU.loopy_t = (ushort)((MMU.loopy_t & 0xF3FF) | ((data & 0x03) << 10)); + + if ((data & 0x80) != 0 && (MMU.PPUREG[0] & 0x80) == 0 && (MMU.PPUREG[2] & 0x80) != 0) + { + nes.cpu.NMI(); // hmm... + } + + MMU.PPUREG[0] = data; + break; + case 0x2001: // PPU Control Register #2(W) + MMU.PPUREG[1] = data; + break; + case 0x2003: // SPR-RAM Address Register(W) + MMU.PPUREG[3] = data; + break; + case 0x2004: // SPR_RAM I/O Register(RW) + MMU.SPRAM[MMU.PPUREG[3]++] = data; + break; + + case 0x2005: // PPU Scroll Register(W2) + //DEBUGOUT( "SCR WRT L:%3d C:%8d\n", ScanlineNo, nes->cpu->GetTotalCycles() ); + if (MMU.PPU56Toggle == 0) + { + // First write + // tile X t:0000000000011111=d:11111000 + MMU.loopy_t = (ushort)((MMU.loopy_t & 0xFFE0) | ((data) >> 3)); + // scroll offset X x=d:00000111 + MMU.loopy_x = (ushort)(data & 0x07); + } + else + { + // Second write + // tile Y t:0000001111100000=d:11111000 + MMU.loopy_t = (ushort)((MMU.loopy_t & 0xFC1F) | (((data) & 0xF8) << 2)); + // scroll offset Y t:0111000000000000=d:00000111 + MMU.loopy_t = (ushort)((MMU.loopy_t & 0x8FFF) | (((data) & 0x07) << 12)); + } + MMU.PPU56Toggle = (byte)(MMU.PPU56Toggle == 0 ? 1 : 0); + break; + case 0x2006: // VRAM Address Register(W2) + if (MMU.PPU56Toggle == 0) + { + // First write + // t:0011111100000000=d:00111111 + // t:1100000000000000=0 + MMU.loopy_t = (ushort)((MMU.loopy_t & 0x00FF) | (((data) & 0x3F) << 8)); + } + else + { + // Second write + // t:0000000011111111=d:11111111 + MMU.loopy_t = (ushort)((MMU.loopy_t & 0xFF00) | data); + // v=t + MMU.loopy_v = MMU.loopy_t; + nes.mapper.PPU_Latch(MMU.loopy_v); + } + MMU.PPU56Toggle = (byte)(MMU.PPU56Toggle == 0 ? 1 : 0); + break; + case 0x2007: // VRAM I/O Register(RW) + ushort vaddr = (ushort)(MMU.loopy_v & 0x3FFF); + if ((MMU.PPUREG[0] & PPU_INC32_BIT) != 0) MMU.loopy_v += 32; + else MMU.loopy_v++; + + if (vaddr >= 0x3000) + { + if (vaddr >= 0x3F00) + { + data &= 0x3F; + if (bVSMode && nVSColorMap != -1) + { + byte temp = VSColorMap[nVSColorMap][data]; + if (temp != 0xFF) + { + data = (byte)(temp & 0x3F); + } + } + + if ((vaddr & 0x000F) == 0) + { + MMU.BGPAL[0] = MMU.SPPAL[0] = data; + } + else if ((vaddr & 0x0010) == 0) + { + MMU.BGPAL[vaddr & 0x000F] = data; + } + else + { + MMU.SPPAL[vaddr & 0x000F] = data; + } + MMU.BGPAL[0x04] = MMU.BGPAL[0x08] = MMU.BGPAL[0x0C] = MMU.BGPAL[0x00]; + MMU.SPPAL[0x00] = MMU.SPPAL[0x04] = MMU.SPPAL[0x08] = MMU.SPPAL[0x0C] = MMU.BGPAL[0x00]; + return; + } + vaddr &= 0xEFFF; + } + if (MMU.PPU_MEM_TYPE[vaddr >> 10] != MMU.BANKTYPE_VROM) + { + MMU.PPU_MEM_BANK[vaddr >> 10][vaddr & 0x03FF] = data; + } + break; + } + } + + internal void DMA(byte data) + { + ushort addr = (ushort)(data << 8); + + for (ushort i = 0; i < 256; i++) + { + MMU.SPRAM[i] = nes.Read((ushort)(addr + i)); + } + } + private enum Screen { SCREEN_WIDTH = 256 + 16,