dev_4VirtualNes #23

Merged
sin365 merged 35 commits from dev_4VirtualNes into master 2024-08-05 17:49:44 +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.Generic;
using System.IO;
using System.Net.Http;
using UnityEngine;
using VirtualNes.Core;
@ -45,5 +46,21 @@ 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);
}
}
}

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

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

View File

@ -3,12 +3,39 @@ namespace VirtualNes.Core
public class APU_INTERNAL : APU_INTERFACE
{
private NES nes;
private int FrameCycle;
private byte FrameIRQoccur;
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)
{
//TODO : ʵÏÖ
return false;
}
private void UpdateFrame()
{
//TODO : ʵÏÖ
}
public override void Reset(float fClock, int nRate)
{
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
{
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)
{
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
{
ushort PC;
byte A;
byte P;
byte X;
byte Y;
byte S;
public ushort PC;
public byte A;
public byte P;
public byte X;
public byte Y;
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
{
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.Collections.Generic;
using System.IO;
using System.Text;
using VirtualNes.Core.Debug;
namespace VirtualNes.Core
@ -13,10 +15,9 @@ 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 bool m_bDiskThrottle;
private int m_CommandRequest;
private int m_nSnapNo;
@ -30,12 +31,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 +96,7 @@ namespace VirtualNes.Core
bVideoMode = false;
NesCfg = NesConfig.GetNTSC();
nescfg = NesConfig.GetNTSC();
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()
{
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();
//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
{
@ -32,6 +34,10 @@
padbitsync[0] = padbitsync[1] = padbitsync[2] = padbitsync[3] = 0;
micbitsync = 0;
}
public void Dispose()
{
}
}
public enum VSType

View File

@ -1,4 +1,6 @@
namespace VirtualNes.Core
using System;
namespace VirtualNes.Core
{
public class PPU
{
@ -9,6 +11,10 @@
private int nVSColorMap;
private byte VSSecurityData;
private byte[] Bit2Rev = new byte[256];
private int ScanlineNo;
/// <summary> 作为lpScreen数组的索引 </summary>
private int lpScanline;
public PPU(NES nes)
{
m_nes = nes;
@ -31,5 +37,24 @@
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.Linq;
using VirtualNes.Core.Debug;
using static UnityEditor.PlayerSettings;
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()
{
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)
{
if (fname.Contains("(E)"))
@ -295,6 +315,41 @@ namespace VirtualNes.Core
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();
}
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
@ -35,5 +44,7 @@ namespace VirtualNes.Core
Stream OpenRom(string fname);
void GetRomPathInfo(string fname, out string fullPath, out string directPath);
Stream OpenFile_DISKSYS();
void SaveSRAMToFile(byte[] sramContent, string romName);
void SaveDISKToFile(byte[] diskFileContent, string romName);
}
}