Merge pull request 'dev_4VirtualNes' (#18) from Alienjack/AxibugEmuOnline:dev_4VirtualNes into dev_4VirtualNes

Reviewed-on: #18
This commit is contained in:
sin365 2024-07-27 00:57:58 +08:00
commit 69aa14ca67
112 changed files with 6293 additions and 161 deletions

View File

@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Net.Http;
using UnityEngine;
using VirtualNes.Core;
@ -45,5 +46,23 @@ namespace AxibugEmuOnline.Client
{
return File.Open($"{Application.streamingAssetsPath}/Disksys.rom", FileMode.Open, FileAccess.Read);
}
public void SaveSRAMToFile(byte[] sramContent, string romName)
{
string sramDirectoryPath = $"{Application.persistentDataPath}/sav";
Directory.CreateDirectory(sramDirectoryPath);
romName = Path.GetFileNameWithoutExtension(romName);
File.WriteAllBytes($"{sramDirectoryPath}/{romName}.sav", sramContent);
}
public void SaveDISKToFile(byte[] diskFileContent, string romName)
{
string diskFileDirectoryPath = $"{Application.persistentDataPath}/dsv";
Directory.CreateDirectory(diskFileDirectoryPath);
romName = Path.GetFileNameWithoutExtension(romName);
File.WriteAllBytes($"{diskFileDirectoryPath}/{romName}.dsv", diskFileContent);
}
public EmulatorConfig Config { get; private set; } = new EmulatorConfig();
}
}

View File

