dev_4VirtualNes #18

Merged
sin365 merged 4 commits from Alienjack/AxibugEmuOnline:dev_4VirtualNes into dev_4VirtualNes 2024-07-27 00:58:01 +08:00
21 changed files with 871 additions and 69 deletions
Showing only changes of commit 51aca30cf7 - Show all commits

View File

@ -2,6 +2,7 @@ using System;
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Net.Http;
using UnityEngine; using UnityEngine;
using VirtualNes.Core; using VirtualNes.Core;
@ -45,5 +46,21 @@ namespace AxibugEmuOnline.Client
{ {
return File.Open($"{Application.streamingAssetsPath}/Disksys.rom", FileMode.Open, FileAccess.Read); 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);
}
} }
} }

View File

@ -6,6 +6,8 @@ namespace AxibugEmuOnline.Client
{ {
public class NesEmulator : MonoBehaviour public class NesEmulator : MonoBehaviour
{ {
private NES m_nesIns;
private void Start() private void Start()
{ {
StartGame("Kirby.nes"); StartGame("Kirby.nes");
@ -13,9 +15,23 @@ namespace AxibugEmuOnline.Client
public void StartGame(string romName) public void StartGame(string romName)
{ {
StopGame();
Supporter.Setup(new CoreSupporter()); Supporter.Setup(new CoreSupporter());
Debuger.Setup(new CoreDebuger()); 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

@ -8,7 +8,7 @@ namespace VirtualNes.Core
{ {
private NES nes; private NES nes;
private byte exsound_select; private byte exsound_select;
private APU_INTERNAL @internal; private APU_INTERNAL @internal = new APU_INTERNAL();
private int last_data; private int last_data;
private int last_diff; private int last_diff;
protected short[] m_SoundBuffer = new short[256]; protected short[] m_SoundBuffer = new short[256];
@ -19,8 +19,6 @@ namespace VirtualNes.Core
public APU(NES parent) public APU(NES parent)
{ {
@internal = new APU_INTERNAL();
exsound_select = 0; exsound_select = 0;
nes = parent; nes = parent;
@ -36,6 +34,15 @@ namespace VirtualNes.Core
for (int i = 0; i < m_bMute.Length; i++) for (int i = 0; i < m_bMute.Length; i++)
m_bMute[i] = true; m_bMute[i] = true;
} }
public void Dispose()
{
}
internal void SyncDPCM(int cycles)
{
@internal.Sync(cycles);
}
} }
public struct QUEUEDATA public struct QUEUEDATA

View File

@ -3,12 +3,39 @@ namespace VirtualNes.Core
public class APU_INTERNAL : APU_INTERFACE public class APU_INTERNAL : APU_INTERFACE
{ {
private NES nes; private NES nes;
private int FrameCycle;
private byte FrameIRQoccur;
public void SetParent(NES parent) public void SetParent(NES parent)
{ {
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)
{
//TODO : ʵÏÖ
return false;
}
private void UpdateFrame()
{
//TODO : ʵÏÖ
}
public override void Reset(float fClock, int nRate) public override void Reset(float fClock, int nRate)
{ {
throw new System.NotImplementedException(); throw new System.NotImplementedException();

View File

@ -1,12 +1,221 @@
namespace VirtualNes.Core #undef DPCM_SYNCCLOCK
using Codice.CM.Client.Differences;
using System;
using System.Runtime.CompilerServices;
namespace VirtualNes.Core
{ {
public class CPU public class CPU
{ {
protected NES m_nes; // 6502 status flags
public const byte C_FLAG = 0x01; // 1: Carry
public const byte Z_FLAG = 0x02; // 1: Zero
public const byte I_FLAG = 0x04; // 1: Irq disabled
public const byte D_FLAG = 0x08; // 1: Decimal mode flag (NES unused)
public const byte B_FLAG = 0x10; // 1: Break
public const byte R_FLAG = 0x20; // 1: Reserved (Always 1)
public const byte V_FLAG = 0x40; // 1: Overflow
public const byte N_FLAG = 0x80; // 1: Negative
// Interrupt
public const byte NMI_FLAG = 0x01;
public const byte IRQ_FLAG = 0x02;
public const byte IRQ_FRAMEIRQ = 0x04;
public const byte IRQ_DPCM = 0x08;
public const byte IRQ_MAPPER = 0x10;
public const byte IRQ_MAPPER2 = 0x20;
public const byte IRQ_TRIGGER = 0x40; // one shot(媽IRQ())
public const byte IRQ_TRIGGER2 = 0x80; // one shot(媽IRQ_NotPending())
public static readonly byte IRQ_MASK = unchecked((byte)(~(NMI_FLAG | IRQ_FLAG)));
// Vector
public const ushort NMI_VECTOR = 0xFFFA;
public const ushort RES_VECTOR = 0xFFFC;
public const ushort IRQ_VECTOR = 0xFFFE;
private NES nes;
private bool m_bClockProcess;
private int TOTAL_cycles;
private int DMA_cycles;
private Mapper mapper;
private APU apu;
private R6502 R;
private byte[] ZN_Table = new byte[256];
public CPU(NES parent) public CPU(NES parent)
{ {
m_nes = parent; nes = parent;
m_bClockProcess = false;
}
public void Dispose() { }
internal long EXEC(int request_cycles)
{
byte opcode = 0;
int OLD_cycles = TOTAL_cycles;
int exec_cycles = 0;
byte nmi_request = 0, irq_request = 0;
ushort EA = 0;
ushort ET = 0;
ushort WT = 0;
byte DT = 0;
while (request_cycles > 0)
{
exec_cycles = 0;
if (DMA_cycles > 0)
{
if (request_cycles <= DMA_cycles)
{
DMA_cycles -= request_cycles;
TOTAL_cycles += request_cycles;
mapper.Clock(request_cycles);
#if DPCM_SYNCCLOCK
apu.SyncDPCM(request_cycles);
#endif
if (m_bClockProcess)
{
nes.Clock(request_cycles);
}
goto _execute_exit;
}
else
{
exec_cycles += DMA_cycles;
DMA_cycles = 0;
}
}
nmi_request = irq_request = 0;
opcode = OP6502(R.PC++);
if (R.Int_Pending != 0)
{
if ((R.Int_Pending & NMI_FLAG) != 0)
{
nmi_request = 0xFF;
byte temp = unchecked((byte)(~NMI_FLAG));
R.Int_Pending &= temp;
}
else if ((R.Int_Pending & IRQ_MASK) != 0)
{
byte temp = unchecked((byte)(~IRQ_TRIGGER2));
R.Int_Pending &= temp;
if (
((R.P & I_FLAG) == 0)
&&
(opcode != 0x40)
)
{
irq_request = 0xFF;
temp = unchecked((byte)(~IRQ_TRIGGER));
R.Int_Pending &= temp;
}
}
}
switch (opcode)
{
case 0x69:
MR_IM(ref DT, ref R); ADC(ref WT, ref DT, ref R);
ADD_CYCLE(2, ref exec_cycles);
break;
case 0x65:
MR_ZP(ref EA, ref DT, ref R); ADC(ref WT, ref DT, ref R);
ADD_CYCLE(3, ref exec_cycles);
break;
case 0x75:
MR_ZX(ref DT, ref EA, ref R); ADC(ref WT, ref DT, ref R);
ADD_CYCLE(4, ref exec_cycles);
break;
}
}
_execute_exit:
#if !DPCM_SYNCCLOCK
apu.SyncDPCM(TOTAL_cycles - OLD_cycles);
#endif
return TOTAL_cycles - OLD_cycles;
}
internal void SetClockProcess(bool bEnable)
{
m_bClockProcess = bEnable;
}
internal byte OP6502(ushort addr)
{
return MMU.CPU_MEM_BANK[addr >> 13][addr & 0x1FFF];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MR_IM(ref byte DT, ref R6502 R)
{
DT = OP6502(R.PC++);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MR_ZP(ref ushort EA, ref byte DT, ref R6502 R)
{
EA = OP6502(R.PC++);
DT = ZPRD(ref EA);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private byte ZPRD(ref ushort A)
{
return MMU.RAM[A];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ADC(ref ushort WT, ref byte DT, ref R6502 R)
{
WT = (ushort)(R.A + DT + (R.P & C_FLAG));
TST_FLAG(WT > 0xFF, C_FLAG, ref R);
var temp = ((~(R.A ^ DT)) & (R.A ^ WT) & 0x80);
TST_FLAG(temp != 0, V_FLAG, ref R);
R.A = (byte)WT;
SET_ZN_FLAG(R.A, ref R);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void TST_FLAG(bool F, byte V, ref R6502 R)
{
byte temp = (byte)~V;
R.P &= temp;
if (F) R.P |= V;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void SET_ZN_FLAG(byte A, ref R6502 R)
{
byte temp = unchecked((byte)(~(Z_FLAG | N_FLAG)));
R.P &= temp;
R.P |= ZN_Table[A];
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void ADD_CYCLE(int V, ref int exec_cycles)
{
exec_cycles += V;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void MR_ZX(ref byte DT, ref ushort EA, ref R6502 R)
{
DT = OP6502(R.PC++);
EA = (ushort)(DT + R.X);
DT = ZPRD(ref EA);
} }
} }
@ -44,13 +253,13 @@
public struct R6502 public struct R6502
{ {
ushort PC; public ushort PC;
byte A; public byte A;
byte P; public byte P;
byte X; public byte X;
byte Y; public byte Y;
byte S; public byte S;
byte Int_Pending; public byte Int_Pending;
} }
} }

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
{
// 昤夋曽幃
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,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,58 @@
namespace VirtualNes.Core
{
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 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 GetNTSC()
{
return 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 GetPAL()
{
return 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

@ -0,0 +1,59 @@
using Codice.CM.Client.Differences;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VirtualNes.Core
{
public class 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,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VirtualNes
{
public static class MMU
{
// CPU 儊儌儕僶儞僋
public static byte[][] CPU_MEM_BANK = new byte[8][]; // 8K扨埵
// NES儊儌儕
public static byte[] RAM = new byte[8 * 1024]; // NES撪憻RAM
public static byte[] WARM = new byte[128 * 1024]; // 儚乕僋/僶僢僋傾僢僾RAM
}
}

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

@ -6,7 +6,8 @@ using System.Threading.Tasks;
namespace VirtualNes.Core namespace VirtualNes.Core
{ {
public class Mapper public abstract class Mapper
{ {
internal virtual void Clock(int cycles) { }
} }
} }

View File

@ -1,6 +1,8 @@
using Codice.CM.Client.Differences;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Text;
using VirtualNes.Core.Debug; using VirtualNes.Core.Debug;
namespace VirtualNes.Core namespace VirtualNes.Core
@ -13,10 +15,9 @@ namespace VirtualNes.Core
public ROM rom; public ROM rom;
public PAD pad; public PAD pad;
public Mapper mapper; public Mapper mapper;
public NesConfig NesCfg; public NesConfig nescfg;
protected List<CHEATCODE> m_CheatCode = new List<CHEATCODE>();
private List<CHEATCODE> m_CheatCode = new List<CHEATCODE>();
private bool m_bDiskThrottle; private bool m_bDiskThrottle;
private int m_CommandRequest; private int m_CommandRequest;
private int m_nSnapNo; private int m_nSnapNo;
@ -30,12 +31,32 @@ namespace VirtualNes.Core
private double m_TapeCycles; private double m_TapeCycles;
private byte m_TapeIn; private byte m_TapeIn;
private byte m_TapeOut; 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 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 m_TurboFileBank;
private int SAVERAM_SIZE; private int SAVERAM_SIZE;
private int nIRQtype; private int nIRQtype;
private bool bFrameIRQ; private bool bFrameIRQ;
private bool bVideoMode; 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) public NES(string fname)
{ {
@ -75,7 +96,7 @@ namespace VirtualNes.Core
bVideoMode = false; bVideoMode = false;
NesCfg = NesConfig.GetNTSC(); nescfg = NesConfig.GetNTSC();
CheatInitial(); CheatInitial();
@ -102,65 +123,241 @@ 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() public void CheatInitial()
{ {
m_CheatCode.Clear(); m_CheatCode.Clear();
} }
public void EmulateFrame(bool bDraw)
{
int scanline = 0;
if (rom.IsNSF())
{
EmulateNSF();
return;
} }
public struct NesConfig CheatCodeProcess();
NES_scanline = scanline;
if (RenderMethod != EnumRenderMethod.TILE_RENDER)
{ {
public float BaseClock; // NTSC:21477270.0 PAL:21281364.0 bZapper = false;
public float CpuClock; // NTSC: 1789772.5 PAL: 1773447.0 while (true)
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()
{ {
return new NesConfig ppu.SetRenderScanline(scanline);
{
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
};
}
public static NesConfig GetPAL() if (scanline == 0)
{ {
return new NesConfig if (RenderMethod < EnumRenderMethod.POST_RENDER)
{ {
BaseClock = 26601714.0f, EmulationCPU(nescfg.ScanlineCycles);
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
};
} }
} }
} }
}
}
internal void EmulationCPU(int basecycles)
{
int cycles;
base_cycles += basecycles;
cycles = (int)((base_cycles / 12) - emul_cycles);
if (cycles > 0)
{
emul_cycles += cpu.EXEC(cycles);
}
}
internal void Reset()
{
SaveSRAM();
SaveDISK();
SaveTurboFile();
//todo : 实现
}
internal void SoftReset()
{
//todo : 实现
}
internal void EmulateNSF()
{
//todo : 实现NSF模拟
throw new NotImplementedException("EmulateNSF");
}
internal void CheatCodeProcess()
{
//todo : 实现作弊码
}
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.WARM[i] != 0x00)
break;
}
if (i < SAVERAM_SIZE)
{
var romName = rom.GetRomName();
Debuger.Log($"Saving SAVERAM...[{romName}]");
Supporter.SaveSRAMToFile(MMU.WARM, 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 (目测是记录玩家操作再Play,优先级很低)
}
}
}

View File

@ -1,4 +1,6 @@
namespace VirtualNes.Core using System;
namespace VirtualNes.Core
{ {
public class PAD public class PAD
{ {
@ -32,6 +34,10 @@
padbitsync[0] = padbitsync[1] = padbitsync[2] = padbitsync[3] = 0; padbitsync[0] = padbitsync[1] = padbitsync[2] = padbitsync[3] = 0;
micbitsync = 0; micbitsync = 0;
} }
public void Dispose()
{
}
} }
public enum VSType public enum VSType

View File

@ -1,4 +1,6 @@
namespace VirtualNes.Core using System;
namespace VirtualNes.Core
{ {
public class PPU public class PPU
{ {
@ -9,6 +11,10 @@
private int nVSColorMap; private int nVSColorMap;
private byte VSSecurityData; private byte VSSecurityData;
private byte[] Bit2Rev = new byte[256]; private byte[] Bit2Rev = new byte[256];
private int ScanlineNo;
/// <summary> 作为lpScreen数组的索引 </summary>
private int lpScanline;
public PPU(NES nes) public PPU(NES nes)
{ {
m_nes = nes; m_nes = nes;
@ -31,5 +37,24 @@
Bit2Rev[i] = c; Bit2Rev[i] = c;
} }
} }
public void Dispose()
{
}
internal void SetRenderScanline(int scanline)
{
ScanlineNo = scanline;
if (scanline < 240)
{
lpScanline = (int)(Screen.SCREEN_WIDTH) * scanline;
}
}
private enum Screen
{
SCREEN_WIDTH = 256 + 16,
SCREEN_HEIGHT = 240
}
} }
} }

View File

@ -3,6 +3,7 @@ using System;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using VirtualNes.Core.Debug; using VirtualNes.Core.Debug;
using static UnityEditor.PlayerSettings;
namespace VirtualNes.Core namespace VirtualNes.Core
{ {
@ -282,11 +283,30 @@ namespace VirtualNes.Core
} }
} }
public void Dispose()
{
lpPRG = null;
lpCHR = null;
lpTrainer = null;
lpDiskBios = null;
lpDisk = null;
}
public bool IsTRAINER() public bool IsTRAINER()
{ {
return (header.control1 & (byte)EnumRomControlByte1.ROM_TRAINER) > 0; return (header.control1 & (byte)EnumRomControlByte1.ROM_TRAINER) > 0;
} }
public bool IsNSF()
{
return bNSF;
}
public bool IsSAVERAM()
{
return (header.control1 & (byte)EnumRomControlByte1.ROM_SAVERAM) > 0;
}
protected void FileNameCheck(string fname) protected void FileNameCheck(string fname)
{ {
if (fname.Contains("(E)")) if (fname.Contains("(E)"))
@ -295,6 +315,41 @@ namespace VirtualNes.Core
return; return;
} }
} }
internal string GetRomName()
{
return name;
}
internal int GetMapperNo()
{
return mapper;
}
internal byte[] GetPROM()
{
return lpPRG;
}
internal byte[] GetDISK()
{
return lpDisk;
}
internal int GetDiskNo()
{
return diskno;
}
internal ulong GetGameID()
{
return fdsgameID;
}
internal ulong GetMakerID()
{
return fdsmakerID;
}
} }

View File

@ -28,6 +28,15 @@ namespace VirtualNes.Core
return s_support.OpenFile_DISKSYS(); return s_support.OpenFile_DISKSYS();
} }
public static void SaveSRAMToFile(byte[] sramContent, string romName)
{
s_support.SaveSRAMToFile(sramContent, romName);
}
public static void SaveDISKToFile(byte[] diskFileContent, string romName)
{
s_support.SaveDISKToFile(diskFileContent, romName);
}
} }
public interface ISupporterImpl public interface ISupporterImpl
@ -35,5 +44,7 @@ namespace VirtualNes.Core
Stream OpenRom(string fname); Stream OpenRom(string fname);
void GetRomPathInfo(string fname, out string fullPath, out string directPath); void GetRomPathInfo(string fname, out string fullPath, out string directPath);
Stream OpenFile_DISKSYS(); Stream OpenFile_DISKSYS();
void SaveSRAMToFile(byte[] sramContent, string romName);
void SaveDISKToFile(byte[] diskFileContent, string romName);
} }
} }