dev_4VirtualNes #18

Merged
sin365 merged 4 commits from Alienjack/AxibugEmuOnline:dev_4VirtualNes into dev_4VirtualNes 2024-07-27 00:58:01 +08:00
18 changed files with 704 additions and 26 deletions
Showing only changes of commit 652616663c - Show all commits

View File

@ -43,6 +43,11 @@ namespace VirtualNes.Core
{
@internal.Sync(cycles);
}
internal byte Read(ushort addr)
{
return @internal.SyncRead(addr);
}
}
public struct QUEUEDATA

View File

@ -1,11 +1,26 @@
using System;
namespace VirtualNes.Core
{
public class APU_INTERNAL : APU_INTERFACE
{
private NES nes;
// Frame Counter
private int FrameCycle;
private int FrameCount;
private int FrameType;
private byte FrameIRQ;
private byte FrameIRQoccur;
// Channels
private RECTANGLE ch0 = new RECTANGLE();
private RECTANGLE ch1 = new RECTANGLE();
private TRIANGLE ch2 = new TRIANGLE();
private NOISE ch3 = new NOISE();
private DPCM ch4 = new DPCM();
public void SetParent(NES parent)
{
nes = parent;
@ -55,5 +70,37 @@ namespace VirtualNes.Core
{
throw new System.NotImplementedException();
}
internal byte SyncRead(ushort addr)
{
byte data = (byte)(addr >> 8);
if (addr == 0x4015)
{
data = 0;
if ((ch0.sync_enable != 0) && ch0.sync_len_count > 0) data |= (1 << 0);
if ((ch1.sync_enable != 0) && ch1.sync_len_count > 0) data |= (1 << 1);
if ((ch2.sync_enable != 0) && ch2.sync_len_count > 0) data |= (1 << 2);
if ((ch3.sync_enable != 0) && ch3.sync_len_count > 0) data |= (1 << 3);
if ((ch4.sync_enable != 0) && (ch4.sync_dmalength != 0)) data |= (1 << 4);
if (FrameIRQoccur != 0) data |= (1 << 6);
if (ch4.sync_irq_enable != 0) data |= (1 << 7);
FrameIRQoccur = 0;
nes.cpu.ClrIRQ(CPU.IRQ_FRAMEIRQ);
}
if (addr == 0x4017)
{
if (FrameIRQoccur != 0)
{
data = 0;
}
else
{
data |= (1 << 6);
}
}
return data;
}
}
}

View File

@ -0,0 +1,35 @@
using Codice.CM.Client.Differences;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VirtualNes.Core
{
public class DPCM
{
public byte[] reg = new byte[4];
public byte enable;
public byte looping;
public byte cur_byte;
public byte dpcm_value;
public int freq;
public int phaseacc;
public int output;
ushort address, cache_addr;
public int dmalength, cache_dmalength;
public int dpcm_output_real, dpcm_output_fake, dpcm_output_old, dpcm_output_offset;
// For sync
public byte[] sync_reg = new byte[4];
public byte sync_enable;
public byte sync_looping;
public byte sync_irq_gen;
public byte sync_irq_enable;
public int sync_cycles, sync_cache_cycles;
public int sync_dmalength, sync_cache_dmalength;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e6289a516ac91b541b2b1807bb07e2b0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VirtualNes.Core
{
public class NOISE
{
public byte[] reg = new byte[4]; // register
public byte enable; // enable
public byte holdnote; // holdnote
public byte volume; // volume
public byte xor_tap;
public int shift_reg;
// For Render
public int phaseacc;
public int freq;
public int len_count;
public int nowvolume;
public int output;
// For Envelope
public byte env_fixed;
public byte env_decay;
public byte env_count;
public byte dummy0;
public int env_vol;
// For sync;
public byte[] sync_reg = new byte[4];
public byte sync_output_enable;
public byte sync_enable;
public byte sync_holdnote;
public byte dummy1;
public int sync_len_count;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8680ce7dbdceb504dbda3b98dbdb1297
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VirtualNes.Core
{
public class RECTANGLE
{
public byte[] reg = new byte[4]; // register
public byte enable; // enable
public byte holdnote; // holdnote
public byte volume; // volume
public byte complement;
// For Render
public int phaseacc;
public int freq;
public int freqlimit;
public int adder;
public int duty;
public int len_count;
public int nowvolume;
// For Envelope
public byte env_fixed;
public byte env_decay;
public byte env_count;
public byte dummy0;
public int env_vol;
// For Sweep
public byte swp_on;
public byte swp_inc;
public byte swp_shift;
public byte swp_decay;
public byte swp_count;
public byte[] dummy1 = new byte[3];
// For sync;
public byte[] sync_reg = new byte[4];
public byte sync_output_enable;
public byte sync_enable;
public byte sync_holdnote;
public byte dummy2;
public int sync_len_count;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6e50831f6c445fe489d7e1737269296e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,29 @@
namespace VirtualNes.Core
{
public class TRIANGLE
{
public byte[] reg = new byte[4];
public byte enable;
public byte holdnote;
public byte counter_start;
public byte dummy0;
public int phaseacc;
public int freq;
public int len_count;
public int lin_count;
public int adder;
public int nowvolume;
// For sync;
public byte[] sync_reg = new byte[4];
public byte sync_enable;
public byte sync_holdnote;
public byte sync_counter_start;
// public byte dummy1;
public int sync_len_count;
public int sync_lin_count;
}
}

View File

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 4ed2788da33fe474facc1d7ce1b34d03
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -135,6 +135,13 @@ namespace VirtualNes.Core
case 0x75:
MR_ZX(ref DT, ref EA, ref R); ADC(ref WT, ref DT, ref R);
ADD_CYCLE(4, ref exec_cycles);
break;
case 0x6D:
MR_AB(ref EA, ref DT, ref R);ADC(ref WT, ref DT, ref R);
ADD_CYCLE(4, ref exec_cycles);
break;
case 0x7D:
break;
}
}
@ -157,26 +164,51 @@ namespace VirtualNes.Core
return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ushort OP6502W(ushort addr)
{
var bytePage = MMU.CPU_MEM_BANK[addr >> 13];
return BitConverter.ToUInt16(bytePage, addr & 0x1FFF);
}
internal byte RD6502(ushort addr)
{
if (addr < 0x2000)
{
// RAM (Mirror $0800, $1000, $1800)
return MMU.RAM[addr & 0x07FF];
}
else if (addr < 0x8000)
{
// Others
return nes.Read(addr);
}
else
{
// Dummy access
mapper.Read(addr, MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF]);
}
// Quick bank read
return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF];
}
private void MR_IM(ref byte DT, ref R6502 R)
{
DT = OP6502(R.PC++);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MR_ZP(ref ushort EA, ref byte DT, ref R6502 R)
{
EA = OP6502(R.PC++);
DT = ZPRD(ref EA);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte ZPRD(ref ushort A)
{
return MMU.RAM[A];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ADC(ref ushort WT, ref byte DT, ref R6502 R)
{
WT = (ushort)(R.A + DT + (R.P & C_FLAG));
@ -187,7 +219,6 @@ namespace VirtualNes.Core
SET_ZN_FLAG(R.A, ref R);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void TST_FLAG(bool F, byte V, ref R6502 R)
{
byte temp = (byte)~V;
@ -196,7 +227,6 @@ namespace VirtualNes.Core
if (F) R.P |= V;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SET_ZN_FLAG(byte A, ref R6502 R)
{
byte temp = unchecked((byte)(~(Z_FLAG | N_FLAG)));
@ -204,19 +234,30 @@ namespace VirtualNes.Core
R.P |= ZN_Table[A];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ADD_CYCLE(int V, ref int exec_cycles)
{
exec_cycles += V;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MR_ZX(ref byte DT, ref ushort EA, ref R6502 R)
{
DT = OP6502(R.PC++);
EA = (ushort)(DT + R.X);
DT = ZPRD(ref EA);
}
private void MR_AB(ref ushort EA, ref byte DT, ref R6502 R)
{
EA = OP6502W(R.PC);
R.PC += 2;
DT = RD6502(EA);
}
internal void ClrIRQ(byte mask)
{
byte temp = (byte)~mask;
R.Int_Pending &= temp;
}
}
public enum StatusFlag6502 : int

View File

@ -1,4 +1,5 @@
using System;
using Codice.CM.Client.Differences;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -11,8 +12,34 @@ namespace VirtualNes
// CPU 儊儌儕僶儞僋
public static byte[][] CPU_MEM_BANK = new byte[8][]; // 8K扨埵
// PPU 儊儌儕僶儞僋
public static byte[][] PPU_MEM_BANK = new byte[12][]; // 1K扨埵
public static byte[] PPU_MEM_TYPE = new byte[12];
public static int[] PPU_MEM_PAGE = new int[12]; // 僗僥乕僩僙乕僽梡
public static byte[] CRAM_USED = new byte[16]; // 僗僥乕僩僙乕僽梡
// NES儊儌儕
public static byte[] RAM = new byte[8 * 1024]; // NES撪憻RAM
public static byte[] WARM = new byte[128 * 1024]; // 儚乕僋/僶僢僋傾僢僾RAM
public static byte[] DRAM = new byte[40 * 1024]; // 僨傿僗僋僔僗僥儉RAM
public static byte[] XRAM = new byte[8 * 1024]; // 僟儈乕僶儞僋
public static byte[] ERAM = new byte[32 * 1024]; // 奼挘婡婍梡RAM
public static byte[] CRAM = new byte[32 * 1024]; // 僉儍儔僋僞僷僞乕儞RAM
public static byte[] VRAM = new byte[4 * 1024]; // 僱乕儉僥乕僽儖/傾僩儕價儏乕僩RAM
public static byte[] SPRAM = new byte[0x100]; // 僗僾儔僀僩RAM
public static byte[] BGPAL = new byte[0x10]; // BG僷儗僢僩
public static byte[] SPPAL = new byte[0x10]; // SP僷儗僢僩
// 儗僕僗僞
public static byte[] CPUREG = new byte[0x18]; // Nes $4000-$4017
public static byte[] PPUREG = new byte[0x04]; // Nes $2000-$2003
// PPU撪晹儗僕僗僞
public static byte PPU56Toggle; // $2005-$2006 Toggle
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
}
}

View File

@ -1,4 +1,5 @@
using System;
using Codice.CM.Client.Differences;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -8,6 +9,85 @@ namespace VirtualNes.Core
{
public abstract class Mapper
{
internal virtual void Clock(int cycles) { }
protected NES nes;
public Mapper(NES parent)
{
nes = parent;
}
public virtual void Dispose() { }
public abstract void Reset();
// $8000-$FFFF Memory write
public virtual void Write(ushort addr, byte data) { }
// $8000-$FFFF Memory read(Dummy)
public virtual void Read(ushort addr, byte data) { }
// $4100-$7FFF Lower Memory read/write
public virtual byte ReadLow(ushort addr)
{
// $6000-$7FFF WRAM
if (addr >= 0x6000 && addr <= 0x7FFF)
{
return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF];
}
return (byte)(addr >> 8);
}
public virtual void WriteLow(ushort addr, byte data)
{
if (addr >= 0x6000 && addr <= 0x7FFF)
{
MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF] = data;
}
}
// $4018-$40FF Extention register read/write
public virtual byte ExRead(ushort addr) { return 0x00; }
public virtual void ExWrite(ushort addr, byte data) { }
public virtual byte ExCmdRead(EXCMDRD cmd) { return 0x00; }
public virtual void ExCmdWrite(EXCMDWR cmd, byte data) { }
// H sync/V sync/Clock sync
public virtual void HSync(int scanline) { }
public virtual void VSync() { }
public virtual void Clock(int cycles) { }
// PPU address bus latch
public virtual void PPU_Latch(ushort addr) { }
// PPU Character latch
public virtual void PPU_ChrLatch(ushort addr) { }
// PPU Extension character/palette
public virtual void PPU_ExtLatchX(int x) { }
public virtual void PPU_ExtLatch(ushort addr, ref byte chr_l, ref byte chr_h, ref byte attr) { }
// For State save
public virtual bool IsStateSave() { return false; }
public virtual void SaveState(byte[] p) { }
public virtual void LoadState(byte[] p) { }
// Extension commands
// For ExCmdRead command
public enum EXCMDRD
{
EXCMDRD_NONE = 0,
EXCMDRD_DISKACCESS,
}
// For ExCmdWrite command
public enum EXCMDWR
{
EXCMDWR_NONE = 0,
EXCMDWR_DISKINSERT,
EXCMDWR_DISKEJECT,
}
public static Mapper CreateMapper(NES parent, int no)
{
switch (no)
{
default:
throw new NotImplementedException($"Mapper#{no} is not Impl");
}
}
}
}

View File

@ -358,6 +358,135 @@ namespace VirtualNes.Core
{
//todo : 实现Tape (目测是记录玩家操作再Play,优先级很低)
}
internal byte Read(ushort addr)
{
switch (addr >> 13)
{
case 0x00: // $0000-$1FFF
return MMU.RAM[addr & 0x07FF];
case 0x01: // $2000-$3FFF
return ppu.Read((ushort)(addr & 0xE007));
case 0x02: // $4000-$5FFF
if (addr < 0x4100)
{
return ReadReg(addr);
}
else
{
return mapper.ReadLow(addr);
}
case 0x03: // $6000-$7FFF
return mapper.ReadLow(addr);
case 0x04: // $8000-$9FFF
case 0x05: // $A000-$BFFF
case 0x06: // $C000-$DFFF
case 0x07: // $E000-$FFFF
return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF];
}
return 0x00; // Warning—\h
}
private byte ReadReg(ushort addr)
{
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:
return apu.Read(addr);
case 0x15:
return apu.Read(addr);
case 0x14:
return (byte)(addr & 0xFF);
case 0x16:
if (rom.IsVSUNISYSTEM())
{
return pad.Read(addr);
}
else
{
return (byte)(pad.Read(addr) | 0x40 | m_TapeOut);
}
case 0x17:
if (rom.IsVSUNISYSTEM())
{
return pad.Read(addr);
}
else
{
return (byte)(pad.Read(addr) | apu.Read(addr));
}
default:
return mapper.ExRead(addr);
}
}
internal byte Barcode2()
{
byte ret = 0x00;
if (!m_bBarcode2 || m_Barcode2seq < 0)
return ret;
switch (m_Barcode2seq)
{
case 0:
m_Barcode2seq++;
m_Barcode2ptr = 0;
ret = 0x04; // d3
break;
case 1:
m_Barcode2seq++;
m_Barcode2bit = m_Barcode2data[m_Barcode2ptr];
m_Barcode2cnt = 0;
ret = 0x04; // d3
break;
case 2:
ret = (byte)((m_Barcode2bit & 0x01) != 0 ? 0x00 : 0x04); // Bit rev.
m_Barcode2bit >>= 1;
if (++m_Barcode2cnt > 7)
{
m_Barcode2seq++;
}
break;
case 3:
if (++m_Barcode2ptr > 19)
{
m_bBarcode2 = false;
m_Barcode2seq = -1;
}
else
{
m_Barcode2seq = 1;
}
break;
default:
break;
}
return ret;
}
}
}

View File

@ -4,18 +4,21 @@ namespace VirtualNes.Core
{
public class PAD
{
protected NES nes;
protected int excontroller_select;
protected EXPAD expad;
protected bool bStrobe;
protected bool bSwapButton;
protected bool bSwapPlayer;
protected bool bZapperMode;
protected VSType nVSSwapType;
protected byte[] padbit = new byte[4];
protected byte micbit;
protected byte[] padbitsync = new byte[4];
protected byte micbitsync;
private NES nes;
private int excontroller_select;
private EXPAD expad;
private bool bStrobe;
private bool bSwapButton;
private bool bSwapPlayer;
private bool bZapperMode;
private VSType nVSSwapType;
private byte[] padbit = new byte[4];
private byte micbit;
private byte[] padbitsync = new byte[4];
private byte micbitsync;
private bool bBarcodeWorld;
public uint pad1bit, pad2bit, pad3bit, pad4bit;
public PAD(NES parent)
{
@ -35,9 +38,47 @@ namespace VirtualNes.Core
micbitsync = 0;
}
public void Dispose()
internal byte Read(ushort addr)
{
byte data = 0x00;
if (addr == 0x4016)
{
data = (byte)(pad1bit & 1);
pad1bit >>= 1;
data |= (byte)(((pad3bit & 1)) << 1);
pad3bit >>= 1;
// Mic
if (!nes.rom.IsVSUNISYSTEM())
{
data |= micbitsync;
}
if (expad != null)
{
data |= expad.Read4016();
}
}
if (addr == 0x4017)
{
data = (byte)(pad2bit & 1);
pad2bit >>= 1;
data |= (byte)((pad4bit & 1) << 1);
pad4bit >>= 1;
if (expad != null)
{
data |= expad.Read4017();
}
if (bBarcodeWorld)
{
data |= nes.Barcode2();
}
}
return data;
}
public void Dispose() { }
}
public enum VSType

View File

@ -1,9 +1,39 @@
using System;
using Codice.CM.Client.Differences;
using System;
namespace VirtualNes.Core
{
public class PPU
{
// PPU Control Register #1 PPU #0
public const byte PPU_VBLANK_BIT = 0x80;
public const byte PPU_SPHIT_BIT = 0x40; // 堘偆丠
public const byte PPU_SP16_BIT = 0x20;
public const byte PPU_BGTBL_BIT = 0x10;
public const byte PPU_SPTBL_BIT = 0x08;
public const byte PPU_INC32_BIT = 0x04;
public const byte PPU_NAMETBL_BIT = 0x03;
// PPU Control Register #2 PPU #1
public const byte PPU_BGCOLOR_BIT = 0xE0;
public const byte PPU_SPDISP_BIT = 0x10;
public const byte PPU_BGDISP_BIT = 0x08;
public const byte PPU_SPCLIP_BIT = 0x04;
public const byte PPU_BGCLIP_BIT = 0x02;
public const byte PPU_COLORMODE_BIT = 0x01;
// PPU Status Register PPU #2
public const byte PPU_VBLANK_FLAG = 0x80;
public const byte PPU_SPHIT_FLAG = 0x40;
public const byte PPU_SPMAX_FLAG = 0x20;
public const byte PPU_WENABLE_FLAG = 0x10;
// SPRITE Attribute
public const byte SP_VMIRROR_BIT = 0x80;
public const byte SP_HMIRROR_BIT = 0x40;
public const byte SP_PRIORITY_BIT = 0x20;
public const byte SP_COLOR_BIT = 0x03;
private NES m_nes;
private byte[] lpScreen;
private byte[] lpColormode;
@ -42,6 +72,59 @@ namespace VirtualNes.Core
{
}
internal byte Read(ushort addr)
{
byte data = 0x00;
switch (addr)
{
// Write only Register
case 0x2000: // PPU Control Register #1(W)
case 0x2001: // PPU Control Register #2(W)
case 0x2003: // SPR-RAM Address Register(W)
case 0x2005: // PPU Scroll Register(W2)
case 0x2006: // VRAM Address Register(W2)
data = MMU.PPU7_Temp; // 懡暘
break;
// Read/Write Register
case 0x2002: // PPU Status Register(R)
//DEBUGOUT( "2002 RD L:%3d C:%8d\n", ScanlineNo, nes->cpu->GetTotalCycles() );
data = (byte)(MMU.PPUREG[2] | VSSecurityData);
MMU.PPU56Toggle = 0;
byte temp = unchecked((byte)~PPU_VBLANK_FLAG);
MMU.PPUREG[2] &= temp;
break;
case 0x2004: // SPR_RAM I/O Register(RW)
data = MMU.SPRAM[MMU.PPUREG[3]++];
break;
case 0x2007: // VRAM I/O Register(RW)
addr = (ushort)(MMU.loopy_v & 0x3FFF);
data = MMU.PPU7_Temp;
if ((MMU.PPUREG[0] & PPU_INC32_BIT) != 0) MMU.loopy_v += 32;
else MMU.loopy_v++;
if (addr >= 0x3000)
{
if (addr >= 0x3F00)
{
// data &= 0x3F;
if ((addr & 0x0010) == 0)
{
return MMU.BGPAL[addr & 0x000F];
}
else
{
return MMU.SPPAL[addr & 0x000F];
}
}
addr &= 0xEFFF;
}
MMU.PPU7_Temp = MMU.PPU_MEM_BANK[addr >> 10][addr & 0x03FF];
break;
}
return data;
}
internal void SetRenderScanline(int scanline)
{
ScanlineNo = scanline;

View File

@ -1,3 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
@ -6,5 +7,23 @@ namespace VirtualNes.Core
{
public class EXPAD
{
protected NES nes;
public EXPAD(NES parent)
{
nes = parent;
}
public virtual void Dispose() { }
public virtual void Reset() { }
public virtual void Strobe() { }
public virtual byte Read4016() { return 0x00; }
public virtual byte Read4017() { return 0x00; }
public virtual void Write4016(byte data) { }
public virtual void Write4017(byte data) { }
public virtual void Sync() { }
public virtual void SetSyncData(int type,int data) { }
public virtual int GetSyncData(int type) { return 0x00; }
}
}

View File

@ -350,6 +350,11 @@ namespace VirtualNes.Core
{
return fdsmakerID;
}
internal bool IsVSUNISYSTEM()
{
return (header.control2 & (byte)EnumRomControlByte2.ROM_VSUNISYSTEM) != 0;
}
}