dev_4VirtualNes #18
@ -10,7 +10,8 @@ namespace AxibugEmuOnline.Client
|
|||||||
|
|
||||||
private void Start()
|
private void Start()
|
||||||
{
|
{
|
||||||
StartGame("Kirby.nes");
|
|
||||||
|
//StartGame("Kirby.nes");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartGame(string romName)
|
public void StartGame(string romName)
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
using Codice.CM.Common;
|
using System;
|
||||||
using System;
|
using VirtualNes.Core.Debug;
|
||||||
using System.Collections;
|
|
||||||
|
|
||||||
namespace VirtualNes.Core
|
namespace VirtualNes.Core
|
||||||
{
|
{
|
||||||
public class APU
|
public class APU
|
||||||
{
|
{
|
||||||
|
public const uint QUEUE_LENGTH = 8192;
|
||||||
|
|
||||||
private NES nes;
|
private NES nes;
|
||||||
private byte exsound_select;
|
private byte exsound_select;
|
||||||
private APU_INTERNAL @internal = new APU_INTERNAL();
|
private APU_INTERNAL @internal = new APU_INTERNAL();
|
||||||
@ -13,8 +14,8 @@ namespace VirtualNes.Core
|
|||||||
private int last_diff;
|
private int last_diff;
|
||||||
protected short[] m_SoundBuffer = new short[256];
|
protected short[] m_SoundBuffer = new short[256];
|
||||||
protected int[] lowpass_filter = new int[4];
|
protected int[] lowpass_filter = new int[4];
|
||||||
protected QUEUE queue;
|
protected QUEUE queue = new QUEUE();
|
||||||
protected QUEUE exqueue;
|
protected QUEUE exqueue = new QUEUE();
|
||||||
protected bool[] m_bMute = new bool[16];
|
protected bool[] m_bMute = new bool[16];
|
||||||
|
|
||||||
public APU(NES parent)
|
public APU(NES parent)
|
||||||
@ -28,8 +29,6 @@ namespace VirtualNes.Core
|
|||||||
|
|
||||||
Array.Clear(m_SoundBuffer, 0, m_SoundBuffer.Length);
|
Array.Clear(m_SoundBuffer, 0, m_SoundBuffer.Length);
|
||||||
Array.Clear(lowpass_filter, 0, lowpass_filter.Length);
|
Array.Clear(lowpass_filter, 0, lowpass_filter.Length);
|
||||||
queue = QUEUE.GetDefault();
|
|
||||||
exqueue = QUEUE.GetDefault();
|
|
||||||
|
|
||||||
for (int i = 0; i < m_bMute.Length; i++)
|
for (int i = 0; i < m_bMute.Length; i++)
|
||||||
m_bMute[i] = true;
|
m_bMute[i] = true;
|
||||||
@ -48,6 +47,49 @@ namespace VirtualNes.Core
|
|||||||
{
|
{
|
||||||
return @internal.SyncRead(addr);
|
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
|
public struct QUEUEDATA
|
||||||
@ -58,19 +100,10 @@ namespace VirtualNes.Core
|
|||||||
public byte reserved;
|
public byte reserved;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct QUEUE
|
public class QUEUE
|
||||||
{
|
{
|
||||||
public int rdptr;
|
public int rdptr;
|
||||||
public int wrptr;
|
public int wrptr;
|
||||||
QUEUEDATA[] data;
|
public QUEUEDATA[] data = new QUEUEDATA[8192];
|
||||||
|
|
||||||
public static QUEUE GetDefault()
|
|
||||||
{
|
|
||||||
var res = new QUEUE();
|
|
||||||
res.rdptr = 0;
|
|
||||||
res.wrptr = 0;
|
|
||||||
res.data = new QUEUEDATA[8192];
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,26 @@ namespace VirtualNes.Core
|
|||||||
{
|
{
|
||||||
public class APU_INTERNAL : APU_INTERFACE
|
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;
|
private NES nes;
|
||||||
// Frame Counter
|
// Frame Counter
|
||||||
private int FrameCycle;
|
private int FrameCycle;
|
||||||
@ -19,7 +39,8 @@ namespace VirtualNes.Core
|
|||||||
private NOISE ch3 = new NOISE();
|
private NOISE ch3 = new NOISE();
|
||||||
private DPCM ch4 = new DPCM();
|
private DPCM ch4 = new DPCM();
|
||||||
|
|
||||||
|
// $4015 Reg
|
||||||
|
private byte reg4015, sync_reg4015;
|
||||||
|
|
||||||
public void SetParent(NES parent)
|
public void SetParent(NES parent)
|
||||||
{
|
{
|
||||||
@ -48,7 +69,27 @@ namespace VirtualNes.Core
|
|||||||
|
|
||||||
private void UpdateFrame()
|
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)
|
public override void Reset(float fClock, int nRate)
|
||||||
@ -102,5 +143,267 @@ namespace VirtualNes.Core
|
|||||||
}
|
}
|
||||||
return data;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
#undef DPCM_SYNCCLOCK
|
#undef DPCM_SYNCCLOCK
|
||||||
|
|
||||||
using Codice.CM.Client.Differences;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Runtime.CompilerServices;
|
using UnityEngine.UI;
|
||||||
|
|
||||||
namespace VirtualNes.Core
|
namespace VirtualNes.Core
|
||||||
{
|
{
|
||||||
public class CPU
|
public class CPU
|
||||||
{
|
{
|
||||||
|
private static int nmicount;
|
||||||
|
|
||||||
// 6502 status flags
|
// 6502 status flags
|
||||||
public const byte C_FLAG = 0x01; // 1: Carry
|
public const byte C_FLAG = 0x01; // 1: Carry
|
||||||
public const byte Z_FLAG = 0x02; // 1: Zero
|
public const byte Z_FLAG = 0x02; // 1: Zero
|
||||||
@ -42,7 +43,7 @@ namespace VirtualNes.Core
|
|||||||
private int DMA_cycles;
|
private int DMA_cycles;
|
||||||
private Mapper mapper;
|
private Mapper mapper;
|
||||||
private APU apu;
|
private APU apu;
|
||||||
private R6502 R;
|
private R6502 R = new R6502();
|
||||||
private byte[] ZN_Table = new byte[256];
|
private byte[] ZN_Table = new byte[256];
|
||||||
|
|
||||||
public CPU(NES parent)
|
public CPU(NES parent)
|
||||||
@ -97,18 +98,18 @@ namespace VirtualNes.Core
|
|||||||
nmi_request = irq_request = 0;
|
nmi_request = irq_request = 0;
|
||||||
opcode = OP6502(R.PC++);
|
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;
|
nmi_request = 0xFF;
|
||||||
byte temp = unchecked((byte)(~NMI_FLAG));
|
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));
|
byte temp = unchecked((byte)(~IRQ_TRIGGER2));
|
||||||
R.Int_Pending &= temp;
|
R.INT_pending &= temp;
|
||||||
if (
|
if (
|
||||||
((R.P & I_FLAG) == 0)
|
((R.P & I_FLAG) == 0)
|
||||||
&&
|
&&
|
||||||
@ -117,7 +118,7 @@ namespace VirtualNes.Core
|
|||||||
{
|
{
|
||||||
irq_request = 0xFF;
|
irq_request = 0xFF;
|
||||||
temp = unchecked((byte)(~IRQ_TRIGGER));
|
temp = unchecked((byte)(~IRQ_TRIGGER));
|
||||||
R.Int_Pending &= temp;
|
R.INT_pending &= temp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -125,23 +126,80 @@ namespace VirtualNes.Core
|
|||||||
switch (opcode)
|
switch (opcode)
|
||||||
{
|
{
|
||||||
case 0x69:
|
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);
|
ADD_CYCLE(2, ref exec_cycles);
|
||||||
break;
|
break;
|
||||||
case 0x65:
|
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);
|
ADD_CYCLE(3, ref exec_cycles);
|
||||||
break;
|
break;
|
||||||
case 0x75:
|
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);
|
ADD_CYCLE(4, ref exec_cycles);
|
||||||
break;
|
break;
|
||||||
case 0x6D:
|
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);
|
ADD_CYCLE(4, ref exec_cycles);
|
||||||
break;
|
break;
|
||||||
case 0x7D:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,8 +210,6 @@ namespace VirtualNes.Core
|
|||||||
return TOTAL_cycles - OLD_cycles;
|
return TOTAL_cycles - OLD_cycles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
internal void SetClockProcess(bool bEnable)
|
internal void SetClockProcess(bool bEnable)
|
||||||
{
|
{
|
||||||
m_bClockProcess = bEnable;
|
m_bClockProcess = bEnable;
|
||||||
@ -193,33 +249,41 @@ namespace VirtualNes.Core
|
|||||||
return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF];
|
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++);
|
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++);
|
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];
|
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));
|
ushort ram1 = MMU.RAM[A];
|
||||||
TST_FLAG(WT > 0xFF, C_FLAG, ref R);
|
ushort ram2 = MMU.RAM[A + 1];
|
||||||
var temp = ((~(R.A ^ DT)) & (R.A ^ WT) & 0x80);
|
ram2 <<= 8;
|
||||||
TST_FLAG(temp != 0, V_FLAG, ref R);
|
return (ushort)(ram1 + ram2);
|
||||||
R.A = (byte)WT;
|
|
||||||
SET_ZN_FLAG(R.A, ref R);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
byte temp = (byte)~V;
|
||||||
R.P &= temp;
|
R.P &= temp;
|
||||||
@ -227,7 +291,7 @@ namespace VirtualNes.Core
|
|||||||
if (F) R.P |= V;
|
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)));
|
byte temp = unchecked((byte)(~(Z_FLAG | N_FLAG)));
|
||||||
R.P &= temp;
|
R.P &= temp;
|
||||||
@ -239,24 +303,126 @@ namespace VirtualNes.Core
|
|||||||
exec_cycles += V;
|
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++);
|
DT = OP6502(R.PC++);
|
||||||
EA = (ushort)(DT + R.X);
|
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);
|
EA = OP6502W(R.PC);
|
||||||
R.PC += 2;
|
R.PC += 2;
|
||||||
DT = RD6502(EA);
|
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)
|
internal void ClrIRQ(byte mask)
|
||||||
{
|
{
|
||||||
byte temp = (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
|
IRQ_VECTOR = 0xFFFE
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct R6502
|
public class R6502
|
||||||
{
|
{
|
||||||
public ushort PC;
|
public ushort PC;
|
||||||
public byte A;
|
public byte A;
|
||||||
@ -301,6 +467,6 @@ namespace VirtualNes.Core
|
|||||||
public byte Y;
|
public byte Y;
|
||||||
public byte S;
|
public byte S;
|
||||||
|
|
||||||
public byte Int_Pending;
|
public byte INT_pending;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -41,5 +41,24 @@ namespace VirtualNes
|
|||||||
public static ushort loopy_t; // same as $2005/$2006
|
public static ushort loopy_t; // same as $2005/$2006
|
||||||
public static ushort loopy_v; // 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偺儈儔乕
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -487,6 +487,111 @@ namespace VirtualNes.Core
|
|||||||
|
|
||||||
return ret;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,66 @@ namespace VirtualNes.Core
|
|||||||
{
|
{
|
||||||
public class PPU
|
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
|
// PPU Control Register #1 PPU #0
|
||||||
public const byte PPU_VBLANK_BIT = 0x80;
|
public const byte PPU_VBLANK_BIT = 0x80;
|
||||||
public const byte PPU_SPHIT_BIT = 0x40; // 堘偆丠
|
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_PRIORITY_BIT = 0x20;
|
||||||
public const byte SP_COLOR_BIT = 0x03;
|
public const byte SP_COLOR_BIT = 0x03;
|
||||||
|
|
||||||
private NES m_nes;
|
private NES nes;
|
||||||
private byte[] lpScreen;
|
private byte[] lpScreen;
|
||||||
private byte[] lpColormode;
|
private byte[] lpColormode;
|
||||||
private bool bVSMode;
|
private bool bVSMode;
|
||||||
@ -47,7 +107,7 @@ namespace VirtualNes.Core
|
|||||||
|
|
||||||
public PPU(NES nes)
|
public PPU(NES nes)
|
||||||
{
|
{
|
||||||
m_nes = nes;
|
this.nes = nes;
|
||||||
lpScreen = null;
|
lpScreen = null;
|
||||||
lpColormode = 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
|
private enum Screen
|
||||||
{
|
{
|
||||||
SCREEN_WIDTH = 256 + 16,
|
SCREEN_WIDTH = 256 + 16,
|
||||||
|
Loading…
Reference in New Issue
Block a user