dev_4VirtualNes #18
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 // 僞僀儖儀乕僗儗儞僟儕儞僌
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9c379fb6535bd23449474dee5018652c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d7e8126382c9728429056ba33afc85eb
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4865f8871b37b0041b77060cf3c62664
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 447095b8c8ae4c74885562c127998e9e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
18
AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs
Normal file
18
AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs
Normal 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
|
||||
}
|
||||
}
|
11
AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs.meta
Normal file
11
AxibugEmuOnline.Client/Assets/VirtualNes.Core/MMU.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 863989820a4fb1d49a7c0c883c5a7078
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -6,7 +6,8 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace VirtualNes.Core
|
||||
{
|
||||
public class Mapper
|
||||
public abstract class Mapper
|
||||
{
|
||||
internal virtual void Clock(int cycles) { }
|
||||
}
|
||||
}
|
||||
|
@ -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 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
|
||||
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()
|
||||
bZapper = false;
|
||||
while (true)
|
||||
{
|
||||
return new NesConfig
|
||||
{
|
||||
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
|
||||
};
|
||||
}
|
||||
ppu.SetRenderScanline(scanline);
|
||||
|
||||
public static NesConfig GetPAL()
|
||||
if (scanline == 0)
|
||||
{
|
||||
return new NesConfig
|
||||
if (RenderMethod < EnumRenderMethod.POST_RENDER)
|
||||
{
|
||||
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
|
||||
};
|
||||
EmulationCPU(nescfg.ScanlineCycles);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,优先级很低)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user