2012-10-23 03:33:57 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.IO;
|
2013-11-04 00:36:15 +00:00
|
|
|
|
|
2013-11-04 01:06:36 +00:00
|
|
|
|
using BizHawk.Emulation.Common;
|
2012-10-23 03:33:57 +00:00
|
|
|
|
using EMU7800.Core;
|
2017-05-05 19:20:28 +00:00
|
|
|
|
using EMU7800.Win;
|
2012-10-23 03:33:57 +00:00
|
|
|
|
|
2013-11-13 03:32:25 +00:00
|
|
|
|
namespace BizHawk.Emulation.Cores.Atari.Atari7800
|
2012-10-23 03:33:57 +00:00
|
|
|
|
{
|
2014-04-25 01:19:57 +00:00
|
|
|
|
[CoreAttributes(
|
|
|
|
|
"EMU7800",
|
2014-06-01 01:57:22 +00:00
|
|
|
|
"",
|
2014-04-25 01:19:57 +00:00
|
|
|
|
isPorted: true,
|
2014-06-01 01:57:22 +00:00
|
|
|
|
isReleased: true,
|
|
|
|
|
portedVersion: "v1.5",
|
2017-04-24 15:32:45 +00:00
|
|
|
|
portedUrl: "http://emu7800.sourceforge.net/")]
|
2014-12-12 01:58:12 +00:00
|
|
|
|
[ServiceNotApplicable(typeof(ISettable<,>), typeof(IDriveLight))]
|
2015-08-06 00:12:09 +00:00
|
|
|
|
public partial class Atari7800 : IEmulator, ISaveRam, IDebuggable, IStatable, IInputPollable, IRegionable
|
2012-10-23 03:33:57 +00:00
|
|
|
|
{
|
2012-12-16 18:16:50 +00:00
|
|
|
|
// TODO:
|
|
|
|
|
// some things don't work when you try to plug in a 2600 game
|
2012-12-12 01:32:58 +00:00
|
|
|
|
static Atari7800()
|
|
|
|
|
{
|
|
|
|
|
// add alpha bits to palette tables
|
|
|
|
|
for (int i = 0; i < TIATables.NTSCPalette.Length; i++)
|
2017-04-24 15:32:45 +00:00
|
|
|
|
{
|
2012-12-12 01:32:58 +00:00
|
|
|
|
TIATables.NTSCPalette[i] |= unchecked((int)0xff000000);
|
2017-04-24 15:32:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-12-12 01:32:58 +00:00
|
|
|
|
for (int i = 0; i < TIATables.PALPalette.Length; i++)
|
2017-04-24 15:32:45 +00:00
|
|
|
|
{
|
2012-12-12 01:32:58 +00:00
|
|
|
|
TIATables.PALPalette[i] |= unchecked((int)0xff000000);
|
2017-04-24 15:32:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 20:51:35 +00:00
|
|
|
|
for (int i = 0; i < MariaTables.NTSCPalette.Length; i++)
|
2017-04-24 15:32:45 +00:00
|
|
|
|
{
|
2017-01-31 20:51:35 +00:00
|
|
|
|
MariaTables.NTSCPalette[i] |= unchecked((int)0xff000000);
|
2017-04-24 15:32:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-01-31 20:51:35 +00:00
|
|
|
|
for (int i = 0; i < MariaTables.PALPalette.Length; i++)
|
2017-04-24 15:32:45 +00:00
|
|
|
|
{
|
2017-01-31 20:51:35 +00:00
|
|
|
|
MariaTables.PALPalette[i] |= unchecked((int)0xff000000);
|
2017-04-24 15:32:45 +00:00
|
|
|
|
}
|
2012-12-12 01:32:58 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-06 00:05:36 +00:00
|
|
|
|
public Atari7800(CoreComm comm, GameInfo game, byte[] rom, string gameDbFn)
|
2015-01-14 21:55:48 +00:00
|
|
|
|
{
|
2017-05-05 19:20:28 +00:00
|
|
|
|
var ser = new BasicServiceProvider(this);
|
|
|
|
|
ser.Register<IVideoProvider>(_avProvider);
|
|
|
|
|
ser.Register<ISoundProvider>(_avProvider);
|
|
|
|
|
ServiceProvider = ser;
|
2017-04-24 15:32:45 +00:00
|
|
|
|
|
2015-01-14 21:55:48 +00:00
|
|
|
|
CoreComm = comm;
|
2017-05-05 19:20:28 +00:00
|
|
|
|
byte[] highscoreBios = comm.CoreFileProvider.GetFirmware("A78", "Bios_HSC", false, "Some functions may not work without the high score BIOS.");
|
|
|
|
|
byte[] palBios = comm.CoreFileProvider.GetFirmware("A78", "Bios_PAL", false, "The game will not run if the correct region BIOS is not available.");
|
|
|
|
|
byte[] ntscBios = comm.CoreFileProvider.GetFirmware("A78", "Bios_NTSC", false, "The game will not run if the correct region BIOS is not available.");
|
2015-01-14 21:55:48 +00:00
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
if (GameProgramLibrary.EMU7800DB == null)
|
2015-01-14 21:55:48 +00:00
|
|
|
|
{
|
2017-05-06 00:05:36 +00:00
|
|
|
|
GameProgramLibrary.EMU7800DB = new GameProgramLibrary(new StreamReader(gameDbFn));
|
2015-01-14 21:55:48 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (rom.Length % 1024 == 128)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine("Trimming 128 byte .a78 header...");
|
|
|
|
|
byte[] newrom = new byte[rom.Length - 128];
|
|
|
|
|
Buffer.BlockCopy(rom, 128, newrom, 0, newrom.Length);
|
|
|
|
|
rom = newrom;
|
|
|
|
|
}
|
2017-04-24 15:32:45 +00:00
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_gameInfo = GameProgramLibrary.EMU7800DB.TryRecognizeRom(rom);
|
|
|
|
|
CoreComm.RomStatusDetails = _gameInfo.ToString();
|
2015-01-14 21:55:48 +00:00
|
|
|
|
Console.WriteLine("Rom Determiniation from 7800DB:");
|
2017-05-05 19:20:28 +00:00
|
|
|
|
Console.WriteLine(_gameInfo.ToString());
|
2015-01-14 21:55:48 +00:00
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_rom = rom;
|
|
|
|
|
_hsbios = highscoreBios;
|
|
|
|
|
_bios = _gameInfo.MachineType == MachineType.A7800PAL ? palBios : ntscBios;
|
|
|
|
|
_pal = _gameInfo.MachineType == MachineType.A7800PAL || _gameInfo.MachineType == MachineType.A2600PAL;
|
2015-01-14 21:55:48 +00:00
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
if (_bios == null)
|
2015-01-14 21:55:48 +00:00
|
|
|
|
{
|
|
|
|
|
throw new MissingFirmwareException("The BIOS corresponding to the region of the game you loaded is required to run Atari 7800 games.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
HardReset();
|
|
|
|
|
}
|
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
public DisplayType Region => _pal ? DisplayType.PAL : DisplayType.NTSC;
|
2012-10-23 03:33:57 +00:00
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
public Atari7800Control ControlAdapter { get; private set; }
|
2012-10-23 03:33:57 +00:00
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
private readonly byte[] _rom;
|
|
|
|
|
private readonly byte[] _hsbios;
|
|
|
|
|
private readonly byte[] _bios;
|
|
|
|
|
private readonly GameProgram _gameInfo;
|
|
|
|
|
private readonly byte[] _hsram = new byte[2048];
|
|
|
|
|
private readonly bool _pal;
|
2017-04-24 15:32:45 +00:00
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
private Cart _cart;
|
|
|
|
|
private MachineBase _theMachine;
|
2012-10-23 03:33:57 +00:00
|
|
|
|
private int _frame = 0;
|
|
|
|
|
|
2017-04-24 15:32:45 +00:00
|
|
|
|
private class ConsoleLogger : ILogger
|
2012-12-10 21:29:50 +00:00
|
|
|
|
{
|
|
|
|
|
public void WriteLine(string format, params object[] args)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(format, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void WriteLine(object value)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine(value);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Write(string format, params object[] args)
|
|
|
|
|
{
|
|
|
|
|
Console.Write(format, args);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Write(object value)
|
|
|
|
|
{
|
|
|
|
|
Console.Write(value);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-04-24 15:32:45 +00:00
|
|
|
|
private void HardReset()
|
2012-10-23 03:33:57 +00:00
|
|
|
|
{
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_cart = Cart.Create(_rom, _gameInfo.CartType);
|
2012-12-10 21:29:50 +00:00
|
|
|
|
ILogger logger = new ConsoleLogger();
|
2012-12-27 22:24:42 +00:00
|
|
|
|
|
|
|
|
|
HSC7800 hsc7800 = null;
|
2017-05-05 19:20:28 +00:00
|
|
|
|
if (_hsbios != null)
|
2012-12-27 22:24:42 +00:00
|
|
|
|
{
|
2017-05-05 19:20:28 +00:00
|
|
|
|
hsc7800 = new HSC7800(_hsbios, _hsram);
|
2012-12-27 22:24:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
Bios7800 bios7800 = new Bios7800(_bios);
|
|
|
|
|
_theMachine = MachineBase.Create(
|
|
|
|
|
_gameInfo.MachineType,
|
|
|
|
|
_cart,
|
2012-12-12 03:40:18 +00:00
|
|
|
|
bios7800,
|
|
|
|
|
hsc7800,
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_gameInfo.LController,
|
|
|
|
|
_gameInfo.RController,
|
2012-12-12 03:40:18 +00:00
|
|
|
|
logger);
|
2012-12-12 15:36:17 +00:00
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_theMachine.Reset();
|
|
|
|
|
_theMachine.InputState.InputPollCallback = InputCallbacks.Call;
|
2012-12-15 02:36:38 +00:00
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
ControlAdapter = new Atari7800Control(_theMachine);
|
2012-12-15 02:36:38 +00:00
|
|
|
|
ControllerDefinition = ControlAdapter.ControlType;
|
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_avProvider.ConnectToMachine(_theMachine, _gameInfo);
|
2017-04-24 15:32:45 +00:00
|
|
|
|
|
2014-11-30 16:01:01 +00:00
|
|
|
|
SetupMemoryDomains(hsc7800);
|
2012-10-23 03:33:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2012-12-16 18:16:50 +00:00
|
|
|
|
#region audio\video
|
|
|
|
|
|
2017-04-24 16:51:59 +00:00
|
|
|
|
private MyAVProvider _avProvider = new MyAVProvider();
|
2012-10-23 03:33:57 +00:00
|
|
|
|
|
2017-04-24 15:32:45 +00:00
|
|
|
|
private class MyAVProvider : IVideoProvider, ISoundProvider, IDisposable
|
2012-10-23 03:33:57 +00:00
|
|
|
|
{
|
2017-05-05 16:21:37 +00:00
|
|
|
|
// to sync exactly with audio as this emulator creates and times it, the frame rate should be exactly 60:1 or 50:1
|
|
|
|
|
private int _frameHz;
|
|
|
|
|
|
2017-04-24 16:51:59 +00:00
|
|
|
|
public FrameBuffer Framebuffer { get; private set; }
|
2017-05-05 19:20:28 +00:00
|
|
|
|
public void ConnectToMachine(MachineBase m, GameProgram g)
|
2012-10-23 03:33:57 +00:00
|
|
|
|
{
|
2017-05-05 16:21:37 +00:00
|
|
|
|
_frameHz = m.FrameHZ;
|
2017-04-24 16:51:59 +00:00
|
|
|
|
Framebuffer = m.CreateFrameBuffer();
|
|
|
|
|
BufferWidth = Framebuffer.VisiblePitch;
|
|
|
|
|
BufferHeight = Framebuffer.Scanlines;
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_vidbuffer = new int[BufferWidth * BufferHeight];
|
2012-12-12 01:32:58 +00:00
|
|
|
|
|
2012-12-12 16:24:14 +00:00
|
|
|
|
uint newsamplerate = (uint)m.SoundSampleFrequency;
|
2017-05-05 19:20:28 +00:00
|
|
|
|
if (newsamplerate != _samplerate)
|
2012-12-12 16:24:14 +00:00
|
|
|
|
{
|
2012-12-16 15:15:54 +00:00
|
|
|
|
// really shouldn't happen (after init), but if it does, we're ready
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_resampler?.Dispose();
|
|
|
|
|
_resampler = new SpeexResampler((SpeexResampler.Quality)3, newsamplerate, 44100, newsamplerate, 44100, null, null);
|
|
|
|
|
_samplerate = newsamplerate;
|
|
|
|
|
_dcfilter = new DCFilter(256);
|
2014-02-21 17:00:06 +00:00
|
|
|
|
}
|
2017-04-24 15:32:45 +00:00
|
|
|
|
|
2017-01-31 20:51:35 +00:00
|
|
|
|
if (g.MachineType == MachineType.A2600PAL)
|
2017-05-05 19:20:28 +00:00
|
|
|
|
{
|
|
|
|
|
_palette = TIATables.PALPalette;
|
|
|
|
|
}
|
2017-01-31 20:51:35 +00:00
|
|
|
|
else if (g.MachineType == MachineType.A7800PAL)
|
2017-05-05 19:20:28 +00:00
|
|
|
|
{
|
|
|
|
|
_palette = MariaTables.PALPalette;
|
|
|
|
|
}
|
2017-01-31 20:51:35 +00:00
|
|
|
|
else if (g.MachineType == MachineType.A2600NTSC)
|
2017-05-05 19:20:28 +00:00
|
|
|
|
{
|
|
|
|
|
_palette = TIATables.NTSCPalette;
|
|
|
|
|
}
|
2017-01-31 20:51:35 +00:00
|
|
|
|
else
|
2017-05-05 19:20:28 +00:00
|
|
|
|
{
|
|
|
|
|
_palette = MariaTables.NTSCPalette;
|
|
|
|
|
}
|
2012-10-23 03:33:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-05-05 19:20:28 +00:00
|
|
|
|
private uint _samplerate;
|
|
|
|
|
private int[] _vidbuffer;
|
|
|
|
|
private SpeexResampler _resampler;
|
|
|
|
|
private DCFilter _dcfilter;
|
|
|
|
|
private int[] _palette;
|
2012-10-23 03:33:57 +00:00
|
|
|
|
|
2012-12-12 00:30:36 +00:00
|
|
|
|
public void FillFrameBuffer()
|
2012-10-23 03:33:57 +00:00
|
|
|
|
{
|
2012-12-12 01:32:58 +00:00
|
|
|
|
unsafe
|
2012-10-23 03:33:57 +00:00
|
|
|
|
{
|
2017-04-24 16:51:59 +00:00
|
|
|
|
fixed (byte* src_ = Framebuffer.VideoBuffer)
|
2017-05-05 19:20:28 +00:00
|
|
|
|
fixed (int* dst_ = _vidbuffer)
|
|
|
|
|
fixed (int* pal = _palette)
|
2012-10-23 03:33:57 +00:00
|
|
|
|
{
|
2013-07-31 00:33:02 +00:00
|
|
|
|
byte* src = src_;
|
|
|
|
|
int* dst = dst_;
|
2017-05-05 19:20:28 +00:00
|
|
|
|
for (int i = 0; i < _vidbuffer.Length; i++)
|
2012-12-12 00:23:01 +00:00
|
|
|
|
{
|
2013-07-31 00:33:02 +00:00
|
|
|
|
*dst++ = pal[*src++];
|
2012-12-12 00:23:01 +00:00
|
|
|
|
}
|
2012-10-23 03:33:57 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public int[] GetVideoBuffer()
|
|
|
|
|
{
|
2017-05-05 19:20:28 +00:00
|
|
|
|
return _vidbuffer;
|
2012-10-23 03:33:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2017-04-24 15:32:45 +00:00
|
|
|
|
public int VirtualWidth => 275;
|
|
|
|
|
public int VirtualHeight => BufferHeight;
|
2012-12-12 00:30:36 +00:00
|
|
|
|
public int BufferWidth { get; private set; }
|
|
|
|
|
public int BufferHeight { get; private set; }
|
2017-04-24 15:32:45 +00:00
|
|
|
|
public int BackgroundColor => unchecked((int)0xff000000);
|
2017-05-05 16:25:38 +00:00
|
|
|
|
public int VsyncNumerator => _frameHz;
|
|
|
|
|
public int VsyncDenominator => 1;
|
2012-10-23 03:33:57 +00:00
|
|
|
|
|
2016-12-11 17:14:42 +00:00
|
|
|
|
#region ISoundProvider
|
|
|
|
|
|
2017-04-24 15:32:45 +00:00
|
|
|
|
public bool CanProvideAsync => false;
|
2016-12-11 17:14:42 +00:00
|
|
|
|
|
|
|
|
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
2012-10-23 20:21:55 +00:00
|
|
|
|
{
|
2017-04-24 16:51:59 +00:00
|
|
|
|
int nsampin = Framebuffer.SoundBufferByteLength;
|
2012-12-12 01:32:58 +00:00
|
|
|
|
unsafe
|
|
|
|
|
{
|
2017-04-24 16:51:59 +00:00
|
|
|
|
fixed (byte* src = Framebuffer.SoundBuffer)
|
2012-12-12 01:32:58 +00:00
|
|
|
|
{
|
2012-12-12 15:36:17 +00:00
|
|
|
|
for (int i = 0; i < nsampin; i++)
|
|
|
|
|
{
|
2012-12-12 16:24:14 +00:00
|
|
|
|
// the buffer values don't really get very large at all,
|
|
|
|
|
// so this doesn't overflow
|
|
|
|
|
short s = (short)(src[i] * 200);
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_resampler.EnqueueSample(s, s);
|
2012-12-12 15:36:17 +00:00
|
|
|
|
}
|
2012-12-12 01:32:58 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2017-05-05 19:20:28 +00:00
|
|
|
|
|
|
|
|
|
_resampler.GetSamplesSync(out samples, out nsamp);
|
|
|
|
|
_dcfilter.PushThroughSamples(samples, nsamp * 2);
|
2012-10-23 20:21:55 +00:00
|
|
|
|
}
|
2012-12-12 01:32:58 +00:00
|
|
|
|
|
2017-04-24 15:32:45 +00:00
|
|
|
|
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
2016-12-11 17:14:42 +00:00
|
|
|
|
|
|
|
|
|
public void SetSyncMode(SyncSoundMode mode)
|
|
|
|
|
{
|
|
|
|
|
if (mode == SyncSoundMode.Async)
|
|
|
|
|
{
|
|
|
|
|
throw new NotSupportedException("Async mode is not supported.");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void GetSamplesAsync(short[] samples)
|
|
|
|
|
{
|
|
|
|
|
throw new InvalidOperationException("Async mode is not supported.");
|
|
|
|
|
}
|
|
|
|
|
|
2012-10-23 03:33:57 +00:00
|
|
|
|
public void DiscardSamples()
|
|
|
|
|
{
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_resampler?.DiscardSamples();
|
2012-10-23 03:33:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
2016-12-11 17:14:42 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
2012-12-12 01:32:58 +00:00
|
|
|
|
public void Dispose()
|
2012-10-23 03:33:57 +00:00
|
|
|
|
{
|
2017-05-05 19:20:28 +00:00
|
|
|
|
if (_resampler != null)
|
2012-12-12 01:32:58 +00:00
|
|
|
|
{
|
2017-05-05 19:20:28 +00:00
|
|
|
|
_resampler.Dispose();
|
|
|
|
|
_resampler = null;
|
2012-12-12 01:32:58 +00:00
|
|
|
|
}
|
2012-10-23 03:33:57 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-12-11 17:14:42 +00:00
|
|
|
|
|
2012-12-16 18:16:50 +00:00
|
|
|
|
#endregion
|
2012-10-23 03:33:57 +00:00
|
|
|
|
}
|
|
|
|
|
}
|