@ -6,6 +6,8 @@ namespace AxibugEmuOnline.Client
{
public class NesEmulator : MonoBehaviour
{
private NES m_nesIns;
private void Start()
{
StartGame("Kirby.nes");
@ -13,9 +15,23 @@ namespace AxibugEmuOnline.Client
public void StartGame(string romName)
{
StopGame();
Supporter.Setup(new CoreSupporter());
Debuger.Setup(new CoreDebuger());
NES nes = new NES(romName);
m_nesIns = new NES(romName);
m_nesIns.Command(NESCOMMAND.NESCMD_HWRESET);
}
public void StopGame()
{
m_nesIns?.Dispose();
m_nesIns = null;
}
private void Update()
{
m_nesIns?.EmulateFrame(true);
}
}
}

View File

@ -1,26 +1,32 @@
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;
private APU_INTERNAL @internal = new APU_INTERNAL();
private APU_VRC6 vrc6 = new APU_VRC6();
private APU_VRC7 vrc7 = new APU_VRC7();
private APU_MMC5 mmc5 = new APU_MMC5();
private APU_FDS fds = new APU_FDS();
private APU_N106 n106 = new APU_N106();
private APU_FME7 fme7 = new APU_FME7();
private int last_data;
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 bool[] m_bMute = new bool[16];
protected QUEUE queue = new QUEUE();
protected QUEUE exqueue = new QUEUE();
protected bool[] m_bMute = new bool[16];
protected double elapsed_time;
public APU(NES parent)
{
@internal = new APU_INTERNAL();
exsound_select = 0;
nes = parent;
@ -30,12 +36,142 @@ 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;
}
public void Dispose()
{
}
internal void SyncDPCM(int cycles)
{
@internal.Sync(cycles);
}
internal byte Read(ushort 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 void SoundSetup()
{
float fClock = nes.nescfg.CpuClock;
int nRate = Supporter.Config.sound.nRate;
@internal.Setup(fClock, nRate);
vrc6.Setup(fClock, nRate);
vrc7.Setup(fClock, nRate);
mmc5.Setup(fClock, nRate);
fds.Setup(fClock, nRate);
n106.Setup(fClock, nRate);
fme7.Setup(fClock, nRate);
}
internal void SelectExSound(byte data)
{
exsound_select = data;
}
internal void Reset()
{
queue = new QUEUE();
exqueue = new QUEUE();
elapsed_time = 0;
float fClock = nes.nescfg.CpuClock;
int nRate = Supporter.Config.sound.nRate;
@internal.Reset(fClock, nRate);
vrc6.Reset(fClock, nRate);
vrc7.Reset(fClock, nRate);
mmc5.Reset(fClock, nRate);
fds.Reset(fClock, nRate);
n106.Reset(fClock, nRate);
fme7.Reset(fClock, nRate);
SoundSetup();
}
internal void ExWrite(ushort addr, byte data)
{
SetExQueue(nes.cpu.GetTotalCycles(), addr, data);
if ((exsound_select & 0x04) != 0)
{
if (addr >= 0x4040 && addr < 0x4100)
{
fds.SyncWrite(addr, data);
}
}
if ((exsound_select & 0x08) != 0)
{
if (addr >= 0x5000 && addr <= 0x5015)
{
mmc5.SyncWrite(addr, data);
}
}
}
private void SetExQueue(int writetime, ushort addr, byte data)
{
exqueue.data[exqueue.wrptr].time = writetime;
exqueue.data[exqueue.wrptr].addr = addr;
exqueue.data[exqueue.wrptr].data = data;
exqueue.wrptr++;
var temp = QUEUE_LENGTH - 1;
exqueue.wrptr = (int)(exqueue.wrptr & temp);
if (exqueue.wrptr == exqueue.rdptr)
{
Debuger.LogError("exqueue overflow.");
}
}
}
public struct QUEUEDATA
@ -46,19 +182,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

@ -0,0 +1,45 @@
using System;
namespace VirtualNes.Core
{
public class APU_FDS : APU_INTERFACE
{
private FDSSOUND fds = new FDSSOUND();
private FDSSOUND fds_sync = new FDSSOUND();
public override void Reset(float fClock, int nRate)
{
throw new System.NotImplementedException();
}
public override void Setup(float fClock, int nRate)
{
throw new System.NotImplementedException();
}
public override void Write(ushort addr, byte data)
{
throw new System.NotImplementedException();
}
public override int Process(int channel)
{
throw new System.NotImplementedException();
}
internal void SyncWrite(ushort addr, byte data)
{
WriteSub(addr, data, fds_sync, 1789772.5d);
}
private void WriteSub(ushort addr, byte data, FDSSOUND ch, double rate)
{
throw new NotImplementedException();
}
private class FDSSOUND
{
//todo : 实现
}
}
}

View File

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

View File

@ -0,0 +1,27 @@
using System;
namespace VirtualNes.Core
{
public class APU_FME7 : APU_INTERFACE
{
public override void Reset(float fClock, int nRate)
{
throw new NotImplementedException();
}
public override void Setup(float fClock, int nRate)
{
throw new NotImplementedException();
}
public override void Write(ushort addr, byte data)
{
throw new NotImplementedException();
}
public override int Process(int channel)
{
throw new NotImplementedException();
}
}
}

View File

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

View File

@ -1,13 +1,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VirtualNes.Core
namespace VirtualNes.Core
{
public abstract class APU_INTERFACE
{
public const float APU_CLOCK = 1789772.5f;
public virtual void Dispose() { }
public abstract void Reset(float fClock, int nRate);
public abstract void Setup(float fClock, int nRate);
public abstract void Write(ushort addr, byte data);
@ -24,5 +22,15 @@ namespace VirtualNes.Core
public virtual int GetStateSize() { return 0; }
public virtual void SaveState(byte[] p) { }
public virtual void LoadState(byte[] p) { }
public static int INT2FIX(int x)
{
return x << 16;
}
public static int FIX2INT(int x)
{
return x >> 16;
}
}
}

View File

@ -2,13 +2,129 @@ 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;
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();
// $4015 Reg
private byte reg4015, sync_reg4015;
public void SetParent(NES parent)
{
nes = parent;
}
public override bool Sync(int cycles)
{
FrameCycle -= cycles * 2;
if (FrameCycle <= 0)
{
FrameCycle += 14915;
UpdateFrame();
}
var result = FrameIRQoccur | (SyncUpdateDPCM(cycles) ? 1 : 0);
return result != 0;
}
private bool SyncUpdateDPCM(int cycles)
{
bool bIRQ = false;
if (ch4.sync_enable != 0)
{
ch4.sync_cycles -= cycles;
while (ch4.sync_cycles < 0)
{
ch4.sync_cycles += ch4.sync_cache_cycles;
if (ch4.sync_dmalength != 0)
{
// if( !(--ch4.sync_dmalength) ) {
if (--ch4.sync_dmalength < 2)
{
if (ch4.sync_looping != 0)
{
ch4.sync_dmalength = ch4.sync_cache_dmalength;
}
else
{
ch4.sync_dmalength = 0;
if (ch4.sync_irq_gen != 0)
{
ch4.sync_irq_enable = 0xFF;
nes.cpu.SetIRQ(CPU.IRQ_DPCM);
}
}
}
}
}
}
if (ch4.sync_irq_enable != 0)
{
bIRQ = true;
}
return bIRQ;
}
private void UpdateFrame()
{
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)
{
throw new System.NotImplementedException();
@ -28,5 +144,299 @@ 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;
}
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

@ -0,0 +1,32 @@
using System;
namespace VirtualNes.Core
{
public class APU_MMC5 : APU_INTERFACE
{
public override void Reset(float fClock, int nRate)
{
throw new System.NotImplementedException();
}
public override void Setup(float fClock, int nRate)
{
throw new System.NotImplementedException();
}
public override void Write(ushort addr, byte data)
{
throw new System.NotImplementedException();
}
public override int Process(int channel)
{
throw new System.NotImplementedException();
}
internal void SyncWrite(ushort addr, byte data)
{
throw new NotImplementedException();
}
}
}

View File

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

View File

@ -0,0 +1,25 @@
namespace VirtualNes.Core
{
public class APU_N106 : APU_INTERFACE
{
public override void Reset(float fClock, int nRate)
{
throw new System.NotImplementedException();
}
public override void Setup(float fClock, int nRate)
{
throw new System.NotImplementedException();
}
public override void Write(ushort addr, byte data)
{
throw new System.NotImplementedException();
}
public override int Process(int channel)
{
throw new System.NotImplementedException();
}
}
}

View File

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

View File

@ -0,0 +1,310 @@
using System;
namespace VirtualNes.Core
{
public class APU_VRC6 : APU_INTERFACE
{
public const int RECTANGLE_VOL_SHIFT = 8;
public const int SAWTOOTH_VOL_SHIFT = 6;
private RECTANGLE ch0 = new RECTANGLE();
private RECTANGLE ch1 = new RECTANGLE();
private SAWTOOTH ch2 = new SAWTOOTH();
private int cycle_rate;
private float cpu_clock;
public APU_VRC6()
{
Reset(APU_CLOCK, 22050);
}
public override void Reset(float fClock, int nRate)
{
ch0.ZeroMemory();
ch1.ZeroMemory();
ch2.ZeroMemory();
Setup(fClock, nRate);
}
public override void Setup(float fClock, int nRate)
{
cpu_clock = fClock;
cycle_rate = (int)(fClock * 65536.0f / nRate);
}
public override void Write(ushort addr, byte data)
{
switch (addr)
{
// VRC6 CH0 rectangle
case 0x9000:
ch0.reg[0] = data;
ch0.gate = (byte)(data & 0x80);
ch0.volume = (byte)(data & 0x0F);
ch0.duty_pos = (byte)((data >> 4) & 0x07);
break;
case 0x9001:
ch0.reg[1] = data;
ch0.freq = INT2FIX((((ch0.reg[2] & 0x0F) << 8) | data) + 1);
break;
case 0x9002:
ch0.reg[2] = data;
ch0.enable = (byte)(data & 0x80);
ch0.freq = INT2FIX((((data & 0x0F) << 8) | ch0.reg[1]) + 1);
break;
// VRC6 CH1 rectangle
case 0xA000:
ch1.reg[0] = data;
ch1.gate = (byte)(data & 0x80);
ch1.volume = (byte)(data & 0x0F);
ch1.duty_pos = (byte)((data >> 4) & 0x07);
break;
case 0xA001:
ch1.reg[1] = data;
ch1.freq = INT2FIX((((ch1.reg[2] & 0x0F) << 8) | data) + 1);
break;
case 0xA002:
ch1.reg[2] = data;
ch1.enable = (byte)(data & 0x80);
ch1.freq = INT2FIX((((data & 0x0F) << 8) | ch1.reg[1]) + 1);
break;
// VRC6 CH2 sawtooth
case 0xB000:
ch2.reg[1] = data;
ch2.phaseaccum = (byte)(data & 0x3F);
break;
case 0xB001:
ch2.reg[1] = data;
ch2.freq = INT2FIX((((ch2.reg[2] & 0x0F) << 8) | data) + 1);
break;
case 0xB002:
ch2.reg[2] = data;
ch2.enable = (byte)(data & 0x80);
ch2.freq = INT2FIX((((data & 0x0F) << 8) | ch2.reg[1]) + 1);
// ch2.adder = 0; // 僋儕傾偡傞偲僲僀僘偺尨場偵側傞
// ch2.accum = 0; // 僋儕傾偡傞偲僲僀僘偺尨場偵側傞
break;
}
}
public override int Process(int channel)
{
switch (channel)
{
case 0:
return RectangleRender(ch0);
case 1:
return RectangleRender(ch1);
case 2:
return SawtoothRender(ch2);
}
return 0;
}
public override int GetFreq(int channel)
{
if (channel == 0 || channel == 1)
{
RECTANGLE ch = null;
if (channel == 0) ch = ch0;
else ch = ch1;
if (ch.enable == 0 || ch.gate != 0 || ch.volume == 0)
return 0;
if (ch.freq < INT2FIX(8))
return 0;
return (int)(256.0f * cpu_clock / (FIX2INT(ch.freq) * 16.0f));
}
if (channel == 2)
{
SAWTOOTH ch = ch2;
if (ch.enable == 0 || ch.phaseaccum == 0)
return 0;
if (ch.freq < INT2FIX(8))
return 0;
return (int)(256.0f * cpu_clock / (FIX2INT(ch.freq) * 14.0f));
}
return 0;
}
private int RectangleRender(RECTANGLE ch)
{
// Enable?
if (ch.enable == 0)
{
ch.output_vol = 0;
ch.adder = 0;
return ch.output_vol;
}
// Digitized output
if (ch.gate != 0)
{
ch.output_vol = ch.volume << RECTANGLE_VOL_SHIFT;
return ch.output_vol;
}
// 堦掕埲忋偺廃攇悢偼張棟偟側偄(柍懯)
if (ch.freq < INT2FIX(8))
{
ch.output_vol = 0;
return ch.output_vol;
}
ch.phaseacc -= cycle_rate;
if (ch.phaseacc >= 0)
return ch.output_vol;
int output = ch.volume << RECTANGLE_VOL_SHIFT;
if (ch.freq > cycle_rate)
{
// add 1 step
ch.phaseacc += ch.freq;
ch.adder = (byte)((ch.adder + 1) & 0x0F);
if (ch.adder <= ch.duty_pos)
ch.output_vol = output;
else
ch.output_vol = -output;
}
else
{
// average calculate
int num_times, total;
num_times = total = 0;
while (ch.phaseacc < 0)
{
ch.phaseacc += ch.freq;
ch.adder = (byte)((ch.adder + 1) & 0x0F);
if (ch.adder <= ch.duty_pos)
total += output;
else
total += -output;
num_times++;
}
ch.output_vol = total / num_times;
}
return ch.output_vol;
}
private int SawtoothRender(SAWTOOTH ch)
{
// Digitized output
if (ch.enable == 0)
{
ch.output_vol = 0;
return ch.output_vol;
}
// 堦掕埲忋偺廃攇悢偼張棟偟側偄(柍懯)
if (ch.freq < INT2FIX(9))
{
return ch.output_vol;
}
ch.phaseacc -= cycle_rate / 2;
if (ch.phaseacc >= 0)
return ch.output_vol;
if (ch.freq > cycle_rate / 2)
{
// add 1 step
ch.phaseacc += ch.freq;
if (++ch.adder >= 7)
{
ch.adder = 0;
ch.accum = 0;
}
ch.accum += ch.phaseaccum;
ch.output_vol = ch.accum << SAWTOOTH_VOL_SHIFT;
}
else
{
// average calculate
int num_times, total;
num_times = total = 0;
while (ch.phaseacc < 0)
{
ch.phaseacc += ch.freq;
if (++ch.adder >= 7)
{
ch.adder = 0;
ch.accum = 0;
}
ch.accum += ch.phaseaccum;
total += ch.accum << SAWTOOTH_VOL_SHIFT;
num_times++;
}
ch.output_vol = (total / num_times);
}
return ch.output_vol;
}
private class RECTANGLE
{
public byte[] reg = new byte[3];
public byte enable;
public byte gate;
public byte volume;
public int phaseacc;
public int freq;
public int output_vol;
public byte adder;
public byte duty_pos;
public void ZeroMemory()
{
Array.Clear(reg, 0, reg.Length);
enable = default;
gate = default;
volume = default;
phaseacc = default;
freq = default;
output_vol = default;
adder = default;
duty_pos = default;
}
}
private class SAWTOOTH
{
public byte[] reg = new byte[3];
public byte enable;
public byte volume;
public int phaseacc;
public int freq;
public int output_vol;
public byte adder;
public byte accum;
public byte phaseaccum;
public void ZeroMemory()
{
Array.Clear(reg, 0, reg.Length);
enable = default;
volume = default;
phaseacc = default;
freq = default;
output_vol = default;
adder = default;
accum = default;
phaseaccum = default;
}
}
}
}

View File

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

View File

@ -0,0 +1,25 @@
namespace VirtualNes.Core
{
public class APU_VRC7 : APU_INTERFACE
{
public override void Reset(float fClock, int nRate)
{
throw new System.NotImplementedException();
}
public override void Setup(float fClock, int nRate)
{
throw new System.NotImplementedException();
}
public override void Write(ushort addr, byte data)
{
throw new System.NotImplementedException();
}
public override int Process(int channel)
{
throw new System.NotImplementedException();
}
}
}

View File

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

View File

@ -0,0 +1,28 @@
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,36 @@
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,85 @@
using System;
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;
public void ZeroMemory()
{
Array.Clear(reg, 0, reg.Length);
enable = 0;
holdnote = 0;
volume = 0;
complement = 0;
phaseacc = 0;
freq = 0;
freqlimit = 0;
adder = 0;
duty = 0;
len_count = 0;
nowvolume = 0;
env_fixed = 0;
env_decay = 0;
env_count = 0;
dummy0 = 0;
env_vol = 0;
swp_on = 0;
swp_inc = 0;
swp_shift = 0;
swp_decay = 0;
swp_count = 0;
Array.Clear(dummy1, 0, dummy1.Length);
Array.Clear(sync_reg, 0, sync_reg.Length);
sync_output_enable = 0;
sync_enable = 0;
sync_holdnote = 0;
dummy2 = 0;
sync_len_count = 0;
}
}
}

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:

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
namespace VirtualNes.Core
{
public class CHEATCODE
{
public byte enable;
public byte type;
public byte length;
public ushort address;
public uint data;
public string comment;
}
class GENIECODE
{
public ushort address;
public byte data;
public byte cmp;
};
}

View File

