diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreSupporter.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreSupporter.cs index 495360b..57019b9 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreSupporter.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/CoreSupporter.cs @@ -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); + } } } diff --git a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs index 28d0241..a22cab9 100644 --- a/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs +++ b/AxibugEmuOnline.Client/Assets/Script/NesEmulator/NesEmulator.cs @@ -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); } } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs index 510f7b7..5d76dd9 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/APU.cs @@ -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 diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs index afb9abc..ca1a0f3 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ApuEX/APU_INTERNAL.cs @@ -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(); diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs index e2780ad..ea35cfc 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CPU.cs @@ -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; } } \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/EnumRenderMethod.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/EnumRenderMethod.cs new file mode 100644 index 0000000..8bdaf21 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/EnumRenderMethod.cs @@ -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 // 僞僀儖儀乕僗儗儞僟儕儞僌 + } +} diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/EnumRenderMethod.cs.meta b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/EnumRenderMethod.cs.meta new file mode 100644 index 0000000..396fe6d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/EnumRenderMethod.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9c379fb6535bd23449474dee5018652c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NESCOMMAND.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NESCOMMAND.cs new file mode 100644 index 0000000..e6ed2f8 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NESCOMMAND.cs @@ -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 + } +} diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NESCOMMAND.cs.meta b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NESCOMMAND.cs.meta new file mode 100644 index 0000000..db767eb --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NESCOMMAND.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d7e8126382c9728429056ba33afc85eb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NesConfig.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NesConfig.cs new file mode 100644 index 0000000..b4166fb --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NesConfig.cs @@ -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 + }; + } + } +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NesConfig.cs.meta b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NesConfig.cs.meta new file mode 100644 index 0000000..d583d21 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/NesConfig.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4865f8871b37b0041b77060cf3c62664 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/State.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/State.cs new file mode 100644 index 0000000..3bbca28 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/State.cs @@ -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; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/State.cs.meta b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/State.cs.meta new file mode 100644 index 0000000..4557eb3 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/CoreLibs/State.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 447095b8c8ae4c74885562c127998e9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs new file mode 100644 index 0000000..e4dab0e --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs @@ -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 + } +} diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs.meta b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs.meta new file mode 100644 index 0000000..a77dd40 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 863989820a4fb1d49a7c0c883c5a7078 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Mapper/Mapper.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Mapper/Mapper.cs index 77eabba..c190f75 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Mapper/Mapper.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Mapper/Mapper.cs @@ -6,7 +6,8 @@ using System.Threading.Tasks; namespace VirtualNes.Core { - public class Mapper + public abstract class Mapper { + internal virtual void Clock(int cycles) { } } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs index 66a5e55..9f59b77 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/NES.cs @@ -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 m_CheatCode = new List(); + public NesConfig nescfg; + private List m_CheatCode = new List(); 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 contents = new List(); + 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; + // ~H + 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,ȼܵ) } } + } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs index a71c5b3..0758a47 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PAD.cs @@ -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 diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs index dd9e29f..0e35183 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/PPU.cs @@ -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; + /// 作为lpScreen数组的索引 + 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 + } } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ROM.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ROM.cs index a0d3a5f..8e910f0 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ROM.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/ROM.cs @@ -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; + } } diff --git a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/Supporter.cs b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/Supporter.cs index 884d34e..8f07b85 100644 --- a/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/Supporter.cs +++ b/AxibugEmuOnline.Client/Assets/VirtualNes.Core/Supporter/Supporter.cs @@ -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); } }