WARNING: use NES core only at low volume and no headphones
This commit is contained in:
parent
54b9c826cf
commit
b85d9d9d91
|
@ -14,6 +14,7 @@ using BizHawk.Emulation.Cores.Nintendo.Gameboy;
|
|||
using BizHawk.Emulation.Cores.Nintendo.GBA;
|
||||
using BizHawk.Emulation.Cores.Nintendo.N64;
|
||||
using BizHawk.Emulation.Cores.Nintendo.NES;
|
||||
using BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES;
|
||||
using BizHawk.Emulation.Cores.Nintendo.SNES;
|
||||
using BizHawk.Emulation.Cores.PCEngine;
|
||||
using BizHawk.Emulation.Cores.Sega.MasterSystem;
|
||||
|
@ -350,12 +351,19 @@ namespace BizHawk.Client.Common
|
|||
nextEmulator = new TI83(nextComm, game, rom.RomData);
|
||||
break;
|
||||
case "NES":
|
||||
nextEmulator = new NES(
|
||||
nextComm,
|
||||
game,
|
||||
rom.FileData,
|
||||
GetCoreSettings<NES>(),
|
||||
GetCoreSyncSettings<NES>());
|
||||
if (false)
|
||||
{
|
||||
nextEmulator = new NES(
|
||||
nextComm,
|
||||
game,
|
||||
rom.FileData,
|
||||
GetCoreSettings<NES>(),
|
||||
GetCoreSyncSettings<NES>());
|
||||
}
|
||||
else
|
||||
{
|
||||
nextEmulator = new QuickNES(nextComm, rom.FileData);
|
||||
}
|
||||
break;
|
||||
case "GB":
|
||||
case "GBC":
|
||||
|
|
|
@ -392,6 +392,8 @@
|
|||
<Compile Include="Consoles\Nintendo\NES\PPU.regs.cs" />
|
||||
<Compile Include="Consoles\Nintendo\NES\PPU.run.cs" />
|
||||
<Compile Include="Consoles\Nintendo\NES\Unif.cs" />
|
||||
<Compile Include="Consoles\Nintendo\QuickNES\LibQuickNES.cs" />
|
||||
<Compile Include="Consoles\Nintendo\QuickNES\QuickNES.cs" />
|
||||
<Compile Include="Consoles\Nintendo\SNES\LibsnesApi.cs" />
|
||||
<Compile Include="Consoles\Nintendo\SNES\LibsnesApi_BRK.cs" />
|
||||
<Compile Include="Consoles\Nintendo\SNES\LibsnesApi_CMD.cs" />
|
||||
|
|
|
@ -54,7 +54,7 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
|
|||
/// <param name="dest">8bpp, at least as big as qn_get_image_dimensions()</param>
|
||||
/// <param name="pitch">byte pitch</param>
|
||||
[DllImport(dllname, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void qn_set_pixels(IntPtr e, byte[] dest, int pitch);
|
||||
public static extern void qn_set_pixels(IntPtr e, IntPtr dest, int pitch);
|
||||
/// <summary>
|
||||
/// emulate a single frame
|
||||
/// </summary>
|
||||
|
@ -65,6 +65,13 @@ namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
|
|||
[DllImport(dllname, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern IntPtr qn_emulate_frame(IntPtr e, int pad1, int pad2);
|
||||
/// <summary>
|
||||
/// blit to rgb32
|
||||
/// </summary>
|
||||
/// <param name="e">Context</param>
|
||||
/// <param name="dest">rgb32 256x240 packed</param>
|
||||
[DllImport(dllname, CallingConvention = CallingConvention.Cdecl)]
|
||||
public static extern void qn_blit(IntPtr e, IntPtr dest);
|
||||
/// <summary>
|
||||
/// get number of times joypad was read in most recent frame
|
||||
/// </summary>
|
||||
/// <param name="e">context</param>
|
||||
|
|
|
@ -0,0 +1,362 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using BizHawk.Emulation.Common;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BizHawk.Emulation.Cores.Consoles.Nintendo.QuickNES
|
||||
{
|
||||
public class QuickNES : IEmulator, IVideoProvider, ISyncSoundProvider
|
||||
{
|
||||
public QuickNES(CoreComm nextComm, byte[] Rom)
|
||||
{
|
||||
CoreComm = nextComm;
|
||||
|
||||
Context = LibQuickNES.qn_new();
|
||||
if (Context == IntPtr.Zero)
|
||||
throw new InvalidOperationException("qn_new() returned NULL");
|
||||
try
|
||||
{
|
||||
LibQuickNES.ThrowStringError(LibQuickNES.qn_loadines(Context, Rom, Rom.Length));
|
||||
|
||||
InitSaveRamBuff();
|
||||
InitSaveStateBuff();
|
||||
InitVideo();
|
||||
InitAudio();
|
||||
}
|
||||
catch
|
||||
{
|
||||
Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
#region Controller
|
||||
|
||||
public ControllerDefinition ControllerDefinition { get { return Emulation.Cores.Nintendo.NES.NES.NESController; } }
|
||||
public IController Controller { get; set; }
|
||||
|
||||
void SetPads(out int j1, out int j2)
|
||||
{
|
||||
j1 = 0;
|
||||
j2 = 0;
|
||||
if (Controller["P1 A"])
|
||||
j1 |= 1;
|
||||
if (Controller["P1 B"])
|
||||
j1 |= 2;
|
||||
if (Controller["P1 Select"])
|
||||
j1 |= 4;
|
||||
if (Controller["P1 Start"])
|
||||
j1 |= 8;
|
||||
if (Controller["P1 Up"])
|
||||
j1 |= 16;
|
||||
if (Controller["P1 Down"])
|
||||
j1 |= 32;
|
||||
if (Controller["P1 Left"])
|
||||
j1 |= 64;
|
||||
if (Controller["P1 Right"])
|
||||
j1 |= 128;
|
||||
if (Controller["P2 A"])
|
||||
j2 |= 1;
|
||||
if (Controller["P2 B"])
|
||||
j2 |= 2;
|
||||
if (Controller["P2 Select"])
|
||||
j2 |= 4;
|
||||
if (Controller["P2 Start"])
|
||||
j2 |= 8;
|
||||
if (Controller["P2 Up"])
|
||||
j2 |= 16;
|
||||
if (Controller["P2 Down"])
|
||||
j2 |= 32;
|
||||
if (Controller["P2 Left"])
|
||||
j2 |= 64;
|
||||
if (Controller["P2 Right"])
|
||||
j2 |= 128;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void FrameAdvance(bool render, bool rendersound = true)
|
||||
{
|
||||
if (Controller["Power"])
|
||||
LibQuickNES.qn_reset(Context, true);
|
||||
if (Controller["Reset"])
|
||||
LibQuickNES.qn_reset(Context, false);
|
||||
|
||||
int j1, j2;
|
||||
SetPads(out j1, out j2);
|
||||
|
||||
Frame++;
|
||||
LibQuickNES.ThrowStringError(LibQuickNES.qn_emulate_frame(Context, j1, j2));
|
||||
IsLagFrame = LibQuickNES.qn_get_joypad_read_count(Context) == 0;
|
||||
if (IsLagFrame)
|
||||
LagCount++;
|
||||
|
||||
Blit();
|
||||
DrainAudio();
|
||||
}
|
||||
|
||||
#region state
|
||||
|
||||
IntPtr Context;
|
||||
|
||||
public int Frame { get; private set; }
|
||||
public int LagCount { get; set; }
|
||||
public bool IsLagFrame { get; private set; }
|
||||
|
||||
#endregion
|
||||
|
||||
public string SystemId { get { return "NES"; } }
|
||||
public bool DeterministicEmulation { get { return true; } }
|
||||
public string BoardName { get { return null; } } // TODO
|
||||
|
||||
#region saveram
|
||||
|
||||
byte[] SaveRamBuff;
|
||||
|
||||
void InitSaveRamBuff()
|
||||
{
|
||||
int size = 0;
|
||||
LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_size(Context, ref size));
|
||||
SaveRamBuff = new byte[size];
|
||||
}
|
||||
|
||||
public byte[] ReadSaveRam()
|
||||
{
|
||||
LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_save(Context, SaveRamBuff, SaveRamBuff.Length));
|
||||
return SaveRamBuff;
|
||||
}
|
||||
|
||||
public void StoreSaveRam(byte[] data)
|
||||
{
|
||||
LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_load(Context, data, data.Length));
|
||||
}
|
||||
|
||||
public void ClearSaveRam()
|
||||
{
|
||||
LibQuickNES.ThrowStringError(LibQuickNES.qn_battery_ram_clear(Context));
|
||||
}
|
||||
|
||||
public bool SaveRamModified
|
||||
{
|
||||
get
|
||||
{
|
||||
return LibQuickNES.qn_has_battery_ram(Context);
|
||||
}
|
||||
set
|
||||
{
|
||||
throw new Exception();
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void ResetCounters()
|
||||
{
|
||||
Frame = 0;
|
||||
IsLagFrame = false;
|
||||
LagCount = 0;
|
||||
}
|
||||
|
||||
#region savestates
|
||||
|
||||
byte[] SaveStateBuff;
|
||||
byte[] SaveStateBuff2;
|
||||
|
||||
void InitSaveStateBuff()
|
||||
{
|
||||
int size = 0;
|
||||
LibQuickNES.ThrowStringError(LibQuickNES.qn_state_size(Context, ref size));
|
||||
SaveStateBuff = new byte[size];
|
||||
SaveStateBuff2 = new byte[size + 13];
|
||||
}
|
||||
|
||||
public void SaveStateText(System.IO.TextWriter writer)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void LoadStateText(System.IO.TextReader reader)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public void SaveStateBinary(System.IO.BinaryWriter writer)
|
||||
{
|
||||
LibQuickNES.ThrowStringError(LibQuickNES.qn_state_save(Context, SaveStateBuff, SaveStateBuff.Length));
|
||||
writer.Write(SaveStateBuff.Length);
|
||||
writer.Write(SaveStateBuff);
|
||||
// other variables
|
||||
writer.Write(IsLagFrame);
|
||||
writer.Write(LagCount);
|
||||
writer.Write(Frame);
|
||||
}
|
||||
|
||||
public void LoadStateBinary(System.IO.BinaryReader reader)
|
||||
{
|
||||
int len = reader.ReadInt32();
|
||||
if (len != SaveStateBuff.Length)
|
||||
throw new InvalidOperationException("Unexpected savestate buffer length!");
|
||||
reader.Read(SaveStateBuff, 0, SaveStateBuff.Length);
|
||||
LibQuickNES.ThrowStringError(LibQuickNES.qn_state_load(Context, SaveStateBuff, SaveStateBuff.Length));
|
||||
// other variables
|
||||
IsLagFrame = reader.ReadBoolean();
|
||||
LagCount = reader.ReadInt32();
|
||||
Frame = reader.ReadInt32();
|
||||
}
|
||||
|
||||
public byte[] SaveStateBinary()
|
||||
{
|
||||
var ms = new System.IO.MemoryStream(SaveStateBuff2, true);
|
||||
var bw = new System.IO.BinaryWriter(ms);
|
||||
SaveStateBinary(bw);
|
||||
bw.Flush();
|
||||
if (ms.Position != SaveStateBuff2.Length)
|
||||
throw new InvalidOperationException("Unexpected savestate length!");
|
||||
bw.Close();
|
||||
return SaveStateBuff2;
|
||||
}
|
||||
|
||||
public bool BinarySaveStatesPreferred { get { return true; } }
|
||||
|
||||
#endregion
|
||||
|
||||
public CoreComm CoreComm
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public MemoryDomainList MemoryDomains
|
||||
{
|
||||
get { return MemoryDomainList.GetDummyList(); }
|
||||
}
|
||||
|
||||
public List<KeyValuePair<string, int>> GetCpuFlagsAndRegisters()
|
||||
{
|
||||
return new List<KeyValuePair<string, int>>();
|
||||
}
|
||||
|
||||
#region settings
|
||||
|
||||
public object GetSettings()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public object GetSyncSettings()
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public bool PutSettings(object o)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool PutSyncSettings(object o)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Context != IntPtr.Zero)
|
||||
{
|
||||
LibQuickNES.qn_delete(Context);
|
||||
Context = IntPtr.Zero;
|
||||
}
|
||||
if (VideoInput != null)
|
||||
{
|
||||
VideoInputH.Free();
|
||||
VideoInput = null;
|
||||
}
|
||||
if (VideoOutput != null)
|
||||
{
|
||||
VideoOutputH.Free();
|
||||
VideoOutput = null;
|
||||
}
|
||||
}
|
||||
|
||||
#region VideoProvider
|
||||
|
||||
int[] VideoOutput;
|
||||
byte[] VideoInput;
|
||||
GCHandle VideoInputH;
|
||||
GCHandle VideoOutputH;
|
||||
|
||||
void InitVideo()
|
||||
{
|
||||
int w = 0, h = 0;
|
||||
LibQuickNES.qn_get_image_dimensions(Context, ref w, ref h);
|
||||
VideoInput = new byte[w * h];
|
||||
VideoInputH = GCHandle.Alloc(VideoInput, GCHandleType.Pinned);
|
||||
LibQuickNES.qn_set_pixels(Context, VideoInputH.AddrOfPinnedObject(), w);
|
||||
VideoOutput = new int[256 * 240];
|
||||
VideoOutputH = GCHandle.Alloc(VideoOutput, GCHandleType.Pinned);
|
||||
}
|
||||
|
||||
void Blit()
|
||||
{
|
||||
LibQuickNES.qn_blit(Context, VideoOutputH.AddrOfPinnedObject());
|
||||
}
|
||||
|
||||
public IVideoProvider VideoProvider { get { return this; } }
|
||||
public int[] GetVideoBuffer() { return VideoOutput; }
|
||||
public int VirtualWidth { get { return 292; } } // probably different on pal
|
||||
public int BufferWidth { get { return 256; } }
|
||||
public int BufferHeight { get { return 240; } }
|
||||
public int BackgroundColor { get { return unchecked((int)0xff000000); } }
|
||||
|
||||
#endregion
|
||||
|
||||
#region SoundProvider
|
||||
|
||||
public ISoundProvider SoundProvider { get { return null; } }
|
||||
public ISyncSoundProvider SyncSoundProvider { get { return this; } }
|
||||
public bool StartAsyncSound() { return false; }
|
||||
public void EndAsyncSound() { }
|
||||
|
||||
void InitAudio()
|
||||
{
|
||||
LibQuickNES.ThrowStringError(LibQuickNES.qn_set_sample_rate(Context, 44100));
|
||||
}
|
||||
|
||||
void DrainAudio()
|
||||
{
|
||||
NumSamples = LibQuickNES.qn_read_audio(Context, MonoBuff, 1024);
|
||||
unsafe
|
||||
{
|
||||
fixed (short *_src = &MonoBuff[0], _dst = &StereoBuff[0])
|
||||
{
|
||||
short* src = _src;
|
||||
short* dst = _dst;
|
||||
for (int i = 0; i < NumSamples; i++)
|
||||
{
|
||||
*dst++ = *src;
|
||||
*dst++ = *src++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
short[] MonoBuff = new short[1024];
|
||||
short[] StereoBuff = new short[2048];
|
||||
int NumSamples = 0;
|
||||
|
||||
public void GetSamples(out short[] samples, out int nsamp)
|
||||
{
|
||||
samples = StereoBuff;
|
||||
nsamp = NumSamples;
|
||||
}
|
||||
|
||||
public void DiscardSamples()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
Binary file not shown.
|
@ -59,6 +59,31 @@ EXPORT const char *qn_emulate_frame(Nes_Emu *e, int pad1, int pad2)
|
|||
return e->emulate_frame(pad1, pad2);
|
||||
}
|
||||
|
||||
EXPORT void qn_blit(Nes_Emu *e, char *dest)
|
||||
{
|
||||
// what is the point of the 256 color bitmap and the dynamic color allocation to it?
|
||||
// why not just render directly to a 512 color bitmap with static palette positions?
|
||||
|
||||
const char *src = (const char *)e->frame().pixels;
|
||||
const int srcpitch = e->frame().pitch;
|
||||
const char *srcend = src + e->image_height * srcpitch;
|
||||
|
||||
const short *lut = e->frame().palette;
|
||||
const Nes_Emu::rgb_t *colors = e->nes_colors;
|
||||
|
||||
for (; src < srcend; src += srcpitch)
|
||||
{
|
||||
for (int i = 0; i < 256; i++)
|
||||
{
|
||||
const Nes_Emu::rgb_t *c = colors + lut[src[i]];
|
||||
*dest++ = c->blue;
|
||||
*dest++ = c->green;
|
||||
*dest++ = c->red;
|
||||
*dest++ = 0xff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EXPORT int qn_get_joypad_read_count(Nes_Emu *e)
|
||||
{
|
||||
return e->frame().joypad_read_count;
|
||||
|
|
|
@ -86,7 +86,7 @@
|
|||
<Optimization>Disabled</Optimization>
|
||||
<DisableSpecificWarnings>4244;4800;4804;4996</DisableSpecificWarnings>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)\..</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions);DISABLE_AUTO_FILE</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions);DISABLE_AUTO_FILE;__LIBRETRO__</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
|
@ -103,7 +103,7 @@
|
|||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<DisableSpecificWarnings>4244;4800;4804;4996</DisableSpecificWarnings>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir)\..</AdditionalIncludeDirectories>
|
||||
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions);DISABLE_AUTO_FILE</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions>_WINDLL;%(PreprocessorDefinitions);DISABLE_AUTO_FILE;__LIBRETRO__</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
|
|
Loading…
Reference in New Issue