@ -1,22 +1,17 @@
using Codice.CM.Client.Differences;
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting.Antlr3.Runtime.Tree;
using UnityEngine;
namespace VirtualNes.Core
{
public static class CRC
{
const int CHAR_BIT = 8;
const ulong CRCPOLY1 = 0x04C11DB7UL;
const ulong CRCPOLY2 = 0xEDB88320UL;
const uint CRCPOLY1 = 0x04C11DB7U;
const uint CRCPOLY2 = 0xEDB88320U;
static bool m_Init;
static bool m_InitRev;
static ulong[] m_CrcTable = new ulong[byte.MaxValue + 1];
static ulong[] m_CrcTableRev = new ulong[byte.MaxValue + 1];
static uint[] m_CrcTable = new uint[byte.MaxValue + 1];
static uint[] m_CrcTableRev = new uint[byte.MaxValue + 1];
public static ulong Crc(int size, Span<byte> c)
{
@ -35,7 +30,7 @@ namespace VirtualNes.Core
}
return ~r & 0xFFFFFFFFUL;
}
public static ulong CrcRev(int size, Span<byte> c)
public static uint CrcRev(int size, Span<byte> c)
{
if (!m_InitRev)
{
@ -43,41 +38,41 @@ namespace VirtualNes.Core
m_InitRev = true;
}
ulong r = 0xFFFFFFFFUL;
uint r = 0xFFFFFFFFU;
int step = 0;
while (--size >= 0)
{
r = (r >> CHAR_BIT) ^ m_CrcTableRev[(byte)r ^ c[step]];
step++;
}
return r ^ 0xFFFFFFFFUL;
return r ^ 0xFFFFFFFFU;
}
static void MakeTable()
{
int i, j;
ulong r;
uint r;
for (i = 0; i <= byte.MaxValue; i++)
{
r = (ulong)i << (32 - CHAR_BIT);
r = (uint)i << (32 - CHAR_BIT);
for (j = 0; j < CHAR_BIT; j++)
{
if ((r & 0x80000000UL) > 0) r = (r << 1) ^ CRCPOLY1;
else r <<= 1;
}
m_CrcTable[i] = r & 0xFFFFFFFFUL;
m_CrcTable[i] = r & 0xFFFFFFFFU;
}
}
static void MakeTableRev()
{
int i, j;
ulong r;
uint r;
for (i = 0; i <= byte.MaxValue; i++)
{
r = (ulong)i;
r = (uint)i;
for (j = 0; j < CHAR_BIT; j++)
{
if ((r & 1) > 0) r = (r >> 1) ^ CRCPOLY2;

View File

@ -0,0 +1,145 @@
using System;
namespace VirtualNes.Core
{
public class OPLL_PATCH
{
public uint TL, FB, EG, ML, AR, DR, SL, RR, KR, KL, AM, PM, WF;
}
public class OPLL_SLOT
{
public OPLL_PATCH patch;
public int type; /* 0 : modulator 1 : carrier */
/* OUTPUT */
public Int32 feedback;
public Int32[] output = new Int32[5]; /* Output value of slot */
/* for Phase Generator (PG) */
public UInt32 sintbl; /* Wavetable */
public UInt32 phase; /* Phase */
public UInt32 dphase; /* Phase increment amount */
public UInt32 pgout; /* output */
/* for Envelope Generator (EG) */
public int fnum; /* F-Number */
public int block; /* Block */
public int volume; /* Current volume */
public int sustine; /* Sustine 1 = ON, 0 = OFF */
public UInt32 tll; /* Total Level + Key scale level*/
public UInt32 rks; /* Key scale offset (Rks) */
public int eg_mode; /* Current state */
public UInt32 eg_phase; /* Phase */
public UInt32 eg_dphase; /* Phase increment amount */
public UInt32 egout; /* output */
/* refer to opll-> */
public UInt32 plfo_pm;
public UInt32 plfo_am;
}
public class OPLL_CH
{
public int patch_number;
public int key_status;
public OPLL_SLOT mod;
public OPLL_SLOT car;
}
public class OPLL
{
public UInt32 adr;
public Int32[] output = new Int32[2];
/* Register */
public byte[] reg = new byte[0x40];
public int[] slot_on_flag = new int[18];
/* Rythm Mode : 0 = OFF, 1 = ON */
public int rythm_mode;
/* Pitch Modulator */
public UInt32 pm_phase;
public Int32 lfo_pm;
/* Amp Modulator */
public Int32 am_phase;
public Int32 lfo_am;
/* Noise Generator */
public UInt32 noise_seed;
public UInt32 whitenoise;
public UInt32 noiseA;
public UInt32 noiseB;
public UInt32 noiseA_phase;
public UInt32 noiseB_phase;
public UInt32 noiseA_idx;
public UInt32 noiseB_idx;
public UInt32 noiseA_dphase;
public UInt32 noiseB_dphase;
}
public static class Emu2413API
{
public static void OPLL_init(UInt32 c, UInt32 r)
{
makePmTable();
makeAmTable();
makeDB2LinTable();
makeAdjustTable();
makeTllTable();
makeRksTable();
makeSinTable();
makeDefaultPatch();
OPLL_setClock(c, r);
}
private static void OPLL_setClock(uint c, uint r)
{
throw new NotImplementedException();
}
private static void makeDefaultPatch()
{
throw new NotImplementedException();
}
private static void makeSinTable()
{
throw new NotImplementedException();
}
private static void makeRksTable()
{
throw new NotImplementedException();
}
private static void makeTllTable()
{
throw new NotImplementedException();
}
private static void makeAdjustTable()
{
throw new NotImplementedException();
}
private static void makeDB2LinTable()
{
throw new NotImplementedException();
}
private static void makeAmTable()
{
throw new NotImplementedException();
}
private static void makePmTable()
{
throw new NotImplementedException();
}
}
}

View File

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

View File

@ -0,0 +1,12 @@
namespace VirtualNes.Core
{
// 昤夋曽幃
public enum EnumRenderMethod
{
POST_ALL_RENDER = 0, // 僗僉儍儞儔僀儞暘偺柦椷幚峴屻丆儗儞僟儕儞僌
PRE_ALL_RENDER = 1, // 儗儞僟儕儞僌偺幚峴屻丆僗僉儍儞儔僀儞暘偺柦椷幚峴
POST_RENDER = 2, // 昞帵婜娫暘偺柦椷幚峴屻丆儗儞僟儕儞僌
PRE_RENDER = 3, // 儗儞僟儕儞僌幚峴屻丆昞帵婜娫暘偺柦椷幚峴
TILE_RENDER = 4 // 僞僀儖儀乕僗儗儞僟儕儞僌
}
}

View File

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

View File

@ -0,0 +1,17 @@
using System.Runtime.CompilerServices;
namespace VirtualNes.Core
{
public static class MemoryUtility
{
public static void ZEROMEMORY(byte[] array, uint length)
{
memset(array, 0, length);
}
public static void memset(byte[] array, byte value, uint length)
{
Unsafe.InitBlock(ref array[0], value, length);
}
}
}

View File

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

View File

@ -0,0 +1,23 @@
namespace VirtualNes.Core
{
public enum NESCOMMAND
{
NESCMD_NONE = 0,
NESCMD_HWRESET,
NESCMD_SWRESET,
NESCMD_EXCONTROLLER, // Commandparam
NESCMD_DISK_THROTTLE_ON,
NESCMD_DISK_THROTTLE_OFF,
NESCMD_DISK_EJECT,
NESCMD_DISK_0A,
NESCMD_DISK_0B,
NESCMD_DISK_1A,
NESCMD_DISK_1B,
NESCMD_DISK_2A,
NESCMD_DISK_2B,
NESCMD_DISK_3A,
NESCMD_DISK_3B,
NESCMD_SOUND_MUTE, // CommandParam
}
}

View File

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

View File

@ -0,0 +1,51 @@
namespace VirtualNes.Core
{
public class NesConfig
{
public float BaseClock; // NTSC:21477270.0 PAL:21281364.0
public float CpuClock; // NTSC: 1789772.5 PAL: 1773447.0
public int TotalScanlines; // NTSC: 262 PAL: 312
public int ScanlineCycles; // NTSC:1364 PAL:1362
public int HDrawCycles; // NTSC:1024 PAL:1024
public int HBlankCycles; // NTSC: 340 PAL: 338
public int ScanlineEndCycles; // NTSC: 4 PAL: 2
public int FrameCycles; // NTSC:29829.52 PAL:35468.94
public int FrameIrqCycles; // NTSC:29829.52 PAL:35468.94
public int FrameRate; // NTSC:60(59.94) PAL:50
public float FramePeriod; // NTSC:16.683 PAL:20.0
public static NesConfig NESCONFIG_NTSC = new NesConfig
{
BaseClock = 21477270.0f,
CpuClock = 1789772.5f,
TotalScanlines = 262,
ScanlineCycles = 1364,
HDrawCycles = 1024,
HBlankCycles = 340,
ScanlineEndCycles = 4,
FrameCycles = 1364 * 262,
FrameIrqCycles = 29830,
FrameRate = 60,
FramePeriod = 1000.0f / 60.0f
};
public static NesConfig NESCONFIG_PAL = new NesConfig
{
BaseClock = 26601714.0f,
CpuClock = 1662607.125f,
TotalScanlines = 312,
ScanlineCycles = 1278,
HDrawCycles = 960,
HBlankCycles = 318,
ScanlineEndCycles = 2,
FrameCycles = 1278 * 312,
FrameIrqCycles = 33252,
FrameRate = 50,
FramePeriod = 1000.0f / 50.0f
};
}
}

View File

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

View File

@ -1,7 +1,4 @@
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace VirtualNes.Core
{
@ -27,7 +24,7 @@ namespace VirtualNes.Core
NSF
}
public struct NSFHEADER
public class NSFHEADER
{
byte[] ID;
byte Version;
@ -43,7 +40,7 @@ namespace VirtualNes.Core
byte[] BankSwitch;
ushort SpeedPAL;
byte NTSC_PALbits;
byte ExtraChipSelect;
public byte ExtraChipSelect;
byte[] Expansion; // must be 0
@ -65,7 +62,7 @@ namespace VirtualNes.Core
}
}
public struct NESHEADER
public class NESHEADER
{
public byte[] ID;
public byte PRG_PAGE_SIZE;

View File

@ -1,13 +1,8 @@
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using UnityEngine;
namespace VirtualNes.Core
{
public static class RomPatch
{
public static void DoPatch(ref ulong crc, ref byte[] lpPRG, ref byte[] lpCHR, ref int mapper, ref NESHEADER header)
public static void DoPatch(ref uint crc, ref byte[] lpPRG, ref byte[] lpCHR, ref int mapper, ref NESHEADER header)
{
// Mapper 000
if (crc == 0x57970078)

View File

@ -0,0 +1,54 @@
using System;
namespace VirtualNes.Core
{
public class DISKFILEHDR
{
public byte[] ID = new byte[12]; // "VirtuaNES DI"
public ushort BlockVersion; // 0x0200:0.30 0x0210:0.31
public ushort Reserved;
public ulong ProgID; // 僾儘僌儔儉ID
public ushort MakerID; // 儊乕僇乕ID
public ushort DiskNo; // 僨傿僗僋悢
public ulong DifferentSize; // 憡堘悢
public byte[] ToBytes()
{
byte[] res = new byte[36];
Array.Copy(ID, res, ID.Length);
var temp = BitConverter.GetBytes(BlockVersion);
res[12] = temp[0];
res[13] = temp[1];
temp = BitConverter.GetBytes(Reserved);
res[14] = temp[0];
res[15] = temp[1];
temp = BitConverter.GetBytes(ProgID);
res[16] = temp[0];
res[17] = temp[1];
res[18] = temp[2];
res[19] = temp[3];
res[20] = temp[4];
res[21] = temp[5];
res[22] = temp[6];
res[23] = temp[7];
temp = BitConverter.GetBytes(MakerID);
res[24] = temp[0];
res[25] = temp[1];
temp = BitConverter.GetBytes(DiskNo);
res[26] = temp[0];
res[27] = temp[1];
temp = BitConverter.GetBytes(ProgID);
res[28] = temp[0];
res[29] = temp[1];
res[30] = temp[2];
res[31] = temp[3];
res[32] = temp[4];
res[33] = temp[5];
res[34] = temp[6];
res[35] = temp[7];
return res;
}
}
}

View File

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

View File

@ -0,0 +1,236 @@
using System;
namespace VirtualNes
{
public static class MMU
{
// CPU 儊儌儕僶儞僋
public static Memory<byte>[] CPU_MEM_BANK = new Memory<byte>[8]; // 8K扨埵
public static byte[] CPU_MEM_TYPE = new byte[8];
public static int[] CPU_MEM_PAGE = new int[8]; // 僗僥乕僩僙乕僽梡
// PPU 儊儌儕僶儞僋
public static Memory<byte>[] PPU_MEM_BANK = new Memory<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[] WRAM = 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
// ROM僨乕僞億僀儞僞
public static byte[] PROM; // PROM ptr
public static byte[] VROM; // VROM ptr
// For dis...
public static byte PROM_ACCESS;
// ROM 僶儞僋僒僀僘
public static int PROM_8K_SIZE, PROM_16K_SIZE, PROM_32K_SIZE;
public static int VROM_1K_SIZE, VROM_2K_SIZE, VROM_4K_SIZE, VROM_8K_SIZE;
// 儊儌儕僞僀僾
// 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偺儈儔乕
internal static void SetPROM_Bank(byte page, Memory<byte> ptr, byte type)
{
CPU_MEM_BANK[page] = ptr;
CPU_MEM_TYPE[page] = type;
CPU_MEM_PAGE[page] = 0;
}
internal static void SetPROM_8K_Bank(byte page, int bank)
{
bank %= PROM_8K_SIZE;
CPU_MEM_BANK[page] = new Memory<byte>(MMU.PROM, 0x2000 * bank, MMU.PROM.Length - 0x2000 * bank);
CPU_MEM_TYPE[page] = BANKTYPE_ROM;
CPU_MEM_PAGE[page] = bank;
}
internal static void SetPROM_16K_Bank(byte page, int bank)
{
SetPROM_8K_Bank((byte)(page + 0), bank * 2 + 0);
SetPROM_8K_Bank((byte)(page + 1), bank * 2 + 1);
}
internal static void SetPROM_32K_Bank(int bank)
{
SetPROM_8K_Bank(4, bank * 4 + 0);
SetPROM_8K_Bank(5, bank * 4 + 1);
SetPROM_8K_Bank(6, bank * 4 + 2);
SetPROM_8K_Bank(7, bank * 4 + 3);
}
internal static void SetPROM_32K_Bank(int bank0, int bank1, int bank2, int bank3)
{
SetPROM_8K_Bank(4, bank0);
SetPROM_8K_Bank(5, bank1);
SetPROM_8K_Bank(6, bank2);
SetPROM_8K_Bank(7, bank3);
}
// PPU VROM bank
internal static void SetVROM_Bank(byte page, Memory<byte> ptr, byte type)
{
PPU_MEM_BANK[page] = ptr;
PPU_MEM_TYPE[page] = type;
PPU_MEM_PAGE[page] = 0;
}
internal static void SetVROM_1K_Bank(byte page, int bank)
{
bank %= VROM_1K_SIZE;
PPU_MEM_BANK[page] = new Memory<byte>(VROM, 0x0400 * bank, VROM.Length - (0x0400 * bank));
PPU_MEM_TYPE[page] = BANKTYPE_VROM;
PPU_MEM_PAGE[page] = bank;
}
internal static void SetVROM_2K_Bank(byte page, int bank)
{
SetVROM_1K_Bank((byte)(page + 0), bank * 2 + 0);
SetVROM_1K_Bank((byte)(page + 1), bank * 2 + 1);
}
internal static void SetVROM_4K_Bank(byte page, int bank)
{
SetVROM_1K_Bank((byte)(page + 0), bank * 4 + 0);
SetVROM_1K_Bank((byte)(page + 1), bank * 4 + 1);
SetVROM_1K_Bank((byte)(page + 2), bank * 4 + 2);
SetVROM_1K_Bank((byte)(page + 3), bank * 4 + 3);
}
internal static void SetVROM_8K_Bank(int bank)
{
for (byte i = 0; i < 8; i++)
{
SetVROM_1K_Bank(i, bank * 8 + i);
}
}
internal static void SetVROM_8K_Bank(int bank0, int bank1, int bank2, int bank3,
int bank4, int bank5, int bank6, int bank7)
{
SetVROM_1K_Bank(0, bank0);
SetVROM_1K_Bank(1, bank1);
SetVROM_1K_Bank(2, bank2);
SetVROM_1K_Bank(3, bank3);
SetVROM_1K_Bank(4, bank4);
SetVROM_1K_Bank(5, bank5);
SetVROM_1K_Bank(6, bank6);
SetVROM_1K_Bank(7, bank7);
}
internal static void SetCRAM_1K_Bank(byte page, int bank)
{
bank &= 0x1F;
PPU_MEM_BANK[page] = new Memory<byte>(MMU.CRAM, 0x0400 * bank, MMU.CRAM.Length - 0x0400 * bank);
PPU_MEM_TYPE[page] = BANKTYPE_CRAM;
PPU_MEM_PAGE[page] = bank;
CRAM_USED[bank >> 2] = 0xFF; // CRAM巊梡僼儔僌
}
internal static void SetCRAM_2K_Bank(byte page, int bank)
{
SetCRAM_1K_Bank((byte)(page + 0), bank * 2 + 0);
SetCRAM_1K_Bank((byte)(page + 1), bank * 2 + 1);
}
internal static void SetCRAM_4K_Bank(byte page, int bank)
{
SetCRAM_1K_Bank((byte)(page + 0), bank * 4 + 0);
SetCRAM_1K_Bank((byte)(page + 1), bank * 4 + 1);
SetCRAM_1K_Bank((byte)(page + 2), bank * 4 + 2);
SetCRAM_1K_Bank((byte)(page + 3), bank * 4 + 3);
}
internal static void SetCRAM_8K_Bank(int bank)
{
for (byte i = 0; i < 8; i++)
{
SetCRAM_1K_Bank(i, bank * 8 + 1);
}
}
internal static void SetVRAM_1K_Bank(byte page, int bank)
{
bank &= 3;
PPU_MEM_BANK[page] = new Memory<byte>(VRAM, 0x0400 * bank, VRAM.Length - 0x0400 * bank);
PPU_MEM_TYPE[page] = BANKTYPE_VRAM;
PPU_MEM_PAGE[page] = bank;
}
internal static void SetVRAM_Bank(int bank0, int bank1, int bank2, int bank3)
{
SetVRAM_1K_Bank(8, bank0);
SetVRAM_1K_Bank(9, bank1);
SetVRAM_1K_Bank(10, bank2);
SetVRAM_1K_Bank(11, bank3);
}
internal static void SetVRAM_Mirror(int type)
{
switch (type)
{
case VRAM_HMIRROR:
SetVRAM_Bank(0, 0, 1, 1);
break;
case VRAM_VMIRROR:
SetVRAM_Bank(0, 1, 0, 1);
break;
case VRAM_MIRROR4L:
SetVRAM_Bank(0, 0, 0, 0);
break;
case VRAM_MIRROR4H:
SetVRAM_Bank(1, 1, 1, 1);
break;
case VRAM_MIRROR4:
SetVRAM_Bank(0, 1, 2, 3);
break;
}
}
internal static void SetVRAM_Mirror(int bank0, int bank1, int bank2, int bank3)
{
SetVRAM_1K_Bank(8, bank0);
SetVRAM_1K_Bank(9, bank1);
SetVRAM_1K_Bank(10, bank2);
SetVRAM_1K_Bank(11, bank3);
}
}
}

View File

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

View File

@ -1,12 +1,88 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VirtualNes.Core
{
public class Mapper
public abstract class Mapper
{
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].Span[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].Span[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

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using VirtualNes.Core.Debug;
namespace VirtualNes.Core
@ -13,14 +14,17 @@ namespace VirtualNes.Core
public ROM rom;
public PAD pad;
public Mapper mapper;
public NesConfig NesCfg;
protected List<CHEATCODE> m_CheatCode = new List<CHEATCODE>();
public NesConfig nescfg;
private List<CHEATCODE> m_CheatCode = new List<CHEATCODE>();
private List<GENIECODE> m_GenieCode = new List<GENIECODE>();
private bool m_bDiskThrottle;
private int m_CommandRequest;
private int m_nSnapNo;
private bool m_bNsfPlaying;
private bool m_bNsfInit;
private int m_nNsfSongNo;
private int m_nNsfSongMode;
private bool m_bMoviePlay;
private bool m_bMovieRec;
private Stream m_fpMovie;
@ -30,12 +34,32 @@ namespace VirtualNes.Core
private double m_TapeCycles;
private byte m_TapeIn;
private byte m_TapeOut;
// For Barcode
private bool m_bBarcode;
private byte m_BarcodeOut;
private byte m_BarcodePtr;
private int m_BarcodeCycles;
private byte[] m_BarcodeData = new byte[256];
// For Barcode
private bool m_bBarcode2;
private int m_Barcode2seq;
private int m_Barcode2ptr;
private int m_Barcode2cnt;
private byte m_Barcode2bit;
private byte[] m_Barcode2data = new byte[32];
private int m_TurboFileBank;
private int SAVERAM_SIZE;
private int nIRQtype;
private bool bFrameIRQ;
private bool bVideoMode;
private int NES_scanline;
private EnumRenderMethod RenderMethod;
private bool bZapper;
private long base_cycles;
private long emul_cycles;
public NES(string fname)
{
@ -75,7 +99,7 @@ namespace VirtualNes.Core
bVideoMode = false;
NesCfg = NesConfig.GetNTSC();
nescfg = NesConfig.NESCONFIG_NTSC;
CheatInitial();
@ -102,65 +126,684 @@ namespace VirtualNes.Core
}
}
public void Command(NESCOMMAND cmd)
{
CommandParam(cmd, 0);
}
public bool CommandParam(NESCOMMAND cmd, int param)
{
switch (cmd)
{
case NESCOMMAND.NESCMD_NONE: break;
case NESCOMMAND.NESCMD_HWRESET:
Reset();
m_CommandRequest = (int)cmd;
break;
case NESCOMMAND.NESCMD_SWRESET:
SoftReset();
m_CommandRequest = (int)cmd;
break;
default:
throw new NotImplementedException($"{cmd} not impl right now");
}
return true;
}
public void CheatInitial()
{
m_CheatCode.Clear();
}
}
public struct NesConfig
{
public float BaseClock; // NTSC:21477270.0 PAL:21281364.0
public float CpuClock; // NTSC: 1789772.5 PAL: 1773447.0
public int TotalScanLines; // NTSC: 262 PAL: 312
public int ScanlingCycles; // NTSC:1364 PAL:1362
public int HDrawCycles; // NTSC:1024 PAL:1024
public int HBlankCycles; // NTSC: 340 PAL: 338
public int ScanlineEndCycles; // NTSC: 4 PAL: 2
public int FrameCycles; // NTSC:29829.52 PAL:35468.94
public int FrameIrqCycles; // NTSC:29829.52 PAL:35468.94
public int FrameRate; // NTSC:60(59.94) PAL:50
public float FramePeriod; // NTSC:16.683 PAL:20.0
public static NesConfig GetNTSC()
public void EmulateFrame(bool bDraw)
{
return new NesConfig
int scanline = 0;
if (rom.IsNSF())
{
BaseClock = 21477270.0f,
CpuClock = 1789772.5f,
TotalScanLines = 262,
ScanlingCycles = 1364,
HDrawCycles = 1024,
HBlankCycles = 340,
ScanlineEndCycles = 4,
FrameCycles = 1364 * 262,
FrameIrqCycles = 29830,
FrameRate = 60,
FramePeriod = 1000.0f / 60.0f
};
EmulateNSF();
return;
}
CheatCodeProcess();
NES_scanline = scanline;
if (RenderMethod != EnumRenderMethod.TILE_RENDER)
{
bZapper = false;
while (true)
{
ppu.SetRenderScanline(scanline);
if (scanline == 0)
{
if (RenderMethod < EnumRenderMethod.POST_RENDER)
{
EmulationCPU(nescfg.ScanlineCycles);
}
}
}
}
}
public static NesConfig GetPAL()
internal void EmulationCPU(int basecycles)
{
return new NesConfig
int cycles;
base_cycles += basecycles;
cycles = (int)((base_cycles / 12) - emul_cycles);
if (cycles > 0)
{
BaseClock = 26601714.0f,
CpuClock = 1662607.125f,
TotalScanLines = 312,
ScanlingCycles = 1278,
HDrawCycles = 960,
HBlankCycles = 318,
ScanlineEndCycles = 2,
FrameCycles = 1278 * 312,
FrameIrqCycles = 33252,
FrameRate = 50,
FramePeriod = 1000.0f / 50.0f
};
emul_cycles += cpu.EXEC(cycles);
}
}
internal void Reset()
{
SaveSRAM();
SaveDISK();
SaveTurboFile();
// RAM Clear
MemoryUtility.ZEROMEMORY(MMU.RAM, (uint)MMU.RAM.Length);
if (rom.GetPROM_CRC() == 0x29401686)
{ // Minna no Taabou no Nakayoshi Dai Sakusen(J)
MemoryUtility.memset(MMU.RAM, 0xFF, (uint)MMU.RAM.Length);
}
// RAM set
if (!rom.IsSAVERAM() && rom.GetMapperNo() != 20)
{
MemoryUtility.memset(MMU.WRAM, 0xFF, (uint)MMU.WRAM.Length);
}
MemoryUtility.ZEROMEMORY(MMU.CRAM, (uint)MMU.CRAM.Length);
MemoryUtility.ZEROMEMORY(MMU.VRAM, (uint)MMU.VRAM.Length);
MemoryUtility.ZEROMEMORY(MMU.SPRAM, (uint)MMU.SPRAM.Length);
MemoryUtility.ZEROMEMORY(MMU.BGPAL, (uint)MMU.BGPAL.Length);
MemoryUtility.ZEROMEMORY(MMU.SPPAL, (uint)MMU.SPPAL.Length);
MemoryUtility.ZEROMEMORY(MMU.CPUREG, (uint)MMU.CPUREG.Length);
MemoryUtility.ZEROMEMORY(MMU.PPUREG, (uint)MMU.PPUREG.Length);
m_bDiskThrottle = false;
SetRenderMethod(EnumRenderMethod.PRE_RENDER);
if (rom.IsPAL())
{
SetVideoMode(true);
}
MMU.PROM = rom.GetPROM();
MMU.VROM = rom.GetVROM();
MMU.PROM_8K_SIZE = rom.GetPROM_SIZE() * 2;
MMU.PROM_16K_SIZE = rom.GetPROM_SIZE();
MMU.PROM_32K_SIZE = rom.GetPROM_SIZE() / 2;
MMU.VROM_1K_SIZE = rom.GetVROM_SIZE() * 8;
MMU.VROM_2K_SIZE = rom.GetVROM_SIZE() * 4;
MMU.VROM_4K_SIZE = rom.GetVROM_SIZE() * 2;
MMU.VROM_8K_SIZE = rom.GetVROM_SIZE();
// デフォルトバンク
if (MMU.VROM_8K_SIZE != 0)
{
MMU.SetVROM_8K_Bank(0);
}
else
{
MMU.SetCRAM_8K_Bank(0);
}
// ミラー
if (rom.Is4SCREEN())
{
MMU.SetVRAM_Mirror(MMU.VRAM_MIRROR4);
}
else if (rom.IsVMIRROR())
{
MMU.SetVRAM_Mirror(MMU.VRAM_VMIRROR);
}
else
{
MMU.SetVRAM_Mirror(MMU.VRAM_HMIRROR);
}
apu.SelectExSound(0);
ppu.Reset();
mapper.Reset();
// Trainer
if (rom.IsTRAINER())
{
Array.Copy(rom.GetTRAINER(), 0, MMU.WRAM, 0x1000, 512);
}
pad.Reset();
cpu.Reset();
apu.Reset();
if (rom.IsNSF())
{
mapper.Reset();
}
base_cycles = emul_cycles = 0;
}
internal void SetVideoMode(bool bMode)
{
bVideoMode = bMode;
if (!bVideoMode)
{
nescfg = NesConfig.NESCONFIG_NTSC;
}
else
{
nescfg = NesConfig.NESCONFIG_PAL;
}
apu.SoundSetup();
}
public void SetRenderMethod(EnumRenderMethod type)
{
RenderMethod = type;
}
internal void SoftReset()
{
pad.Reset();
cpu.Reset();
apu.Reset();
if (rom.IsNSF())
{
mapper.Reset();
}
m_bDiskThrottle = false;
base_cycles = emul_cycles = 0;
}
internal void EmulateNSF()
{
R6502 reg = null;
ppu.Reset();
mapper.VSync();
//DEBUGOUT( "Frame\n" );
if (m_bNsfPlaying)
{
if (m_bNsfInit)
{
MemoryUtility.ZEROMEMORY(MMU.RAM, (uint)MMU.RAM.Length);
if ((rom.GetNsfHeader().ExtraChipSelect & 0x04) == 0)
{
MemoryUtility.ZEROMEMORY(MMU.RAM, 0x2000);
}
apu.Reset();
apu.Write(0x4015, 0x0F);
apu.Write(0x4017, 0xC0);
apu.ExWrite(0x4080, 0x80); // FDS Volume 0
apu.ExWrite(0x408A, 0xE8); // FDS Envelope Speed
cpu.GetContext(ref reg);
reg.PC = 0x4710; // Init Address
reg.A = (byte)m_nNsfSongNo;
reg.X = (byte)m_nNsfSongMode;
reg.Y = 0;
reg.S = 0xFF;
reg.P = CPU.Z_FLAG | CPU.R_FLAG | CPU.I_FLAG;
// 安全対策を兼ねてあえてループに(1秒分)
for (int i = 0; i < nescfg.TotalScanlines * 60; i++)
{
EmulationCPU(nescfg.ScanlineCycles);
cpu.GetContext(ref reg);
// 無限ループに入ったことを確認したら抜ける
if (reg.PC == 0x4700)
{
break;
}
}
m_bNsfInit = false;
}
cpu.GetContext(ref reg);
// 無限ループに入っていたら再設定する
if (reg.PC == 0x4700)
{
reg.PC = 0x4720; // Play Address
reg.A = 0;
reg.S = 0xFF;
}
for (int i = 0; i < nescfg.TotalScanlines; i++)
{
EmulationCPU(nescfg.ScanlineCycles);
}
}
else
{
cpu.GetContext(ref reg);
reg.PC = 0x4700; // 無限ループ
reg.S = 0xFF;
EmulationCPU(nescfg.ScanlineCycles * nescfg.TotalScanlines);
}
}
internal void CheatCodeProcess()
{
//todo : ハオマヨラ<EFBE96>ラツ<EFBE97>
}
public void Dispose()
{
cpu?.Dispose();
ppu?.Dispose();
apu?.Dispose();
pad?.Dispose();
rom?.Dispose();
}
private void SaveSRAM()
{
int i;
if (rom.IsNSF()) return;
if (rom.IsSAVERAM()) return;
for (i = 0; i < SAVERAM_SIZE; i++)
{
if (MMU.WRAM[i] != 0x00)
break;
}
if (i < SAVERAM_SIZE)
{
var romName = rom.GetRomName();
Debuger.Log($"Saving SAVERAM...[{romName}]");
Supporter.SaveSRAMToFile(MMU.WRAM, romName);
}
}
private void SaveDISK()
{
if (rom.GetMapperNo() != 20)
return;
int i = 0;
Stream fp = null;
DISKFILEHDR ifh;
byte[] lpDisk = rom.GetPROM();
byte[] lpWrite = rom.GetDISK();
long DiskSize = 16 + 65500 * rom.GetDiskNo();
ulong data;
try
{
ifh = new DISKFILEHDR();
ifh.ID = ASCIIEncoding.ASCII.GetBytes("VirtuaNES DI");
ifh.BlockVersion = 0x0210;
ifh.ProgID = rom.GetGameID();
ifh.MakerID = (ushort)rom.GetMakerID();
ifh.DiskNo = (ushort)rom.GetDiskNo();
for (i = 16; i < DiskSize; i++)
{
if (lpWrite[i] > 0)
ifh.DifferentSize++;
}
if (ifh.DifferentSize == 0)
return;
List<byte> contents = new List<byte>();
contents.AddRange(ifh.ToBytes());
for (i = 16; i < DiskSize; i++)
{
if (lpWrite[i] > 0)
{
data = (ulong)(i & 0x00FFFFFF);
data |= ((ulong)lpDisk[i] & 0xFF) << 24;
contents.AddRange(BitConverter.GetBytes(data));
}
}
Supporter.SaveDISKToFile(contents.ToArray(), rom.GetRomName());
}
catch (Exception ex)
{
Debuger.LogError(ex.ToString());
}
}
private void SaveTurboFile()
{
//todo : ハオマヨ
}
internal void Clock(int cycles)
{
Tape(cycles);
Barcode(cycles);
}
private void Barcode(int cycles)
{
if (m_bBarcode)
{
m_BarcodeCycles += cycles;
if (m_BarcodeCycles > 1000)
{
m_BarcodeCycles = 0;
// 停止?
if (m_BarcodeData[m_BarcodePtr] != 0xFF)
{
m_BarcodeOut = m_BarcodeData[m_BarcodePtr++];
}
else
{
m_bBarcode = false;
m_BarcodeOut = 0;
Debuger.Log("Barcode data trasnfer complete!!");
if (!(IsTapePlay() || IsTapeRec()))
{
cpu.SetClockProcess(false);
}
}
}
}
}
public bool IsTapeRec()
{
return m_bTapeRec;
}
public bool IsTapePlay()
{
return m_bTapePlay;
}
internal void Tape(int cycles)
{
//todo : ハオマヨTape (トソイ簗ヌシヌツシヘ貍メイルラ<EFBE99>ル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].Span[addr & 0x1FFF];
}
return 0x00; // Warning予防
}
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;
}
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 GenieCodeProcess()
{
ushort addr;
for (int i = 0; i < m_GenieCode.Count; i++)
{
addr = m_GenieCode[i].address;
if ((addr & 0x8000) != 0)
{
// 8character codes
if (MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF] == m_GenieCode[i].cmp)
{
MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF] = m_GenieCode[i].data;
}
}
else
{
// 6character codes
addr |= 0x8000;
MMU.CPU_MEM_BANK[addr >> 13].Span[addr & 0x1FFF] = m_GenieCode[i].data;
}
}
}
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);
MMU.CPUREG[addr & 0xFF] = data;
m_TapeIn = data;
break;
case 0x17:
MMU.CPUREG[addr & 0xFF] = data;
pad.Write(addr, data);
apu.Write(addr, data);
break;
// VirtuaNES固有ポート
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

@ -1,19 +1,29 @@
namespace VirtualNes.Core
using Codice.CM.Client.Differences;
using System;
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;
private int[][] padcnt = new int[4][]
{
new int[2],new int[2],new int[2],new int[2],
};
public uint pad1bit, pad2bit, pad3bit, pad4bit;
public PAD(NES parent)
{
@ -32,6 +42,368 @@
padbitsync[0] = padbitsync[1] = padbitsync[2] = padbitsync[3] = 0;
micbitsync = 0;
}
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() { }
internal void Write(ushort addr, byte data)
{
if (addr == 0x4016)
{
if ((data & 0x01) != 0)
{
bStrobe = true;
}
else if (bStrobe)
{
bStrobe = false;
Strobe();
if (expad != null)
{
expad.Strobe();
}
}
if (expad != null)
{
expad.Write4016(data);
}
}
if (addr == 0x4017)
{
if (expad != null)
{
expad.Write4017(data);
}
}
}
private void Strobe()
{
// For VS-Unisystem
if (nes.rom.IsVSUNISYSTEM())
{
uint pad1 = (uint)(padbitsync[0] & 0xF3);
uint pad2 = (uint)(padbitsync[1] & 0xF3);
uint st1 = (uint)(padbitsync[0] & 0x08) >> 3;
uint st2 = (uint)(padbitsync[1] & 0x08) >> 3;
switch (nVSSwapType)
{
case VSType.VS_TYPE0:
pad1bit = pad1 | (st1 << 2);
pad2bit = pad2 | (st2 << 2);
break;
case VSType.VS_TYPE1:
pad1bit = pad2 | (st1 << 2);
pad2bit = pad1 | (st2 << 2);
break;
case VSType.VS_TYPE2:
pad1bit = pad1 | (st1 << 2) | (st2 << 3);
pad2bit = pad2;
break;
case VSType.VS_TYPE3:
pad1bit = pad2 | (st1 << 2) | (st2 << 3);
pad2bit = pad1;
break;
case VSType.VS_TYPE4:
pad1bit = pad1 | (st1 << 2) | 0x08; // 0x08=Start Protect
pad2bit = pad2 | (st2 << 2) | 0x08; // 0x08=Start Protect
break;
case VSType.VS_TYPE5:
pad1bit = pad2 | (st1 << 2) | 0x08; // 0x08=Start Protect
pad2bit = pad1 | (st2 << 2) | 0x08; // 0x08=Start Protect
break;
case VSType.VS_TYPE6:
pad1bit = pad1 | (st1 << 2) | (((uint)padbitsync[0] & 0x04) << 1);
pad2bit = pad2 | (st2 << 2) | (((uint)padbitsync[1] & 0x04) << 1);
break;
case VSType.VS_TYPEZ:
pad1bit = 0;
pad2bit = 0;
break;
}
// Coin 2偲旐傞堊偵徚偡
micbit = 0;
}
else
{
if (Supporter.Config.emulator.bFourPlayer)
{
// NES type
pad1bit = padbitsync[0] | ((uint)padbitsync[2] << 8) | 0x00080000;
pad2bit = padbitsync[1] | ((uint)padbitsync[3] << 8) | 0x00040000;
}
else
{
// Famicom type
pad1bit = padbitsync[0];
pad2bit = padbitsync[1];
}
}
pad3bit = padbitsync[2];
pad4bit = padbitsync[3];
}
internal void Reset()
{
pad1bit = pad2bit = 0;
bStrobe = false;
bBarcodeWorld = false;
for (int x = 0; x < 4; x++)
{
for (int y = 0; y < 2; y++)
{
padcnt[x][y] = 0;
}
}
// Select Extension Devices
uint crc = nes.rom.GetPROM_CRC();
if (crc == 0xfbfc6a6c // Adventures of Bayou Billy, The(E)
|| crc == 0xcb275051 // Adventures of Bayou Billy, The(U)
|| crc == 0xfb69c131 // Baby Boomer(Unl)(U)
|| crc == 0xf2641ad0 // Barker Bill's Trick Shooting(U)
|| crc == 0xbc1dce96 // Chiller (Unl)(U)
|| crc == 0x90ca616d // Duck Hunt(JUE)
|| crc == 0x59e3343f // Freedom Force(U)
|| crc == 0x242a270c // Gotcha!(U)
|| crc == 0x7b5bd2de // Gumshoe(UE)
|| crc == 0x255b129c // Gun Sight(J)
|| crc == 0x8963ae6e // Hogan's Alley(JU)
|| crc == 0x51d2112f // Laser Invasion(U)
|| crc == 0x0a866c94 // Lone Ranger, The(U)
// || crc == 0xe4c04eea // Mad City(J)
|| crc == 0x9eef47aa // Mechanized Attack(U)
|| crc == 0xc2db7551 // Shooting Range(U)
|| crc == 0x163e86c0 // To The Earth(U)
|| crc == 0x42d893e4 // Operation Wolf(J)
|| crc == 0x1388aeb9 // Operation Wolf(U)
|| crc == 0x0d3cf705 // Wild Gunman(J)
|| crc == 0x389960db)
{ // Wild Gunman(JUE)
SetExController(EXCONTROLLER.EXCONTROLLER_ZAPPER);
}
if (crc == 0x35893b67 // Arkanoid(J)
|| crc == 0x6267fbd1)
{ // Arkanoid 2(J)
SetExController(EXCONTROLLER.EXCONTROLLER_PADDLE);
}
if (crc == 0xff6621ce // Hyper Olympic(J)
|| crc == 0xdb9418e8 // Hyper Olympic(Tonosama Ban)(J)
|| crc == 0xac98cd70)
{ // Hyper Sports(J)
SetExController(EXCONTROLLER.EXCONTROLLER_HYPERSHOT);
}
if (crc == 0xf9def527 // Family BASIC(Ver2.0)
|| crc == 0xde34526e // Family BASIC(Ver2.1a)
|| crc == 0xf050b611 // Family BASIC(Ver3)
|| crc == 0x3aaeed3f // Family BASIC(Ver3)(Alt)
|| crc == 0x868FCD89 // Family BASIC(Ver1.0)
|| crc == 0x2D6B7E5A // PLAYBOX BASIC(J) (Prototype_v0.0)
|| crc == 0xDA03D908)
{ // PLAYBOX BASIC (J)
SetExController(EXCONTROLLER.EXCONTROLLER_KEYBOARD);
}
if (crc == 0x589b6b0d // Supor Computer V3.0
|| crc == 0x8b265862 // Supor English
|| crc == 0x41401c6d // Supor Computer V4.0
|| crc == 0x82F1Fb96 // Supor Computer(Russia) V1.0
|| crc == 0xd5d6eac4)
{ // EDU(C) Computer
SetExController(EXCONTROLLER.EXCONTROLLER_SUPOR_KEYBOARD);
nes.SetVideoMode(true);
}
if (crc == 0xc68363f6 // Crazy Climber(J)
|| crc == 0x2989ead6 // Smash TV(U) [!]
|| crc == 0x0b8f8128)
{ // Smash TV(E) [!]
SetExController(EXCONTROLLER.EXCONTROLLER_CRAZYCLIMBER);
}
if (crc == 0x20d22251)
{ // Top rider(J)
SetExController(EXCONTROLLER.EXCONTROLLER_TOPRIDER);
}
if (crc == 0x0cd00488)
{ // Space Shadow(J)
SetExController(EXCONTROLLER.EXCONTROLLER_SPACESHADOWGUN);
}
if (crc == 0x8c8fa83b // Family Trainer - Athletic World (J)
|| crc == 0x7e704a14 // Family Trainer - Jogging Race (J)
|| crc == 0x2330a5d3)
{ // Family Trainer - Rairai Kyonshiizu (J)
SetExController(EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_A);
}
if (crc == 0xf8da2506 // Family Trainer - Aerobics Studio (J)
|| crc == 0xca26a0f1 // Family Trainer - Dai Undoukai (J)
|| crc == 0x28068b8c // Family Trainer - Fuuun Takeshi Jou 2 (J)
|| crc == 0x10bb8f9a // Family Trainer - Manhattan Police (J)
|| crc == 0xad3df455 // Family Trainer - Meiro Dai Sakusen (J)
|| crc == 0x8a5b72c0 // Family Trainer - Running Stadium (J)
|| crc == 0x59794f2d)
{ // Family Trainer - Totsugeki Fuuun Takeshi Jou (J)
SetExController(EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_B);
}
if (crc == 0x9fae4d46 // Ide Yousuke Meijin no Jissen Mahjong (J)
|| crc == 0x7b44fb2a)
{ // Ide Yousuke Meijin no Jissen Mahjong 2 (J)
SetExController(EXCONTROLLER.EXCONTROLLER_MAHJANG);
}
if (crc == 0x786148b6)
{ // Exciting Boxing (J)
SetExController(EXCONTROLLER.EXCONTROLLER_EXCITINGBOXING);
}
if (crc == 0xc3c0811d // Oeka Kids - Anpanman no Hiragana Daisuki (J)
|| crc == 0x9d048ea4)
{ // Oeka Kids - Anpanman to Oekaki Shiyou!! (J)
SetExController(EXCONTROLLER.EXCONTROLLER_OEKAKIDS_TABLET);
}
if (crc == 0x67898319)
{ // Barcode World (J)
bBarcodeWorld = true;
}
// VS-Unisystem
if (nes.rom.IsVSUNISYSTEM())
{
if (crc == 0xff5135a3 // VS Hogan's Alley
|| crc == 0xed588f00 // VS Duck Hunt
|| crc == 0x17ae56be)
{ // VS Freedom Force
SetExController(EXCONTROLLER.EXCONTROLLER_VSZAPPER);
}
else
{
SetExController(EXCONTROLLER.EXCONTROLLER_VSUNISYSTEM);
}
}
if (crc == 0x21b099f3)
{ // Gyromite (JUE)
SetExController(EXCONTROLLER.EXCONTROLLER_GYROMITE);
}
}
private void SetExController(EXCONTROLLER type)
{
excontroller_select = (int)type;
expad?.Dispose();
expad = null;
bZapperMode = false;
// ExPad Instance create
switch (type)
{
case EXCONTROLLER.EXCONTROLLER_ZAPPER:
expad = new EXPAD_Zapper(nes);
bZapperMode = true;
break;
case EXCONTROLLER.EXCONTROLLER_PADDLE:
expad = new EXPAD_Paddle(nes);
break;
case EXCONTROLLER.EXCONTROLLER_HYPERSHOT:
expad = new EXPAD_HyperShot(nes);
break;
case EXCONTROLLER.EXCONTROLLER_KEYBOARD:
expad = new EXPAD_Keyboard(nes);
break;
case EXCONTROLLER.EXCONTROLLER_SUPOR_KEYBOARD:
expad = new EXPAD_Supor_Keyboard(nes);
break;
case EXCONTROLLER.EXCONTROLLER_CRAZYCLIMBER:
expad = new EXPAD_CrazyClimber(nes);
break;
case EXCONTROLLER.EXCONTROLLER_TOPRIDER:
expad = new EXPAD_Toprider(nes);
break;
case EXCONTROLLER.EXCONTROLLER_SPACESHADOWGUN:
expad = new EXPAD_SpaceShadowGun(nes);
bZapperMode = true;
break;
case EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_A:
case EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_B:
expad = new EXPAD_FamlyTrainer(nes);
break;
case EXCONTROLLER.EXCONTROLLER_EXCITINGBOXING:
expad = new EXPAD_ExcitingBoxing(nes);
break;
case EXCONTROLLER.EXCONTROLLER_MAHJANG:
expad = new EXPAD_Mahjang(nes);
break;
case EXCONTROLLER.EXCONTROLLER_OEKAKIDS_TABLET:
expad = new EXPAD_OekakidsTablet(nes);
break;
case EXCONTROLLER.EXCONTROLLER_TURBOFILE:
expad = new EXPAD_TurboFile(nes);
break;
case EXCONTROLLER.EXCONTROLLER_VSUNISYSTEM:
expad = new EXPAD_VSUnisystem(nes);
break;
case EXCONTROLLER.EXCONTROLLER_VSZAPPER:
expad = new EXPAD_VSZapper(nes);
bZapperMode = true;
break;
case EXCONTROLLER.EXCONTROLLER_GYROMITE:
expad = new EXPAD_Gyromite(nes);
break;
default:
break;
}
if (expad != null)
{
expad.Reset();
}
}
}
public enum VSType
@ -45,4 +417,31 @@
VS_TYPE6, // SELECT1P=START1P/SELECT2P=START2P 1P/2P Reverse (For Golf)
VS_TYPEZ, // ZAPPER
}
public enum EXCONTROLLER
{
EXCONTROLLER_NONE = 0,
EXCONTROLLER_PADDLE,
EXCONTROLLER_HYPERSHOT,
EXCONTROLLER_ZAPPER,
EXCONTROLLER_KEYBOARD,
EXCONTROLLER_CRAZYCLIMBER,
EXCONTROLLER_TOPRIDER,
EXCONTROLLER_SPACESHADOWGUN,
EXCONTROLLER_FAMILYTRAINER_A,
EXCONTROLLER_FAMILYTRAINER_B,
EXCONTROLLER_EXCITINGBOXING,
EXCONTROLLER_MAHJANG,
EXCONTROLLER_OEKAKIDS_TABLET,
EXCONTROLLER_TURBOFILE,
EXCONTROLLER_VSUNISYSTEM,
EXCONTROLLER_VSZAPPER,
EXCONTROLLER_GYROMITE,
EXCONTROLLER_STACKUP,
EXCONTROLLER_SUPOR_KEYBOARD,
}
}

