dev_4VirtualNes #23

Merged
sin365 merged 35 commits from dev_4VirtualNes into master 2024-08-05 17:49:44 +08:00
7 changed files with 883 additions and 60 deletions
Showing only changes of commit 88214310d7 - Show all commits

View File

@ -10,7 +10,8 @@ namespace AxibugEmuOnline.Client
private void Start()
{
StartGame("Kirby.nes");
//StartGame("Kirby.nes");
}
public void StartGame(string romName)

View File

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

View File

@ -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ƒ|<7C>[ƒ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;
}
}
}
}

View File

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

View File

@ -41,5 +41,24 @@ namespace VirtualNes
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
// 儊儌儕僞僀僾
// 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偺儈儔乕
}
}

View File

@ -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ƒ|<7C>[ƒ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;
}
}
}

View File

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