forked from sin365/AxibugEmuOnline
211 lines
5.6 KiB
C#
211 lines
5.6 KiB
C#
using AxibugEmuOnline.Client.ClientCore;
|
||
using System;
|
||
using System.Diagnostics;
|
||
using System.IO;
|
||
using System.Xml.Linq;
|
||
using UnityEngine;
|
||
using VirtualNes.Core;
|
||
using VirtualNes.Core.Debug;
|
||
|
||
namespace AxibugEmuOnline.Client
|
||
{
|
||
public class NesEmulator : MonoBehaviour, IEmuCore
|
||
{
|
||
public EnumPlatform Platform => EnumPlatform.NES;
|
||
|
||
//模拟器核心实例化对象
|
||
public NES NesCore { get; private set; }
|
||
|
||
//视频驱动(这里是Unity接收模拟器画面数据的并渲染出来的实现)
|
||
public VideoProvider VideoProvider;
|
||
//音频驱动(这里是Unity接收模拟器音频数据的并播放出来的实现)
|
||
public AudioProvider AudioProvider;
|
||
//是否暂停
|
||
private bool m_bPause;
|
||
/// <summary> 是否暂停 </summary>
|
||
public bool IsPause => m_bPause;
|
||
|
||
private void Start()
|
||
{
|
||
//关闭垂直同步
|
||
QualitySettings.vSyncCount = 0;
|
||
//设为60帧
|
||
Application.targetFrameRate = 60;
|
||
VideoProvider.NesEmu = this;
|
||
AudioProvider.NesEmu = this;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 指定ROM开始游戏
|
||
/// </summary>
|
||
/// <param name="rom"></param>
|
||
public void StartGame(RomFile rom)
|
||
{
|
||
StopGame();
|
||
|
||
Supporter.Setup(new CoreSupporter());
|
||
Debuger.Setup(new CoreDebuger());
|
||
|
||
App.nesRomLib.AddRomFile(rom);
|
||
|
||
try
|
||
{
|
||
NesCore = new NES(rom.FileName);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
NesCore = null;
|
||
App.log.Error(ex.ToString());
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 停止游戏
|
||
/// </summary>
|
||
public void StopGame()
|
||
{
|
||
NesCore?.Dispose();
|
||
NesCore = null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Unity的逐帧驱动
|
||
/// </summary>
|
||
private unsafe void Update()
|
||
{
|
||
if (m_bPause) return;
|
||
|
||
if (NesCore != null)
|
||
{
|
||
PushEmulatorFrame();
|
||
if (InGameUI.Instance.IsNetPlay)
|
||
FixEmulatorFrame();
|
||
|
||
var screenBuffer = NesCore.ppu.GetScreenPtr();
|
||
VideoProvider.SetDrawData(screenBuffer);
|
||
}
|
||
}
|
||
|
||
//是否跳帧,单机无效
|
||
private void FixEmulatorFrame()
|
||
{
|
||
var skipFrameCount = App.roomMgr.netReplay.GetSkipFrameCount();
|
||
|
||
if (skipFrameCount > 0) App.log.Debug($"SKIP FRAME : {skipFrameCount}");
|
||
for (int i = 0; i < skipFrameCount; i++)
|
||
{
|
||
if (!PushEmulatorFrame()) break;
|
||
}
|
||
}
|
||
|
||
ControllerState lastState;
|
||
//推进帧
|
||
private bool PushEmulatorFrame()
|
||
{
|
||
Supporter.SampleInput(NesCore.FrameCount);
|
||
var controlState = Supporter.GetControllerState();
|
||
|
||
//如果未收到Input数据,核心帧不推进
|
||
if (!controlState.valid) return false;
|
||
|
||
#if UNITY_EDITOR
|
||
if (controlState != lastState)
|
||
{
|
||
App.log.Info($"[LOCALDEBUG]{NesCore.FrameCount}-->{controlState}");
|
||
}
|
||
#endif
|
||
|
||
NesCore.pad.Sync(controlState);
|
||
NesCore.EmulateFrame(true);
|
||
|
||
lastState = controlState;
|
||
|
||
return true;
|
||
}
|
||
|
||
public void Pause()
|
||
{
|
||
m_bPause = true;
|
||
}
|
||
|
||
public void Resume()
|
||
{
|
||
m_bPause = false;
|
||
}
|
||
|
||
|
||
public void DoReset()
|
||
{
|
||
NesCore.Reset();
|
||
}
|
||
|
||
public void SetupScheme()
|
||
{
|
||
CommandDispatcher.Instance.Current = CommandDispatcher.Instance.Gaming;
|
||
}
|
||
|
||
public void LoadState(object state)
|
||
{
|
||
NesCore.LoadState((State)state);
|
||
}
|
||
|
||
public object GetState()
|
||
{
|
||
return NesCore.GetState();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取即时存档
|
||
/// </summary>
|
||
/// <returns></returns>
|
||
public byte[] GetStateBytes()
|
||
{
|
||
return NesCore.GetState().ToBytes();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 加载即时存档
|
||
/// </summary>
|
||
/// <param name="data"></param>
|
||
public void LoadStateFromBytes(byte[] data)
|
||
{
|
||
State st = new State();
|
||
st.FromByte(data);
|
||
NesCore.LoadState(st);
|
||
}
|
||
|
||
public uint Frame => NesCore.FrameCount;
|
||
|
||
#if UNITY_EDITOR
|
||
/// <summary>
|
||
/// 编辑器用
|
||
/// </summary>
|
||
[Conditional("UNITY_EDITOR")]
|
||
[ContextMenu("ImportNesDB")]
|
||
public void ImportNesDB()
|
||
{
|
||
var db = Resources.Load<RomDB>("NES/ROMDB");
|
||
db.Clear();
|
||
|
||
var xmlStr = File.ReadAllText("nes20db.xml");
|
||
var xml = XDocument.Parse(xmlStr);
|
||
var games = xml.Element("nes20db").Elements("game");
|
||
foreach (var game in games)
|
||
{
|
||
var crcStr = game.Element("rom").Attribute("crc32").Value;
|
||
var crc = uint.Parse($"{crcStr}", System.Globalization.NumberStyles.HexNumber);
|
||
|
||
var mapper = int.Parse($"{game.Element("pcb").Attribute("mapper").Value}");
|
||
|
||
if (mapper > 255) continue;
|
||
db.AddInfo(new RomDB.RomInfo { CRC = crc, Mapper = mapper });
|
||
}
|
||
|
||
UnityEditor.EditorUtility.SetDirty(db);
|
||
UnityEditor.AssetDatabase.SaveAssets();
|
||
}
|
||
|
||
#endif
|
||
}
|
||
}
|