View File

@ -1,17 +1,124 @@
namespace VirtualNes.Core
using Codice.CM.Client.Differences;
using System;
namespace VirtualNes.Core
{
public class PPU
{
private NES m_nes;
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; // 堘偆丠
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 nes;
private bool bExtLatch; // For MMC5
private bool bChrLatch; // For MMC2/MMC4
private bool bExtNameTable; // For Super Monkey no Dai Bouken
private bool bExtMono; // For Final Fantasy
private ushort loopy_y;
private ushort loopy_shift;
private byte[] lpScreen;
/// <summary> 作为lpScreen数组的索引 </summary>
private int lpScanline;
private int ScanlineNo;
private byte[] lpColormode;
private bool bVSMode;
private int nVSColorMap;
private byte VSSecurityData;
private byte[] Bit2Rev = new byte[256];
public PPU(NES nes)
{
m_nes = nes;
this.nes = nes;
lpScreen = null;
lpColormode = null;
@ -31,5 +138,235 @@
Bit2Rev[i] = c;
}
}
public void Dispose() { }
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].Span[addr & 0x03FF];
break;
}
return data;
}
internal void SetRenderScanline(int scanline)
{
ScanlineNo = scanline;
if (scanline < 240)
{
lpScanline = (int)(Screen.SCREEN_WIDTH) * scanline;
}
}
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].Span[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));
}
}
internal void Reset()
{
bExtLatch = false;
bChrLatch = false;
bExtNameTable = false;
bExtMono = false;
MMU.PPUREG[0] = MMU.PPUREG[1] = 0;
MMU.PPU56Toggle = 0;
MMU.PPU7_Temp = 0xFF; // VS Excitebike偱偍偐偟偔側傞($2006傪撉傒偵峴偔僶僌偑偁傞)
// PPU7_Temp = 0;
MMU.loopy_v = MMU.loopy_t = 0;
MMU.loopy_x = loopy_y = 0;
loopy_shift = 0;
if (lpScreen != null)
MemoryUtility.memset(lpScreen, 0x3F, (int)(Screen.SCREEN_WIDTH) * (int)(Screen.SCREEN_HEIGHT));
if (lpColormode != null)
MemoryUtility.memset(lpColormode, 0, (int)(Screen.SCREEN_HEIGHT));
}
private enum Screen
{
SCREEN_WIDTH = 256 + 16,
SCREEN_HEIGHT = 240
}
}
}

View File

@ -1,10 +1,24 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
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

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_CrazyClimber : EXPAD
{
public EXPAD_CrazyClimber(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_ExcitingBoxing : EXPAD
{
public EXPAD_ExcitingBoxing(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_FamlyTrainer : EXPAD
{
public EXPAD_FamlyTrainer(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_Gyromite : EXPAD
{
public EXPAD_Gyromite(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_HyperShot : EXPAD
{
public EXPAD_HyperShot(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_Keyboard : EXPAD
{
public EXPAD_Keyboard(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_Mahjang : EXPAD
{
public EXPAD_Mahjang(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_OekakidsTablet : EXPAD
{
public EXPAD_OekakidsTablet(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
public class EXPAD_Paddle : EXPAD
{
public EXPAD_Paddle(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_SpaceShadowGun : EXPAD
{
public EXPAD_SpaceShadowGun(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_Supor_Keyboard : EXPAD
{
public EXPAD_Supor_Keyboard(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_Toprider : EXPAD
{
public EXPAD_Toprider(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_TurboFile : EXPAD
{
public EXPAD_TurboFile(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_VSUnisystem : EXPAD
{
public EXPAD_VSUnisystem(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,9 @@
namespace VirtualNes.Core
{
internal class EXPAD_VSZapper : EXPAD
{
public EXPAD_VSZapper(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VirtualNes.Core
{
/// <summary>
/// 光枪
/// </summary>
public class EXPAD_Zapper : EXPAD
{
public EXPAD_Zapper(NES parent) : base(parent)
{
}
}
}

View File

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

View File

@ -1,7 +1,5 @@
using Codice.CM.Client.Differences;
using System;
using System;
using System.IO;
using System.Linq;
using VirtualNes.Core.Debug;
namespace VirtualNes.Core
@ -21,13 +19,13 @@ namespace VirtualNes.Core
protected byte[] lpTrainer;
protected byte[] lpDiskBios;
protected byte[] lpDisk;
protected ulong crc;
protected ulong crcall;
protected ulong crcvrom;
protected uint crc;
protected uint crcall;
protected uint crcvrom;
protected int mapper;
protected int diskno;
protected ulong fdsmakerID;
protected ulong fdsgameID;
protected uint fdsmakerID;
protected uint fdsgameID;
public ROM(string fname)
{
@ -255,7 +253,7 @@ namespace VirtualNes.Core
crc = crcall = crcvrom = 0;
fdsmakerID = lpPRG[0x1F];
fdsgameID = (ulong)((lpPRG[0x20] << 24) | (lpPRG[0x21] << 16) | (lpPRG[0x22] << 8) | (lpPRG[0x23] << 0));
fdsgameID = (uint)((lpPRG[0x20] << 24) | (lpPRG[0x21] << 16) | (lpPRG[0x22] << 8) | (lpPRG[0x23] << 0));
}
}
else //NSF
@ -282,11 +280,34 @@ namespace VirtualNes.Core
}
}
public void Dispose()
{
lpPRG = null;
lpCHR = null;
lpTrainer = null;
lpDiskBios = null;
lpDisk = null;
}
public bool IsTRAINER()
{
return (header.control1 & (byte)EnumRomControlByte1.ROM_TRAINER) > 0;
}
public bool IsNSF()
{
return bNSF;
}
public bool IsPAL()
{
return bPAL;
}
public bool IsSAVERAM()
{
return (header.control1 & (byte)EnumRomControlByte1.ROM_SAVERAM) > 0;
}
protected void FileNameCheck(string fname)
{
if (fname.Contains("(E)"))
@ -295,6 +316,86 @@ namespace VirtualNes.Core
return;
}
}
internal string GetRomName()
{
return name;
}
internal int GetMapperNo()
{
return mapper;
}
internal byte[] GetPROM()
{
return lpPRG;
}
internal byte[] GetVROM()
{
return lpCHR;
}
internal byte[] GetDISK()
{
return lpDisk;
}
internal int GetDiskNo()
{
return diskno;
}
internal ulong GetGameID()
{
return fdsgameID;
}
internal ulong GetMakerID()
{
return fdsmakerID;
}
internal bool IsVSUNISYSTEM()
{
return (header.control2 & (byte)EnumRomControlByte2.ROM_VSUNISYSTEM) != 0;
}
internal uint GetPROM_CRC()
{
return crc;
}
internal byte GetPROM_SIZE()
{
return header.PRG_PAGE_SIZE;
}
internal byte GetVROM_SIZE()
{
return header.CHR_PAGE_SIZE;
}
internal bool Is4SCREEN()
{
return (header.control1 & (byte)EnumRomControlByte1.ROM_4SCREEN) != 0;
}
internal bool IsVMIRROR()
{
return (header.control1 & (byte)EnumRomControlByte1.ROM_VMIRROR) != 0;
}
internal byte[] GetTRAINER()
{
return lpTrainer;
}
internal NSFHEADER GetNsfHeader()
{
return nsfheader;
}
}

View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fd0714f580724604da063207fe69e274
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,6 @@
namespace VirtualNes.Core
{
public class CfgController
{
}
}

View File

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

View File

@ -0,0 +1,18 @@
namespace VirtualNes.Core
{
public class CfgEmulator
{
public bool bIllegalOp { get; set; } = false;
public bool bAutoFrameSkip { get; set; } = true;
public bool bThrottle { get; set; } = true;
public int nThrottleFPS { get; set; } = 120;
public bool bBackground { get; set; } = false;
public int nPriority { get; set; } = 3;
public bool bFourPlayer { get; set; } = true;
public bool bCrcCheck { get; set; } = true;
public bool bDiskThrottle { get; set; } = true;
public bool bLoadFullscreen { get; set; } = false;
public bool bPNGsnapshot { get; set; } = false;
public bool bAutoIPS { get; set; } = false;
}
}

View File

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

View File

@ -0,0 +1,6 @@
namespace VirtualNes.Core
{
public class CfgExtraSound
{
}
}

View File

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

View File

@ -1,6 +1,7 @@
namespace VirtualNes.Core
{
public class CHEATCODE
public class CfgGeneral
{
}
}

View File

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

View File

@ -0,0 +1,6 @@
namespace VirtualNes.Core
{
public class CfgGraphics
{
}
}

View File

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

View File

@ -0,0 +1,6 @@
namespace VirtualNes.Core
{
public class CfgLanguage
{
}
}

View File

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

View File

@ -0,0 +1,6 @@
namespace VirtualNes.Core
{
public class CfgLauncher
{
}
}

View File

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

View File

@ -0,0 +1,6 @@
namespace VirtualNes.Core
{
public class CfgMovie
{
}
}

View File

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

Some files were not shown because too many files have changed in this diff Show More