diff --git a/Assets/Plugins/Essgee.meta b/Assets/Plugins/Essgee.meta new file mode 100644 index 0000000..d4cbc45 --- /dev/null +++ b/Assets/Plugins/Essgee.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 88819cf01cddd694b9da30d934d2a564 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/AppEnvironment.cs b/Assets/Plugins/Essgee/AppEnvironment.cs new file mode 100644 index 0000000..825b9be --- /dev/null +++ b/Assets/Plugins/Essgee/AppEnvironment.cs @@ -0,0 +1,17 @@ +using UnityEngine; + +public static class AppEnvironment +{ +#if DEBUG + public static readonly bool DebugMode = true; +#else + public static readonly bool DebugMode = false; +#endif + public static readonly bool EnableCustomUnhandledExceptionHandler = true; + public static readonly bool TemporaryDisableCustomExceptionForm = false; + + public static readonly bool EnableLogger = false; + public static readonly bool EnableSuperSlowCPULogger = false; + + public static readonly bool EnableOpenGLDebug = false; +} \ No newline at end of file diff --git a/Assets/Plugins/Essgee/AppEnvironment.cs.meta b/Assets/Plugins/Essgee/AppEnvironment.cs.meta new file mode 100644 index 0000000..197f722 --- /dev/null +++ b/Assets/Plugins/Essgee/AppEnvironment.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ce38d9b5f910b4c4185e3fab7eb45ecf \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Configuration.cs b/Assets/Plugins/Essgee/Configuration.cs new file mode 100644 index 0000000..b41c4a1 --- /dev/null +++ b/Assets/Plugins/Essgee/Configuration.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; +using System.Drawing; + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +using Essgee.Emulation.Configuration; +using Essgee.Utilities; + +namespace Essgee +{ + public class Configuration + { + public const int RecentFilesCapacity = 15; + public const string DefaultShaderName = "Basic"; + + public bool LimitFps { get; set; } + public bool ShowFps { get; set; } + public bool Mute { get; set; } + public float Volume { get; set; } + public int SampleRate { get; set; } + public bool LowPassFilter { get; set; } + public int ScreenSize { get; set; } + [JsonConverter(typeof(StringEnumConverter))] + public ScreenSizeMode ScreenSizeMode { get; set; } + public string LastShader { get; set; } + public bool EnableXInput { get; set; } + public bool EnableRumble { get; set; } + public bool AutoPause { get; set; } + + public List<string> RecentFiles { get; set; } + + [JsonConverter(typeof(InterfaceDictionaryConverter<IConfiguration>))] + public Dictionary<string, IConfiguration> Machines { get; set; } + + public Dictionary<string, Point> DebugWindows { get; set; } + + public Configuration() + { + LimitFps = true; + ShowFps = false; + Mute = false; + Volume = 1.0f; + SampleRate = 44100; + LowPassFilter = true; + ScreenSize = 2; + ScreenSizeMode = ScreenSizeMode.Scale; + LastShader = DefaultShaderName; + EnableXInput = false; + EnableRumble = false; + AutoPause = true; + + RecentFiles = new List<string>(RecentFilesCapacity); + + Machines = new Dictionary<string, IConfiguration>(); + + DebugWindows = new Dictionary<string, Point>(); + } + } +} diff --git a/Assets/Plugins/Essgee/Configuration.cs.meta b/Assets/Plugins/Essgee/Configuration.cs.meta new file mode 100644 index 0000000..575b88c --- /dev/null +++ b/Assets/Plugins/Essgee/Configuration.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1cb6affe10cc31441b6301f115fbc2ab \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation.meta b/Assets/Plugins/Essgee/Emulation.meta new file mode 100644 index 0000000..309068c --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 289897a5723c6484ca800ed09fb528f5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Audio.meta b/Assets/Plugins/Essgee/Emulation/Audio.meta new file mode 100644 index 0000000..73a47ea --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 957a410fe17ff6242ab46d90146f525d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.Wave.cs b/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.Wave.cs new file mode 100644 index 0000000..b1bbc7f --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.Wave.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.Audio +{ + public partial class CGBAudio + { + public class CGBWave : Wave, IDMGAudioChannel + { + public override void Reset() + { + base.Reset(); + + for (var i = 0; i < sampleBuffer.Length; i += 2) + { + sampleBuffer[i + 0] = 0x00; + sampleBuffer[i + 1] = 0xFF; + } + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.Wave.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.Wave.cs.meta new file mode 100644 index 0000000..23d2fff --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.Wave.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 595436ed6c508904399695fee24279ff \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.cs b/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.cs new file mode 100644 index 0000000..9f32f39 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.Audio +{ + public partial class CGBAudio : DMGAudio, IAudio + { + public CGBAudio() + { + channelSampleBuffer = new List<short>[numChannels]; + for (int i = 0; i < numChannels; i++) channelSampleBuffer[i] = new List<short>(); + + mixedSampleBuffer = new List<short>(); + + channel1 = new Square(true); + channel2 = new Square(false); + channel3 = new CGBWave(); + channel4 = new Noise(); + + samplesPerFrame = cyclesPerFrame = cyclesPerSample = -1; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.cs.meta new file mode 100644 index 0000000..58d2261 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/CGBAudio.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 40f2c16e17787974881d5614068c46b7 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Noise.cs b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Noise.cs new file mode 100644 index 0000000..0ba3a39 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Noise.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.Audio +{ + public partial class DMGAudio + { + public class Noise : IDMGAudioChannel + { + static readonly int[] divisors = new int[] + { + 8, 16, 32, 48, 64, 80, 96, 112 + }; + + // NR41 + byte lengthLoad; + + // NR42 + byte envelopeStartingVolume, envelopePeriodReload; + bool envelopeAddMode; + + // NR43 + byte clockShift, divisorCode; + bool lfsrWidthMode; + + // NR44 + bool trigger, lengthEnable; + + // + + // Noise + int noiseCounter; + ushort lfsr; + + // Envelope + int volume, envelopeCounter; + bool isEnvelopeUpdateEnabled; + + // Misc + bool isChannelEnabled, isDacEnabled; + int lengthCounter; + + public int OutputVolume { get; private set; } + + public bool IsActive { get { return lengthCounter != 0; } } + + public Noise() + { + // + } + + public void Reset() + { + noiseCounter = 0; + lfsr = 0; + + volume = 15; + envelopeCounter = 0; + isEnvelopeUpdateEnabled = false; + + isChannelEnabled = isDacEnabled = false; + lengthCounter = 0; + + OutputVolume = volume; + } + + public void LengthCounterClock() + { + if (lengthCounter > 0 && lengthEnable) + { + lengthCounter--; + if (lengthCounter == 0) + isChannelEnabled = false; + } + } + + public void SweepClock() + { + throw new Exception("Channel type does not support sweep"); + } + + public void VolumeEnvelopeClock() + { + envelopeCounter--; + if (envelopeCounter == 0) + { + envelopeCounter = envelopePeriodReload; + + if (isEnvelopeUpdateEnabled) + { + var newVolume = volume; + if (envelopeAddMode) newVolume++; + else newVolume--; + + if (newVolume >= 0 && newVolume <= 15) + volume = newVolume; + else + isEnvelopeUpdateEnabled = false; + } + } + } + + public void Step() + { + if (!isChannelEnabled) return; + + noiseCounter--; + if (noiseCounter == 0) + { + noiseCounter = divisors[divisorCode] << clockShift; + + var result = (lfsr & 0b1) ^ ((lfsr >> 1) & 0b1); + lfsr = (ushort)((lfsr >> 1) | (result << 14)); + + if (lfsrWidthMode) + lfsr = (ushort)((lfsr & 0b10111111) | (result << 6)); + } + + OutputVolume = isDacEnabled && ((lfsr & 0b1) == 0) ? volume : 0; + } + + private void Trigger() + { + isChannelEnabled = true; + + if (lengthCounter == 0) lengthCounter = 64; + + noiseCounter = divisors[divisorCode] << clockShift; + volume = envelopeStartingVolume; + envelopeCounter = envelopePeriodReload; + isEnvelopeUpdateEnabled = true; + + lfsr = 0x7FFF; + } + + public void WritePort(byte port, byte value) + { + switch (port) + { + case 0: + break; + + case 1: + lengthLoad = (byte)((value >> 0) & 0b111111); + + lengthCounter = 64 - lengthLoad; + break; + + case 2: + envelopeStartingVolume = (byte)((value >> 4) & 0b1111); + envelopeAddMode = ((value >> 3) & 0b1) == 0b1; + envelopePeriodReload = (byte)((value >> 0) & 0b111); + + isDacEnabled = ((value >> 3) & 0b11111) != 0; + break; + + case 3: + clockShift = (byte)((value >> 4) & 0b1111); + lfsrWidthMode = ((value >> 3) & 0b1) == 0b1; + divisorCode = (byte)((value >> 0) & 0b111); + break; + + case 4: + trigger = ((value >> 7) & 0b1) == 0b1; + lengthEnable = ((value >> 6) & 0b1) == 0b1; + + if (trigger) Trigger(); + break; + } + } + + public byte ReadPort(byte port) + { + switch (port) + { + case 0: + return 0xFF; + + case 1: + return 0xFF; + + case 2: + return (byte)( + (envelopeStartingVolume << 4) | + (envelopeAddMode ? (1 << 3) : 0) | + (envelopePeriodReload << 0)); + + case 3: + return (byte)( + (clockShift << 4) | + (lfsrWidthMode ? (1 << 3) : 0) | + (divisorCode << 0)); + + case 4: + return (byte)( + 0xBF | + (lengthEnable ? (1 << 6) : 0)); + + default: + return 0xFF; + } + } + + public void WriteWaveRam(byte offset, byte value) + { + throw new Exception("Channel type does have Wave RAM"); + } + + public byte ReadWaveRam(byte offset) + { + throw new Exception("Channel type does have Wave RAM"); + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Noise.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Noise.cs.meta new file mode 100644 index 0000000..8d27ee9 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Noise.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0992ef0e8260e124c9667aea4433025c \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Square.cs b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Square.cs new file mode 100644 index 0000000..fff6359 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Square.cs @@ -0,0 +1,273 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.Audio +{ + public partial class DMGAudio + { + public class Square : IDMGAudioChannel + { + static readonly bool[,] dutyCycleTable = new bool[,] + { + { false, false, false, false, false, false, false, true, }, // 00000001 12.5% + { true, false, false, false, false, false, false, true, }, // 10000001 25% + { true, false, false, false, false, true, true, true, }, // 10000111 50% + { false, true, true, true, true, true, true, false, } // 01111110 75% + }; + + // NR10/20 + byte sweepPeriodReload, sweepShift; + bool sweepNegate; + + // NR11/21 + byte dutyCycle, lengthLoad; + + // NR12/22 + byte envelopeStartingVolume, envelopePeriodReload; + bool envelopeAddMode; + + // NR13/23 + byte frequencyLSB; + + // NR14/24 + bool trigger, lengthEnable; + byte frequencyMSB; + + // + + readonly bool channelSupportsSweep; + + // Sweep + bool isSweepEnabled; + int sweepCounter, sweepFreqShadow; + + // Frequency + int frequencyCounter; + + // Envelope + int volume, envelopeCounter; + bool isEnvelopeUpdateEnabled; + + // Misc + bool isChannelEnabled, isDacEnabled; + int lengthCounter, dutyCounter; + + public int OutputVolume { get; private set; } + + public bool IsActive { get { return lengthCounter != 0; } } + + public Square(bool hasSweep) + { + channelSupportsSweep = hasSweep; + } + + public void Reset() + { + isSweepEnabled = false; + sweepCounter = sweepFreqShadow = 0; + + frequencyCounter = 0; + + volume = 15; + envelopeCounter = 0; + isEnvelopeUpdateEnabled = false; + + isChannelEnabled = isDacEnabled = false; + lengthCounter = dutyCounter = 0; + + OutputVolume = volume; + } + + public void LengthCounterClock() + { + if (lengthCounter > 0 && lengthEnable) + { + lengthCounter--; + if (lengthCounter == 0) + isChannelEnabled = false; + } + } + + public void SweepClock() + { + if (!channelSupportsSweep) return; + + sweepCounter--; + if (sweepCounter == 0) + { + sweepCounter = sweepPeriodReload; + + if (isSweepEnabled && sweepPeriodReload != 0) + { + var newFrequency = PerformSweepCalculations(); + if (newFrequency <= 2047 && sweepShift != 0) + { + sweepFreqShadow = newFrequency; + frequencyMSB = (byte)((newFrequency >> 8) & 0b111); + frequencyLSB = (byte)(newFrequency & 0xFF); + PerformSweepCalculations(); + } + } + } + } + + public void VolumeEnvelopeClock() + { + envelopeCounter--; + if (envelopeCounter == 0) + { + envelopeCounter = envelopePeriodReload; + + if (isEnvelopeUpdateEnabled) + { + var newVolume = volume; + if (envelopeAddMode) newVolume++; + else newVolume--; + + if (newVolume >= 0 && newVolume <= 15) + volume = newVolume; + else + isEnvelopeUpdateEnabled = false; + } + } + } + + public void Step() + { + if (!isChannelEnabled) return; + + frequencyCounter--; + if (frequencyCounter == 0) + { + frequencyCounter = (2048 - ((frequencyMSB << 8) | frequencyLSB)) * 4; + dutyCounter++; + dutyCounter %= 8; + } + + OutputVolume = isDacEnabled && dutyCycleTable[dutyCycle, dutyCounter] ? volume : 0; + } + + private void Trigger() + { + isChannelEnabled = true; + + if (lengthCounter == 0) lengthCounter = 64; + + frequencyCounter = (2048 - ((frequencyMSB << 8) | frequencyLSB)) * 4; + volume = envelopeStartingVolume; + envelopeCounter = envelopePeriodReload; + isEnvelopeUpdateEnabled = true; + + if (channelSupportsSweep) + { + sweepFreqShadow = (frequencyMSB << 8) | frequencyLSB; + sweepCounter = sweepPeriodReload; + isSweepEnabled = sweepPeriodReload != 0 || sweepShift != 0; + if (sweepShift != 0) + PerformSweepCalculations(); + } + } + + private int PerformSweepCalculations() + { + var newFrequency = sweepFreqShadow >> sweepShift; + if (sweepNegate) newFrequency = -newFrequency; + newFrequency += sweepFreqShadow; + if (newFrequency > 2047) isChannelEnabled = false; + return newFrequency; + } + + public void WritePort(byte port, byte value) + { + switch (port) + { + case 0: + if (channelSupportsSweep) + { + sweepPeriodReload = (byte)((value >> 4) & 0b111); + sweepNegate = ((value >> 3) & 0b1) == 0b1; + sweepShift = (byte)((value >> 0) & 0b111); + } + break; + + case 1: + dutyCycle = (byte)((value >> 6) & 0b11); + lengthLoad = (byte)((value >> 0) & 0b111111); + + lengthCounter = 64 - lengthLoad; + break; + + case 2: + envelopeStartingVolume = (byte)((value >> 4) & 0b1111); + envelopeAddMode = ((value >> 3) & 0b1) == 0b1; + envelopePeriodReload = (byte)((value >> 0) & 0b111); + + isDacEnabled = ((value >> 3) & 0b11111) != 0; + break; + + case 3: + frequencyLSB = value; + break; + + case 4: + trigger = ((value >> 7) & 0b1) == 0b1; + lengthEnable = ((value >> 6) & 0b1) == 0b1; + frequencyMSB = (byte)((value >> 0) & 0b111); + + if (trigger) Trigger(); + break; + } + } + + public byte ReadPort(byte port) + { + switch (port) + { + case 0: + if (channelSupportsSweep) + { + return (byte)( + 0x80 | + (sweepPeriodReload << 4) | + (sweepNegate ? (1 << 3) : 0) | + (sweepShift << 0)); + } + else + return 0xFF; + + case 1: + return (byte)( + 0x3F | + (dutyCycle << 6)); + + case 2: + return (byte)( + (envelopeStartingVolume << 4) | + (envelopeAddMode ? (1 << 3) : 0) | + (envelopePeriodReload << 0)); + + case 4: + return (byte)( + 0xBF | + (lengthEnable ? (1 << 6) : 0)); + + default: + return 0xFF; + } + } + + public void WriteWaveRam(byte offset, byte value) + { + throw new Exception("Channel type does have Wave RAM"); + } + + public byte ReadWaveRam(byte offset) + { + throw new Exception("Channel type does have Wave RAM"); + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Square.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Square.cs.meta new file mode 100644 index 0000000..639fff9 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Square.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eab50abebc5d5f341889e031ded6a571 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Wave.cs b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Wave.cs new file mode 100644 index 0000000..d5434a4 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Wave.cs @@ -0,0 +1,190 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.Audio +{ + public partial class DMGAudio + { + public class Wave : IDMGAudioChannel + { + // NR30 + bool isDacEnabled; + + // NR31 + byte lengthLoad; + + // NR32 + byte volumeCode; + + // NR33 + byte frequencyLSB; + + // NR34 + bool trigger, lengthEnable; + byte frequencyMSB; + + // Wave + protected byte[] sampleBuffer; + int frequencyCounter, positionCounter, volume; + + // Misc + bool isChannelEnabled; + int lengthCounter; + + public int OutputVolume { get; private set; } + + public bool IsActive { get { return isDacEnabled; } } // TODO: correct? lengthCounter check makes Zelda Oracle games hang + + public Wave() + { + sampleBuffer = new byte[16]; + } + + public virtual void Reset() + { + for (var i = 0; i < sampleBuffer.Length; i++) sampleBuffer[i] = (byte)StandInfo.Random.Next(255); + frequencyCounter = positionCounter = 0; + volume = 15; + + isChannelEnabled = isDacEnabled = false; + lengthCounter = 0; + + OutputVolume = volume; + } + + public void LengthCounterClock() + { + if (lengthCounter > 0 && lengthEnable) + { + lengthCounter--; + if (lengthCounter == 0) + isChannelEnabled = false; + } + } + + public void SweepClock() + { + throw new Exception("Channel type does not support sweep"); + } + + public void VolumeEnvelopeClock() + { + throw new Exception("Channel type does not support envelope"); + } + + public void Step() + { + if (!isChannelEnabled) return; + + frequencyCounter--; + if (frequencyCounter == 0) + { + frequencyCounter = (2048 - ((frequencyMSB << 8) | frequencyLSB)) * 2; + positionCounter++; + positionCounter %= 32; + + var value = sampleBuffer[positionCounter / 2]; + if ((positionCounter & 0b1) == 0) value >>= 4; + value &= 0b1111; + + if (volumeCode != 0) + volume = value >> (volumeCode - 1); + else + volume = 0; + } + + OutputVolume = isDacEnabled ? volume : 0; + } + + private void Trigger() + { + isChannelEnabled = true; + + if (lengthCounter == 0) lengthCounter = 256; + + frequencyCounter = (2048 - ((frequencyMSB << 8) | frequencyLSB)) * 2; + positionCounter = 0; + } + + public void WritePort(byte port, byte value) + { + switch (port) + { + case 0: + isDacEnabled = ((value >> 7) & 0b1) == 0b1; + break; + + case 1: + lengthLoad = value; + + lengthCounter = 256 - lengthLoad; + break; + + case 2: + volumeCode = (byte)((value >> 5) & 0b11); + break; + + case 3: + frequencyLSB = value; + break; + + case 4: + trigger = ((value >> 7) & 0b1) == 0b1; + lengthEnable = ((value >> 6) & 0b1) == 0b1; + frequencyMSB = (byte)((value >> 0) & 0b111); + + if (trigger) Trigger(); + break; + } + } + + public byte ReadPort(byte port) + { + switch (port) + { + case 0: + return (byte)( + 0x7F | + (isDacEnabled ? (1 << 7) : 0)); + + case 1: + return 0xFF; + + case 2: + return (byte)( + 0x9F | + (volumeCode << 5)); + + case 4: + return (byte)( + 0xBF | + (lengthEnable ? (1 << 6) : 0)); + + default: + return 0xFF; + } + } + + // TODO: more details on behavior on access w/ channel enabled + + public void WriteWaveRam(byte offset, byte value) + { + if (!isDacEnabled) + sampleBuffer[offset & (sampleBuffer.Length - 1)] = value; + else + sampleBuffer[positionCounter & (sampleBuffer.Length - 1)] = value; + } + + public byte ReadWaveRam(byte offset) + { + if (!isDacEnabled) + return sampleBuffer[offset & (sampleBuffer.Length - 1)]; + else + return 0xFF; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Wave.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Wave.cs.meta new file mode 100644 index 0000000..acfa2fa --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.Wave.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a9f4769005789914785d05d63578c931 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.cs b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.cs new file mode 100644 index 0000000..4a17e69 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.cs @@ -0,0 +1,418 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.EventArguments; +using Essgee.Utilities; + +namespace Essgee.Emulation.Audio +{ + public partial class DMGAudio : IAudio + { + // https://gbdev.gg8.se/wiki/articles/Gameboy_sound_hardware + // http://emudev.de/gameboy-emulator/bleeding-ears-time-to-add-audio/ + // https://github.com/GhostSonic21/GhostBoy/blob/master/GhostBoy/APU.cpp + + protected const int numChannels = 4; + + protected const string channel1OptionName = "AudioEnableCh1Square"; + protected const string channel2OptionName = "AudioEnableCh2Square"; + protected const string channel3OptionName = "AudioEnableCh3Wave"; + protected const string channel4OptionName = "AudioEnableCh4Noise"; + + protected IDMGAudioChannel channel1, channel2, channel3, channel4; + + // FF24 - NR50 + byte[] volumeRightLeft; + bool[] vinEnableRightLeft; + + // FF25 - NR51 + bool[] channel1Enable, channel2Enable, channel3Enable, channel4Enable; + + // FF26 - NR52 + bool isSoundHwEnabled; + + protected int frameSequencerReload, frameSequencerCounter, frameSequencer; + + protected List<short>[] channelSampleBuffer; + protected List<short> mixedSampleBuffer; + public virtual event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples; + public virtual void OnEnqueueSamples(EnqueueSamplesEventArgs e) { EnqueueSamples?.Invoke(this, e); } + + protected int sampleRate, numOutputChannels; + + // + + double clockRate, refreshRate; + protected int samplesPerFrame, cyclesPerFrame, cyclesPerSample; + [StateRequired] + int sampleCycleCount, frameCycleCount; + + protected bool channel1ForceEnable, channel2ForceEnable, channel3ForceEnable, channel4ForceEnable; + + public (string Name, string Description)[] RuntimeOptions => new (string name, string description)[] + { + (channel1OptionName, "Channel 1 (Square)"), + (channel2OptionName, "Channel 2 (Square)"), + (channel3OptionName, "Channel 3 (Wave)"), + (channel4OptionName, "Channel 4 (Noise)") + }; + + public DMGAudio() + { + channelSampleBuffer = new List<short>[numChannels]; + for (int i = 0; i < numChannels; i++) channelSampleBuffer[i] = new List<short>(); + + mixedSampleBuffer = new List<short>(); + + channel1 = new Square(true); + channel2 = new Square(false); + channel3 = new Wave(); + channel4 = new Noise(); + + samplesPerFrame = cyclesPerFrame = cyclesPerSample = -1; + + channel1ForceEnable = true; + channel2ForceEnable = true; + channel3ForceEnable = true; + channel4ForceEnable = true; + } + + public object GetRuntimeOption(string name) + { + switch (name) + { + case channel1OptionName: return channel1ForceEnable; + case channel2OptionName: return channel2ForceEnable; + case channel3OptionName: return channel3ForceEnable; + case channel4OptionName: return channel4ForceEnable; + default: return null; + } + } + + public void SetRuntimeOption(string name, object value) + { + switch (name) + { + case channel1OptionName: channel1ForceEnable = (bool)value; break; + case channel2OptionName: channel2ForceEnable = (bool)value; break; + case channel3OptionName: channel3ForceEnable = (bool)value; break; + case channel4OptionName: channel4ForceEnable = (bool)value; break; + } + } + + public void SetSampleRate(int rate) + { + sampleRate = rate; + ConfigureTimings(); + } + + public void SetOutputChannels(int channels) + { + numOutputChannels = channels; + ConfigureTimings(); + } + + public void SetClockRate(double clock) + { + clockRate = clock; + ConfigureTimings(); + } + + public void SetRefreshRate(double refresh) + { + refreshRate = refresh; + ConfigureTimings(); + } + + private void ConfigureTimings() + { + samplesPerFrame = (int)(sampleRate / refreshRate); + cyclesPerFrame = (int)Math.Round(clockRate / refreshRate); + cyclesPerSample = (cyclesPerFrame / samplesPerFrame); + + volumeRightLeft = new byte[numOutputChannels]; + vinEnableRightLeft = new bool[numOutputChannels]; + + channel1Enable = new bool[numOutputChannels]; + channel2Enable = new bool[numOutputChannels]; + channel3Enable = new bool[numOutputChannels]; + channel4Enable = new bool[numOutputChannels]; + + FlushSamples(); + } + + public virtual void Startup() + { + Reset(); + + if (samplesPerFrame == -1) throw new EmulationException("GB PSG: Timings not configured, invalid samples per frame"); + if (cyclesPerFrame == -1) throw new EmulationException("GB PSG: Timings not configured, invalid cycles per frame"); + if (cyclesPerSample == -1) throw new EmulationException("GB PSG: Timings not configured, invalid cycles per sample"); + } + + public virtual void Shutdown() + { + // + } + + public virtual void Reset() + { + FlushSamples(); + + channel1.Reset(); + channel2.Reset(); + channel3.Reset(); + channel4.Reset(); + + for (var i = 0; i < numOutputChannels; i++) + { + volumeRightLeft[i] = 0; + vinEnableRightLeft[i] = false; + + channel1Enable[i] = false; + channel2Enable[i] = false; + channel3Enable[i] = false; + channel4Enable[i] = false; + } + + frameSequencerReload = (int)(clockRate / 512); + frameSequencerCounter = frameSequencerReload; + frameSequencer = 0; + + sampleCycleCount = frameCycleCount = 0; + } + + public void Step(int clockCyclesInStep) + { + if (!isSoundHwEnabled) return; + + sampleCycleCount += clockCyclesInStep; + frameCycleCount += clockCyclesInStep; + + for (int i = 0; i < clockCyclesInStep; i++) + { + frameSequencerCounter--; + if (frameSequencerCounter == 0) + { + frameSequencerCounter = frameSequencerReload; + + switch (frameSequencer) + { + case 0: + channel1.LengthCounterClock(); + channel2.LengthCounterClock(); + channel3.LengthCounterClock(); + channel4.LengthCounterClock(); + break; + + case 1: + break; + + case 2: + channel1.SweepClock(); + channel1.LengthCounterClock(); + channel2.LengthCounterClock(); + channel3.LengthCounterClock(); + channel4.LengthCounterClock(); + break; + + case 3: + break; + + case 4: + channel1.LengthCounterClock(); + channel2.LengthCounterClock(); + channel3.LengthCounterClock(); + channel4.LengthCounterClock(); + break; + + case 5: + break; + + case 6: + channel1.SweepClock(); + channel1.LengthCounterClock(); + channel2.LengthCounterClock(); + channel3.LengthCounterClock(); + channel4.LengthCounterClock(); + break; + + case 7: + channel1.VolumeEnvelopeClock(); + channel2.VolumeEnvelopeClock(); + channel4.VolumeEnvelopeClock(); + break; + } + + frameSequencer++; + if (frameSequencer >= 8) + frameSequencer = 0; + } + + channel1.Step(); + channel2.Step(); + channel3.Step(); + channel4.Step(); + } + + if (sampleCycleCount >= cyclesPerSample) + { + GenerateSample(); + + sampleCycleCount -= cyclesPerSample; + } + + if (mixedSampleBuffer.Count >= (samplesPerFrame * numOutputChannels)) + { + OnEnqueueSamples(new EnqueueSamplesEventArgs( + numChannels, + channelSampleBuffer.Select(x => x.ToArray()).ToArray(), + new bool[] { !channel1ForceEnable, !channel2ForceEnable, !channel3ForceEnable, !channel4ForceEnable }, + mixedSampleBuffer.ToArray())); + + FlushSamples(); + } + + if (frameCycleCount >= cyclesPerFrame) + { + frameCycleCount -= cyclesPerFrame; + sampleCycleCount = frameCycleCount; + } + } + + protected virtual void GenerateSample() + { + for (int i = 0; i < numOutputChannels; i++) + { + /* Generate samples */ + var ch1 = (short)(((channel1Enable[i] ? channel1.OutputVolume : 0) * (volumeRightLeft[i] + 1)) << 8); + var ch2 = (short)(((channel2Enable[i] ? channel2.OutputVolume : 0) * (volumeRightLeft[i] + 1)) << 8); + var ch3 = (short)(((channel3Enable[i] ? channel3.OutputVolume : 0) * (volumeRightLeft[i] + 1)) << 8); + var ch4 = (short)(((channel4Enable[i] ? channel4.OutputVolume : 0) * (volumeRightLeft[i] + 1)) << 8); + + channelSampleBuffer[0].Add(ch1); + channelSampleBuffer[1].Add(ch2); + channelSampleBuffer[2].Add(ch3); + channelSampleBuffer[3].Add(ch4); + + /* Mix samples */ + var mixed = 0; + if (channel1ForceEnable) mixed += ch1; + if (channel2ForceEnable) mixed += ch2; + if (channel3ForceEnable) mixed += ch3; + if (channel4ForceEnable) mixed += ch4; + mixed /= numChannels; + + mixedSampleBuffer.Add((short)mixed); + } + } + + public void FlushSamples() + { + for (int i = 0; i < numChannels; i++) + channelSampleBuffer[i].Clear(); + + mixedSampleBuffer.Clear(); + } + + public virtual byte ReadPort(byte port) + { + // Channels + if (port >= 0x10 && port <= 0x14) + return channel1.ReadPort((byte)(port - 0x10)); + else if (port >= 0x15 && port <= 0x19) + return channel2.ReadPort((byte)(port - 0x15)); + else if (port >= 0x1A && port <= 0x1E) + return channel3.ReadPort((byte)(port - 0x1A)); + else if (port >= 0x1F && port <= 0x23) + return channel4.ReadPort((byte)(port - 0x1F)); + + // Channel 3 Wave RAM + else if (port >= 0x30 && port <= 0x3F) + return channel3.ReadWaveRam((byte)(port - 0x30)); + + // Control ports + else + switch (port) + { + case 0x24: + return (byte)( + (vinEnableRightLeft[1] ? (1 << 7) : 0) | + (volumeRightLeft[1] << 4) | + (vinEnableRightLeft[0] ? (1 << 3) : 0) | + (volumeRightLeft[0] << 0)); + + case 0x25: + return (byte)( + (channel4Enable[1] ? (1 << 7) : 0) | + (channel3Enable[1] ? (1 << 6) : 0) | + (channel2Enable[1] ? (1 << 5) : 0) | + (channel1Enable[1] ? (1 << 4) : 0) | + (channel4Enable[0] ? (1 << 3) : 0) | + (channel3Enable[0] ? (1 << 2) : 0) | + (channel2Enable[0] ? (1 << 1) : 0) | + (channel1Enable[0] ? (1 << 0) : 0)); + + case 0x26: + return (byte)( + 0x70 | + (isSoundHwEnabled ? (1 << 7) : 0) | + (channel4.IsActive ? (1 << 3) : 0) | + (channel3.IsActive ? (1 << 2) : 0) | + (channel2.IsActive ? (1 << 1) : 0) | + (channel1.IsActive ? (1 << 0) : 0)); + + default: + return 0xFF; + } + } + + public virtual void WritePort(byte port, byte value) + { + // Channels + if (port >= 0x10 && port <= 0x14) + channel1.WritePort((byte)(port - 0x10), value); + else if (port >= 0x15 && port <= 0x19) + channel2.WritePort((byte)(port - 0x15), value); + else if (port >= 0x1A && port <= 0x1E) + channel3.WritePort((byte)(port - 0x1A), value); + else if (port >= 0x1F && port <= 0x23) + channel4.WritePort((byte)(port - 0x1F), value); + + // Channel 3 Wave RAM + else if (port >= 0x30 && port <= 0x3F) + channel3.WriteWaveRam((byte)(port - 0x30), value); + + // Control ports + else + switch (port) + { + case 0x24: + vinEnableRightLeft[1] = ((value >> 7) & 0b1) == 0b1; + volumeRightLeft[1] = (byte)((value >> 4) & 0b111); + vinEnableRightLeft[0] = ((value >> 3) & 0b1) == 0b1; + volumeRightLeft[0] = (byte)((value >> 0) & 0b111); + break; + + case 0x25: + channel4Enable[1] = ((value >> 7) & 0b1) == 0b1; + channel3Enable[1] = ((value >> 6) & 0b1) == 0b1; + channel2Enable[1] = ((value >> 5) & 0b1) == 0b1; + channel1Enable[1] = ((value >> 4) & 0b1) == 0b1; + channel4Enable[0] = ((value >> 3) & 0b1) == 0b1; + channel3Enable[0] = ((value >> 2) & 0b1) == 0b1; + channel2Enable[0] = ((value >> 1) & 0b1) == 0b1; + channel1Enable[0] = ((value >> 0) & 0b1) == 0b1; + break; + + case 0x26: + isSoundHwEnabled = ((value >> 7) & 0b1) == 0b1; + break; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.cs.meta new file mode 100644 index 0000000..d9dc4d6 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/DMGAudio.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b1912726afeeb8a48bd9e35bf55fe8e3 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Audio/IAudio.cs b/Assets/Plugins/Essgee/Emulation/Audio/IAudio.cs new file mode 100644 index 0000000..3254c45 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/IAudio.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.EventArguments; + +namespace Essgee.Emulation.Audio +{ + interface IAudio + { + event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples; + void OnEnqueueSamples(EnqueueSamplesEventArgs e); + + (string Name, string Description)[] RuntimeOptions { get; } + + object GetRuntimeOption(string name); + void SetRuntimeOption(string name, object value); + + void Startup(); + void Shutdown(); + void Reset(); + void Step(int clockCyclesInStep); + + void SetSampleRate(int rate); + void SetOutputChannels(int channels); + void SetClockRate(double clock); + void SetRefreshRate(double refresh); + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/IAudio.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/IAudio.cs.meta new file mode 100644 index 0000000..ced8ebc --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/IAudio.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c7d71d4c287c7024f88c3e76da01431f \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Audio/IDMGAudioChannel.cs b/Assets/Plugins/Essgee/Emulation/Audio/IDMGAudioChannel.cs new file mode 100644 index 0000000..8a37c6e --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/IDMGAudioChannel.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.Audio +{ + public interface IDMGAudioChannel + { + int OutputVolume { get; } + bool IsActive { get; } + + void Reset(); + void LengthCounterClock(); + void SweepClock(); + void VolumeEnvelopeClock(); + void Step(); + + void WritePort(byte port, byte value); + byte ReadPort(byte port); + void WriteWaveRam(byte offset, byte value); + byte ReadWaveRam(byte offset); + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/IDMGAudioChannel.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/IDMGAudioChannel.cs.meta new file mode 100644 index 0000000..beebd85 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/IDMGAudioChannel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b23834762d9307449a8e4b923fb2688b \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Audio/SN76489.cs b/Assets/Plugins/Essgee/Emulation/Audio/SN76489.cs new file mode 100644 index 0000000..41dd4c6 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/SN76489.cs @@ -0,0 +1,387 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.EventArguments; +using Essgee.Utilities; + +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Audio +{ + public class SN76489 : IAudio + { + /* http://www.smspower.org/Development/SN76489 */ + /* Differences in various system's PSGs: http://forums.nesdev.com/viewtopic.php?p=190216#p190216 */ + + protected const int numChannels = 4, numToneChannels = 3, noiseChannelIndex = 3; + + protected const string channel1OptionName = "AudioEnableCh1Square"; + protected const string channel2OptionName = "AudioEnableCh2Square"; + protected const string channel3OptionName = "AudioEnableCh3Square"; + protected const string channel4OptionName = "AudioEnableCh4Noise"; + + /* Noise generation constants */ + protected virtual ushort noiseLfsrMask => 0x7FFF; + protected virtual ushort noiseTappedBits => 0x0003; /* Bits 0 and 1 */ + protected virtual int noiseBitShift => 14; + + /* Sample generation & event handling */ + protected List<short>[] channelSampleBuffer; + protected List<short> mixedSampleBuffer; + public virtual event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples; + public virtual void OnEnqueueSamples(EnqueueSamplesEventArgs e) { EnqueueSamples?.Invoke(this, e); } + + /* Audio output variables */ + protected int sampleRate, numOutputChannels; + + /* Channel registers */ + [StateRequired] + protected ushort[] volumeRegisters; /* Channels 0-3: 4 bits */ + [StateRequired] + protected ushort[] toneRegisters; /* Channels 0-2 (tone): 10 bits; channel 3 (noise): 3 bits */ + + /* Channel counters */ + [StateRequired] + protected ushort[] channelCounters; /* 10-bit counters */ + [StateRequired] + protected bool[] channelOutput; + + /* Volume attenuation table */ + protected short[] volumeTable; /* 2dB change per volume register step */ + + /* Latched channel/type */ + [StateRequired] + byte latchedChannel, latchedType; + + /* Linear-feedback shift register, for noise generation */ + [StateRequired] + protected ushort noiseLfsr; /* 15-bit */ + + /* Timing variables */ + double clockRate, refreshRate; + int samplesPerFrame, cyclesPerFrame, cyclesPerSample; + [StateRequired] + int sampleCycleCount, frameCycleCount, dividerCount; + + /* User-facing channel toggles */ + protected bool channel1ForceEnable, channel2ForceEnable, channel3ForceEnable, channel4ForceEnable; + + public (string Name, string Description)[] RuntimeOptions => new (string name, string description)[] + { + (channel1OptionName, "Channel 1 (Square)"), + (channel2OptionName, "Channel 2 (Square)"), + (channel3OptionName, "Channel 3 (Square)"), + (channel4OptionName, "Channel 4 (Noise)") + }; + + public SN76489() + { + channelSampleBuffer = new List<short>[numChannels]; + for (int i = 0; i < numChannels; i++) channelSampleBuffer[i] = new List<short>(); + + mixedSampleBuffer = new List<short>(); + + volumeRegisters = new ushort[numChannels]; + toneRegisters = new ushort[numChannels]; + + channelCounters = new ushort[numChannels]; + channelOutput = new bool[numChannels]; + + volumeTable = new short[16]; + for (int i = 0; i < volumeTable.Length; i++) + volumeTable[i] = (short)(short.MaxValue * Math.Pow(2.0, i * -2.0 / 6.0)); + volumeTable[15] = 0; + + samplesPerFrame = cyclesPerFrame = cyclesPerSample = -1; + + channel1ForceEnable = true; + channel2ForceEnable = true; + channel3ForceEnable = true; + channel4ForceEnable = true; + } + + public object GetRuntimeOption(string name) + { + switch (name) + { + case channel1OptionName: return channel1ForceEnable; + case channel2OptionName: return channel2ForceEnable; + case channel3OptionName: return channel3ForceEnable; + case channel4OptionName: return channel4ForceEnable; + default: return null; + } + } + + public void SetRuntimeOption(string name, object value) + { + switch (name) + { + case channel1OptionName: channel1ForceEnable = (bool)value; break; + case channel2OptionName: channel2ForceEnable = (bool)value; break; + case channel3OptionName: channel3ForceEnable = (bool)value; break; + case channel4OptionName: channel4ForceEnable = (bool)value; break; + } + } + + public void SetSampleRate(int rate) + { + sampleRate = rate; + ConfigureTimings(); + } + + public void SetOutputChannels(int channels) + { + numOutputChannels = channels; + ConfigureTimings(); + } + + public void SetClockRate(double clock) + { + clockRate = clock; + ConfigureTimings(); + } + + public void SetRefreshRate(double refresh) + { + refreshRate = refresh; + ConfigureTimings(); + } + + private void ConfigureTimings() + { + samplesPerFrame = (int)(sampleRate / refreshRate); + cyclesPerFrame = (int)(clockRate / refreshRate); + cyclesPerSample = (cyclesPerFrame / samplesPerFrame); + + FlushSamples(); + } + + public virtual void Startup() + { + Reset(); + + if (samplesPerFrame == -1) throw new EmulationException("SN76489: Timings not configured, invalid samples per frame"); + if (cyclesPerFrame == -1) throw new EmulationException("SN76489: Timings not configured, invalid cycles per frame"); + if (cyclesPerSample == -1) throw new EmulationException("SN76489: Timings not configured, invalid cycles per sample"); + } + + public virtual void Shutdown() + { + // + } + + public virtual void Reset() + { + FlushSamples(); + + latchedChannel = latchedType = 0x00; + noiseLfsr = 0x4000; + + for (int i = 0; i < numChannels; i++) + { + volumeRegisters[i] = 0x000F; + toneRegisters[i] = 0x0000; + } + + sampleCycleCount = frameCycleCount = dividerCount = 0; + } + + public void Step(int clockCyclesInStep) + { + sampleCycleCount += clockCyclesInStep; + frameCycleCount += clockCyclesInStep; + + for (int i = 0; i < clockCyclesInStep; i++) + { + dividerCount++; + if (dividerCount == 16) + { + for (int ch = 0; ch < numToneChannels; ch++) + StepToneChannel(ch); + StepNoiseChannel(); + + dividerCount = 0; + } + } + + if (sampleCycleCount >= cyclesPerSample) + { + GenerateSample(); + + sampleCycleCount -= cyclesPerSample; + } + + if (mixedSampleBuffer.Count >= (samplesPerFrame * numOutputChannels)) + { + OnEnqueueSamples(new EnqueueSamplesEventArgs( + numChannels, + channelSampleBuffer.Select(x => x.ToArray()).ToArray(), + new bool[] { !channel1ForceEnable, !channel2ForceEnable, !channel3ForceEnable, !channel4ForceEnable }, + mixedSampleBuffer.ToArray())); + + FlushSamples(); + } + + if (frameCycleCount >= cyclesPerFrame) + { + frameCycleCount -= cyclesPerFrame; + sampleCycleCount = frameCycleCount; + } + } + + private void StepToneChannel(int ch) + { + /* Check for counter underflow */ + if ((channelCounters[ch] & 0x03FF) > 0) + channelCounters[ch]--; + + /* Counter underflowed, reload and flip output bit, then generate sample */ + if ((channelCounters[ch] & 0x03FF) == 0) + { + channelCounters[ch] = (ushort)(toneRegisters[ch] & 0x03FF); + channelOutput[ch] = !channelOutput[ch]; + } + } + + private void StepNoiseChannel() + { + int chN = noiseChannelIndex; + { + /* Check for counter underflow */ + if ((channelCounters[chN] & 0x03FF) > 0) + channelCounters[chN]--; + + /* Counter underflowed, reload and flip output bit */ + if ((channelCounters[chN] & 0x03FF) == 0) + { + switch (toneRegisters[chN] & 0x3) + { + case 0x0: channelCounters[chN] = 0x10; break; + case 0x1: channelCounters[chN] = 0x20; break; + case 0x2: channelCounters[chN] = 0x40; break; + case 0x3: channelCounters[chN] = (ushort)(toneRegisters[2] & 0x03FF); break; + } + channelOutput[chN] = !channelOutput[chN]; + + if (channelOutput[chN]) + { + /* Check noise type, then generate sample */ + bool isWhiteNoise = (((toneRegisters[chN] >> 2) & 0x1) == 0x1); + + ushort newLfsrBit = (ushort)((isWhiteNoise ? CheckParity((ushort)(noiseLfsr & noiseTappedBits)) : (noiseLfsr & 0x01)) << noiseBitShift); + + noiseLfsr = (ushort)((newLfsrBit | (noiseLfsr >> 1)) & noiseLfsrMask); + } + } + } + } + + protected virtual void GenerateSample() + { + for (int i = 0; i < numOutputChannels; i++) + { + /* Generate samples */ + var ch1 = (short)(volumeTable[volumeRegisters[0]] * ((toneRegisters[0] < 2 ? true : channelOutput[0]) ? 1.0 : 0.0)); + var ch2 = (short)(volumeTable[volumeRegisters[1]] * ((toneRegisters[1] < 2 ? true : channelOutput[1]) ? 1.0 : 0.0)); + var ch3 = (short)(volumeTable[volumeRegisters[2]] * ((toneRegisters[2] < 2 ? true : channelOutput[2]) ? 1.0 : 0.0)); + var ch4 = (short)(volumeTable[volumeRegisters[3]] * (noiseLfsr & 0x1)); + + channelSampleBuffer[0].Add(ch1); + channelSampleBuffer[1].Add(ch2); + channelSampleBuffer[2].Add(ch3); + channelSampleBuffer[3].Add(ch4); + + /* Mix samples */ + var mixed = 0; + if (channel1ForceEnable) mixed += ch1; + if (channel2ForceEnable) mixed += ch2; + if (channel3ForceEnable) mixed += ch3; + if (channel4ForceEnable) mixed += ch4; + mixed /= numChannels; + + mixedSampleBuffer.Add((short)mixed); + } + } + + public void FlushSamples() + { + for (int i = 0; i < numChannels; i++) + channelSampleBuffer[i].Clear(); + + mixedSampleBuffer.Clear(); + } + + private ushort CheckParity(ushort val) + { + val ^= (ushort)(val >> 8); + val ^= (ushort)(val >> 4); + val ^= (ushort)(val >> 2); + val ^= (ushort)(val >> 1); + return (ushort)(val & 0x1); + } + + public virtual byte ReadPort(byte port) + { + throw new EmulationException("SN76489: Cannot read ports"); + } + + public virtual void WritePort(byte port, byte data) + { + if (IsBitSet(data, 7)) + { + /* LATCH/DATA byte; get channel (0-3) and type (0 is tone/noise, 1 is volume) */ + latchedChannel = (byte)((data >> 5) & 0x03); + latchedType = (byte)((data >> 4) & 0x01); + + /* Mask off non-data bits */ + data &= 0x0F; + + /* If target is channel 3 noise (3 bits), mask off highest bit */ + if (latchedChannel == 3 && latchedType == 0) + data &= 0x07; + + /* Write to register */ + if (latchedType == 0) + { + /* Data is tone/noise */ + toneRegisters[latchedChannel] = (ushort)((toneRegisters[latchedChannel] & 0x03F0) | data); + } + else + { + /* Data is volume */ + volumeRegisters[latchedChannel] = data; + } + } + else + { + /* DATA byte; mask off non-data bits */ + data &= 0x3F; + + /* Write to register */ + if (latchedType == 0) + { + /* Data is tone/noise */ + if (latchedChannel == 3) + { + /* Target is channel 3 noise, mask off excess bits and write to low bits of register */ + toneRegisters[latchedChannel] = (ushort)(data & 0x07); + } + else + { + /* Target is not channel 3 noise, write to high bits of register */ + toneRegisters[latchedChannel] = (ushort)((toneRegisters[latchedChannel] & 0x000F) | (data << 4)); + } + } + else + { + /* Data is volume; mask off excess bits and write to low bits of register */ + volumeRegisters[latchedChannel] = (ushort)(data & 0x0F); + } + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/SN76489.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/SN76489.cs.meta new file mode 100644 index 0000000..2369837 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/SN76489.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5b081ff484b016a4faafebb32b5d6bd1 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Audio/SegaGGPSG.cs b/Assets/Plugins/Essgee/Emulation/Audio/SegaGGPSG.cs new file mode 100644 index 0000000..905d3cd --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/SegaGGPSG.cs @@ -0,0 +1,77 @@ +using System; + +using Essgee.Utilities; + +namespace Essgee.Emulation.Audio +{ + public class SegaGGPSG : SegaSMSPSG + { + public const int PortStereoControl = 0x06; + + [StateRequired] + readonly bool[] channel0Enable, channel1Enable, channel2Enable, channel3Enable; + + public SegaGGPSG() : base() + { + channel0Enable = new bool[2]; + channel1Enable = new bool[2]; + channel2Enable = new bool[2]; + channel3Enable = new bool[2]; + } + + public override void Reset() + { + base.Reset(); + + WritePort(PortStereoControl, 0xFF); + } + + protected override void GenerateSample() + { + for (int i = 0; i < numOutputChannels; i++) + { + /* Generate samples */ + var ch1 = (channel0Enable[i] ? (short)(volumeTable[volumeRegisters[0]] * ((toneRegisters[0] < 2 ? true : channelOutput[0]) ? 1.0 : 0.0)) : (short)0); + var ch2 = (channel1Enable[i] ? (short)(volumeTable[volumeRegisters[1]] * ((toneRegisters[1] < 2 ? true : channelOutput[1]) ? 1.0 : 0.0)) : (short)0); + var ch3 = (channel2Enable[i] ? (short)(volumeTable[volumeRegisters[2]] * ((toneRegisters[2] < 2 ? true : channelOutput[2]) ? 1.0 : 0.0)) : (short)0); + var ch4 = (channel3Enable[i] ? (short)(volumeTable[volumeRegisters[3]] * (noiseLfsr & 0x1)) : (short)0); + + channelSampleBuffer[0].Add(ch1); + channelSampleBuffer[1].Add(ch2); + channelSampleBuffer[2].Add(ch3); + channelSampleBuffer[3].Add(ch4); + + /* Mix samples */ + var mixed = 0; + if (channel1ForceEnable) mixed += ch1; + if (channel2ForceEnable) mixed += ch2; + if (channel3ForceEnable) mixed += ch3; + if (channel4ForceEnable) mixed += ch4; + mixed /= numChannels; + + mixedSampleBuffer.Add((short)mixed); + } + } + + public override void WritePort(byte port, byte data) + { + if (port == PortStereoControl) + { + /* Stereo control */ + channel0Enable[0] = ((data & 0x10) != 0); /* Ch1 Left */ + channel0Enable[1] = ((data & 0x01) != 0); /* Ch1 Right */ + + channel1Enable[0] = ((data & 0x20) != 0); /* Ch2 Left */ + channel1Enable[1] = ((data & 0x02) != 0); /* Ch2 Right */ + + channel2Enable[0] = ((data & 0x40) != 0); /* Ch3 Left */ + channel2Enable[1] = ((data & 0x04) != 0); /* Ch3 Right */ + + channel3Enable[0] = ((data & 0x80) != 0); /* Ch4 Left */ + channel3Enable[1] = ((data & 0x08) != 0); /* Ch4 Right */ + } + else + base.WritePort(port, data); + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/SegaGGPSG.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/SegaGGPSG.cs.meta new file mode 100644 index 0000000..0be21bf --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/SegaGGPSG.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 63b4d9dd3dcd81942aab4e783f7ca197 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Audio/SegaSMSPSG.cs b/Assets/Plugins/Essgee/Emulation/Audio/SegaSMSPSG.cs new file mode 100644 index 0000000..1115492 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/SegaSMSPSG.cs @@ -0,0 +1,23 @@ +using System; + +using Essgee.EventArguments; + +namespace Essgee.Emulation.Audio +{ + public class SegaSMSPSG : SN76489 + { + /* LFSR is 16 bits, tapped bits are 0 and 3 (mask 0x0009), going into bit 15 */ + protected override ushort noiseLfsrMask => 0xFFFF; + protected override ushort noiseTappedBits => 0x0009; + protected override int noiseBitShift => 15; + + public SegaSMSPSG() : base() { } + + public override void Reset() + { + base.Reset(); + + noiseLfsr = 0x8000; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Audio/SegaSMSPSG.cs.meta b/Assets/Plugins/Essgee/Emulation/Audio/SegaSMSPSG.cs.meta new file mode 100644 index 0000000..69fd565 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Audio/SegaSMSPSG.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bdf808e342a96d74fbbe4a6ea16509eb \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU.meta b/Assets/Plugins/Essgee/Emulation/CPU.meta new file mode 100644 index 0000000..3717bf1 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ac5ab16607ba9574992b980c27ee0bba +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/CPU/ICPU.cs b/Assets/Plugins/Essgee/Emulation/CPU/ICPU.cs new file mode 100644 index 0000000..d5e4f8f --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/ICPU.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + interface ICPU + { + void Startup(); + void Shutdown(); + void Reset(); + int Step(); + + void SetStackPointer(ushort value); + void SetProgramCounter(ushort value); + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/ICPU.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/ICPU.cs.meta new file mode 100644 index 0000000..7461968 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/ICPU.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d1eb29143cb5d8e49bb3ebe09e89a215 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.CycleCounts.cs b/Assets/Plugins/Essgee/Emulation/CPU/SM83.CycleCounts.cs new file mode 100644 index 0000000..bc37762 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.CycleCounts.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public partial class SM83 + { + public static class CycleCounts + { + public const int AdditionalJumpCond8Taken = 4; + public const int AdditionalRetCondTaken = 12; + public const int AdditionalCallCondTaken = 12; + + // 32 cycles == dummy + public static readonly int[] NoPrefix = new int[] + { + 4, 12, 8, 8, 4, 4, 8, 4, 20, 8, 8, 8, 4, 4, 8, 4, /* 0x00 - 0x0F */ + 4, 12, 8, 8, 4, 4, 8, 4, 12, 8, 8, 8, 4, 4, 8, 4, /* 0x10 - 0x1F */ + 8, 12, 8, 8, 4, 4, 8, 4, 8, 8, 8, 8, 4, 4, 8, 4, /* 0x20 - 0x2F */ + 8, 12, 8, 8, 12, 12, 12, 4, 8, 8, 8, 8, 4, 4, 8, 4, /* 0x30 - 0x3F */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x40 - 0x4F */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x50 - 0x5F */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x60 - 0x6F */ + 8, 8, 8, 8, 8, 8, 4, 8, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x70 - 0x7F */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x80 - 0x8F */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0x90 - 0x9F */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0xA0 - 0xAF */ + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, /* 0xB0 - 0xBF */ + 8, 12, 12, 16, 12, 16, 8, 16, 8, 16, 12, 32, 12, 24, 8, 16, /* 0xC0 - 0xCF */ + 8, 12, 12, 32, 12, 16, 8, 16, 8, 16, 12, 32, 12, 32, 8, 16, /* 0xD0 - 0xDF */ + 12, 12, 8, 32, 32, 16, 8, 16, 16, 4, 16, 32, 32, 32, 8, 16, /* 0xE0 - 0xEF */ + 12, 12, 8, 4, 32, 16, 8, 16, 12, 8, 16, 4, 32, 32, 8, 16 /* 0xF0 - 0xFF */ + }; + + public static readonly int[] PrefixCB = new int[] + { + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0x00 - 0x0F */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0x10 - 0x1F */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0x20 - 0x2F */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0x30 - 0x3F */ + 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, /* 0x40 - 0x4F */ + 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, /* 0x50 - 0x5F */ + 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, /* 0x60 - 0x6F */ + 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, /* 0x70 - 0x7F */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0x80 - 0x8F */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0x90 - 0x9F */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0xA0 - 0xAF */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0xB0 - 0xBF */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0xC0 - 0xCF */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0xD0 - 0xDF */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, /* 0xE0 - 0xEF */ + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8 /* 0xF0 - 0xFF */ + }; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.CycleCounts.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/SM83.CycleCounts.cs.meta new file mode 100644 index 0000000..cd0268b --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.CycleCounts.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e1d2dab87d28f044ca71114b277aee3a \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.Disassembly.cs b/Assets/Plugins/Essgee/Emulation/CPU/SM83.Disassembly.cs new file mode 100644 index 0000000..8b3a717 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.Disassembly.cs @@ -0,0 +1,205 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public partial class SM83 + { + static readonly string[] opcodeMnemonicNoPrefix = + { + /* +00 +01 +02 +03 +04 +05 +06 +07 */ + "NOP", "LD BC, 0x{0:X4}", "LD (BC), A", "INC BC", "INC B", "DEC B", "LD B, 0x{0:X2}", "RLCA", /* 0x00 */ + "LD (0x{0:X4}), SP", "ADD HL, BC", "LD A, (BC)", "DEC BC", "INC C", "DEC C", "LD C, 0x{0:X2}", "RRCA", /* 0x08 */ + "STOP", "LD DE, 0x{0:X4}", "LD (DE), A", "INC DE", "INC D", "DEC D", "LD D, 0x{0:X2}", "RLA", /* 0x10 */ + "JR 0x{0:X2}", "ADD HL, DE", "LD A, (DE)", "DEC DE", "INC E", "DEC E", "LD E, 0x{0:X2}", "RRA", /* 0x18 */ + "JR NZ, 0x{0:X2}", "LD HL, 0x{0:X4}", "LDI (HL), A", "INC HL", "INC H", "DEC H", "LD H, 0x{0:X2}", "DAA", /* 0x20 */ + "JR Z, 0x{0:X2}", "ADD HL, HL", "LDI A, (HL)", "DEC HL", "INC L", "DEC L", "LD L, 0x{0:X2}", "CPL", /* 0x28 */ + "JR NC, 0x{0:X2}", "LD SP, 0x{0:X4}", "LDD (HL), A", "INC SP", "INC (HL)", "DEC (HL)", "LD (HL), 0x{0:X2}", "SCF", /* 0x30 */ + "JR C, 0x{0:X2}", "ADD HL, SP", "LDD A, (HL)", "DEC SP", "INC A", "DEC A", "LD A, 0x{0:X2}", "CCF", /* 0x38 */ + "LD B, B", "LD B, C", "LD B, D", "LD B, E", "LD B, H", "LD B, L", "LD B, (HL)", "LD B, A", /* 0x40 */ + "LD C, B", "LD C, C", "LD C, D", "LD C, E", "LD C, H", "LD C, L", "LD C, (HL)", "LD C, A", /* 0x48 */ + "LD D, B", "LD D, C", "LD D, D", "LD D, E", "LD D, H", "LD D, L", "LD D, (HL)", "LD D, A", /* 0x50 */ + "LD E, B", "LD E, C", "LD E, D", "LD E, E", "LD E, H", "LD E, L", "LD E, (HL)", "LD E, A", /* 0x58 */ + "LD H, B", "LD H, C", "LD H, D", "LD H, E", "LD H, H", "LD H, L", "LD H, (HL)", "LD H, A", /* 0x60 */ + "LD L, B", "LD L, C", "LD L, D", "LD L, E", "LD L, H", "LD L, L", "LD L, (HL)", "LD L, A", /* 0x68 */ + "LD (HL), B", "LD (HL), C", "LD (HL), D", "LD (HL), E", "LD (HL), H", "LD (HL), L", "HALT", "LD (HL), A", /* 0x70 */ + "LD A, B", "LD A, C", "LD A, D", "LD A, E", "LD A, H", "LD A, L", "LD A, (HL)", "LD A, A", /* 0x78 */ + "ADD B", "ADD C", "ADD D", "ADD E", "ADD H", "ADD L", "ADD (HL)", "ADD A", /* 0x80 */ + "ADC B", "ADC C", "ADC D", "ADC E", "ADC H", "ADC L", "ADC (HL)", "ADC A", /* 0x88 */ + "SUB B", "SUB C", "SUB D", "SUB E", "SUB H", "SUB L", "SUB (HL)", "SUB A", /* 0x90 */ + "SBC B", "SBC C", "SBC D", "SBC E", "SBC H", "SBC L", "SBC (HL)", "SBC A", /* 0x98 */ + "AND B", "AND C", "AND D", "AND E", "AND H", "AND L", "AND (HL)", "AND A", /* 0xA0 */ + "XOR B", "XOR C", "XOR D", "XOR E", "XOR H", "XOR L", "XOR (HL)", "XOR A", /* 0xA8 */ + "OR B", "OR C", "OR D", "OR E", "OR H", "OR L", "OR (HL)", "OR A", /* 0xB0 */ + "CP B", "CP C", "CP D", "CP E", "CP H", "CP L", "CP (HL)", "CP A", /* 0xB8 */ + "RET NZ", "POP BC", "JP NZ, 0x{0:X4}", "JP 0x{0:X4}", "CALL NZ, 0x{0:X4}", "PUSH BC", "ADD 0x{0:X2}", "RST 00", /* 0xC0 */ + "RET Z", "RET", "JP Z, 0x{0:X4}", string.Empty, "CALL Z, 0x{0:X4}", "CALL 0x{0:X4}", "ADC 0x{0:X2}", "RST 08", /* 0xC8 */ + "RET NC", "POP DE", "JP NC, 0x{0:X4}", ".DB 0xD3", "CALL NC, 0x{0:X4}", "PUSH DE", "SUB 0x{0:X2}", "RST 10", /* 0xD0 */ + "RET C", "RETI", "JP C, 0x{0:X4}", ".DB 0xDB", "CALL C, 0x{0:X4}", ".DB 0xDD", "SBC 0x{0:X2}", "RST 18", /* 0xD8 */ + "LD (FF00+0x{0:X2}), A", "POP HL", "LD (FF00+C), A", ".DB 0xE3", ".DB 0xE4", "PUSH HL", "AND 0x{0:X2}", "RST 20", /* 0xE0 */ + "ADD SP, 0x{0:X2}", "LD PC, HL", "LD (0x{0:X4}), A", ".DB 0xEB", ".DB 0xEC", ".DB 0xED", "XOR 0x{0:X2}", "RST 28", /* 0xE8 */ + "LD A, (FF00+0x{0:X2})", "POP AF", "LD A, (FF00+C)", "DI", ".DB 0xF4", "PUSH AF", "OR 0x{0:X2}", "RST 30", /* 0xF0 */ + "LD HL, SP+0x{0:X2}", "LD SP, HL", "LD A, (0x{0:X4})", "EI", ".DB 0xFC", ".DB 0xFD", "CP 0x{0:X2}", "RST 38" /* 0xF8 */ + }; + + static readonly int[] opcodeLengthNoPrefix = + { + 1, 3, 1, 1, 1, 1, 2, 1, 3, 1, 1, 1, 1, 1, 2, 1, + 2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, + 2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, + 2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 3, 3, 3, 1, 2, 1, 1, 1, 3, 2, 3, 3, 2, 1, + 1, 1, 3, 1, 3, 1, 2, 1, 1, 1, 3, 1, 3, 1, 2, 1, + 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 1, 1, 2, 1, + 2, 1, 1, 1, 1, 1, 2, 1, 2, 1, 3, 1, 1, 1, 2, 1 + }; + + static readonly string[] opcodeMnemonicPrefixCB = new string[] + { + /* +00 +01 +02 +03 +04 +05 +06 +07 */ + "RLC B", "RLC C", "RLC D", "RLC E", "RLC H", "RLC L", "RLC (HL)", "RLC A", /* 0x00 */ + "RRC B", "RRC C", "RRC D", "RRC E", "RRC H", "RRC L", "RRC (HL)", "RRC A", /* 0x08 */ + "RL B", "RL C", "RL D", "RL E", "RL H", "RL L", "RL (HL)", "RL A", /* 0x10 */ + "RR B", "RR C", "RR D", "RR E", "RR H", "RR L", "RR (HL)", "RR A", /* 0x18 */ + "SLA B", "SLA C", "SLA D", "SLA E", "SLA H", "SLA L", "SLA (HL)", "SLA A", /* 0x20 */ + "SRA B", "SRA C", "SRA D", "SRA E", "SRA H", "SRA L", "SRA (HL)", "SRA A", /* 0x28 */ + "SWAP B", "SWAP C", "SWAP D", "SWAP E", "SWAP H", "SWAP L", "SWAP (HL)", "SWAP A", /* 0x30 */ + "SRL B", "SRL C", "SRL D", "SRL E", "SRL H", "SRL L", "SRL (HL)", "SRL A", /* 0x38 */ + "BIT 0, B", "BIT 0, C", "BIT 0, D", "BIT 0, E", "BIT 0, H", "BIT 0, L", "BIT 0, (HL)", "BIT 0, A", /* 0x40 */ + "BIT 1, B", "BIT 1, C", "BIT 1, D", "BIT 1, E", "BIT 1, H", "BIT 1, L", "BIT 1, (HL)", "BIT 1, A", /* 0x48 */ + "BIT 2, B", "BIT 2, C", "BIT 2, D", "BIT 2, E", "BIT 2, H", "BIT 2, L", "BIT 2, (HL)", "BIT 2, A", /* 0x50 */ + "BIT 3, B", "BIT 3, C", "BIT 3, D", "BIT 3, E", "BIT 3, H", "BIT 3, L", "BIT 3, (HL)", "BIT 3, A", /* 0x58 */ + "BIT 4, B", "BIT 4, C", "BIT 4, D", "BIT 4, E", "BIT 4, H", "BIT 4, L", "BIT 4, (HL)", "BIT 4, A", /* 0x60 */ + "BIT 5, B", "BIT 5, C", "BIT 5, D", "BIT 5, E", "BIT 5, H", "BIT 5, L", "BIT 5, (HL)", "BIT 5, A", /* 0x68 */ + "BIT 6, B", "BIT 6, C", "BIT 6, D", "BIT 6, E", "BIT 6, H", "BIT 6, L", "BIT 6, (HL)", "BIT 6, A", /* 0x70 */ + "BIT 7, B", "BIT 7, C", "BIT 7, D", "BIT 7, E", "BIT 7, H", "BIT 7, L", "BIT 7, (HL)", "BIT 7, A", /* 0x78 */ + "RES 0, B", "RES 0, C", "RES 0, D", "RES 0, E", "RES 0, H", "RES 0, L", "RES 0, (HL)", "RES 0, A", /* 0x80 */ + "RES 1, B", "RES 1, C", "RES 1, D", "RES 1, E", "RES 1, H", "RES 1, L", "RES 1, (HL)", "RES 1, A", /* 0x88 */ + "RES 2, B", "RES 2, C", "RES 2, D", "RES 2, E", "RES 2, H", "RES 2, L", "RES 2, (HL)", "RES 2, A", /* 0x90 */ + "RES 3, B", "RES 3, C", "RES 3, D", "RES 3, E", "RES 3, H", "RES 3, L", "RES 3, (HL)", "RES 3, A", /* 0x98 */ + "RES 4, B", "RES 4, C", "RES 4, D", "RES 4, E", "RES 4, H", "RES 4, L", "RES 4, (HL)", "RES 4, A", /* 0xA0 */ + "RES 5, B", "RES 5, C", "RES 5, D", "RES 5, E", "RES 5, H", "RES 5, L", "RES 5, (HL)", "RES 5, A", /* 0xA8 */ + "RES 6, B", "RES 6, C", "RES 6, D", "RES 6, E", "RES 6, H", "RES 6, L", "RES 6, (HL)", "RES 6, A", /* 0xB0 */ + "RES 7, B", "RES 7, C", "RES 7, D", "RES 7, E", "RES 7, H", "RES 7, L", "RES 7, (HL)", "RES 7, A", /* 0xB8 */ + "SET 0, B", "SET 0, C", "SET 0, D", "SET 0, E", "SET 0, H", "SET 0, L", "SET 0, (HL)", "SET 0, A", /* 0xC0 */ + "SET 1, B", "SET 1, C", "SET 1, D", "SET 1, E", "SET 1, H", "SET 1, L", "SET 1, (HL)", "SET 1, A", /* 0xC8 */ + "SET 2, B", "SET 2, C", "SET 2, D", "SET 2, E", "SET 2, H", "SET 2, L", "SET 2, (HL)", "SET 2, A", /* 0xD0 */ + "SET 3, B", "SET 3, C", "SET 3, D", "SET 3, E", "SET 3, H", "SET 3, L", "SET 3, (HL)", "SET 3, A", /* 0xD8 */ + "SET 4, B", "SET 4, C", "SET 4, D", "SET 4, E", "SET 4, H", "SET 4, L", "SET 4, (HL)", "SET 4, A", /* 0xE0 */ + "SET 5, B", "SET 5, C", "SET 5, D", "SET 5, E", "SET 5, H", "SET 5, L", "SET 5, (HL)", "SET 5, A", /* 0xE8 */ + "SET 6, B", "SET 6, C", "SET 6, D", "SET 6, E", "SET 6, H", "SET 6, L", "SET 6, (HL)", "SET 6, A", /* 0xF0 */ + "SET 7, B", "SET 7, C", "SET 7, D", "SET 7, E", "SET 7, H", "SET 7, L", "SET 7, (HL)", "SET 7, A" /* 0xF8 */ + }; + + static readonly int[] opcodeLengthPrefixCB = new int[] + { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + }; + + public static string PrintRegisters(SM83 cpu) + { + return $"AF:{cpu.af.Word:X4} BC:{cpu.bc.Word:X4} DE:{cpu.de.Word:X4} HL:{cpu.hl.Word:X4} SP:{cpu.sp:X4}"; + } + + public static string PrintFlags(SM83 cpu) + { + return $"{(cpu.IsFlagSet(Flags.Zero) ? "Z" : "-")}{(cpu.IsFlagSet(Flags.Subtract) ? "N" : "-")}{(cpu.IsFlagSet(Flags.HalfCarry) ? "H" : "-")}{(cpu.IsFlagSet(Flags.Carry) ? "C" : "-")}"; + } + + public static string PrintInterrupt(SM83 cpu) + { + var intFlags = (InterruptSource)cpu.memoryReadDelegate(0xFF0F); + var intEnable = (InterruptSource)cpu.memoryReadDelegate(0xFFFF); + + var intFlagsString = + $"{((intFlags & InterruptSource.VBlank) != 0 ? "V" : "-")}" + + $"{((intFlags & InterruptSource.LCDCStatus) != 0 ? "L" : "-")}" + + $"{((intFlags & InterruptSource.TimerOverflow) != 0 ? "T" : "-")}" + + $"{((intFlags & InterruptSource.SerialIO) != 0 ? "S" : "-")}" + + $"{((intFlags & InterruptSource.Keypad) != 0 ? "K" : "-")}"; + + var intEnableString = + $"{((intEnable & InterruptSource.VBlank) != 0 ? "V" : "-")}" + + $"{((intEnable & InterruptSource.LCDCStatus) != 0 ? "L" : "-")}" + + $"{((intEnable & InterruptSource.TimerOverflow) != 0 ? "T" : "-")}" + + $"{((intEnable & InterruptSource.SerialIO) != 0 ? "S" : "-")}" + + $"{((intEnable & InterruptSource.Keypad) != 0 ? "K" : "-")}"; + + return $"IME:{(cpu.ime ? "1" : "0")} IF:{intFlagsString} IE:{intEnableString}"; + } + + public static string DisassembleOpcode(SM83 cpu, ushort address) + { + var opcode = DisassembleGetOpcodeBytes(cpu, address); + return $"0x{address:X4} | {DisassembleMakeByteString(cpu, opcode).PadRight(15)} | {DisassembleMakeMnemonicString(cpu, opcode)}"; + } + + public static byte[] DisassembleGetOpcodeBytes(SM83 cpu, ushort address) + { + var opcode = new byte[3]; + for (int i = 0; i < opcode.Length; i++) + opcode[i] = address + i <= 0xFFFF ? cpu.memoryReadDelegate((ushort)(address + i)) : (byte)0; + return opcode; + } + + public static int DisassembleGetOpcodeLen(SM83 cpu, byte[] opcode) + { + if (opcode[0] == 0xCB) + return opcodeLengthPrefixCB[opcode[1]]; + else + return opcodeLengthNoPrefix[opcode[0]]; + } + + public static string DisassembleMakeByteString(SM83 cpu, byte[] opcode) + { + return string.Join(" ", opcode.Select(x => $"{x:X2}").Take(DisassembleGetOpcodeLen(cpu, opcode))); + } + + public static string DisassembleMakeMnemonicString(SM83 cpu, byte[] opcode) + { + var len = DisassembleGetOpcodeLen(cpu, opcode); + var start = opcode[0] == 0xCB ? 1 : 0; + var mnemonics = opcode[0] == 0xCB ? opcodeMnemonicPrefixCB : opcodeMnemonicNoPrefix; + + switch (len - start) + { + case 1: return mnemonics[opcode[start]]; + case 2: return string.Format(mnemonics[opcode[start]], opcode[start + 1]); + case 3: return string.Format(mnemonics[opcode[start]], (opcode[start + 2] << 8 | opcode[start + 1])); + default: return string.Empty; + } + } + + private string MakeUnimplementedOpcodeString(string prefix, ushort address) + { + var opcode = DisassembleGetOpcodeBytes(this, address); + return $"Unimplemented {(prefix != string.Empty ? prefix + " " : prefix)}opcode {DisassembleMakeByteString(this, opcode)} ({DisassembleMakeMnemonicString(this, opcode)})"; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.Disassembly.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/SM83.Disassembly.cs.meta new file mode 100644 index 0000000..d48ca21 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.Disassembly.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7de4aca2c5f71b94b9509b8ad7b6ef8f \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesNoPrefix.cs b/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesNoPrefix.cs new file mode 100644 index 0000000..cef4865 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesNoPrefix.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public partial class SM83 + { + static SimpleOpcodeDelegate[] opcodesNoPrefix = new SimpleOpcodeDelegate[] + { + /* 0x00 */ + new SimpleOpcodeDelegate((c) => { /* NOP */ }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate16(ref c.bc.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.bc.Word, c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Increment16(ref c.bc.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftAccumulatorCircular(); }), + new SimpleOpcodeDelegate((c) => { c.WriteMemory16(c.ReadMemory16(c.pc), c.sp); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.bc.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterFromMemory8(ref c.af.High, c.bc.Word, false); }), + new SimpleOpcodeDelegate((c) => { c.Decrement16(ref c.bc.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightAccumulatorCircular(); }), + /* 0x10 */ + new SimpleOpcodeDelegate((c) => { c.Stop(); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate16(ref c.de.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.de.Word, c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Increment16(ref c.de.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftAccumulator(); }), + new SimpleOpcodeDelegate((c) => { c.Jump8(); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.de.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterFromMemory8(ref c.af.High, c.de.Word, false); }), + new SimpleOpcodeDelegate((c) => { c.Decrement16(ref c.de.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightAccumulator(); }), + /* 0x20 */ + new SimpleOpcodeDelegate((c) => { c.JumpConditional8(!c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate16(ref c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.WriteMemory8(c.hl.Word++, c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Increment16(ref c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.DecimalAdjustAccumulator(); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional8(c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.af.High = c.ReadMemory8(c.hl.Word++); }), + new SimpleOpcodeDelegate((c) => { c.Decrement16(ref c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.af.High ^= 0xFF; c.SetFlag(Flags.Subtract | Flags.HalfCarry); }), + /* 0x30 */ + new SimpleOpcodeDelegate((c) => { c.JumpConditional8(!c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate16(ref c.sp); }), + new SimpleOpcodeDelegate((c) => { c.WriteMemory8(c.hl.Word--, c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Increment16(ref c.sp); }), + new SimpleOpcodeDelegate((c) => { c.IncrementMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.DecrementMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.SetFlag(Flags.Carry); c.ClearFlag(Flags.Subtract | Flags.HalfCarry); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional8(c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.sp); }), + new SimpleOpcodeDelegate((c) => { c.af.High = c.ReadMemory8(c.hl.Word--);}), + new SimpleOpcodeDelegate((c) => { c.Decrement16(ref c.sp); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.ClearFlag(Flags.Subtract); c.ClearFlag(Flags.HalfCarry); c.SetClearFlagConditional(Flags.Carry, !c.IsFlagSet(Flags.Carry)); }), + /* 0x40 */ + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.bc.High = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.bc.Low = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.af.High, false); }), + /* 0x50 */ + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.de.High = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.de.Low = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.af.High, false); }), + /* 0x60 */ + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.hl.High = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.hl.Low = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.af.High, false); }), + /* 0x70 */ + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.EnterHaltState(); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.af.High = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.af.High, false); }), + /* 0x80 */ + new SimpleOpcodeDelegate((c) => { c.Add8(c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.ReadMemory8(c.hl.Word), false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.bc.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.bc.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.de.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.de.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.hl.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.hl.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.ReadMemory8(c.hl.Word), true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.af.High, true); }), + /* 0x90 */ + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.ReadMemory8(c.hl.Word), false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.bc.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.bc.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.de.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.de.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.hl.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.hl.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.ReadMemory8(c.hl.Word), true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.af.High, true); }), + /* 0xA0 */ + new SimpleOpcodeDelegate((c) => { c.And8(c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.ReadMemory8(c.hl.Word)); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.ReadMemory8(c.hl.Word)); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.af.High); }), + /* 0xB0 */ + new SimpleOpcodeDelegate((c) => { c.Or8(c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.ReadMemory8(c.hl.Word)); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.ReadMemory8(c.hl.Word)); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.af.High); }), + /* 0xC0 */ + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(!c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.Pop(ref c.bc); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(!c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(true); }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(!c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.Push(c.bc); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.ReadMemory8(c.pc++), false); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0000); }), + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.Return(); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { /* CB - handled elsewhere */ }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.Call16(); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.ReadMemory8(c.pc++), true); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0008); }), + /* 0xD0 */ + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(!c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.Pop(ref c.de); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(!c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(!c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.Push(c.de); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.ReadMemory8(c.pc++), false); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0010); }), + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.ime = true; c.imeDelay = false; c.Return(); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.ReadMemory8(c.pc++), true); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0018); }), + /* 0xE0 */ + new SimpleOpcodeDelegate((c) => { c.WriteMemory8((ushort)(0xFF00 + c.ReadMemory8(c.pc++)), c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Pop(ref c.hl); }), + new SimpleOpcodeDelegate((c) => { c.WriteMemory8((ushort)(0xFF00 + c.bc.Low), c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.Push(c.hl); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0020); }), + new SimpleOpcodeDelegate((c) => { c.AddSPNN(); }), + new SimpleOpcodeDelegate((c) => { c.pc = c.hl.Word; }), + new SimpleOpcodeDelegate((c) => { c.WriteMemory8(c.ReadMemory16(c.pc), c.af.High); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0028); }), + /* 0xF0 */ + new SimpleOpcodeDelegate((c) => { c.af.High = c.ReadMemory8((ushort)(0xFF00 + c.ReadMemory8(c.pc++))); }), + new SimpleOpcodeDelegate((c) => { c.PopAF(); }), + new SimpleOpcodeDelegate((c) => { c.af.High = c.ReadMemory8((ushort)(0xFF00 + c.bc.Low)); }), + new SimpleOpcodeDelegate((c) => { c.DisableInterrupts(); }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.Push(c.af); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0030); }), + new SimpleOpcodeDelegate((c) => { c.LoadHLSPNN(); }), + new SimpleOpcodeDelegate((c) => { c.sp = c.hl.Word; }), + new SimpleOpcodeDelegate((c) => { c.af.High = c.ReadMemory8(c.ReadMemory16(c.pc)); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.EnableInterrupts(); }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.pc--; }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0038); }) + }; + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesNoPrefix.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesNoPrefix.cs.meta new file mode 100644 index 0000000..4c94393 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesNoPrefix.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e4fe2d8ddc2d10849b6c7c6d64db0d4f \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesPrefixCB.cs b/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesPrefixCB.cs new file mode 100644 index 0000000..60f7f9e --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesPrefixCB.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public partial class SM83 + { + static SimpleOpcodeDelegate[] opcodesPrefixCB = new SimpleOpcodeDelegate[] + { + /* 0x00 */ + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.af.High); }), + /* 0x10 */ + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.af.High); }), + /* 0x20 */ + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.af.High); }), + /* 0x30 */ + new SimpleOpcodeDelegate((c) => { c.Swap(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.Swap(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.Swap(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.Swap(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.Swap(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.Swap(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.Swap(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.Swap(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.af.High); }), + /* 0x40 */ + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 1); }), + /* 0x50 */ + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 3); }), + /* 0x60 */ + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 5); }), + /* 0x70 */ + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 7); }), + /* 0x80 */ + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 1); }), + /* 0x90 */ + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 3); }), + /* 0xA0 */ + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 5); }), + /* 0xB0 */ + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 7); }), + /* 0xC0 */ + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 1); }), + /* 0xD0 */ + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 3); }), + /* 0xE0 */ + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 5); }), + /* 0xF0 */ + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 7); }), + }; + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesPrefixCB.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesPrefixCB.cs.meta new file mode 100644 index 0000000..6e4d3df --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.OpcodesPrefixCB.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 36c6024a407a43f43bf41f1e38274ad9 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.Register.cs b/Assets/Plugins/Essgee/Emulation/CPU/SM83.Register.cs new file mode 100644 index 0000000..9bb458e --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.Register.cs @@ -0,0 +1,25 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Essgee.Emulation.CPU +{ + public partial class SM83 + { + [DebuggerDisplay("{Word}")] + [StructLayout(LayoutKind.Explicit)] + [Serializable] + public struct Register + { + [NonSerialized] + [FieldOffset(0)] + public byte Low; + [NonSerialized] + [FieldOffset(1)] + public byte High; + + [FieldOffset(0)] + public ushort Word; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.Register.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/SM83.Register.cs.meta new file mode 100644 index 0000000..024f0ea --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.Register.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e29886b06189649459149576fcb83444 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.cs b/Assets/Plugins/Essgee/Emulation/CPU/SM83.cs new file mode 100644 index 0000000..7040a66 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.cs @@ -0,0 +1,977 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.Utilities; + +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.CPU +{ + public partial class SM83 : ICPU + { + [Flags] + enum Flags : byte + { + UnusedBit0 = (1 << 0), /* (0) */ + UnusedBit1 = (1 << 1), /* (0) */ + UnusedBit2 = (1 << 2), /* (0) */ + UnusedBit3 = (1 << 3), /* (0) */ + Carry = (1 << 4), /* C */ + HalfCarry = (1 << 5), /* H */ + Subtract = (1 << 6), /* N */ + Zero = (1 << 7) /* Z */ + } + + [Flags] + public enum InterruptSource : byte + { + VBlank = 0, + LCDCStatus = 1, + TimerOverflow = 2, + SerialIO = 3, + Keypad = 4 + } + + public delegate byte MemoryReadDelegate(ushort address); + public delegate void MemoryWriteDelegate(ushort address, byte value); + + public delegate void RequestInterruptDelegate(InterruptSource source); + + delegate void SimpleOpcodeDelegate(SM83 c); + + protected MemoryReadDelegate memoryReadDelegate; + protected MemoryWriteDelegate memoryWriteDelegate; + + [StateRequired] + protected Register af, bc, de, hl; + [StateRequired] + protected ushort sp, pc; + + [StateRequired] + protected bool ime, imeDelay, halt, doHaltBug; + + [StateRequired] + protected byte op; + + [StateRequired] + protected int currentCycles; + + string logFile; + int numLogEntries; + string[] logEntries; + + public SM83(MemoryReadDelegate memoryRead, MemoryWriteDelegate memoryWrite) + { + af = bc = de = hl = new Register(); + + memoryReadDelegate = memoryRead; + memoryWriteDelegate = memoryWrite; + + if (AppEnvironment.EnableSuperSlowCPULogger) + { + logFile = @"D:\Temp\Essgee\log-lr35902.txt"; + numLogEntries = 0; + logEntries = new string[2000]; + } + } + + public virtual void Startup() + { + Reset(); + + if (memoryReadDelegate == null) throw new EmulationException("SM83: Memory read method is null"); + if (memoryWriteDelegate == null) throw new EmulationException("SM83: Memory write method is null"); + } + + public virtual void Shutdown() + { + if (AppEnvironment.EnableSuperSlowCPULogger && logEntries != null) + { + System.IO.File.AppendAllText(logFile, string.Join("", logEntries.Take(numLogEntries))); + } + + // + } + + public virtual void Reset() + { + af.Word = bc.Word = de.Word = hl.Word = 0; + pc = 0; + sp = 0; + + ime = imeDelay = halt = doHaltBug = false; + + currentCycles = 0; + } + + public int Step() + { + currentCycles = 0; + + /* Handle delayed interrupt enable */ + if (imeDelay) + { + ime = true; + imeDelay = false; + } + + /* Check interrupts */ + HandleInterrupts(); + + if (halt) + { + /* CPU halted */ + currentCycles = 4; + } + else + { + if (AppEnvironment.EnableSuperSlowCPULogger && logEntries != null) + { + string disasm = string.Format("{0} | {1} | {2} | {3}\n", DisassembleOpcode(this, pc).PadRight(48), PrintRegisters(this), PrintFlags(this), PrintInterrupt(this)); + logEntries[numLogEntries++] = disasm; + if (numLogEntries >= logEntries.Length) + { + System.IO.File.AppendAllText(logFile, string.Join("", logEntries)); + numLogEntries = 0; + } + } + + /* Do HALT bug */ + if (doHaltBug) + { + pc--; + doHaltBug = false; + } + + /* Fetch and execute opcode */ + op = ReadMemory8(pc++); + switch (op) + { + case 0xCB: ExecuteOpCB(); break; + default: ExecuteOpcodeNoPrefix(op); break; + } + } + + return currentCycles; + } + + #region Opcode Execution and Cycle Management + + private void ExecuteOpcodeNoPrefix(byte op) + { + opcodesNoPrefix[op](this); + currentCycles += CycleCounts.NoPrefix[op]; + } + + private void ExecuteOpCB() + { + byte cbOp = ReadMemory8(pc++); + opcodesPrefixCB[cbOp](this); + currentCycles += CycleCounts.PrefixCB[cbOp]; + } + + #endregion + + #region Helpers (Flags, etc.) + + public void SetStackPointer(ushort value) + { + sp = value; + } + + public void SetProgramCounter(ushort value) + { + pc = value; + } + + public void SetRegisterAF(ushort value) + { + af.Word = value; + } + + public void SetRegisterBC(ushort value) + { + bc.Word = value; + } + + public void SetRegisterDE(ushort value) + { + de.Word = value; + } + + public void SetRegisterHL(ushort value) + { + hl.Word = value; + } + + private void SetFlag(Flags flags) + { + af.Low |= (byte)flags; + } + + private void ClearFlag(Flags flags) + { + af.Low &= (byte)~flags; + } + + private void SetClearFlagConditional(Flags flags, bool condition) + { + if (condition) + af.Low |= (byte)flags; + else + af.Low &= (byte)~flags; + } + + private bool IsFlagSet(Flags flags) + { + return (((Flags)af.Low & flags) == flags); + } + + #endregion + + #region Interrupt, Halt and Stop State Handling + + public void RequestInterrupt(InterruptSource source) + { + memoryWriteDelegate(0xFF0F, (byte)(memoryReadDelegate(0xFF0F) | (byte)(1 << (byte)source))); + } + + private void HandleInterrupts() + { + var intEnable = memoryReadDelegate(0xFFFF); + var intFlags = memoryReadDelegate(0xFF0F); + + if ((intEnable & intFlags) != 0) + { + LeaveHaltState(); + + if (ime) + { + if (ServiceInterrupt(InterruptSource.VBlank, intEnable, intFlags)) return; + if (ServiceInterrupt(InterruptSource.LCDCStatus, intEnable, intFlags)) return; + if (ServiceInterrupt(InterruptSource.TimerOverflow, intEnable, intFlags)) return; + if (ServiceInterrupt(InterruptSource.SerialIO, intEnable, intFlags)) return; + if (ServiceInterrupt(InterruptSource.Keypad, intEnable, intFlags)) return; + } + } + } + + private bool ServiceInterrupt(InterruptSource intSource, byte intEnable, byte intFlags) + { + var intSourceBit = (byte)(1 << (byte)intSource); + + if (((intEnable & intSourceBit) == intSourceBit) && ((intFlags & intSourceBit) == intSourceBit)) + { + ime = false; + + currentCycles += 20; + + return RestartFromInterrupt(intSource); + } + + return false; + } + + protected virtual void EnterHaltState() + { + if (ime) + { + halt = true; + pc--; + } + else + { + if ((memoryReadDelegate(0xFF0F) & memoryReadDelegate(0xFFFF) & 0x1F) != 0) + doHaltBug = true; + else + halt = true; + } + } + + private void LeaveHaltState() + { + if (halt) + { + halt = false; + if (ime) + pc++; + } + } + + #endregion + + #region Memory Access Functions + + private byte ReadMemory8(ushort address) + { + return memoryReadDelegate(address); + } + + private void WriteMemory8(ushort address, byte value) + { + memoryWriteDelegate(address, value); + } + + private ushort ReadMemory16(ushort address) + { + return (ushort)((memoryReadDelegate((ushort)(address + 1)) << 8) | memoryReadDelegate(address)); + } + + private void WriteMemory16(ushort address, ushort value) + { + memoryWriteDelegate(address, (byte)(value & 0xFF)); + memoryWriteDelegate((ushort)(address + 1), (byte)(value >> 8)); + } + + #endregion + + #region Opcodes: 8-Bit Load Group + + protected void LoadRegisterFromMemory8(ref byte register, ushort address, bool specialRegs) + { + LoadRegister8(ref register, ReadMemory8(address), specialRegs); + } + + protected void LoadRegisterImmediate8(ref byte register, bool specialRegs) + { + LoadRegister8(ref register, ReadMemory8(pc++), specialRegs); + } + + protected void LoadRegister8(ref byte register, byte value, bool specialRegs) + { + register = value; + } + + protected void LoadMemory8(ushort address, byte value) + { + WriteMemory8(address, value); + } + + #endregion + + #region Opcodes: 16-Bit Load Group + + protected void LoadRegisterImmediate16(ref ushort register) + { + LoadRegister16(ref register, ReadMemory16(pc)); + pc += 2; + } + + protected void LoadRegister16(ref ushort register, ushort value) + { + register = value; + } + + protected void Push(Register register) + { + WriteMemory8(--sp, register.High); + WriteMemory8(--sp, register.Low); + } + + protected void Pop(ref Register register) + { + register.Low = ReadMemory8(sp++); + register.High = ReadMemory8(sp++); + } + + #endregion + + #region Opcodes: 8-Bit Arithmetic Group + + protected void Add8(byte operand, bool withCarry) + { + int operandWithCarry = (operand + (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0)); + int result = (af.High + operandWithCarry); + + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0)); + SetClearFlagConditional(Flags.Carry, (result > 0xFF)); + + af.High = (byte)result; + } + + protected void Subtract8(byte operand, bool withCarry) + { + int operandWithCarry = (operand + (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0)); + int result = (af.High - operandWithCarry); + + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + SetFlag(Flags.Subtract); + SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0)); + SetClearFlagConditional(Flags.Carry, (af.High < operandWithCarry)); + + af.High = (byte)result; + } + + protected void And8(byte operand) + { + int result = (af.High & operand); + + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + ClearFlag(Flags.Subtract); + SetFlag(Flags.HalfCarry); + ClearFlag(Flags.Carry); + + af.High = (byte)result; + } + + protected void Or8(byte operand) + { + int result = (af.High | operand); + + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + ClearFlag(Flags.Carry); + + af.High = (byte)result; + } + + protected void Xor8(byte operand) + { + int result = (af.High ^ operand); + + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + ClearFlag(Flags.Carry); + + af.High = (byte)result; + } + + protected void Cp8(byte operand) + { + int result = (af.High - operand); + + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + SetFlag(Flags.Subtract); + SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0)); + SetClearFlagConditional(Flags.Carry, (af.High < operand)); + } + + protected void Increment8(ref byte register) + { + byte result = (byte)(register + 1); + + SetClearFlagConditional(Flags.Zero, (result == 0x00)); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.HalfCarry, ((register & 0x0F) == 0x0F)); + // C + + register = result; + } + + protected void IncrementMemory8(ushort address) + { + byte value = ReadMemory8(address); + Increment8(ref value); + WriteMemory8(address, value); + } + + protected void Decrement8(ref byte register) + { + byte result = (byte)(register - 1); + + SetClearFlagConditional(Flags.Zero, (result == 0x00)); + SetFlag(Flags.Subtract); + SetClearFlagConditional(Flags.HalfCarry, ((register & 0x0F) == 0x00)); + // C + + register = result; + } + + protected void DecrementMemory8(ushort address) + { + byte value = ReadMemory8(address); + Decrement8(ref value); + WriteMemory8(address, value); + } + + #endregion + + #region Opcodes: General-Purpose Arithmetic and CPU Control Group + + protected void DecimalAdjustAccumulator() + { + int value = af.High; + + if (!IsFlagSet(Flags.Subtract)) + { + if (IsFlagSet(Flags.HalfCarry) || ((value & 0x0F) > 9)) + value += 0x06; + if (IsFlagSet(Flags.Carry) || (value > 0x9F)) + value += 0x60; + } + else + { + if (IsFlagSet(Flags.HalfCarry)) + value = (value - 0x06) & 0xFF; + if (IsFlagSet(Flags.Carry)) + value -= 0x60; + } + + ClearFlag(Flags.HalfCarry); + ClearFlag(Flags.Zero); + + if ((value & 0x100) != 0) SetFlag(Flags.Carry); + + value &= 0xFF; + + if (value == 0) SetFlag(Flags.Zero); + + af.High = (byte)value; + } + + protected void Negate() + { + int result = (0 - af.High); + + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + SetFlag(Flags.Subtract); + SetClearFlagConditional(Flags.HalfCarry, ((0 - (af.High & 0x0F)) < 0)); + SetClearFlagConditional(Flags.Carry, (af.High != 0x00)); + + af.High = (byte)result; + } + + protected void EnableInterrupts() + { + ime = false; + imeDelay = true; + } + + protected void DisableInterrupts() + { + ime = false; + } + + #endregion + + #region Opcodes: 16-Bit Arithmetic Group + + protected void Add16(ref Register dest, ushort operand) + { + int operandWithCarry = (short)operand; + int result = (dest.Word + operandWithCarry); + + // Z + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.HalfCarry, (((dest.Word & 0x0FFF) + (operandWithCarry & 0x0FFF)) > 0x0FFF)); + SetClearFlagConditional(Flags.Carry, (((dest.Word & 0xFFFF) + (operandWithCarry & 0xFFFF)) > 0xFFFF)); + + dest.Word = (ushort)result; + } + + protected void Increment16(ref ushort register) + { + register++; + } + + protected void Decrement16(ref ushort register) + { + register--; + } + + #endregion + + #region Opcodes: Rotate and Shift Group + + protected byte RotateLeft(ushort address) + { + byte value = ReadMemory8(address); + RotateLeft(ref value); + WriteMemory8(address, value); + return value; + } + + protected void RotateLeft(ref byte value) + { + bool isCarrySet = IsFlagSet(Flags.Carry); + bool isMsbSet = IsBitSet(value, 7); + value <<= 1; + if (isCarrySet) SetBit(ref value, 0); + + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected byte RotateLeftCircular(ushort address) + { + byte value = ReadMemory8(address); + RotateLeftCircular(ref value); + WriteMemory8(address, value); + return value; + } + + protected void RotateLeftCircular(ref byte value) + { + bool isMsbSet = IsBitSet(value, 7); + value <<= 1; + if (isMsbSet) SetBit(ref value, 0); + + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected byte RotateRight(ushort address) + { + byte value = ReadMemory8(address); + RotateRight(ref value); + WriteMemory8(address, value); + return value; + } + + protected void RotateRight(ref byte value) + { + bool isCarrySet = IsFlagSet(Flags.Carry); + bool isLsbSet = IsBitSet(value, 0); + value >>= 1; + if (isCarrySet) SetBit(ref value, 7); + + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + protected byte RotateRightCircular(ushort address) + { + byte value = ReadMemory8(address); + RotateRightCircular(ref value); + WriteMemory8(address, value); + return value; + } + + protected void RotateRightCircular(ref byte value) + { + bool isLsbSet = IsBitSet(value, 0); + value >>= 1; + if (isLsbSet) SetBit(ref value, 7); + + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + protected void RotateLeftAccumulator() + { + bool isCarrySet = IsFlagSet(Flags.Carry); + bool isMsbSet = IsBitSet(af.High, 7); + af.High <<= 1; + if (isCarrySet) SetBit(ref af.High, 0); + + ClearFlag(Flags.Zero); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected void RotateLeftAccumulatorCircular() + { + bool isMsbSet = IsBitSet(af.High, 7); + af.High <<= 1; + if (isMsbSet) SetBit(ref af.High, 0); + + ClearFlag(Flags.Zero); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected void RotateRightAccumulator() + { + bool isCarrySet = IsFlagSet(Flags.Carry); + bool isLsbSet = IsBitSet(af.High, 0); + af.High >>= 1; + if (isCarrySet) SetBit(ref af.High, 7); + + ClearFlag(Flags.Zero); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + protected void RotateRightAccumulatorCircular() + { + bool isLsbSet = IsBitSet(af.High, 0); + af.High >>= 1; + if (isLsbSet) SetBit(ref af.High, 7); + + ClearFlag(Flags.Zero); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + protected byte ShiftLeftArithmetic(ushort address) + { + byte value = ReadMemory8(address); + ShiftLeftArithmetic(ref value); + WriteMemory8(address, value); + return value; + } + + protected void ShiftLeftArithmetic(ref byte value) + { + bool isMsbSet = IsBitSet(value, 7); + value <<= 1; + + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected byte ShiftRightArithmetic(ushort address) + { + byte value = ReadMemory8(address); + ShiftRightArithmetic(ref value); + WriteMemory8(address, value); + return value; + } + + protected void ShiftRightArithmetic(ref byte value) + { + bool isLsbSet = IsBitSet(value, 0); + bool isMsbSet = IsBitSet(value, 7); + value >>= 1; + if (isMsbSet) SetBit(ref value, 7); + + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + protected byte ShiftRightLogical(ushort address) + { + byte value = ReadMemory8(address); + ShiftRightLogical(ref value); + WriteMemory8(address, value); + return value; + } + + protected void ShiftRightLogical(ref byte value) + { + bool isLsbSet = IsBitSet(value, 0); + value >>= 1; + + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + #endregion + + #region Opcodes: Bit Set, Reset and Test Group + + protected byte SetBit(ushort address, int bit) + { + byte value = ReadMemory8(address); + SetBit(ref value, bit); + WriteMemory8(address, value); + return value; + } + + protected void SetBit(ref byte value, int bit) + { + value |= (byte)(1 << bit); + } + + protected byte ResetBit(ushort address, int bit) + { + byte value = ReadMemory8(address); + ResetBit(ref value, bit); + WriteMemory8(address, value); + return value; + } + + protected void ResetBit(ref byte value, int bit) + { + value &= (byte)~(1 << bit); + } + + protected void TestBit(ushort address, int bit) + { + byte value = ReadMemory8(address); + + TestBit(value, bit); + } + + protected void TestBit(byte value, int bit) + { + bool isBitSet = ((value & (1 << bit)) != 0); + + SetClearFlagConditional(Flags.Zero, !isBitSet); + ClearFlag(Flags.Subtract); + SetFlag(Flags.HalfCarry); + // C + } + + #endregion + + #region Opcodes: Jump Group + + protected void Jump8() + { + pc += (ushort)(((sbyte)ReadMemory8(pc)) + 1); + } + + protected void JumpConditional8(bool condition) + { + if (condition) + { + Jump8(); + currentCycles += CycleCounts.AdditionalJumpCond8Taken; + } + else + pc++; + } + + protected void JumpConditional16(bool condition) + { + if (condition) + pc = ReadMemory16(pc); + else + pc += 2; + } + + #endregion + + #region Opcodes: Call and Return Group + + protected void Call16() + { + WriteMemory8(--sp, (byte)((pc + 2) >> 8)); + WriteMemory8(--sp, (byte)((pc + 2) & 0xFF)); + pc = ReadMemory16(pc); + } + + protected void CallConditional16(bool condition) + { + if (condition) + { + Call16(); + currentCycles += CycleCounts.AdditionalCallCondTaken; + } + else + pc += 2; + } + + protected void Return() + { + pc = ReadMemory16(sp); + sp += 2; + } + + protected void ReturnConditional(bool condition) + { + if (condition) + { + Return(); + currentCycles += CycleCounts.AdditionalRetCondTaken; + } + } + + protected void Restart(ushort address) + { + WriteMemory8(--sp, (byte)(pc >> 8)); + WriteMemory8(--sp, (byte)(pc & 0xFF)); + pc = address; + } + + protected bool RestartFromInterrupt(InterruptSource intSource) + { + // https://github.com/Gekkio/mooneye-gb/blob/ca7ff30/tests/acceptance/interrupts/ie_push.s + + var address = (ushort)(0x0040 + (byte)((int)intSource << 3)); + var intSourceBit = (byte)(1 << (byte)intSource); + + WriteMemory8(--sp, (byte)(pc >> 8)); + + var newIntEnable = memoryReadDelegate(0xFFFF); + var continueRestart = (newIntEnable & intSourceBit) != 0; + + WriteMemory8(--sp, (byte)(pc & 0xFF)); + + if (continueRestart) + { + pc = address; + memoryWriteDelegate(0xFF0F, (byte)(memoryReadDelegate(0xFF0F) & (byte)~intSourceBit)); + } + else + pc = 0x0000; + + return continueRestart; + } + + #endregion + + #region Opcodes: SM83-specific Opcodes + + protected void PopAF() + { + af.Low = (byte)(ReadMemory8(sp++) & 0xF0); + af.High = ReadMemory8(sp++); + } + + protected void Swap(ushort address) + { + byte value = ReadMemory8(address); + Swap(ref value); + WriteMemory8(address, value); + } + + protected void Swap(ref byte value) + { + value = (byte)((value & 0xF0) >> 4 | (value & 0x0F) << 4); + + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.HalfCarry); + ClearFlag(Flags.Carry); + } + + protected virtual void Stop() + { + pc++; + } + + private void AddSPNN() + { + byte offset = ReadMemory8(pc++); + + ClearFlag(Flags.Zero); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.HalfCarry, (((sp & 0x0F) + (offset & 0x0F)) > 0x0F)); + SetClearFlagConditional(Flags.Carry, (((sp & 0xFF) + (byte)(offset & 0xFF)) > 0xFF)); + + sp = (ushort)(sp + (sbyte)offset); + } + + private void LoadHLSPNN() + { + byte offset = ReadMemory8(pc++); + + ClearFlag(Flags.Zero); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.HalfCarry, (((sp & 0x0F) + (offset & 0x0F)) > 0x0F)); + SetClearFlagConditional(Flags.Carry, (((sp & 0xFF) + (byte)(offset & 0xFF)) > 0xFF)); + + hl.Word = (ushort)(sp + (sbyte)offset); + } + + #endregion + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/SM83.cs.meta new file mode 100644 index 0000000..00c9c49 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 18c6dc23d0278e847b2cc8483998c6fc \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83CGB.cs b/Assets/Plugins/Essgee/Emulation/CPU/SM83CGB.cs new file mode 100644 index 0000000..9948871 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83CGB.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public class SM83CGB : SM83 + { + // TODO: better way of implementing this? + public bool IsDoubleSpeed { get; private set; } + + public SM83CGB(MemoryReadDelegate memoryRead, MemoryWriteDelegate memoryWrite) : base(memoryRead, memoryWrite) { } + + protected override void EnterHaltState() + { + if (ime) + { + halt = true; + pc--; + } + else + { + if ((memoryReadDelegate(0xFF0F) & memoryReadDelegate(0xFFFF) & 0x1F) != 0) + currentCycles += 8; + else + halt = true; + } + } + + protected override void Stop() + { + pc++; + + // Perform speed switch; get IO register value + var key1 = memoryReadDelegate(0xFF4D); + + // Is speed switch pending? + if ((key1 & 0b1) != 0) + { + // Clear pending flag + key1 &= 0xFE; + + if (((key1 >> 7) & 0b1) != 0) + { + // Was double speed, now normal speed + key1 &= 0x7F; + IsDoubleSpeed = false; + } + else + { + // Was normal speed, now double speed + key1 |= 0x80; + IsDoubleSpeed = true; + } + + // Write register value + memoryWriteDelegate(0xFF4D, key1); + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/SM83CGB.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/SM83CGB.cs.meta new file mode 100644 index 0000000..7692894 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/SM83CGB.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cf45a7372b4385e429895c45fe9f4db0 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.CycleCounts.cs b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.CycleCounts.cs new file mode 100644 index 0000000..210cc18 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.CycleCounts.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public partial class Z80A + { + public static class CycleCounts + { + public const int AdditionalJumpCond8Taken = 5; + public const int AdditionalRetCondTaken = 6; + public const int AdditionalCallCondTaken = 7; + public const int AdditionalRepeatByteOps = 5; + public const int AdditionalDDFDOps = 4; + public const int AdditionalDDFDCBOps = 8; + + public static readonly int[] NoPrefix = new int[] + { + 4, 10, 7, 6, 4, 4, 7, 4, 4, 11, 7, 6, 4, 4, 7, 4, /* 0x00 - 0x0F */ + 8, 10, 7, 6, 4, 4, 7, 4, 12, 11, 7, 6, 4, 4, 7, 4, /* 0x10 - 0x1F */ + 7, 10, 16, 6, 4, 4, 7, 4, 7, 11, 16, 6, 4, 4, 4, 4, /* 0x20 - 0x2F */ + 7, 10, 13, 6, 11, 11, 10, 4, 7, 11, 13, 6, 4, 4, 7, 4, /* 0x30 - 0x3F */ + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, /* 0x40 - 0x4F */ + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, /* 0x50 - 0x5F */ + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, /* 0x60 - 0x6F */ + 7, 7, 7, 7, 7, 7, 4, 7, 4, 4, 4, 4, 4, 4, 7, 4, /* 0x70 - 0x7F */ + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, /* 0x80 - 0x8F */ + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, /* 0x90 - 0x9F */ + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, /* 0xA0 - 0xAF */ + 4, 4, 4, 4, 4, 4, 7, 4, 4, 4, 4, 4, 4, 4, 7, 4, /* 0xB0 - 0xBF */ + 5, 10, 10, 10, 10, 11, 7, 11, 5, 10, 10, 0, 10, 17, 7, 11, /* 0xC0 - 0xCF */ + 5, 10, 10, 11, 10, 11, 7, 11, 5, 4, 10, 11, 10, 0, 7, 11, /* 0xD0 - 0xDF */ + 5, 10, 10, 19, 10, 11, 7, 11, 5, 4, 10, 4, 10, 0, 7, 11, /* 0xE0 - 0xEF */ + 5, 10, 10, 4, 10, 11, 7, 11, 5, 6, 10, 4, 10, 0, 7, 11 /* 0xF0 - 0xFF */ + }; + + public static readonly int[] PrefixED = new int[] + { + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0x00 - 0x0F */ + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0x10 - 0x1F */ + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0x20 - 0x2F */ + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0x30 - 0x3F */ + 12, 12, 15, 20, 8, 14, 8, 9, 12, 12, 15, 20, 8, 14, 8, 9, /* 0x40 - 0x4F */ + 12, 12, 15, 20, 8, 14, 8, 9, 12, 12, 15, 20, 8, 14, 8, 9, /* 0x50 - 0x5F */ + 12, 12, 15, 20, 8, 14, 8, 18, 12, 12, 15, 20, 8, 14, 8, 18, /* 0x60 - 0x6F */ + 12, 12, 15, 20, 8, 14, 8, 4, 12, 12, 15, 20, 8, 14, 8, 4, /* 0x70 - 0x7F */ + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0x80 - 0x8F */ + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0x90 - 0x9F */ + 16, 16, 16, 16, 8, 8, 8, 8, 16, 16, 16, 16, 8, 8, 8, 8, /* 0xA0 - 0xAF */ + 16, 16, 16, 16, 8, 8, 8, 8, 16, 16, 16, 16, 8, 8, 8, 8, /* 0xB0 - 0xBF */ + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0xC0 - 0xCF */ + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0xD0 - 0xDF */ + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, /* 0xE0 - 0xEF */ + 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8 /* 0xF0 - 0xFF */ + }; + + public static readonly int[] PrefixCB = new int[] + { + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0x00 - 0x0F */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0x10 - 0x1F */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0x20 - 0x2F */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0x30 - 0x3F */ + 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, /* 0x40 - 0x4F */ + 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, /* 0x50 - 0x5F */ + 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, /* 0x60 - 0x6F */ + 8, 8, 8, 8, 8, 8, 12, 8, 8, 8, 8, 8, 8, 8, 12, 8, /* 0x70 - 0x7F */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0x80 - 0x8F */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0x90 - 0x9F */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0xA0 - 0xAF */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0xB0 - 0xBF */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0xC0 - 0xCF */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0xD0 - 0xDF */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8, /* 0xE0 - 0xEF */ + 8, 8, 8, 8, 8, 8, 15, 8, 8, 8, 8, 8, 8, 8, 15, 8 /* 0xF0 - 0xFF */ + }; + + public static readonly int[] PrefixDDFD = new int[] + { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0F */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1F */ + 0, 14, 20, 10, 0, 0, 0, 0, 0, 15, 20, 10, 0, 0, 0, 0, /* 0x20 - 0x2F */ + 0, 0, 0, 0, 23, 23, 19, 0, 0, 15, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3F */ + 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, /* 0x40 - 0x4F */ + 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, /* 0x50 - 0x5F */ + 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, /* 0x60 - 0x6F */ + 19, 19, 19, 19, 19, 19, 0, 19, 0, 0, 0, 0, 0, 0, 19, 0, /* 0x70 - 0x7F */ + 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, /* 0x80 - 0x8F */ + 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, /* 0x90 - 0x9F */ + 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, /* 0xA0 - 0xAF */ + 0, 0, 0, 0, 0, 0, 19, 0, 0, 0, 0, 0, 0, 0, 19, 0, /* 0xB0 - 0xBF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xC0 - 0xCF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xD0 - 0xDF */ + 0, 14, 0, 23, 0, 15, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, /* 0xE0 - 0xEF */ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0 /* 0xF0 - 0xFF */ + }; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.CycleCounts.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.CycleCounts.cs.meta new file mode 100644 index 0000000..a72f1fa --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.CycleCounts.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3c40dfcb38d98fe45a313cee0df21449 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Disassembly.cs b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Disassembly.cs new file mode 100644 index 0000000..e2c07de --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Disassembly.cs @@ -0,0 +1,489 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public partial class Z80A + { + // TODO: add mnemonics for undocumented opcodes + + static readonly string[] opcodeMnemonicNoPrefix = new string[] + { + "NOP", "LD BC, 0x{0:X4}", "LD (BC), A", "INC BC", "INC B", "DEC B", "LD B, 0x{0:X2}", "RLCA", /* 0x00 */ + "EX AF, AF'", "ADD HL, BC", "LD A, (BC)", "DEC BC", "INC C", "DEC C", "LD C, 0x{0:X2}", "RRCA", /* 0x08 */ + "DJNZ 0x{0:X2}", "LD DE, 0x{0:X4}", "LD (DE), A", "INC DE", "INC D", "DEC D", "LD D, 0x{0:X2}", "RLA", /* 0x10 */ + "JR 0x{0:X2}", "ADD HL, DE", "LD A, (DE)", "DEC DE", "INC E", "DEC E", "LD E, 0x{0:X2}", "RRA", /* 0x18 */ + "JR NZ, 0x{0:X2}", "LD HL, 0x{0:X4}", "LD (0x{0:X4}), HL", "INC HL", "INC H", "DEC H", "LD H, 0x{0:X2}", "DAA", /* 0x20 */ + "JR Z, 0x{0:X2}", "ADD HL, HL", "LD HL, (0x{0:X4})", "DEC HL", "INC L", "DEC L", "LD L, 0x{0:X2}", "CPL", /* 0x28 */ + "JR NC, 0x{0:X2}", "LD SP, 0x{0:X4}", "LD (0x{0:X4}), A", "INC SP", "INC (HL)", "DEC (HL)", "LD (HL), 0x{0:X2}", "SCF", /* 0x30 */ + "JR C, 0x{0:X2}", "ADD HL, SP", "LD A, (0x{0:X4})", "DEC SP", "INC A", "DEC A", "LD A, 0x{0:X2}", "CCF", /* 0x38 */ + "LD B, B", "LD B, C", "LD B, D", "LD B, E", "LD B, H", "LD B, L", "LD B, (HL)", "LD B, A", /* 0x40 */ + "LD C, B", "LD C, C", "LD C, D", "LD C, E", "LD C, H", "LD C, L", "LD C, (HL)", "LD C, A", /* 0x48 */ + "LD D, B", "LD D, C", "LD D, D", "LD D, E", "LD D, H", "LD D, L", "LD D, (HL)", "LD D, A", /* 0x50 */ + "LD E, B", "LD E, C", "LD E, D", "LD E, E", "LD E, H", "LD E, L", "LD E, (HL)", "LD E, A", /* 0x58 */ + "LD H, B", "LD H, C", "LD H, D", "LD H, E", "LD H, H", "LD H, L", "LD H, (HL)", "LD H, A", /* 0x60 */ + "LD L, B", "LD L, C", "LD L, D", "LD L, E", "LD L, H", "LD L, L", "LD L, (HL)", "LD L, A", /* 0x68 */ + "LD (HL), B", "LD (HL), C", "LD (HL), D", "LD (HL), E", "LD (HL), H", "LD (HL), L", "HALT", "LD (HL), A", /* 0x70 */ + "LD A, B", "LD A, C", "LD A, D", "LD A, E", "LD A, H", "LD A, L", "LD A, (HL)", "LD A, A", /* 0x78 */ + "ADD A, B", "ADD A, C", "ADD A, D", "ADD A, E", "ADD A, H", "ADD A, L", "ADD A, (HL)", "ADD A, A", /* 0x80 */ + "ADC A, B", "ADC A, C", "ADC A, D", "ADC A, E", "ADC A, H", "ADC A, L", "ADC A, (HL)", "ADC A, A", /* 0x88 */ + "SUB B", "SUB C", "SUB D", "SUB E", "SUB H", "SUB L", "SUB (HL)", "SUB A", /* 0x90 */ + "SBC B", "SBC C", "SBC D", "SBC E", "SBC H", "SBC L", "SBC (HL)", "SBC A", /* 0x98 */ + "AND B", "AND C", "AND D", "AND E", "AND H", "AND L", "AND (HL)", "AND A", /* 0xA0 */ + "XOR B", "XOR C", "XOR D", "XOR E", "XOR H", "XOR L", "XOR (HL)", "XOR A", /* 0xA8 */ + "OR B", "OR C", "OR D", "OR E", "OR H", "OR L", "OR (HL)", "OR A", /* 0xA0 */ + "CP B", "CP C", "CP D", "CP E", "CP H", "CP L", "CP (HL)", "CP A", /* 0xB8 */ + "RET NZ", "POP BC", "JP NZ, 0x{0:X4}", "JP 0x{0:X4}", "CALL NZ, 0x{0:X4}", "PUSH BC", "ADD A, 0x{0:X2}", "RST 00", /* 0xC0 */ + "RET Z", "RET", "JP Z, 0x{0:X4}", string.Empty, "CALL Z, 0x{0:X4}", "CALL 0x{0:X4}", "ADC A, 0x{0:X2}", "RST 08", /* 0xC8 */ + "RET NC", "POP DE", "JP NC, 0x{0:X4}", "OUT 0x{0:X2}, A", "CALL NC, 0x{0:X4}", "PUSH DE", "SUB 0x{0:X2}", "RST 10", /* 0xD0 */ + "RET C", "EXX", "JP C, 0x{0:X4}", "IN A, 0x{0:X2}", "CALL C, 0x{0:X4}", string.Empty, "SBC 0x{0:X2}", "RST 18", /* 0xD8 */ + "RET PO", "POP HL", "JP PO, 0x{0:X4}", "EX (SP), HL", "CALL PO, 0x{0:X4}", "PUSH HL", "AND 0x{0:X2}", "RST 20", /* 0xE0 */ + "RET PE", "JP (HL)", "JP PE, 0x{0:X4}", "EX DE, HL", "CALL PE, 0x{0:X4}", string.Empty, "XOR 0x{0:X2}", "RST 28", /* 0xE8 */ + "RET P", "POP AF", "JP P, 0x{0:X4}", "DI", "CALL P, 0x{0:X4}", "PUSH AF", "OR 0x{0:X2}", "RST 30", /* 0xF0 */ + "RET M", "LD SP, HL", "JP M, 0x{0:X4}", "EI", "CALL M, 0x{0:X4}", string.Empty, "CP 0x{0:X2}", "RST 38" /* 0xF0 */ + }; + + static readonly int[] opcodeLengthNoPrefix = new int[] + { + 1, 3, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 2, 1, + 2, 3, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1, 1, 1, 2, 1, + 2, 3, 3, 1, 1, 1, 2, 1, 2, 1, 3, 1, 1, 1, 2, 1, + 2, 3, 3, 1, 1, 1, 2, 1, 2, 1, 3, 1, 1, 1, 2, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 3, 3, 3, 1, 2, 1, 1, 1, 3, -1, 3, 3, 2, 1, + 1, 1, 3, 2, 3, 1, 2, 1, 1, 1, 3, 2, 3, -1, 2, 1, + 1, 1, 3, 1, 3, 1, 2, 1, 1, 1, 3, 1, 3, -1, 2, 1, + 1, 1, 3, 1, 3, 1, 2, 1, 1, 1, 3, 1, 3, -1, 2, 1, + }; + + static readonly string[] opcodeMnemonicPrefixED = new string[] + { + ".DB 0xED, 0x00", ".DB 0xED, 0x01", ".DB 0xED, 0x02", ".DB 0xED, 0x03", ".DB 0xED, 0x04", ".DB 0xED, 0x05", ".DB 0xED, 0x06", ".DB 0xED, 0x07", /* 0x00 */ + ".DB 0xED, 0x08", ".DB 0xED, 0x09", ".DB 0xED, 0x0A", ".DB 0xED, 0x0B", ".DB 0xED, 0x0C", ".DB 0xED, 0x0D", ".DB 0xED, 0x0E", ".DB 0xED, 0x0F", /* 0x08 */ + ".DB 0xED, 0x10", ".DB 0xED, 0x11", ".DB 0xED, 0x12", ".DB 0xED, 0x13", ".DB 0xED, 0x14", ".DB 0xED, 0x15", ".DB 0xED, 0x16", ".DB 0xED, 0x17", /* 0x10 */ + ".DB 0xED, 0x18", ".DB 0xED, 0x19", ".DB 0xED, 0x1A", ".DB 0xED, 0x1B", ".DB 0xED, 0x1C", ".DB 0xED, 0x1D", ".DB 0xED, 0x1E", ".DB 0xED, 0x1F", /* 0x18 */ + ".DB 0xED, 0x20", ".DB 0xED, 0x21", ".DB 0xED, 0x22", ".DB 0xED, 0x23", ".DB 0xED, 0x24", ".DB 0xED, 0x25", ".DB 0xED, 0x26", ".DB 0xED, 0x27", /* 0x20 */ + ".DB 0xED, 0x28", ".DB 0xED, 0x29", ".DB 0xED, 0x2A", ".DB 0xED, 0x2B", ".DB 0xED, 0x2C", ".DB 0xED, 0x2D", ".DB 0xED, 0x2E", ".DB 0xED, 0x2F", /* 0x28 */ + ".DB 0xED, 0x30", ".DB 0xED, 0x31", ".DB 0xED, 0x32", ".DB 0xED, 0x33", ".DB 0xED, 0x34", ".DB 0xED, 0x35", ".DB 0xED, 0x36", ".DB 0xED, 0x37", /* 0x30 */ + ".DB 0xED, 0x38", ".DB 0xED, 0x39", ".DB 0xED, 0x3A", ".DB 0xED, 0x3B", ".DB 0xED, 0x3C", ".DB 0xED, 0x3D", ".DB 0xED, 0x3E", ".DB 0xED, 0x3F", /* 0x38 */ + "IN B, (C)", "OUT (C), B", "SBC HL, BC", "LD (0x{0:X4}), BC", "NEG", "RETN", "IM 0", "LD I, A", /* 0x40 */ + "IN C, (C)", "OUT (C), C", "ADC HL, BC", "LD BC, (0x{0:X4})", ".DB 0xED, 0x4C", "RETI", ".DB 0xED, 0x4E", "LD R, A", /* 0x48 */ + "IN D, (C)", "OUT (C), D", "SBC HL, DE", "LD (0x{0:X4}), DE", ".DB 0xED, 0x54", ".DB 0xED, 0x55", "IM 1", "LD A, I", /* 0x50 */ + "IN E, (C)", "OUT (C), E", "ADC HL, DE", "LD DE, (0x{0:X4})", ".DB 0xED, 0x5C", ".DB 0xED, 0x5D", "IM 2", "LD A, R", /* 0x58 */ + "IN H, (C)", "OUT (C), H", "SBC HL, HL", ".DB 0xED, 0x63", ".DB 0xED, 0x64", ".DB 0xED, 0x65", ".DB 0xED, 0x66", "RRD", /* 0x60 */ + "IN L, (C)", "OUT (C), L", "ADC HL, HL", ".DB 0xED, 0x6B", ".DB 0xED, 0x6C", ".DB 0xED, 0x6D", ".DB 0xED, 0x6E", "RLD", /* 0x68 */ + ".DB 0xED, 0x70", ".DB 0xED, 0x71", "SBC HL, SP", "LD (0x{0:X4}), SP", ".DB 0xED, 0x74", ".DB 0xED, 0x75", ".DB 0xED, 0x76", ".DB 0xED, 0x77", /* 0x70 */ + "IN A, (C)", "OUT (C), A", "ADC HL, SP", "LD SP, (0x{0:X4})", ".DB 0xED, 0x7C", ".DB 0xED, 0x7D", ".DB 0xED, 0x7E", ".DB 0xED, 0x7F", /* 0x78 */ + ".DB 0xED, 0x80", ".DB 0xED, 0x81", ".DB 0xED, 0x82", ".DB 0xED, 0x83", ".DB 0xED, 0x84", ".DB 0xED, 0x85", ".DB 0xED, 0x86", ".DB 0xED, 0x87", /* 0x80 */ + ".DB 0xED, 0x88", ".DB 0xED, 0x89", ".DB 0xED, 0x8A", ".DB 0xED, 0x8B", ".DB 0xED, 0x8C", ".DB 0xED, 0x8D", ".DB 0xED, 0x8E", ".DB 0xED, 0x8F", /* 0x88 */ + ".DB 0xED, 0x90", ".DB 0xED, 0x91", ".DB 0xED, 0x92", ".DB 0xED, 0x93", ".DB 0xED, 0x94", ".DB 0xED, 0x95", ".DB 0xED, 0x96", ".DB 0xED, 0x97", /* 0x90 */ + ".DB 0xED, 0x98", ".DB 0xED, 0x99", ".DB 0xED, 0x9A", ".DB 0xED, 0x9B", ".DB 0xED, 0x9C", ".DB 0xED, 0x9D", ".DB 0xED, 0x9E", ".DB 0xED, 0x9F", /* 0x98 */ + "LDI", "CPI", "INI", "OUTI", ".DB 0xED, 0xA4", ".DB 0xED, 0xA5", ".DB 0xED, 0xA6", ".DB 0xED, 0xA7", /* 0xA0 */ + "LDD", "CPD", "IND", "OUTD", ".DB 0xED, 0xAC", ".DB 0xED, 0xAD", ".DB 0xED, 0xAE", ".DB 0xED, 0xAF", /* 0xA8 */ + "LDIR", "CPIR", "INIR", "OTIR", ".DB 0xED, 0xB4", ".DB 0xED, 0xB5", ".DB 0xED, 0xB6", ".DB 0xED, 0xB7", /* 0xB0 */ + "LDDR", "CPDR", "INDR", "OTDR", ".DB 0xED, 0xBC", ".DB 0xED, 0xBD", ".DB 0xED, 0xBE", ".DB 0xED, 0xBF", /* 0xB8 */ + ".DB 0xED, 0xC0", ".DB 0xED, 0xC1", ".DB 0xED, 0xC2", ".DB 0xED, 0xC3", ".DB 0xED, 0xC4", ".DB 0xED, 0xC5", ".DB 0xED, 0xC6", ".DB 0xED, 0xC7", /* 0xC0 */ + ".DB 0xED, 0xC8", ".DB 0xED, 0xC9", ".DB 0xED, 0xCA", ".DB 0xED, 0xCB", ".DB 0xED, 0xCC", ".DB 0xED, 0xCD", ".DB 0xED, 0xCE", ".DB 0xED, 0xCF", /* 0xC8 */ + ".DB 0xED, 0xD0", ".DB 0xED, 0xD1", ".DB 0xED, 0xD2", ".DB 0xED, 0xD3", ".DB 0xED, 0xD4", ".DB 0xED, 0xD5", ".DB 0xED, 0xD6", ".DB 0xED, 0xD7", /* 0xD0 */ + ".DB 0xED, 0xD8", ".DB 0xED, 0xD9", ".DB 0xED, 0xDA", ".DB 0xED, 0xDB", ".DB 0xED, 0xDC", ".DB 0xED, 0xDD", ".DB 0xED, 0xDE", ".DB 0xED, 0xDF", /* 0xD8 */ + ".DB 0xED, 0xE0", ".DB 0xED, 0xE1", ".DB 0xED, 0xE2", ".DB 0xED, 0xE3", ".DB 0xED, 0xE4", ".DB 0xED, 0xE5", ".DB 0xED, 0xE6", ".DB 0xED, 0xE7", /* 0xE0 */ + ".DB 0xED, 0xE8", ".DB 0xED, 0xE9", ".DB 0xED, 0xEA", ".DB 0xED, 0xEB", ".DB 0xED, 0xEC", ".DB 0xED, 0xED", ".DB 0xED, 0xEE", ".DB 0xED, 0xEF", /* 0xE8 */ + ".DB 0xED, 0xF0", ".DB 0xED, 0xF1", ".DB 0xED, 0xF2", ".DB 0xED, 0xF3", ".DB 0xED, 0xF4", ".DB 0xED, 0xF5", ".DB 0xED, 0xF6", ".DB 0xED, 0xF7", /* 0xF0 */ + ".DB 0xED, 0xF8", ".DB 0xED, 0xF9", ".DB 0xED, 0xFA", ".DB 0xED, 0xFB", ".DB 0xED, 0xFC", ".DB 0xED, 0xFD", ".DB 0xED, 0xFE", ".DB 0xED, 0xFF" /* 0xF8 */ + }; + + static readonly int[] opcodeLengthPrefixED = new int[] + { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, + 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + }; + + static readonly string[] opcodeMnemonicPrefixCB = new string[] + { + "RLC B", "RLC C", "RLC D", "RLC E", "RLC H", "RLC L", "RLC (HL)", "RLC A", /* 0x00 */ + "RRC B", "RRC C", "RRC D", "RRC E", "RRC H", "RRC L", "RRC (HL)", "RRC A", /* 0x08 */ + "RL B", "RL C", "RL D", "RL E", "RL H", "RL L", "RL (HL)", "RL A", /* 0x10 */ + "RR B", "RR C", "RR D", "RR E", "RR H", "RR L", "RR (HL)", "RR A", /* 0x18 */ + "SLA B", "SLA C", "SLA D", "SLA E", "SLA H", "SLA L", "SLA (HL)", "SLA A", /* 0x20 */ + "SRA B", "SRA C", "SRA D", "SRA E", "SRA H", "SRA L", "SRA (HL)", "SRA A", /* 0x28 */ + "SLL B", "SLL C", "SLL D", "SLL E", "SLL H", "SLL L", "SLL (HL)", "SLL A", /* 0x30 */ + "SRL B", "SRL C", "SRL D", "SRL E", "SRL H", "SRL L", "SRL (HL)", "SRL A", /* 0x38 */ + "BIT 0, B", "BIT 0, C", "BIT 0, D", "BIT 0, E", "BIT 0, H", "BIT 0, L", "BIT 0, (HL)", "BIT 0, A", /* 0x40 */ + "BIT 1, B", "BIT 1, C", "BIT 1, D", "BIT 1, E", "BIT 1, H", "BIT 1, L", "BIT 1, (HL)", "BIT 1, A", /* 0x48 */ + "BIT 2, B", "BIT 2, C", "BIT 2, D", "BIT 2, E", "BIT 2, H", "BIT 2, L", "BIT 2, (HL)", "BIT 2, A", /* 0x50 */ + "BIT 3, B", "BIT 3, C", "BIT 3, D", "BIT 3, E", "BIT 3, H", "BIT 3, L", "BIT 3, (HL)", "BIT 3, A", /* 0x58 */ + "BIT 4, B", "BIT 4, C", "BIT 4, D", "BIT 4, E", "BIT 4, H", "BIT 4, L", "BIT 4, (HL)", "BIT 4, A", /* 0x60 */ + "BIT 5, B", "BIT 5, C", "BIT 5, D", "BIT 5, E", "BIT 5, H", "BIT 5, L", "BIT 5, (HL)", "BIT 5, A", /* 0x68 */ + "BIT 6, B", "BIT 6, C", "BIT 6, D", "BIT 6, E", "BIT 6, H", "BIT 6, L", "BIT 6, (HL)", "BIT 6, A", /* 0x70 */ + "BIT 7, B", "BIT 7, C", "BIT 7, D", "BIT 7, E", "BIT 7, H", "BIT 7, L", "BIT 7, (HL)", "BIT 7, A", /* 0x78 */ + "RES 0, B", "RES 0, C", "RES 0, D", "RES 0, E", "RES 0, H", "RES 0, L", "RES 0, (HL)", "RES 0, A", /* 0x80 */ + "RES 1, B", "RES 1, C", "RES 1, D", "RES 1, E", "RES 1, H", "RES 1, L", "RES 1, (HL)", "RES 1, A", /* 0x88 */ + "RES 2, B", "RES 2, C", "RES 2, D", "RES 2, E", "RES 2, H", "RES 2, L", "RES 2, (HL)", "RES 2, A", /* 0x90 */ + "RES 3, B", "RES 3, C", "RES 3, D", "RES 3, E", "RES 3, H", "RES 3, L", "RES 3, (HL)", "RES 3, A", /* 0x98 */ + "RES 4, B", "RES 4, C", "RES 4, D", "RES 4, E", "RES 4, H", "RES 4, L", "RES 4, (HL)", "RES 4, A", /* 0xA0 */ + "RES 5, B", "RES 5, C", "RES 5, D", "RES 5, E", "RES 5, H", "RES 5, L", "RES 5, (HL)", "RES 5, A", /* 0xA8 */ + "RES 6, B", "RES 6, C", "RES 6, D", "RES 6, E", "RES 6, H", "RES 6, L", "RES 6, (HL)", "RES 6, A", /* 0xB0 */ + "RES 7, B", "RES 7, C", "RES 7, D", "RES 7, E", "RES 7, H", "RES 7, L", "RES 7, (HL)", "RES 7, A", /* 0xB8 */ + "SET 0, B", "SET 0, C", "SET 0, D", "SET 0, E", "SET 0, H", "SET 0, L", "SET 0, (HL)", "SET 0, A", /* 0xC0 */ + "SET 1, B", "SET 1, C", "SET 1, D", "SET 1, E", "SET 1, H", "SET 1, L", "SET 1, (HL)", "SET 1, A", /* 0xC8 */ + "SET 2, B", "SET 2, C", "SET 2, D", "SET 2, E", "SET 2, H", "SET 2, L", "SET 2, (HL)", "SET 2, A", /* 0xD0 */ + "SET 3, B", "SET 3, C", "SET 3, D", "SET 3, E", "SET 3, H", "SET 3, L", "SET 3, (HL)", "SET 3, A", /* 0xD8 */ + "SET 4, B", "SET 4, C", "SET 4, D", "SET 4, E", "SET 4, H", "SET 4, L", "SET 4, (HL)", "SET 4, A", /* 0xE0 */ + "SET 5, B", "SET 5, C", "SET 5, D", "SET 5, E", "SET 5, H", "SET 5, L", "SET 5, (HL)", "SET 5, A", /* 0xE8 */ + "SET 6, B", "SET 6, C", "SET 6, D", "SET 6, E", "SET 6, H", "SET 6, L", "SET 6, (HL)", "SET 6, A", /* 0xF0 */ + "SET 7, B", "SET 7, C", "SET 7, D", "SET 7, E", "SET 7, H", "SET 7, L", "SET 7, (HL)", "SET 7, A" /* 0xF8 */ + }; + + static readonly int[] opcodeLength_CB = new int[] + { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + }; + + static readonly string[] opcodeMnemonicPrefixDD = new string[] + { + ".DB 0xDD, 0x00", ".DB 0xDD, 0x01", ".DB 0xDD, 0x02", ".DB 0xDD, 0x03", ".DB 0xDD, 0x04", ".DB 0xDD, 0x05", ".DB 0xDD, 0x06", ".DB 0xDD, 0x07", /* 0x00 */ + ".DB 0xDD, 0x08", "ADD IX, BC", ".DB 0xDD, 0x0A", ".DB 0xDD, 0x0B", ".DB 0xDD, 0x0C", ".DB 0xDD, 0x0D", ".DB 0xDD, 0x0E", ".DB 0xDD, 0x0F", /* 0x08 */ + ".DB 0xDD, 0x10", ".DB 0xDD, 0x11", ".DB 0xDD, 0x12", ".DB 0xDD, 0x13", ".DB 0xDD, 0x14", ".DB 0xDD, 0x15", ".DB 0xDD, 0x16", ".DB 0xDD, 0x17", /* 0x10 */ + ".DB 0xDD, 0x18", "ADD IX, DE", ".DB 0xDD, 0x1A", ".DB 0xDD, 0x1B", ".DB 0xDD, 0x1C", ".DB 0xDD, 0x1D", ".DB 0xDD, 0x1E", ".DB 0xDD, 0x1F", /* 0x18 */ + ".DB 0xDD, 0x20", "LD IX, 0x{0:X4}", "LD (0x{0:X4}), IX", "INC IX", "INC IXH", "DEC IXH", "LD IXH, 0x{0:X2}", ".DB 0xDD, 0x27", /* 0x20 */ + ".DB 0xDD, 0x28", "ADD IX, IX", "LD IX, (0x{0:X4})", "DEC IX", "INC IXL", "DEC IXL", "LD IXL, 0x{0:X2}", ".DB 0xDD, 0x2F", /* 0x28 */ + ".DB 0xDD, 0x30", ".DB 0xDD, 0x31", ".DB 0xDD, 0x32", ".DB 0xDD, 0x33", "INC (IX+0x{0:X2})", "DEC (IX+0x{0:X2})", "LD (IX+0x{0:X2}), 0x{0:X2}", ".DB 0xDD, 0x37", /* 0x30 */ + ".DB 0xDD, 0x38", "ADD IX, SP", ".DB 0xDD, 0x3A", ".DB 0xDD, 0x3B", ".DB 0xDD, 0x3C", ".DB 0xDD, 0x3D", ".DB 0xDD, 0x3E", ".DB 0xDD, 0x3F", /* 0x38 */ + ".DB 0xDD, 0x40", ".DB 0xDD, 0x41", ".DB 0xDD, 0x42", ".DB 0xDD, 0x43", ".DB 0xDD, 0x44", ".DB 0xDD, 0x45", "LD B, (IX+0x{0:X2})", ".DB 0xDD, 0x47", /* 0x40 */ + ".DB 0xDD, 0x48", ".DB 0xDD, 0x49", ".DB 0xDD, 0x4A", ".DB 0xDD, 0x4B", ".DB 0xDD, 0x4C", ".DB 0xDD, 0x4D", "LD C, (IX+0x{0:X2})", ".DB 0xDD, 0x4F", /* 0x48 */ + ".DB 0xDD, 0x50", ".DB 0xDD, 0x51", ".DB 0xDD, 0x52", ".DB 0xDD, 0x53", ".DB 0xDD, 0x54", ".DB 0xDD, 0x55", "LD D, (IX+0x{0:X2})", ".DB 0xDD, 0x57", /* 0x50 */ + ".DB 0xDD, 0x58", ".DB 0xDD, 0x59", ".DB 0xDD, 0x5A", ".DB 0xDD, 0x5B", ".DB 0xDD, 0x5C", ".DB 0xDD, 0x5D", "LD E, (IX+0x{0:X2})", ".DB 0xDD, 0x5F", /* 0x58 */ + ".DB 0xDD, 0x60", ".DB 0xDD, 0x61", ".DB 0xDD, 0x62", ".DB 0xDD, 0x63", ".DB 0xDD, 0x64", ".DB 0xDD, 0x65", "LD H, (IX+0x{0:X2})", ".DB 0xDD, 0x67", /* 0x60 */ + ".DB 0xDD, 0x68", ".DB 0xDD, 0x69", ".DB 0xDD, 0x6A", ".DB 0xDD, 0x6B", ".DB 0xDD, 0x6C", ".DB 0xDD, 0x6D", "LD L, (IX+0x{0:X2})", ".DB 0xDD, 0x6F", /* 0x68 */ + "LD (IX+0x{0:X2}), B", "LD (IX+0x{0:X2}), C", "LD (IX+0x{0:X2}), D", "LD (IX+0x{0:X2}), E", "LD (IX+0x{0:X2}), H", "LD (IX+0x{0:X2}), L", ".DB 0xDD, 0x76", "LD (IX+0x{0:X2}), A", /* 0x70 */ + ".DB 0xDD, 0x78", ".DB 0xDD, 0x79", ".DB 0xDD, 0x7A", ".DB 0xDD, 0x7B", ".DB 0xDD, 0x7C", ".DB 0xDD, 0x7D", "LD A, (IX+0x{0:X2})", ".DB 0xDD, 0x7F", /* 0x78 */ + ".DB 0xDD, 0x80", ".DB 0xDD, 0x81", ".DB 0xDD, 0x82", ".DB 0xDD, 0x83", ".DB 0xDD, 0x84", ".DB 0xDD, 0x85", "ADD A, (IX+0x{0:X2})", ".DB 0xDD, 0x87", /* 0x80 */ + ".DB 0xDD, 0x88", ".DB 0xDD, 0x89", ".DB 0xDD, 0x8A", ".DB 0xDD, 0x8B", ".DB 0xDD, 0x8C", ".DB 0xDD, 0x8D", "ADC A, (IX+0x{0:X2})", ".DB 0xDD, 0x8F", /* 0x88 */ + ".DB 0xDD, 0x90", ".DB 0xDD, 0x91", ".DB 0xDD, 0x92", ".DB 0xDD, 0x93", ".DB 0xDD, 0x94", ".DB 0xDD, 0x95", "SUB (IX+0x{0:X2})", ".DB 0xDD, 0x97", /* 0x90 */ + ".DB 0xDD, 0x98", ".DB 0xDD, 0x99", ".DB 0xDD, 0x9A", ".DB 0xDD, 0x9B", ".DB 0xDD, 0x9C", ".DB 0xDD, 0x9D", "SBC (IX+0x{0:X2})", ".DB 0xDD, 0x9F", /* 0x98 */ + ".DB 0xDD, 0xA0", ".DB 0xDD, 0xA1", ".DB 0xDD, 0xA2", ".DB 0xDD, 0xA3", ".DB 0xDD, 0xA4", ".DB 0xDD, 0xA5", "AND (IX+0x{0:X2})", ".DB 0xDD, 0xA7", /* 0xA0 */ + ".DB 0xDD, 0xA8", ".DB 0xDD, 0xA9", ".DB 0xDD, 0xAA", ".DB 0xDD, 0xAB", ".DB 0xDD, 0xAC", ".DB 0xDD, 0xAD", "XOR (IX+0x{0:X2})", ".DB 0xDD, 0xAF", /* 0xA8 */ + ".DB 0xDD, 0xB0", ".DB 0xDD, 0xB1", ".DB 0xDD, 0xB2", ".DB 0xDD, 0xB3", ".DB 0xDD, 0xB4", ".DB 0xDD, 0xB5", "OR (IX+0x{0:X2})", ".DB 0xDD, 0xB7", /* 0xB0 */ + ".DB 0xDD, 0xB8", ".DB 0xDD, 0xB9", ".DB 0xDD, 0xBA", ".DB 0xDD, 0xBB", ".DB 0xDD, 0xBC", ".DB 0xDD, 0xBD", "CP (IX+0x{0:X2})", ".DB 0xDD, 0xBF", /* 0xB8 */ + ".DB 0xDD, 0xC0", ".DB 0xDD, 0xC1", ".DB 0xDD, 0xC2", ".DB 0xDD, 0xC3", ".DB 0xDD, 0xC4", ".DB 0xDD, 0xC5", ".DB 0xDD, 0xC6", ".DB 0xDD, 0xC7", /* 0xC0 */ + ".DB 0xDD, 0xC8", ".DB 0xDD, 0xC9", ".DB 0xDD, 0xCA", string.Empty, ".DB 0xDD, 0xCC", ".DB 0xDD, 0xCD", ".DB 0xDD, 0xCE", ".DB 0xDD, 0xCF", /* 0xC8 */ + ".DB 0xDD, 0xD0", ".DB 0xDD, 0xD1", ".DB 0xDD, 0xD2", ".DB 0xDD, 0xD3", ".DB 0xDD, 0xD4", ".DB 0xDD, 0xD5", ".DB 0xDD, 0xD6", ".DB 0xDD, 0xD7", /* 0xD0 */ + ".DB 0xDD, 0xD8", ".DB 0xDD, 0xD9", ".DB 0xDD, 0xDA", ".DB 0xDD, 0xDB", ".DB 0xDD, 0xDC", ".DB 0xDD, 0xDD", ".DB 0xDD, 0xDE", ".DB 0xDD, 0xDF", /* 0xD8 */ + ".DB 0xED, 0xE0", "POP IX", ".DB 0xED, 0xE2", "EX (SP), IX", ".DB 0xED, 0xE4", "PUSH IX", ".DB 0xED, 0xE6", ".DB 0xED, 0xE7", /* 0xE0 */ + ".DB 0xED, 0xE8", "JP (IX)", ".DB 0xED, 0xEA", ".DB 0xED, 0xEB", ".DB 0xED, 0xEC", ".DB 0xED, 0xED", ".DB 0xED, 0xEE", ".DB 0xED, 0xEF", /* 0xE8 */ + ".DB 0xFD, 0xF0", ".DB 0xFD, 0xF1", ".DB 0xFD, 0xF2", ".DB 0xFD, 0xF3", ".DB 0xFD, 0xF4", ".DB 0xFD, 0xF5", ".DB 0xFD, 0xF6", ".DB 0xFD, 0xF7", /* 0xF0 */ + ".DB 0xFD, 0xF8", "LD SP, IX", ".DB 0xFD, 0xFA", ".DB 0xFD, 0xFB", ".DB 0xFD, 0xFC", ".DB 0xFD, 0xFD", ".DB 0xFD, 0xFE", ".DB 0xFD, 0xFF" /* 0xF8 */ + }; + + static readonly string[] opcodeMnemonicPrefixFD = new string[] + { + ".DB 0xFD, 0x00", ".DB 0xFD, 0x01", ".DB 0xFD, 0x02", ".DB 0xFD, 0x03", ".DB 0xFD, 0x04", ".DB 0xFD, 0x05", ".DB 0xFD, 0x06", ".DB 0xFD, 0x07", /* 0x00 */ + ".DB 0xFD, 0x08", "ADD IY, BC", ".DB 0xFD, 0x0A", ".DB 0xFD, 0x0B", ".DB 0xFD, 0x0C", ".DB 0xFD, 0x0D", ".DB 0xFD, 0x0E", ".DB 0xFD, 0x0F", /* 0x08 */ + ".DB 0xFD, 0x10", ".DB 0xFD, 0x11", ".DB 0xFD, 0x12", ".DB 0xFD, 0x13", ".DB 0xFD, 0x14", ".DB 0xFD, 0x15", ".DB 0xFD, 0x16", ".DB 0xFD, 0x17", /* 0x10 */ + ".DB 0xFD, 0x18", "ADD IY, DE", ".DB 0xFD, 0x1A", ".DB 0xFD, 0x1B", ".DB 0xFD, 0x1C", ".DB 0xFD, 0x1D", ".DB 0xFD, 0x1E", ".DB 0xFD, 0x1F", /* 0x18 */ + ".DB 0xFD, 0x20", "LD IY, 0x{0:X4}", "LD (0x{0:X4}), IY", "INC IY", ".DB 0xFD, 0x24", ".DB 0xFD, 0x25", ".DB 0xFD, 0x26", ".DB 0xFD, 0x27", /* 0x20 */ + ".DB 0xFD, 0x28", "ADD IY, IY", "LD IY, (0x{0:X4})", "DEC IY", ".DB 0xFD, 0x2C", ".DB 0xFD, 0x2D", ".DB 0xFD, 0x2E", ".DB 0xFD, 0x2F", /* 0x28 */ + ".DB 0xFD, 0x30", ".DB 0xFD, 0x31", ".DB 0xFD, 0x32", ".DB 0xFD, 0x33", "INC (IY+0x{0:X2})", "DEC (IY+0x{0:X2})", "LD (IY+0x{0:X2}), 0x{0:X2}", ".DB 0xFD, 0x37", /* 0x30 */ + ".DB 0xFD, 0x38", "ADD IY, SP", ".DB 0xFD, 0x3A", ".DB 0xFD, 0x3B", ".DB 0xFD, 0x3C", ".DB 0xFD, 0x3D", ".DB 0xFD, 0x3E", ".DB 0xFD, 0x3F", /* 0x38 */ + ".DB 0xFD, 0x40", ".DB 0xFD, 0x41", ".DB 0xFD, 0x42", ".DB 0xFD, 0x43", ".DB 0xFD, 0x44", ".DB 0xFD, 0x45", "LD B, (IY+0x{0:X2})", ".DB 0xFD, 0x47", /* 0x40 */ + ".DB 0xFD, 0x48", ".DB 0xFD, 0x49", ".DB 0xFD, 0x4A", ".DB 0xFD, 0x4B", ".DB 0xFD, 0x4C", ".DB 0xFD, 0x4D", "LD C, (IY+0x{0:X2})", ".DB 0xFD, 0x4F", /* 0x48 */ + ".DB 0xFD, 0x50", ".DB 0xFD, 0x51", ".DB 0xFD, 0x52", ".DB 0xFD, 0x53", ".DB 0xFD, 0x54", ".DB 0xFD, 0x55", "LD D, (IY+0x{0:X2})", ".DB 0xFD, 0x57", /* 0x50 */ + ".DB 0xFD, 0x58", ".DB 0xFD, 0x59", ".DB 0xFD, 0x5A", ".DB 0xFD, 0x5B", ".DB 0xFD, 0x5C", ".DB 0xFD, 0x5D", "LD E, (IY+0x{0:X2})", ".DB 0xFD, 0x5F", /* 0x58 */ + ".DB 0xFD, 0x60", ".DB 0xFD, 0x61", ".DB 0xFD, 0x62", ".DB 0xFD, 0x63", ".DB 0xFD, 0x64", ".DB 0xFD, 0x65", "LD H, (IY+0x{0:X2})", ".DB 0xFD, 0x67", /* 0x60 */ + ".DB 0xFD, 0x68", ".DB 0xFD, 0x69", ".DB 0xFD, 0x6A", ".DB 0xFD, 0x6B", ".DB 0xFD, 0x6C", ".DB 0xFD, 0x6D", "LD L, (IY+0x{0:X2})", ".DB 0xFD, 0x6F", /* 0x68 */ + "LD (IY+0x{0:X2}), B", "LD (IY+0x{0:X2}), C", "LD (IY+0x{0:X2}), D", "LD (IY+0x{0:X2}), E", "LD (IY+0x{0:X2}), H", "LD (IY+0x{0:X2}), L", ".DB 0xFD, 0x76", "LD (IY+0x{0:X2}), A", /* 0x70 */ + ".DB 0xFD, 0x78", ".DB 0xFD, 0x79", ".DB 0xFD, 0x7A", ".DB 0xFD, 0x7B", ".DB 0xFD, 0x7C", ".DB 0xFD, 0x7D", "LD A, (IY+0x{0:X2})", ".DB 0xFD, 0x7F", /* 0x78 */ + ".DB 0xFD, 0x80", ".DB 0xFD, 0x81", ".DB 0xFD, 0x82", ".DB 0xFD, 0x83", ".DB 0xFD, 0x84", ".DB 0xFD, 0x85", "ADD A, (IY+0x{0:X2})", ".DB 0xFD, 0x87", /* 0x80 */ + ".DB 0xFD, 0x88", ".DB 0xFD, 0x89", ".DB 0xFD, 0x8A", ".DB 0xFD, 0x8B", ".DB 0xFD, 0x8C", ".DB 0xFD, 0x8D", "ADC A, (IY+0x{0:X2})", ".DB 0xFD, 0x8F", /* 0x88 */ + ".DB 0xFD, 0x90", ".DB 0xFD, 0x91", ".DB 0xFD, 0x92", ".DB 0xFD, 0x93", ".DB 0xFD, 0x94", ".DB 0xFD, 0x95", "SUB (IY+0x{0:X2})", ".DB 0xFD, 0x97", /* 0x90 */ + ".DB 0xFD, 0x98", ".DB 0xFD, 0x99", ".DB 0xFD, 0x9A", ".DB 0xFD, 0x9B", ".DB 0xFD, 0x9C", ".DB 0xFD, 0x9D", "SBC (IY+0x{0:X2})", ".DB 0xFD, 0x9F", /* 0x98 */ + ".DB 0xFD, 0xA0", ".DB 0xFD, 0xA1", ".DB 0xFD, 0xA2", ".DB 0xFD, 0xA3", ".DB 0xFD, 0xA4", ".DB 0xFD, 0xA5", "AND (IY+0x{0:X2})", ".DB 0xFD, 0xA7", /* 0xA0 */ + ".DB 0xFD, 0xA8", ".DB 0xFD, 0xA9", ".DB 0xFD, 0xAA", ".DB 0xFD, 0xAB", ".DB 0xFD, 0xAC", ".DB 0xFD, 0xAD", "XOR (IY+0x{0:X2})", ".DB 0xFD, 0xAF", /* 0xA8 */ + ".DB 0xFD, 0xB0", ".DB 0xFD, 0xB1", ".DB 0xFD, 0xB2", ".DB 0xFD, 0xB3", ".DB 0xFD, 0xB4", ".DB 0xFD, 0xB5", "OR (IY+0x{0:X2})", ".DB 0xFD, 0xB7", /* 0xB0 */ + ".DB 0xFD, 0xB8", ".DB 0xFD, 0xB9", ".DB 0xFD, 0xBA", ".DB 0xFD, 0xBB", ".DB 0xFD, 0xBC", ".DB 0xFD, 0xBD", "CP (IY+0x{0:X2})", ".DB 0xFD, 0xBF", /* 0xB8 */ + ".DB 0xFD, 0xC0", ".DB 0xFD, 0xC1", ".DB 0xFD, 0xC2", ".DB 0xFD, 0xC3", ".DB 0xFD, 0xC4", ".DB 0xFD, 0xC5", ".DB 0xFD, 0xC6", ".DB 0xFD, 0xC7", /* 0xC0 */ + ".DB 0xFD, 0xC8", ".DB 0xFD, 0xC9", ".DB 0xFD, 0xCA", string.Empty, ".DB 0xFD, 0xCC", ".DB 0xFD, 0xCD", ".DB 0xFD, 0xCE", ".DB 0xFD, 0xCF", /* 0xC8 */ + ".DB 0xFD, 0xD0", ".DB 0xFD, 0xD1", ".DB 0xFD, 0xD2", ".DB 0xFD, 0xD3", ".DB 0xFD, 0xD4", ".DB 0xFD, 0xD5", ".DB 0xFD, 0xD6", ".DB 0xFD, 0xD7", /* 0xD0 */ + ".DB 0xFD, 0xD8", ".DB 0xFD, 0xD9", ".DB 0xFD, 0xDA", ".DB 0xFD, 0xDB", ".DB 0xFD, 0xDC", ".DB 0xFD, 0xFD", ".DB 0xFD, 0xDE", ".DB 0xFD, 0xDF", /* 0xD8 */ + ".DB 0xED, 0xE0", "POP IY", ".DB 0xED, 0xE2", "EX (SP), IY", ".DB 0xED, 0xE4", "PUSH IY", ".DB 0xED, 0xE6", ".DB 0xED, 0xE7", /* 0xE0 */ + ".DB 0xED, 0xE8", "JP (IY)", ".DB 0xED, 0xEA", ".DB 0xED, 0xEB", ".DB 0xED, 0xEC", ".DB 0xED, 0xED", ".DB 0xED, 0xEE", ".DB 0xED, 0xEF", /* 0xE8 */ + ".DB 0xFD, 0xF0", ".DB 0xFD, 0xF1", ".DB 0xFD, 0xF2", ".DB 0xFD, 0xF3", ".DB 0xFD, 0xF4", ".DB 0xFD, 0xF5", ".DB 0xFD, 0xF6", ".DB 0xFD, 0xF7", /* 0xF0 */ + ".DB 0xFD, 0xF8", "LD SP, IY", ".DB 0xFD, 0xFA", ".DB 0xFD, 0xFB", ".DB 0xFD, 0xFC", ".DB 0xFD, 0xFD", ".DB 0xFD, 0xFE", ".DB 0xFD, 0xFF" /* 0xF8 */ + }; + + static readonly string[] opcodeMnemonicPrefixDDCB = new string[] + { + ".DB 0xDD, 0xCB, 0x00", ".DB 0xDD, 0xCB, 0x01", ".DB 0xDD, 0xCB, 0x02", ".DB 0xDD, 0xCB, 0x03", ".DB 0xDD, 0xCB, 0x04", ".DB 0xDD, 0xCB, 0x05", "RLC (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x07", /* 0x00 */ + ".DB 0xDD, 0xCB, 0x08", ".DB 0xDD, 0xCB, 0x09", ".DB 0xDD, 0xCB, 0x0A", ".DB 0xDD, 0xCB, 0x0B", ".DB 0xDD, 0xCB, 0x0C", ".DB 0xDD, 0xCB, 0x0D", "RRC (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x0F", /* 0x08 */ + ".DB 0xDD, 0xCB, 0x10", ".DB 0xDD, 0xCB, 0x11", ".DB 0xDD, 0xCB, 0x12", ".DB 0xDD, 0xCB, 0x13", ".DB 0xDD, 0xCB, 0x14", ".DB 0xDD, 0xCB, 0x15", "RL (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x17", /* 0x10 */ + ".DB 0xDD, 0xCB, 0x18", ".DB 0xDD, 0xCB, 0x19", ".DB 0xDD, 0xCB, 0x1A", ".DB 0xDD, 0xCB, 0x1B", ".DB 0xDD, 0xCB, 0x1C", ".DB 0xDD, 0xCB, 0x1D", "RR (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x1F", /* 0x18 */ + ".DB 0xDD, 0xCB, 0x20", ".DB 0xDD, 0xCB, 0x21", ".DB 0xDD, 0xCB, 0x22", ".DB 0xDD, 0xCB, 0x23", ".DB 0xDD, 0xCB, 0x24", ".DB 0xDD, 0xCB, 0x25", "SLA (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x27", /* 0x20 */ + ".DB 0xDD, 0xCB, 0x28", ".DB 0xDD, 0xCB, 0x29", ".DB 0xDD, 0xCB, 0x2A", ".DB 0xDD, 0xCB, 0x2B", ".DB 0xDD, 0xCB, 0x2C", ".DB 0xDD, 0xCB, 0x2D", "SRA (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x2F", /* 0x28 */ + ".DB 0xDD, 0xCB, 0x30", ".DB 0xDD, 0xCB, 0x31", ".DB 0xDD, 0xCB, 0x32", ".DB 0xDD, 0xCB, 0x33", ".DB 0xDD, 0xCB, 0x34", ".DB 0xDD, 0xCB, 0x35", "SLL (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x37", /* 0x30 */ + ".DB 0xDD, 0xCB, 0x38", ".DB 0xDD, 0xCB, 0x39", ".DB 0xDD, 0xCB, 0x3A", ".DB 0xDD, 0xCB, 0x3B", ".DB 0xDD, 0xCB, 0x3C", ".DB 0xDD, 0xCB, 0x3D", "SRL (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x3F", /* 0x38 */ + ".DB 0xDD, 0xCB, 0x40", ".DB 0xDD, 0xCB, 0x41", ".DB 0xDD, 0xCB, 0x42", ".DB 0xDD, 0xCB, 0x43", ".DB 0xDD, 0xCB, 0x44", ".DB 0xDD, 0xCB, 0x45", "BIT 0, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x47", /* 0x40 */ + ".DB 0xDD, 0xCB, 0x48", ".DB 0xDD, 0xCB, 0x49", ".DB 0xDD, 0xCB, 0x4A", ".DB 0xDD, 0xCB, 0x4B", ".DB 0xDD, 0xCB, 0x4C", ".DB 0xDD, 0xCB, 0x4D", "BIT 1, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x4F", /* 0x48 */ + ".DB 0xDD, 0xCB, 0x50", ".DB 0xDD, 0xCB, 0x51", ".DB 0xDD, 0xCB, 0x52", ".DB 0xDD, 0xCB, 0x53", ".DB 0xDD, 0xCB, 0x54", ".DB 0xDD, 0xCB, 0x55", "BIT 2, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x57", /* 0x50 */ + ".DB 0xDD, 0xCB, 0x58", ".DB 0xDD, 0xCB, 0x59", ".DB 0xDD, 0xCB, 0x5A", ".DB 0xDD, 0xCB, 0x5B", ".DB 0xDD, 0xCB, 0x5C", ".DB 0xDD, 0xCB, 0x5D", "BIT 3, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x5F", /* 0x58 */ + ".DB 0xDD, 0xCB, 0x60", ".DB 0xDD, 0xCB, 0x61", ".DB 0xDD, 0xCB, 0x62", ".DB 0xDD, 0xCB, 0x63", ".DB 0xDD, 0xCB, 0x64", ".DB 0xDD, 0xCB, 0x65", "BIT 4, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x67", /* 0x60 */ + ".DB 0xDD, 0xCB, 0x68", ".DB 0xDD, 0xCB, 0x69", ".DB 0xDD, 0xCB, 0x6A", ".DB 0xDD, 0xCB, 0x6B", ".DB 0xDD, 0xCB, 0x6C", ".DB 0xDD, 0xCB, 0x6D", "BIT 5, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x6F", /* 0x68 */ + ".DB 0xDD, 0xCB, 0x70", ".DB 0xDD, 0xCB, 0x71", ".DB 0xDD, 0xCB, 0x72", ".DB 0xDD, 0xCB, 0x73", ".DB 0xDD, 0xCB, 0x74", ".DB 0xDD, 0xCB, 0x75", "BIT 6, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x77", /* 0x70 */ + ".DB 0xDD, 0xCB, 0x78", ".DB 0xDD, 0xCB, 0x79", ".DB 0xDD, 0xCB, 0x7A", ".DB 0xDD, 0xCB, 0x7B", ".DB 0xDD, 0xCB, 0x7C", ".DB 0xDD, 0xCB, 0x7D", "BIT 7, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x7F", /* 0x78 */ + ".DB 0xDD, 0xCB, 0x80", ".DB 0xDD, 0xCB, 0x81", ".DB 0xDD, 0xCB, 0x82", ".DB 0xDD, 0xCB, 0x83", ".DB 0xDD, 0xCB, 0x84", ".DB 0xDD, 0xCB, 0x85", "RES 0, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x87", /* 0x80 */ + ".DB 0xDD, 0xCB, 0x88", ".DB 0xDD, 0xCB, 0x89", ".DB 0xDD, 0xCB, 0x8A", ".DB 0xDD, 0xCB, 0x8B", ".DB 0xDD, 0xCB, 0x8C", ".DB 0xDD, 0xCB, 0x8D", "RES 1, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x8F", /* 0x88 */ + ".DB 0xDD, 0xCB, 0x90", ".DB 0xDD, 0xCB, 0x91", ".DB 0xDD, 0xCB, 0x92", ".DB 0xDD, 0xCB, 0x93", ".DB 0xDD, 0xCB, 0x94", ".DB 0xDD, 0xCB, 0x95", "RES 2, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x97", /* 0x90 */ + ".DB 0xDD, 0xCB, 0x98", ".DB 0xDD, 0xCB, 0x99", ".DB 0xDD, 0xCB, 0x9A", ".DB 0xDD, 0xCB, 0x9B", ".DB 0xDD, 0xCB, 0x9C", ".DB 0xDD, 0xCB, 0x9D", "RES 3, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0x9F", /* 0x98 */ + ".DB 0xDD, 0xCB, 0xA0", ".DB 0xDD, 0xCB, 0xA1", ".DB 0xDD, 0xCB, 0xA2", ".DB 0xDD, 0xCB, 0xA3", ".DB 0xDD, 0xCB, 0xA4", ".DB 0xDD, 0xCB, 0xA5", "RES 4, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0xA7", /* 0xA0 */ + ".DB 0xDD, 0xCB, 0xA8", ".DB 0xDD, 0xCB, 0xA9", ".DB 0xDD, 0xCB, 0xAA", ".DB 0xDD, 0xCB, 0xAB", ".DB 0xDD, 0xCB, 0xAC", ".DB 0xDD, 0xCB, 0xAD", "RES 5, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0xAF", /* 0xA8 */ + ".DB 0xDD, 0xCB, 0xB0", ".DB 0xDD, 0xCB, 0xB1", ".DB 0xDD, 0xCB, 0xB2", ".DB 0xDD, 0xCB, 0xB3", ".DB 0xDD, 0xCB, 0xB4", ".DB 0xDD, 0xCB, 0xB5", "RES 6, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0xB7", /* 0xB0 */ + ".DB 0xDD, 0xCB, 0xB8", ".DB 0xDD, 0xCB, 0xB9", ".DB 0xDD, 0xCB, 0xBA", ".DB 0xDD, 0xCB, 0xBB", ".DB 0xDD, 0xCB, 0xBC", ".DB 0xDD, 0xCB, 0xBD", "RES 7, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0xBF", /* 0xB8 */ + ".DB 0xDD, 0xCB, 0xC0", ".DB 0xDD, 0xCB, 0xC1", ".DB 0xDD, 0xCB, 0xC2", ".DB 0xDD, 0xCB, 0xC3", ".DB 0xDD, 0xCB, 0xC4", ".DB 0xDD, 0xCB, 0xC5", "SET 0, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0xC7", /* 0xC0 */ + ".DB 0xDD, 0xCB, 0xC8", ".DB 0xDD, 0xCB, 0xC9", ".DB 0xDD, 0xCB, 0xCA", ".DB 0xDD, 0xCB, 0xCB", ".DB 0xDD, 0xCB, 0xCC", ".DB 0xDD, 0xCB, 0xCD", "SET 1, (IX+0x{0:X2})", ".DB 0xDD, 0xCB, 0xCF", /* 0xC8 */ + ".DB 0xDD, 0xDB, 0xD0", ".DB 0xDD, 0xDB, 0xD1", ".DB 0xDD, 0xDB, 0xD2", ".DB 0xDD, 0xDB, 0xD3", ".DB 0xDD, 0xDB, 0xD4", ".DB 0xDD, 0xDB, 0xD5", "SET 2, (IX+0x{0:X2})", ".DB 0xDD, 0xDB, 0xD7", /* 0xD0 */ + ".DB 0xDD, 0xDB, 0xD8", ".DB 0xDD, 0xDB, 0xD9", ".DB 0xDD, 0xDB, 0xDA", ".DB 0xDD, 0xDB, 0xDB", ".DB 0xDD, 0xDB, 0xDC", ".DB 0xDD, 0xDB, 0xDD", "SET 3, (IX+0x{0:X2})", ".DB 0xDD, 0xDB, 0xDF", /* 0xD8 */ + ".DB 0xDD, 0xEB, 0xE0", ".DB 0xDD, 0xEB, 0xE1", ".DB 0xDD, 0xEB, 0xE2", ".DB 0xDD, 0xEB, 0xE3", ".DB 0xDD, 0xEB, 0xE4", ".DB 0xDD, 0xEB, 0xE5", "SET 4, (IX+0x{0:X2})", ".DB 0xDD, 0xEB, 0xE7", /* 0xE0 */ + ".DB 0xDD, 0xEB, 0xE8", ".DB 0xDD, 0xEB, 0xE9", ".DB 0xDD, 0xEB, 0xEA", ".DB 0xDD, 0xEB, 0xEB", ".DB 0xDD, 0xEB, 0xEC", ".DB 0xDD, 0xEB, 0xED", "SET 5, (IX+0x{0:X2})", ".DB 0xDD, 0xEB, 0xEF", /* 0xE8 */ + ".DB 0xDD, 0xFB, 0xF0", ".DB 0xDD, 0xFB, 0xF1", ".DB 0xDD, 0xFB, 0xF2", ".DB 0xDD, 0xFB, 0xF3", ".DB 0xDD, 0xFB, 0xF4", ".DB 0xDD, 0xFB, 0xF5", "SET 6, (IX+0x{0:X2})", ".DB 0xDD, 0xFB, 0xF7", /* 0xF0 */ + ".DB 0xDD, 0xFB, 0xF8", ".DB 0xDD, 0xFB, 0xF9", ".DB 0xDD, 0xFB, 0xFA", ".DB 0xDD, 0xFB, 0xFB", ".DB 0xDD, 0xFB, 0xFC", ".DB 0xDD, 0xFB, 0xFD", "SET 7, (IX+0x{0:X2})", ".DB 0xDD, 0xFB, 0xFF" /* 0xF8 */ + }; + + static readonly string[] opcodeMnemonicPrefixFDCB = new string[] + { + ".DB 0xFD, 0xCB, 0x00", ".DB 0xFD, 0xCB, 0x01", ".DB 0xFD, 0xCB, 0x02", ".DB 0xFD, 0xCB, 0x03", ".DB 0xFD, 0xCB, 0x04", ".DB 0xFD, 0xCB, 0x05", "RLC (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x07", /* 0x00 */ + ".DB 0xFD, 0xCB, 0x08", ".DB 0xFD, 0xCB, 0x09", ".DB 0xFD, 0xCB, 0x0A", ".DB 0xFD, 0xCB, 0x0B", ".DB 0xFD, 0xCB, 0x0C", ".DB 0xFD, 0xCB, 0x0D", "RRC (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x0F", /* 0x08 */ + ".DB 0xFD, 0xCB, 0x10", ".DB 0xFD, 0xCB, 0x11", ".DB 0xFD, 0xCB, 0x12", ".DB 0xFD, 0xCB, 0x13", ".DB 0xFD, 0xCB, 0x14", ".DB 0xFD, 0xCB, 0x15", "RL (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x17", /* 0x10 */ + ".DB 0xFD, 0xCB, 0x18", ".DB 0xFD, 0xCB, 0x19", ".DB 0xFD, 0xCB, 0x1A", ".DB 0xFD, 0xCB, 0x1B", ".DB 0xFD, 0xCB, 0x1C", ".DB 0xFD, 0xCB, 0x1D", "RR (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x1F", /* 0x18 */ + ".DB 0xFD, 0xCB, 0x20", ".DB 0xFD, 0xCB, 0x21", ".DB 0xFD, 0xCB, 0x22", ".DB 0xFD, 0xCB, 0x23", ".DB 0xFD, 0xCB, 0x24", ".DB 0xFD, 0xCB, 0x25", "SLA (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x27", /* 0x20 */ + ".DB 0xFD, 0xCB, 0x28", ".DB 0xFD, 0xCB, 0x29", ".DB 0xFD, 0xCB, 0x2A", ".DB 0xFD, 0xCB, 0x2B", ".DB 0xFD, 0xCB, 0x2C", ".DB 0xFD, 0xCB, 0x2D", "SRA (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x2F", /* 0x28 */ + ".DB 0xFD, 0xCB, 0x30", ".DB 0xFD, 0xCB, 0x31", ".DB 0xFD, 0xCB, 0x32", ".DB 0xFD, 0xCB, 0x33", ".DB 0xFD, 0xCB, 0x34", ".DB 0xFD, 0xCB, 0x35", ".DB 0xFD, 0xCB, 0x36", ".DB 0xFD, 0xCB, 0x37", /* 0x30 */ + ".DB 0xFD, 0xCB, 0x38", ".DB 0xFD, 0xCB, 0x39", ".DB 0xFD, 0xCB, 0x3A", ".DB 0xFD, 0xCB, 0x3B", ".DB 0xFD, 0xCB, 0x3C", ".DB 0xFD, 0xCB, 0x3D", "SRL (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x3F", /* 0x38 */ + ".DB 0xFD, 0xCB, 0x40", ".DB 0xFD, 0xCB, 0x41", ".DB 0xFD, 0xCB, 0x42", ".DB 0xFD, 0xCB, 0x43", ".DB 0xFD, 0xCB, 0x44", ".DB 0xFD, 0xCB, 0x45", "BIT 0, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x47", /* 0x40 */ + ".DB 0xFD, 0xCB, 0x48", ".DB 0xFD, 0xCB, 0x49", ".DB 0xFD, 0xCB, 0x4A", ".DB 0xFD, 0xCB, 0x4B", ".DB 0xFD, 0xCB, 0x4C", ".DB 0xFD, 0xCB, 0x4D", "BIT 1, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x4F", /* 0x48 */ + ".DB 0xFD, 0xCB, 0x50", ".DB 0xFD, 0xCB, 0x51", ".DB 0xFD, 0xCB, 0x52", ".DB 0xFD, 0xCB, 0x53", ".DB 0xFD, 0xCB, 0x54", ".DB 0xFD, 0xCB, 0x55", "BIT 2, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x57", /* 0x50 */ + ".DB 0xFD, 0xCB, 0x58", ".DB 0xFD, 0xCB, 0x59", ".DB 0xFD, 0xCB, 0x5A", ".DB 0xFD, 0xCB, 0x5B", ".DB 0xFD, 0xCB, 0x5C", ".DB 0xFD, 0xCB, 0x5D", "BIT 3, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x5F", /* 0x58 */ + ".DB 0xFD, 0xCB, 0x60", ".DB 0xFD, 0xCB, 0x61", ".DB 0xFD, 0xCB, 0x62", ".DB 0xFD, 0xCB, 0x63", ".DB 0xFD, 0xCB, 0x64", ".DB 0xFD, 0xCB, 0x65", "BIT 4, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x67", /* 0x60 */ + ".DB 0xFD, 0xCB, 0x68", ".DB 0xFD, 0xCB, 0x69", ".DB 0xFD, 0xCB, 0x6A", ".DB 0xFD, 0xCB, 0x6B", ".DB 0xFD, 0xCB, 0x6C", ".DB 0xFD, 0xCB, 0x6D", "BIT 5, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x6F", /* 0x68 */ + ".DB 0xFD, 0xCB, 0x70", ".DB 0xFD, 0xCB, 0x71", ".DB 0xFD, 0xCB, 0x72", ".DB 0xFD, 0xCB, 0x73", ".DB 0xFD, 0xCB, 0x74", ".DB 0xFD, 0xCB, 0x75", "BIT 6, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x77", /* 0x70 */ + ".DB 0xFD, 0xCB, 0x78", ".DB 0xFD, 0xCB, 0x79", ".DB 0xFD, 0xCB, 0x7A", ".DB 0xFD, 0xCB, 0x7B", ".DB 0xFD, 0xCB, 0x7C", ".DB 0xFD, 0xCB, 0x7D", "BIT 7, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x7F", /* 0x78 */ + ".DB 0xFD, 0xCB, 0x80", ".DB 0xFD, 0xCB, 0x81", ".DB 0xFD, 0xCB, 0x82", ".DB 0xFD, 0xCB, 0x83", ".DB 0xFD, 0xCB, 0x84", ".DB 0xFD, 0xCB, 0x85", "RES 0, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x87", /* 0x80 */ + ".DB 0xFD, 0xCB, 0x88", ".DB 0xFD, 0xCB, 0x89", ".DB 0xFD, 0xCB, 0x8A", ".DB 0xFD, 0xCB, 0x8B", ".DB 0xFD, 0xCB, 0x8C", ".DB 0xFD, 0xCB, 0x8D", "RES 1, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x8F", /* 0x88 */ + ".DB 0xFD, 0xCB, 0x90", ".DB 0xFD, 0xCB, 0x91", ".DB 0xFD, 0xCB, 0x92", ".DB 0xFD, 0xCB, 0x93", ".DB 0xFD, 0xCB, 0x94", ".DB 0xFD, 0xCB, 0x95", "RES 2, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x97", /* 0x90 */ + ".DB 0xFD, 0xCB, 0x98", ".DB 0xFD, 0xCB, 0x99", ".DB 0xFD, 0xCB, 0x9A", ".DB 0xFD, 0xCB, 0x9B", ".DB 0xFD, 0xCB, 0x9C", ".DB 0xFD, 0xCB, 0x9D", "RES 3, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0x9F", /* 0x98 */ + ".DB 0xFD, 0xCB, 0xA0", ".DB 0xFD, 0xCB, 0xA1", ".DB 0xFD, 0xCB, 0xA2", ".DB 0xFD, 0xCB, 0xA3", ".DB 0xFD, 0xCB, 0xA4", ".DB 0xFD, 0xCB, 0xA5", "RES 4, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0xA7", /* 0xA0 */ + ".DB 0xFD, 0xCB, 0xA8", ".DB 0xFD, 0xCB, 0xA9", ".DB 0xFD, 0xCB, 0xAA", ".DB 0xFD, 0xCB, 0xAB", ".DB 0xFD, 0xCB, 0xAC", ".DB 0xFD, 0xCB, 0xAD", "RES 5, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0xAF", /* 0xA8 */ + ".DB 0xFD, 0xCB, 0xB0", ".DB 0xFD, 0xCB, 0xB1", ".DB 0xFD, 0xCB, 0xB2", ".DB 0xFD, 0xCB, 0xB3", ".DB 0xFD, 0xCB, 0xB4", ".DB 0xFD, 0xCB, 0xB5", "RES 6, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0xB7", /* 0xB0 */ + ".DB 0xFD, 0xCB, 0xB8", ".DB 0xFD, 0xCB, 0xB9", ".DB 0xFD, 0xCB, 0xBA", ".DB 0xFD, 0xCB, 0xBB", ".DB 0xFD, 0xCB, 0xBC", ".DB 0xFD, 0xCB, 0xBD", "RES 7, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0xBF", /* 0xB8 */ + ".DB 0xFD, 0xCB, 0xC0", ".DB 0xFD, 0xCB, 0xC1", ".DB 0xFD, 0xCB, 0xC2", ".DB 0xFD, 0xCB, 0xC3", ".DB 0xFD, 0xCB, 0xC4", ".DB 0xFD, 0xCB, 0xC5", "SET 0, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0xC7", /* 0xC0 */ + ".DB 0xFD, 0xCB, 0xC8", ".DB 0xFD, 0xCB, 0xC9", ".DB 0xFD, 0xCB, 0xCA", ".DB 0xFD, 0xCB, 0xCB", ".DB 0xFD, 0xCB, 0xCC", ".DB 0xFD, 0xCB, 0xCD", "SET 1, (IX+0x{0:X2})", ".DB 0xFD, 0xCB, 0xCF", /* 0xC8 */ + ".DB 0xFD, 0xDB, 0xD0", ".DB 0xFD, 0xDB, 0xD1", ".DB 0xFD, 0xDB, 0xD2", ".DB 0xFD, 0xDB, 0xD3", ".DB 0xFD, 0xDB, 0xD4", ".DB 0xFD, 0xDB, 0xD5", "SET 2, (IX+0x{0:X2})", ".DB 0xFD, 0xDB, 0xD7", /* 0xD0 */ + ".DB 0xFD, 0xDB, 0xD8", ".DB 0xFD, 0xDB, 0xD9", ".DB 0xFD, 0xDB, 0xDA", ".DB 0xFD, 0xDB, 0xDB", ".DB 0xFD, 0xDB, 0xDC", ".DB 0xFD, 0xDB, 0xFD", "SET 3, (IX+0x{0:X2})", ".DB 0xFD, 0xDB, 0xDF", /* 0xD8 */ + ".DB 0xFD, 0xEB, 0xE0", ".DB 0xFD, 0xEB, 0xE1", ".DB 0xFD, 0xEB, 0xE2", ".DB 0xFD, 0xEB, 0xE3", ".DB 0xFD, 0xEB, 0xE4", ".DB 0xFD, 0xEB, 0xE5", "SET 4, (IX+0x{0:X2})", ".DB 0xFD, 0xEB, 0xE7", /* 0xE0 */ + ".DB 0xFD, 0xEB, 0xE8", ".DB 0xFD, 0xEB, 0xE9", ".DB 0xFD, 0xEB, 0xEA", ".DB 0xFD, 0xEB, 0xEB", ".DB 0xFD, 0xEB, 0xEC", ".DB 0xFD, 0xEB, 0xED", "SET 5, (IX+0x{0:X2})", ".DB 0xFD, 0xEB, 0xEF", /* 0xE8 */ + ".DB 0xFD, 0xFB, 0xF0", ".DB 0xFD, 0xFB, 0xF1", ".DB 0xFD, 0xFB, 0xF2", ".DB 0xFD, 0xFB, 0xF3", ".DB 0xFD, 0xFB, 0xF4", ".DB 0xFD, 0xFB, 0xF5", "SET 6, (IX+0x{0:X2})", ".DB 0xFD, 0xFB, 0xF7", /* 0xF0 */ + ".DB 0xFD, 0xFB, 0xF8", ".DB 0xFD, 0xFB, 0xF9", ".DB 0xFD, 0xFB, 0xFA", ".DB 0xFD, 0xFB, 0xFB", ".DB 0xFD, 0xFB, 0xFC", ".DB 0xFD, 0xFB, 0xFD", "SET 7, (IX+0x{0:X2})", ".DB 0xFD, 0xFB, 0xFF" /* 0xF8 */ + }; + + static readonly int[] opcodeLengthPrefixDDFD = new int[] + { + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 4, 4, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 3, 3, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 3, 3, 3, 3, 3, 3, 2, 3, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 3, 2, 2, 2, 2, 2, 2, 2, 3, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, -1, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + }; + + static readonly int[] opcodeLengthPrefixDDFDCB = new int[] + { + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + }; + + public static string PrintRegisters(Z80A cpu) + { + return string.Format("AF:{0:X4} BC:{1:X4} DE:{2:X4} HL:{3:X4} IX:{4:X4} IY:{5:X4} SP:{6:X4}", cpu.af.Word, cpu.bc.Word, cpu.de.Word, cpu.hl.Word, cpu.ix.Word, cpu.iy.Word, cpu.sp); + } + + public static string PrintFlags(Z80A cpu) + { + return string.Format("[{7}{6}{5}{4}{3}{2}{1}{0}]", + cpu.IsFlagSet(Flags.Carry) ? "C" : "-", + cpu.IsFlagSet(Flags.Subtract) ? "N" : "-", + cpu.IsFlagSet(Flags.ParityOrOverflow) ? "P" : "-", + cpu.IsFlagSet(Flags.UnusedBitX) ? "X" : "-", + cpu.IsFlagSet(Flags.HalfCarry) ? "H" : "-", + cpu.IsFlagSet(Flags.UnusedBitY) ? "Y" : "-", + cpu.IsFlagSet(Flags.Zero) ? "Z" : "-", + cpu.IsFlagSet(Flags.Sign) ? "S" : "-"); + } + + public static string PrintInterrupt(Z80A cpu) + { + return string.Format("[IM{0} {1} {2} {3}]", cpu.im, (cpu.iff1 ? "EI" : "DI"), (cpu.halt ? "HLT" : "---"), (cpu.intState == InterruptState.Assert ? "ASR" : "---")); + } + + public static string DisassembleOpcode(Z80A cpu, ushort address) + { + byte[] opcode = DisassembleGetOpcodeBytes(cpu, address); + return string.Format("0x{0:X4} | {1} | {2}", address, DisassembleMakeByteString(cpu, opcode).PadRight(15), DisassembleMakeMnemonicString(cpu, opcode)); + } + + public static byte[] DisassembleGetOpcodeBytes(Z80A cpu, ushort address) + { + byte[] opcode = new byte[5]; + for (int i = 0; i < opcode.Length; i++) + opcode[i] = (address + i <= 0xFFFF ? cpu.ReadMemory8((ushort)(address + i)) : (byte)0); + return opcode; + } + + public static int DisassembleGetOpcodeLen(Z80A cpu, byte[] opcode) + { + switch (opcode[0]) + { + case 0xCB: return opcodeLength_CB[opcode[1]]; + case 0xED: return opcodeLengthPrefixED[opcode[1]]; + + case 0xDD: + case 0xFD: + if (opcode[1] == 0xCB) + return opcodeLengthPrefixDDFDCB[opcode[3]]; + else + return opcodeLengthPrefixDDFD[opcode[1]]; + + default: return opcodeLengthNoPrefix[opcode[0]]; + } + } + + public static string DisassembleMakeByteString(Z80A cpu, byte[] opcode) + { + return string.Join(" ", opcode.Select(x => string.Format("{0:X2}", x)).Take(DisassembleGetOpcodeLen(cpu, opcode))); + } + + public static string DisassembleMakeMnemonicString(Z80A cpu, byte[] opcode) + { + int len = DisassembleGetOpcodeLen(cpu, opcode); + + int start = 0; + string[] mnemonics = opcodeMnemonicNoPrefix; + bool isDDFDCB = false; + + switch (opcode[0]) + { + case 0xCB: start = 1; mnemonics = opcodeMnemonicPrefixCB; break; + case 0xED: start = 1; mnemonics = opcodeMnemonicPrefixED; break; + + case 0xDD: + if (opcode[1] == 0xCB) + { + mnemonics = opcodeMnemonicPrefixDDCB; + isDDFDCB = true; + } + else + { + start = 1; + mnemonics = opcodeMnemonicPrefixDD; + } + break; + + case 0xFD: + if (opcode[1] == 0xCB) + { + mnemonics = opcodeMnemonicPrefixFDCB; + isDDFDCB = true; + } + else + { + start = 1; + mnemonics = opcodeMnemonicPrefixFD; + } + break; + } + + if (mnemonics == null) return "(unimplemented)"; + + if (!isDDFDCB) + { + switch (len - start) + { + case 1: return mnemonics[opcode[start]]; + case 2: return string.Format(mnemonics[opcode[start]], opcode[start + 1]); + case 3: return string.Format(mnemonics[opcode[start]], (opcode[start + 2] << 8 | opcode[start + 1])); + default: return string.Empty; + } + } + else + { + return string.Format(mnemonics[opcode[3]], opcode[2]); + } + } + + private string MakeUnimplementedOpcodeString(string prefix, ushort address) + { + byte[] opcode = DisassembleGetOpcodeBytes(this, address); + return string.Format("Unimplemented {0}opcode {1} ({2})", (prefix != string.Empty ? prefix + " " : prefix), DisassembleMakeByteString(this, opcode), DisassembleMakeMnemonicString(this, opcode)); + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Disassembly.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Disassembly.cs.meta new file mode 100644 index 0000000..b8e260b --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Disassembly.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9fa152db63418e74ab7f359175172290 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesNoPrefix.cs b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesNoPrefix.cs new file mode 100644 index 0000000..3b65608 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesNoPrefix.cs @@ -0,0 +1,289 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.CPU +{ + public partial class Z80A + { + static SimpleOpcodeDelegate[] opcodesNoPrefix = new SimpleOpcodeDelegate[] + { + /* 0x00 */ + new SimpleOpcodeDelegate((c) => { /* NOP */ }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate16(ref c.bc.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.bc.Word, c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Increment16(ref c.bc.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftAccumulatorCircular(); }), + new SimpleOpcodeDelegate((c) => { c.ExchangeRegisters16(ref c.af, ref c.af_); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.bc.Word, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterFromMemory8(ref c.af.High, c.bc.Word, false); }), + new SimpleOpcodeDelegate((c) => { c.Decrement16(ref c.bc.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightAccumulatorCircular(); }), + /* 0x10 */ + new SimpleOpcodeDelegate((c) => { c.DecrementJumpNonZero(); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate16(ref c.de.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.de.Word, c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Increment16(ref c.de.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftAccumulator(); }), + new SimpleOpcodeDelegate((c) => { c.Jump8(); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.de.Word, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterFromMemory8(ref c.af.High, c.de.Word, false); }), + new SimpleOpcodeDelegate((c) => { c.Decrement16(ref c.de.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightAccumulator(); }), + /* 0x20 */ + new SimpleOpcodeDelegate((c) => { c.JumpConditional8(!c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate16(ref c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory16(c.ReadMemory16(c.pc), c.hl.Word); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Increment16(ref c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.DecimalAdjustAccumulator(); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional8(c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.hl.Word, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister16(ref c.hl.Word, c.ReadMemory16(c.ReadMemory16(c.pc))); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Decrement16(ref c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.af.High ^= 0xFF; c.SetFlag(Flags.Subtract | Flags.HalfCarry); c.SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(c.af.High, 5)); c.SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(c.af.High, 3)); }), + /* 0x30 */ + new SimpleOpcodeDelegate((c) => { c.JumpConditional8(!c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate16(ref c.sp); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.ReadMemory16(c.pc), c.af.High); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Increment16(ref c.sp); }), + new SimpleOpcodeDelegate((c) => { c.IncrementMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.DecrementMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.SetFlag(Flags.Carry); c.ClearFlag(Flags.Subtract | Flags.HalfCarry); c.SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(c.af.High, 5)); c.SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(c.af.High, 3)); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional8(c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.sp, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterFromMemory8(ref c.af.High, c.ReadMemory16(c.pc), false); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Decrement16(ref c.sp); }), + new SimpleOpcodeDelegate((c) => { c.Increment8(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Decrement8(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegisterImmediate8(ref c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.SetClearFlagConditional(Flags.HalfCarry, c.IsFlagSet(Flags.Carry)); c.SetClearFlagConditional(Flags.Carry, !c.IsFlagSet(Flags.Carry)); c.ClearFlag(Flags.Subtract); c.SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(c.af.High, 5)); c.SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(c.af.High, 3)); }), + /* 0x40 */ + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.bc.High = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.High, c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.bc.Low = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.bc.Low, c.af.High, false); }), + /* 0x50 */ + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.de.High = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.High, c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.de.Low = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.de.Low, c.af.High, false); }), + /* 0x60 */ + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.hl.High = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.High, c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.hl.Low = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.hl.Low, c.af.High, false); }), + /* 0x70 */ + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.EnterHaltState(); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory8(c.hl.Word, c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.af.High = c.ReadMemory8(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.af.High, false); }), + /* 0x80 */ + new SimpleOpcodeDelegate((c) => { c.Add8(c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.ReadMemory8(c.hl.Word), false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.bc.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.bc.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.de.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.de.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.hl.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.hl.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.ReadMemory8(c.hl.Word), true); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.af.High, true); }), + /* 0x90 */ + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.bc.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.bc.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.de.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.de.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.hl.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.hl.Low, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.ReadMemory8(c.hl.Word), false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.af.High, false); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.bc.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.bc.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.de.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.de.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.hl.High, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.hl.Low, true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.ReadMemory8(c.hl.Word), true); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.af.High, true); }), + /* 0xA0 */ + new SimpleOpcodeDelegate((c) => { c.And8(c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.ReadMemory8(c.hl.Word)); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.ReadMemory8(c.hl.Word)); }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.af.High); }), + /* 0xB0 */ + new SimpleOpcodeDelegate((c) => { c.Or8(c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.ReadMemory8(c.hl.Word)); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.ReadMemory8(c.hl.Word)); }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.af.High); }), + /* 0xC0 */ + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(!c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.Pop(ref c.bc); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(!c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(true); }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(!c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.Push(c.bc); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.ReadMemory8(c.pc++), false); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0000); }), + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.Return(); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { /* CB - handled elsewhere */ }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(c.IsFlagSet(Flags.Zero)); }), + new SimpleOpcodeDelegate((c) => { c.Call16(); }), + new SimpleOpcodeDelegate((c) => { c.Add8(c.ReadMemory8(c.pc++), true); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0008); }), + /* 0xD0 */ + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(!c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.Pop(ref c.de); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(!c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.WritePort(c.ReadMemory8(c.pc++), c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(!c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.Push(c.de); }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.ReadMemory8(c.pc++), false); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0010); }), + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.ExchangeRegisters16(ref c.bc, ref c.bc_); c.ExchangeRegisters16(ref c.de, ref c.de_); c.ExchangeRegisters16(ref c.hl, ref c.hl_); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { c.af.High = c.ReadPort(c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(c.IsFlagSet(Flags.Carry)); }), + new SimpleOpcodeDelegate((c) => { /* DD - handled elsewhere */ }), + new SimpleOpcodeDelegate((c) => { c.Subtract8(c.ReadMemory8(c.pc++), true); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0018); }), + /* 0xE0 */ + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(!c.IsFlagSet(Flags.ParityOrOverflow)); }), + new SimpleOpcodeDelegate((c) => { c.Pop(ref c.hl); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(!c.IsFlagSet(Flags.ParityOrOverflow)); }), + new SimpleOpcodeDelegate((c) => { c.ExchangeStackRegister16(ref c.hl); }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(!c.IsFlagSet(Flags.ParityOrOverflow)); }), + new SimpleOpcodeDelegate((c) => { c.Push(c.hl); }), + new SimpleOpcodeDelegate((c) => { c.And8(c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0020); }), + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(c.IsFlagSet(Flags.ParityOrOverflow)); }), + new SimpleOpcodeDelegate((c) => { c.pc = c.hl.Word; }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(c.IsFlagSet(Flags.ParityOrOverflow)); }), + new SimpleOpcodeDelegate((c) => { c.ExchangeRegisters16(ref c.de, ref c.hl); }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(c.IsFlagSet(Flags.ParityOrOverflow)); }), + new SimpleOpcodeDelegate((c) => { /* ED - handled elsewhere */ }), + new SimpleOpcodeDelegate((c) => { c.Xor8(c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0028); }), + /* 0xF0 */ + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(!c.IsFlagSet(Flags.Sign)); }), + new SimpleOpcodeDelegate((c) => { c.Pop(ref c.af); }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(!c.IsFlagSet(Flags.Sign)); }), + new SimpleOpcodeDelegate((c) => { c.iff1 = c.iff2 = false; }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(!c.IsFlagSet(Flags.Sign)); }), + new SimpleOpcodeDelegate((c) => { c.Push(c.af); }), + new SimpleOpcodeDelegate((c) => { c.Or8(c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0030); }), + new SimpleOpcodeDelegate((c) => { c.ReturnConditional(c.IsFlagSet(Flags.Sign)); }), + new SimpleOpcodeDelegate((c) => { c.sp = c.hl.Word; }), + new SimpleOpcodeDelegate((c) => { c.JumpConditional16(c.IsFlagSet(Flags.Sign)); }), + new SimpleOpcodeDelegate((c) => { c.eiDelay = true; }), + new SimpleOpcodeDelegate((c) => { c.CallConditional16(c.IsFlagSet(Flags.Sign)); }), + new SimpleOpcodeDelegate((c) => { /* FD - handled elsewhere */ }), + new SimpleOpcodeDelegate((c) => { c.Cp8(c.ReadMemory8(c.pc++)); }), + new SimpleOpcodeDelegate((c) => { c.Restart(0x0038); }) + }; + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesNoPrefix.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesNoPrefix.cs.meta new file mode 100644 index 0000000..5088845 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesNoPrefix.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a8c690c6cf2d024418c1412af53e453f \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixCB.cs b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixCB.cs new file mode 100644 index 0000000..f55b5d9 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixCB.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public partial class Z80A + { + static SimpleOpcodeDelegate[] opcodesPrefixCB = new SimpleOpcodeDelegate[] + { + /* 0x00 */ + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeftCircular(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.RotateRightCircular(ref c.af.High); }), + /* 0x10 */ + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.RotateRight(ref c.af.High); }), + /* 0x20 */ + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftArithmetic(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightArithmetic(ref c.af.High); }), + /* 0x30 */ + new SimpleOpcodeDelegate((c) => { c.ShiftLeftLogical(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftLogical(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftLogical(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftLogical(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftLogical(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftLogical(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftLogical(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.ShiftLeftLogical(ref c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(c.hl.Word); }), + new SimpleOpcodeDelegate((c) => { c.ShiftRightLogical(ref c.af.High); }), + /* 0x40 */ + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 1); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 1); }), + /* 0x50 */ + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 3); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 3); }), + /* 0x60 */ + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 5); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 5); }), + /* 0x70 */ + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.bc.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.de.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.hl.Word, 7); }), + new SimpleOpcodeDelegate((c) => { c.TestBit(c.af.High, 7); }), + /* 0x80 */ + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 1); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 1); }), + /* 0x90 */ + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 3); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 3); }), + /* 0xA0 */ + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 5); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 5); }), + /* 0xB0 */ + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.bc.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.de.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.hl.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(c.hl.Word, 7); }), + new SimpleOpcodeDelegate((c) => { c.ResetBit(ref c.af.High, 7); }), + /* 0xC0 */ + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 0); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 1); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 1); }), + /* 0xD0 */ + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 2); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 3); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 3); }), + /* 0xE0 */ + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 4); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 5); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 5); }), + /* 0xF0 */ + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 6); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.bc.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.de.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.High, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.hl.Low, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(c.hl.Word, 7); }), + new SimpleOpcodeDelegate((c) => { c.SetBit(ref c.af.High, 7); }), + }; + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixCB.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixCB.cs.meta new file mode 100644 index 0000000..8da79c8 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixCB.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1eaf728772256b548a5dd0f76aac95a8 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs new file mode 100644 index 0000000..a8c2efd --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public partial class Z80A + { + static DDFDOpcodeDelegate[] opcodesPrefixDDFD = new DDFDOpcodeDelegate[] + { + /* 0x00 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { /* NOP */ }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterImmediate16(ref c.bc.Word); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.bc.Word, c.af.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment16(ref c.bc.Word); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment8(ref c.bc.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement8(ref c.bc.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterImmediate8(ref c.bc.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.RotateLeftAccumulatorCircular(); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ExchangeRegisters16(ref c.af, ref c.af_); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add16(ref r, c.bc.Word, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterFromMemory8(ref c.af.High, c.bc.Word, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement16(ref c.bc.Word); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment8(ref c.bc.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement8(ref c.bc.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterImmediate8(ref c.bc.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.RotateRightAccumulatorCircular(); }), + /* 0x10 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.DecrementJumpNonZero(); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterImmediate16(ref c.de.Word); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.de.Word, c.af.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment16(ref c.de.Word); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment8(ref c.de.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement8(ref c.de.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterImmediate8(ref c.de.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.RotateLeftAccumulator(); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Jump8(); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add16(ref r, c.de.Word, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterFromMemory8(ref c.af.High, c.de.Word, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement16(ref c.de.Word); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment8(ref c.de.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement8(ref c.de.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterImmediate8(ref c.de.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.RotateRightAccumulator(); }), + /* 0x20 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional8(!c.IsFlagSet(Flags.Zero)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterImmediate16(ref r.Word); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory16(c.ReadMemory16(c.pc), r.Word); c.pc += 2; }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment16(ref r.Word); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment8(ref r.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement8(ref r.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { r.High = c.ReadMemory8(c.pc++); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.DecimalAdjustAccumulator(); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional8(c.IsFlagSet(Flags.Zero)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add16(ref r, r.Word, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister16(ref r.Word, c.ReadMemory16(c.ReadMemory16(c.pc))); c.pc += 2; }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement16(ref r.Word); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment8(ref r.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement8(ref r.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { r.Low = c.ReadMemory8(c.pc++); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.af.High ^= 0xFF; c.SetFlag(Flags.Subtract | Flags.HalfCarry); }), + /* 0x30 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional8(!c.IsFlagSet(Flags.Carry)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterImmediate16(ref c.sp); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.ReadMemory16(c.pc), c.af.High); c.pc += 2; }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment16(ref c.sp); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.IncrementMemory8(c.CalculateIXIYAddress(r)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.DecrementMemory8(c.CalculateIXIYAddress(r)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.CalculateIXIYAddress(r), c.ReadMemory8(c.pc++)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.SetFlag(Flags.Carry); c.ClearFlag(Flags.Subtract | Flags.HalfCarry); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional8(c.IsFlagSet(Flags.Carry)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add16(ref r, c.sp, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterFromMemory8(ref c.af.High, c.ReadMemory16(c.pc), false); c.pc += 2; }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement16(ref c.sp); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Increment8(ref c.af.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Decrement8(ref c.af.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegisterImmediate8(ref c.af.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ClearFlag(Flags.Subtract); c.SetClearFlagConditional(Flags.Carry, !c.IsFlagSet(Flags.Carry)); }), + /* 0x40 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.High, c.bc.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.High, c.bc.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.High, c.de.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.High, c.de.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.High, r.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.High, r.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.bc.High = c.ReadMemory8(c.CalculateIXIYAddress(r)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.High, c.af.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.Low, c.bc.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.Low, c.bc.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.Low, c.de.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.Low, c.de.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.Low, r.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.Low, r.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.bc.Low = c.ReadMemory8(c.CalculateIXIYAddress(r)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.bc.Low, c.af.High, false); }), + /* 0x50 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.High, c.bc.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.High, c.bc.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.High, c.de.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.High, c.de.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.High, r.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.High, r.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.de.High = c.ReadMemory8(c.CalculateIXIYAddress(r)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.High, c.af.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.Low, c.bc.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.Low, c.bc.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.Low, c.de.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.Low, c.de.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.Low, r.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.Low, r.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.de.Low = c.ReadMemory8(c.CalculateIXIYAddress(r)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.de.Low, c.af.High, false); }), + /* 0x60 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.High, c.bc.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.High, c.bc.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.High, c.de.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.High, c.de.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.High, r.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.High, r.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.hl.High = c.ReadMemory8(c.CalculateIXIYAddress(r)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.High, c.af.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.Low, c.bc.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.Low, c.bc.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.Low, c.de.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.Low, c.de.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.Low, r.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.Low, r.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.hl.Low = c.ReadMemory8(c.CalculateIXIYAddress(r)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref r.Low, c.af.High, false); }), + /* 0x70 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.CalculateIXIYAddress(r), c.bc.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.CalculateIXIYAddress(r), c.bc.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.CalculateIXIYAddress(r), c.de.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.CalculateIXIYAddress(r), c.de.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.CalculateIXIYAddress(r), c.hl.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.CalculateIXIYAddress(r), c.hl.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.EnterHaltState(); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadMemory8(c.CalculateIXIYAddress(r), c.af.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.af.High, c.bc.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.af.High, c.bc.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.af.High, c.de.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.af.High, c.de.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.af.High, r.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.af.High, r.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.af.High = c.ReadMemory8(c.CalculateIXIYAddress(r)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.LoadRegister8(ref c.af.High, c.af.High, false); }), + /* 0x80 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.bc.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.bc.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.de.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.de.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(r.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(r.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.ReadMemory8(c.CalculateIXIYAddress(r)), false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.af.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.bc.High, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.bc.Low, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.de.High, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.de.Low, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(r.High, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(r.Low, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.ReadMemory8(c.CalculateIXIYAddress(r)), true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.af.High, true); }), + /* 0x90 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.bc.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.bc.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.de.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.de.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(r.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(r.Low, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.ReadMemory8(c.CalculateIXIYAddress(r)), false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.af.High, false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.bc.High, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.bc.Low, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.de.High, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.de.Low, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(r.High, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(r.Low, true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.ReadMemory8(c.CalculateIXIYAddress(r)), true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.af.High, true); }), + /* 0xA0 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.And8(c.bc.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.And8(c.bc.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.And8(c.de.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.And8(c.de.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.And8(r.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.And8(r.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.And8(c.ReadMemory8(c.CalculateIXIYAddress(r))); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.And8(c.af.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Xor8(c.bc.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Xor8(c.bc.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Xor8(c.de.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Xor8(c.de.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Xor8(r.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Xor8(r.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Xor8(c.ReadMemory8(c.CalculateIXIYAddress(r))); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Xor8(c.af.High); }), + /* 0xB0 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Or8(c.bc.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Or8(c.bc.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Or8(c.de.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Or8(c.de.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Or8(r.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Or8(r.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Or8(c.ReadMemory8(c.CalculateIXIYAddress(r))); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Or8(c.af.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Cp8(c.bc.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Cp8(c.bc.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Cp8(c.de.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Cp8(c.de.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Cp8(r.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Cp8(r.Low); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Cp8(c.ReadMemory8(c.CalculateIXIYAddress(r))); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Cp8(c.af.High); }), + /* 0xC0 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ReturnConditional(!c.IsFlagSet(Flags.Zero)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Pop(ref c.bc); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional16(!c.IsFlagSet(Flags.Zero)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional16(true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.CallConditional16(!c.IsFlagSet(Flags.Zero)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Push(c.bc); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.ReadMemory8(c.pc++), false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Restart(0x0000); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ReturnConditional(c.IsFlagSet(Flags.Zero)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Return(); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional16(c.IsFlagSet(Flags.Zero)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ExecuteOpDDFDCB(c.ReadMemory8((ushort)(c.pc + 1)), ref r); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.CallConditional16(c.IsFlagSet(Flags.Zero)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Call16(); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Add8(c.ReadMemory8(c.pc++), true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Restart(0x0008); }), + /* 0xD0 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ReturnConditional(!c.IsFlagSet(Flags.Carry)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Pop(ref c.de); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional16(!c.IsFlagSet(Flags.Carry)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.WritePort(c.ReadMemory8(c.pc++), c.af.High); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.CallConditional16(!c.IsFlagSet(Flags.Carry)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Push(c.de); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.ReadMemory8(c.pc++), false); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Restart(0x0010); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ReturnConditional(c.IsFlagSet(Flags.Carry)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ExchangeRegisters16(ref c.bc, ref c.bc_); c.ExchangeRegisters16(ref c.de, ref c.de_); c.ExchangeRegisters16(ref c.hl, ref c.hl_); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional16(c.IsFlagSet(Flags.Carry)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.af.High = c.ReadPort(c.ReadMemory8(c.pc++)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.CallConditional16(c.IsFlagSet(Flags.Carry)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { /* DD - treat as NOP */ }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Subtract8(c.ReadMemory8(c.pc++), true); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Restart(0x0018); }), + /* 0xE0 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ReturnConditional(!c.IsFlagSet(Flags.ParityOrOverflow)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Pop(ref r); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional16(!c.IsFlagSet(Flags.ParityOrOverflow)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ExchangeStackRegister16(ref r); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.CallConditional16(!c.IsFlagSet(Flags.ParityOrOverflow)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Push(r); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.And8(c.ReadMemory8(c.pc++)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Restart(0x0020); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ReturnConditional(c.IsFlagSet(Flags.ParityOrOverflow)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.pc = r.Word; }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional16(c.IsFlagSet(Flags.ParityOrOverflow)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ExchangeRegisters16(ref c.de, ref c.hl); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.CallConditional16(c.IsFlagSet(Flags.ParityOrOverflow)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { /* ED - treat as NOP */ }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Xor8(c.ReadMemory8(c.pc++)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Restart(0x0028); }), + /* 0xF0 */ + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ReturnConditional(!c.IsFlagSet(Flags.Sign)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Pop(ref c.af); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional16(!c.IsFlagSet(Flags.Sign)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.iff1 = c.iff2 = false; }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.CallConditional16(!c.IsFlagSet(Flags.Sign)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Push(c.af); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Or8(c.ReadMemory8(c.pc++)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Restart(0x0030); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.ReturnConditional(c.IsFlagSet(Flags.Sign)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.sp = r.Word; }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.JumpConditional16(c.IsFlagSet(Flags.Sign)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.eiDelay = true; }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.CallConditional16(c.IsFlagSet(Flags.Sign)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { /* FD - treat as NOP */ }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Cp8(c.ReadMemory8(c.pc++)); }), + new DDFDOpcodeDelegate((Z80A c, ref Register r) => { c.Restart(0x0038); }) + }; + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs.meta new file mode 100644 index 0000000..6aa7faf --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 59243a6d2ecd14d41816ff23fd5204cb \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs new file mode 100644 index 0000000..7bcc066 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public partial class Z80A + { + static DDFDCBOpcodeDelegate[] opcodesPrefixDDFDCB = new DDFDCBOpcodeDelegate[] + { + /* 0x00 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.RotateLeftCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.RotateLeftCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.RotateLeftCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.RotateLeftCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.RotateLeftCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.RotateLeftCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.RotateLeftCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.RotateLeftCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.RotateRightCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.RotateRightCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.RotateRightCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.RotateRightCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.RotateRightCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.RotateRightCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.RotateRightCircular(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.RotateRightCircular(address); }), + /* 0x10 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.RotateLeft(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.RotateLeft(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.RotateLeft(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.RotateLeft(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.RotateLeft(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.RotateLeft(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.RotateLeft(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.RotateLeft(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.RotateRight(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.RotateRight(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.RotateRight(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.RotateRight(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.RotateRight(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.RotateRight(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.RotateRight(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.RotateRight(address); }), + /* 0x20 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ShiftLeftArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ShiftLeftArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ShiftLeftArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ShiftLeftArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ShiftLeftArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ShiftLeftArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ShiftLeftArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ShiftLeftArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ShiftRightArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ShiftRightArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ShiftRightArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ShiftRightArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ShiftRightArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ShiftRightArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ShiftRightArithmetic(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ShiftRightArithmetic(address); }), + /* 0x30 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ShiftLeftLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ShiftLeftLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ShiftLeftLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ShiftLeftLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ShiftLeftLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ShiftLeftLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ShiftLeftLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ShiftLeftLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ShiftRightLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ShiftRightLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ShiftRightLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ShiftRightLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ShiftRightLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ShiftRightLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ShiftRightLogical(address); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ShiftRightLogical(address); }), + /* 0x40 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 1); }), + /* 0x50 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 3); }), + /* 0x60 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 5); }), + /* 0x70 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.TestBit(address, 7); }), + /* 0x80 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ResetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ResetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ResetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ResetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ResetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ResetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ResetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ResetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ResetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ResetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ResetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ResetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ResetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ResetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ResetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ResetBit(address, 1); }), + /* 0x90 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ResetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ResetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ResetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ResetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ResetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ResetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ResetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ResetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ResetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ResetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ResetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ResetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ResetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ResetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ResetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ResetBit(address, 3); }), + /* 0xA0 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ResetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ResetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ResetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ResetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ResetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ResetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ResetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ResetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ResetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ResetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ResetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ResetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ResetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ResetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ResetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ResetBit(address, 5); }), + /* 0xB0 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ResetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ResetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ResetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ResetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ResetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ResetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ResetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ResetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.ResetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.ResetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.ResetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.ResetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.ResetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.ResetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.ResetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.ResetBit(address, 7); }), + /* 0xC0 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.SetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.SetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.SetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.SetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.SetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.SetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.SetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.SetBit(address, 0); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.SetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.SetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.SetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.SetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.SetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.SetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.SetBit(address, 1); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.SetBit(address, 1); }), + /* 0xD0 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.SetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.SetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.SetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.SetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.SetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.SetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.SetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.SetBit(address, 2); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.SetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.SetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.SetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.SetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.SetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.SetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.SetBit(address, 3); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.SetBit(address, 3); }), + /* 0xE0 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.SetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.SetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.SetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.SetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.SetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.SetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.SetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.SetBit(address, 4); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.SetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.SetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.SetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.SetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.SetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.SetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.SetBit(address, 5); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.SetBit(address, 5); }), + /* 0xF0 */ + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.SetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.SetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.SetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.SetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.SetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.SetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.SetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.SetBit(address, 6); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.High = c.SetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.bc.Low = c.SetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.High = c.SetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.de.Low = c.SetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.High = c.SetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.hl.Low = c.SetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.SetBit(address, 7); }), + new DDFDCBOpcodeDelegate((Z80A c, ref Register r, ushort address) => { c.af.High = c.SetBit(address, 7); }), + }; + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs.meta new file mode 100644 index 0000000..7e9f34d --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9f1c07a0773a3a94486e1d50ed7552cb \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixED.cs b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixED.cs new file mode 100644 index 0000000..787c8a8 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixED.cs @@ -0,0 +1,287 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.CPU +{ + public partial class Z80A + { + static SimpleOpcodeDelegate[] opcodesPrefixED = new SimpleOpcodeDelegate[] + { + /* 0x00 */ + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + /* 0x10 */ + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + /* 0x20 */ + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + /* 0x30 */ + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + /* 0x40 */ + new SimpleOpcodeDelegate((c) => { c.PortInput(ref c.bc.High, c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.WritePort(c.bc.Low, c.bc.High); }), + new SimpleOpcodeDelegate((c) => { c.Subtract16(ref c.hl, c.bc.Word, true); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory16(c.ReadMemory16(c.pc), c.bc.Word); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Negate(); }), + new SimpleOpcodeDelegate((c) => { c.iff1 = c.iff2; c.Return(); }), + new SimpleOpcodeDelegate((c) => { c.im = 0; }), + new SimpleOpcodeDelegate((c) => { c.i = c.af.High; }), + new SimpleOpcodeDelegate((c) => { c.PortInput(ref c.bc.Low, c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.WritePort(c.bc.Low, c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.bc.Word, true); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister16(ref c.bc.Word, c.ReadMemory16(c.ReadMemory16(c.pc))); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Negate(); }), + new SimpleOpcodeDelegate((c) => { c.Return(); c.iff1 = c.iff2; }), + new SimpleOpcodeDelegate((c) => { c.im = 0; }), + new SimpleOpcodeDelegate((c) => { c.r = c.af.High; }), + /* 0x50 */ + new SimpleOpcodeDelegate((c) => { c.PortInput(ref c.de.High, c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.WritePort(c.bc.Low, c.de.High); }), + new SimpleOpcodeDelegate((c) => { c.Subtract16(ref c.hl, c.de.Word, true); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory16(c.ReadMemory16(c.pc), c.de.Word); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Negate(); }), + new SimpleOpcodeDelegate((c) => { c.iff1 = c.iff2; c.Return(); }), + new SimpleOpcodeDelegate((c) => { c.im = 1; }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.i, true); }), + new SimpleOpcodeDelegate((c) => { c.PortInput(ref c.de.Low, c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.WritePort(c.bc.Low, c.de.Low); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.de.Word, true); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister16(ref c.de.Word, c.ReadMemory16(c.ReadMemory16(c.pc))); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Negate(); }), + new SimpleOpcodeDelegate((c) => { c.iff1 = c.iff2; c.Return(); }), + new SimpleOpcodeDelegate((c) => { c.im = 2; }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister8(ref c.af.High, c.r, true); }), + /* 0x60 */ + new SimpleOpcodeDelegate((c) => { c.PortInput(ref c.hl.High, c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.WritePort(c.bc.Low, c.hl.High); }), + new SimpleOpcodeDelegate((c) => { c.Subtract16(ref c.hl, c.hl.Word, true); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory16(c.ReadMemory16(c.pc), c.hl.Word); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Negate(); }), + new SimpleOpcodeDelegate((c) => { c.iff1 = c.iff2; c.Return(); }), + new SimpleOpcodeDelegate((c) => { c.im = 0; }), + new SimpleOpcodeDelegate((c) => { c.RotateRight4B(); }), + new SimpleOpcodeDelegate((c) => { c.PortInput(ref c.hl.Low, c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.WritePort(c.bc.Low, c.hl.Low); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.hl.Word, true); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister16(ref c.hl.Word, c.ReadMemory16(c.ReadMemory16(c.pc))); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Negate(); }), + new SimpleOpcodeDelegate((c) => { c.iff1 = c.iff2; c.Return(); }), + new SimpleOpcodeDelegate((c) => { c.im = 0; }), + new SimpleOpcodeDelegate((c) => { c.RotateLeft4B(); }), + /* 0x70 */ + new SimpleOpcodeDelegate((c) => { c.PortInputFlagsOnly(c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.WritePort(c.bc.Low, 0x00); }), + new SimpleOpcodeDelegate((c) => { c.Subtract16(ref c.hl, c.sp, true); }), + new SimpleOpcodeDelegate((c) => { c.LoadMemory16(c.ReadMemory16(c.pc), c.sp); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Negate(); }), + new SimpleOpcodeDelegate((c) => { c.iff1 = c.iff2; c.Return(); }), + new SimpleOpcodeDelegate((c) => { c.im = 1; }), + new SimpleOpcodeDelegate((c) => { /* NOP */ }), + new SimpleOpcodeDelegate((c) => { c.PortInput(ref c.af.High, c.bc.Low); }), + new SimpleOpcodeDelegate((c) => { c.WritePort(c.bc.Low, c.af.High); }), + new SimpleOpcodeDelegate((c) => { c.Add16(ref c.hl, c.sp, true); }), + new SimpleOpcodeDelegate((c) => { c.LoadRegister16(ref c.sp, c.ReadMemory16(c.ReadMemory16(c.pc))); c.pc += 2; }), + new SimpleOpcodeDelegate((c) => { c.Negate(); }), + new SimpleOpcodeDelegate((c) => { c.iff1 = c.iff2; c.Return(); }), + new SimpleOpcodeDelegate((c) => { c.im = 2; }), + new SimpleOpcodeDelegate((c) => { /* NOP */ }), + /* 0x80 */ + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + /* 0x90 */ + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + /* 0xA0 */ + new SimpleOpcodeDelegate((c) => { c.LoadIncrement(); }), + new SimpleOpcodeDelegate((c) => { c.CompareIncrement(); }), + new SimpleOpcodeDelegate((c) => { c.PortInputIncrement(); }), + new SimpleOpcodeDelegate((c) => { c.PortOutputIncrement(); }), + new SimpleOpcodeDelegate((c) => { /* A4 - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* A5 - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* A6 - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* A7 - nothing */ }), + new SimpleOpcodeDelegate((c) => { c.LoadDecrement(); }), + new SimpleOpcodeDelegate((c) => { c.CompareDecrement(); }), + new SimpleOpcodeDelegate((c) => { c.PortInputDecrement(); }), + new SimpleOpcodeDelegate((c) => { c.PortOutputDecrement(); }), + new SimpleOpcodeDelegate((c) => { /* AC - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* AD - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* AE - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* AF - nothing */ }), + /* 0xB0 */ + new SimpleOpcodeDelegate((c) => { c.LoadIncrementRepeat(); }), + new SimpleOpcodeDelegate((c) => { c.CompareIncrementRepeat(); }), + new SimpleOpcodeDelegate((c) => { c.PortInputIncrementRepeat(); }), + new SimpleOpcodeDelegate((c) => { c.PortOutputIncrementRepeat(); }), + new SimpleOpcodeDelegate((c) => { /* B4 - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* B5 - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* B6 - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* B7 - nothing */ }), + new SimpleOpcodeDelegate((c) => { c.LoadDecrementRepeat(); }), + new SimpleOpcodeDelegate((c) => { c.CompareDecrementRepeat(); }), + new SimpleOpcodeDelegate((c) => { c.PortInputDecrementRepeat(); }), + new SimpleOpcodeDelegate((c) => { c.PortOutputDecrementRepeat(); }), + new SimpleOpcodeDelegate((c) => { /* BC - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* BD - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* BE - nothing */ }), + new SimpleOpcodeDelegate((c) => { /* BF - nothing */ }), + /* 0xC0 */ + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + /* 0xD0 */ + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + /* 0xE0 */ + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + /* 0xF0 */ + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }), + new SimpleOpcodeDelegate((c) => { /* NOP (2x) */ }) + }; + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixED.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixED.cs.meta new file mode 100644 index 0000000..d58a23f --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.OpcodesPrefixED.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5a8dae171d8e67546b93ab06aafbcb53 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Register.cs b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Register.cs new file mode 100644 index 0000000..e82820c --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Register.cs @@ -0,0 +1,25 @@ +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace Essgee.Emulation.CPU +{ + public partial class Z80A + { + [DebuggerDisplay("{Word}")] + [StructLayout(LayoutKind.Explicit)] + [Serializable] + public struct Register + { + [NonSerialized] + [FieldOffset(0)] + public byte Low; + [NonSerialized] + [FieldOffset(1)] + public byte High; + + [FieldOffset(0)] + public ushort Word; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Register.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Register.cs.meta new file mode 100644 index 0000000..37eb995 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.Register.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 09764bf46e9451741a9fa2016022c5ea \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.cs b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.cs new file mode 100644 index 0000000..4e00d1d --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.cs @@ -0,0 +1,1504 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.Utilities; + +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.CPU +{ + public partial class Z80A : ICPU + { + [Flags] + enum Flags : byte + { + Carry = (1 << 0), /* C */ + Subtract = (1 << 1), /* N */ + ParityOrOverflow = (1 << 2), /* P */ + UnusedBitX = (1 << 3), /* (X) */ + HalfCarry = (1 << 4), /* H */ + UnusedBitY = (1 << 5), /* (Y) */ + Zero = (1 << 6), /* Z */ + Sign = (1 << 7) /* S */ + } + + public delegate byte MemoryReadDelegate(ushort address); + public delegate void MemoryWriteDelegate(ushort address, byte value); + public delegate byte PortReadDelegate(byte port); + public delegate void PortWriteDelegate(byte port, byte value); + + delegate void SimpleOpcodeDelegate(Z80A c); + delegate void DDFDOpcodeDelegate(Z80A c, ref Register register); + delegate void DDFDCBOpcodeDelegate(Z80A c, ref Register register, ushort address); + + MemoryReadDelegate memoryReadDelegate; + MemoryWriteDelegate memoryWriteDelegate; + PortReadDelegate portReadDelegate; + PortWriteDelegate portWriteDelegate; + + [StateRequired] + protected Register af, bc, de, hl; + [StateRequired] + protected Register af_, bc_, de_, hl_; + [StateRequired] + protected Register ix, iy; + [StateRequired] + protected byte i, r; + [StateRequired] + protected ushort sp, pc; + + [StateRequired] + protected bool iff1, iff2, eiDelay, halt; + [StateRequired] + protected byte im; + + [StateRequired] + protected byte op; + + [StateRequired] + InterruptState intState, nmiState; + + [StateRequired] + int currentCycles; + + public Z80A(MemoryReadDelegate memoryRead, MemoryWriteDelegate memoryWrite, PortReadDelegate portRead, PortWriteDelegate portWrite) + { + af = bc = de = hl = new Register(); + af_ = bc_ = de_ = hl_ = new Register(); + ix = iy = new Register(); + + memoryReadDelegate = memoryRead; + memoryWriteDelegate = memoryWrite; + portReadDelegate = portRead; + portWriteDelegate = portWrite; + } + + public virtual void Startup() + { + Reset(); + + if (memoryReadDelegate == null) throw new EmulationException("Z80A: Memory read method is null"); + if (memoryWriteDelegate == null) throw new EmulationException("Z80A: Memory write method is null"); + if (portReadDelegate == null) throw new EmulationException("Z80A: Port read method is null"); + if (portWriteDelegate == null) throw new EmulationException("Z80A: Port write method is null"); + } + + public virtual void Shutdown() + { + // + } + + public virtual void Reset() + { + af.Word = bc.Word = de.Word = hl.Word = 0; + af_.Word = bc_.Word = de_.Word = hl_.Word = 0; + ix.Word = iy.Word = 0; + i = r = 0; + pc = 0; + sp = 0; + + iff1 = iff2 = eiDelay = halt = false; + im = 0; + + intState = nmiState = InterruptState.Clear; + + currentCycles = 0; + } + + public int Step() + { + currentCycles = 0; + + /* Handle delayed interrupt enable */ + if (eiDelay) + { + eiDelay = false; + iff1 = iff2 = true; + } + else + { + /* Check INT line */ + if (intState == InterruptState.Assert) + { + ServiceInterrupt(); + } + + /* Check NMI line */ + if (nmiState == InterruptState.Assert) + { + nmiState = InterruptState.Clear; + ServiceNonMaskableInterrupt(); + } + } + + + if (AppEnvironment.EnableSuperSlowCPULogger) + { + string disasm = string.Format("{0} | {1} | {2} | {3}\n", DisassembleOpcode(this, pc).PadRight(48), PrintRegisters(this), PrintFlags(this), PrintInterrupt(this)); + System.IO.File.AppendAllText(@"D:\Temp\Essgee\log.txt", disasm); + } + + /* Fetch and execute opcode */ + op = ReadMemory8(pc++); + switch (op) + { + case 0xCB: ExecuteOpCB(); break; + case 0xDD: ExecuteOpDD(); break; + case 0xED: ExecuteOpED(); break; + case 0xFD: ExecuteOpFD(); break; + default: ExecuteOpcodeNoPrefix(op); break; + } + + return currentCycles; + } + + #region Opcode Execution and Cycle Management + + private void ExecuteOpcodeNoPrefix(byte op) + { + IncrementRefresh(); + opcodesNoPrefix[op](this); + + currentCycles += CycleCounts.NoPrefix[op]; + } + + private void ExecuteOpED() + { + IncrementRefresh(); + byte edOp = ReadMemory8(pc++); + + IncrementRefresh(); + opcodesPrefixED[edOp](this); + + currentCycles += CycleCounts.PrefixED[edOp]; + } + + private void ExecuteOpCB() + { + IncrementRefresh(); + byte cbOp = ReadMemory8(pc++); + + IncrementRefresh(); + opcodesPrefixCB[cbOp](this); + + currentCycles += CycleCounts.PrefixCB[cbOp]; + } + + private void ExecuteOpDD() + { + IncrementRefresh(); + byte ddOp = ReadMemory8(pc++); + + if (ddOp != 0xDD) + { + IncrementRefresh(); + opcodesPrefixDDFD[ddOp](this, ref ix); + } + + currentCycles += (CycleCounts.PrefixDDFD[ddOp] != 0 ? CycleCounts.PrefixDDFD[ddOp] : CycleCounts.NoPrefix[ddOp] + CycleCounts.AdditionalDDFDOps); + } + + private void ExecuteOpFD() + { + IncrementRefresh(); + byte fdOp = ReadMemory8(pc++); + + if (fdOp != 0xFD) + { + IncrementRefresh(); + opcodesPrefixDDFD[fdOp](this, ref iy); + } + + currentCycles += (CycleCounts.PrefixDDFD[fdOp] != 0 ? CycleCounts.PrefixDDFD[fdOp] : CycleCounts.NoPrefix[fdOp] + CycleCounts.AdditionalDDFDOps); + } + + private void ExecuteOpDDFDCB(byte op, ref Register register) + { + IncrementRefresh(); + sbyte operand = (sbyte)ReadMemory8(pc); + ushort address = (ushort)(register.Word + operand); + pc += 2; + + IncrementRefresh(); + opcodesPrefixDDFDCB[op](this, ref register, address); + + currentCycles += (CycleCounts.PrefixCB[op] + CycleCounts.AdditionalDDFDCBOps); + } + + #endregion + + #region Helpers (Refresh Register, Flags, etc.) + + public void SetStackPointer(ushort value) + { + sp = value; + } + + public void SetProgramCounter(ushort value) + { + pc = value; + } + + private void IncrementRefresh() + { + r = (byte)(((r + 1) & 0x7F) | (r & 0x80)); + } + + private void SetFlag(Flags flags) + { + af.Low |= (byte)flags; + } + + private void ClearFlag(Flags flags) + { + af.Low &= (byte)~flags; + } + + private void SetClearFlagConditional(Flags flags, bool condition) + { + if (condition) + af.Low |= (byte)flags; + else + af.Low &= (byte)~flags; + } + + private bool IsFlagSet(Flags flags) + { + return (((Flags)af.Low & flags) == flags); + } + + private void CalculateAndSetParity(byte value) + { + int bitsSet = 0; + while (value != 0) { bitsSet += (value & 0x01); value >>= 1; } + SetClearFlagConditional(Flags.ParityOrOverflow, (bitsSet == 0 || (bitsSet % 2) == 0)); + } + + private ushort CalculateIXIYAddress(Register register) + { + return (ushort)(register.Word + (sbyte)ReadMemory8(pc++)); + } + + #endregion + + #region Interrupt and Halt State Handling + + public void SetInterruptLine(InterruptType type, InterruptState state) + { + switch (type) + { + case InterruptType.Maskable: + intState = state; + break; + + case InterruptType.NonMaskable: + nmiState = state; + break; + + default: throw new EmulationException("Z80A: Unknown interrupt type"); + } + } + + private void ServiceInterrupt() + { + if (!iff1) return; + + LeaveHaltState(); + iff1 = iff2 = false; + + switch (im) + { + case 0x00: + /* Execute opcode(s) from data bus */ + /* TODO: no real data bus emulation, just execute opcode 0xFF instead (Xenon 2 SMS, http://www.smspower.org/forums/1172-EmulatingInterrupts#5395) */ + ExecuteOpcodeNoPrefix(0xFF); + currentCycles += 30; + break; + + case 0x01: + /* Restart to location 0x0038, same as opcode 0xFF */ + ExecuteOpcodeNoPrefix(0xFF); + currentCycles += 30; + break; + + case 0x02: + /* Indirect call via I register */ + /* TODO: unsupported at the moment, not needed in currently emulated systems */ + IncrementRefresh(); + break; + } + } + + private void ServiceNonMaskableInterrupt() + { + IncrementRefresh(); + Restart(0x0066); + + iff2 = iff1; + iff1 = halt = false; + + currentCycles += 11; + } + + private void EnterHaltState() + { + halt = true; + pc--; + } + + private void LeaveHaltState() + { + if (halt) + { + halt = false; + pc++; + } + } + + #endregion + + #region Memory and Port Access Functions + + private byte ReadMemory8(ushort address) + { + return memoryReadDelegate(address); + } + + private void WriteMemory8(ushort address, byte value) + { + memoryWriteDelegate(address, value); + } + + private ushort ReadMemory16(ushort address) + { + return (ushort)((memoryReadDelegate((ushort)(address + 1)) << 8) | memoryReadDelegate(address)); + } + + private void WriteMemory16(ushort address, ushort value) + { + memoryWriteDelegate(address, (byte)(value & 0xFF)); + memoryWriteDelegate((ushort)(address + 1), (byte)(value >> 8)); + } + + private byte ReadPort(byte port) + { + return portReadDelegate(port); + } + + private void WritePort(byte port, byte value) + { + portWriteDelegate(port, value); + } + + #endregion + + #region Opcodes: 8-Bit Load Group + + protected void LoadRegisterFromMemory8(ref byte register, ushort address, bool specialRegs) + { + LoadRegister8(ref register, ReadMemory8(address), specialRegs); + } + + protected void LoadRegisterImmediate8(ref byte register, bool specialRegs) + { + LoadRegister8(ref register, ReadMemory8(pc++), specialRegs); + } + + protected void LoadRegister8(ref byte register, byte value, bool specialRegs) + { + register = value; + + // Register is I or R? + if (specialRegs) + { + SetClearFlagConditional(Flags.Sign, IsBitSet(register, 7)); + SetClearFlagConditional(Flags.Zero, (register == 0x00)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.ParityOrOverflow, (iff2)); + ClearFlag(Flags.Subtract); + // C + } + } + + protected void LoadMemory8(ushort address, byte value) + { + WriteMemory8(address, value); + } + + #endregion + + #region Opcodes: 16-Bit Load Group + + protected void LoadRegisterImmediate16(ref ushort register) + { + LoadRegister16(ref register, ReadMemory16(pc)); + pc += 2; + } + + protected void LoadRegister16(ref ushort register, ushort value) + { + register = value; + } + + protected void LoadMemory16(ushort address, ushort value) + { + WriteMemory16(address, value); + } + + protected void Push(Register register) + { + WriteMemory8(--sp, register.High); + WriteMemory8(--sp, register.Low); + } + + protected void Pop(ref Register register) + { + register.Low = ReadMemory8(sp++); + register.High = ReadMemory8(sp++); + } + + #endregion + + #region Opcodes: Exchange, Block Transfer and Search Group + + protected void ExchangeRegisters16(ref Register reg1, ref Register reg2) + { + ushort tmp = reg1.Word; + reg1.Word = reg2.Word; + reg2.Word = tmp; + } + + protected void ExchangeStackRegister16(ref Register reg) + { + byte sl = ReadMemory8(sp); + byte sh = ReadMemory8((ushort)(sp + 1)); + + WriteMemory8(sp, reg.Low); + WriteMemory8((ushort)(sp + 1), reg.High); + + reg.Low = sl; + reg.High = sh; + } + + protected void LoadIncrement() + { + byte hlValue = ReadMemory8(hl.Word); + WriteMemory8(de.Word, hlValue); + Increment16(ref de.Word); + Increment16(ref hl.Word); + Decrement16(ref bc.Word); + + byte n = (byte)(hlValue + af.High); + + // S + // Z + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(n, 1)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(n, 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (bc.Word != 0)); + ClearFlag(Flags.Subtract); + // C + } + + protected void LoadIncrementRepeat() + { + LoadIncrement(); + + if (bc.Word != 0) + { + currentCycles += CycleCounts.AdditionalRepeatByteOps; + pc -= 2; + } + } + + protected void LoadDecrement() + { + byte hlValue = ReadMemory8(hl.Word); + WriteMemory8(de.Word, hlValue); + Decrement16(ref de.Word); + Decrement16(ref hl.Word); + Decrement16(ref bc.Word); + + byte n = (byte)(hlValue + af.High); + + // S + // Z + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(n, 1)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(n, 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (bc.Word != 0)); + ClearFlag(Flags.Subtract); + // C + } + + protected void LoadDecrementRepeat() + { + LoadDecrement(); + + if (bc.Word != 0) + { + currentCycles += CycleCounts.AdditionalRepeatByteOps; + pc -= 2; + } + } + + protected void CompareIncrement() + { + byte operand = ReadMemory8(hl.Word); + int result = (af.High - (sbyte)operand); + + hl.Word++; + bc.Word--; + + bool halfCarry = (((af.High ^ result ^ operand) & 0x10) != 0); + byte n = (byte)(result - (halfCarry ? 1 : 0)); + + SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7)); + SetClearFlagConditional(Flags.Zero, (af.High == operand)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(n, 1)); + SetClearFlagConditional(Flags.HalfCarry, halfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(n, 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (bc.Word != 0)); + SetFlag(Flags.Subtract); + // C + } + + protected void CompareIncrementRepeat() + { + CompareIncrement(); + + if (bc.Word != 0 && !IsFlagSet(Flags.Zero)) + { + currentCycles += CycleCounts.AdditionalRepeatByteOps; + pc -= 2; + } + } + + protected void CompareDecrement() + { + byte operand = ReadMemory8(hl.Word); + int result = (af.High - (sbyte)operand); + + hl.Word--; + bc.Word--; + + bool halfCarry = (((af.High ^ result ^ operand) & 0x10) != 0); + byte n = (byte)(result - (halfCarry ? 1 : 0)); + + SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7)); + SetClearFlagConditional(Flags.Zero, (af.High == operand)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(n, 1)); + SetClearFlagConditional(Flags.HalfCarry, halfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(n, 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (bc.Word != 0)); + SetFlag(Flags.Subtract); + // C + } + + protected void CompareDecrementRepeat() + { + CompareDecrement(); + + if (bc.Word != 0 && !IsFlagSet(Flags.Zero)) + { + currentCycles += CycleCounts.AdditionalRepeatByteOps; + pc -= 2; + } + } + + #endregion + + #region Opcodes: 8-Bit Arithmetic Group + + protected void Add8(byte operand, bool withCarry) + { + int operandWithCarry = (operand + (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0)); + int result = (af.High + operandWithCarry); + + SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7)); + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)result, 5)); + SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0)); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)result, 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (((operand ^ af.High ^ 0x80) & (af.High ^ result) & 0x80) != 0)); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, (result > 0xFF)); + + af.High = (byte)result; + } + + protected void Subtract8(byte operand, bool withCarry) + { + int operandWithCarry = (operand + (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0)); + int result = (af.High - operandWithCarry); + + SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7)); + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)result, 5)); + SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0)); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)result, 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (((operand ^ af.High) & (af.High ^ result) & 0x80) != 0)); + SetFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, (af.High < operandWithCarry)); + + af.High = (byte)result; + } + + protected void And8(byte operand) + { + int result = (af.High & operand); + + SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7)); + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)result, 5)); + SetFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)result, 3)); + CalculateAndSetParity((byte)result); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.Carry); + + af.High = (byte)result; + } + + protected void Or8(byte operand) + { + int result = (af.High | operand); + + SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7)); + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)result, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)result, 3)); + CalculateAndSetParity((byte)result); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.Carry); + + af.High = (byte)result; + } + + protected void Xor8(byte operand) + { + int result = (af.High ^ operand); + + SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7)); + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)result, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)result, 3)); + CalculateAndSetParity((byte)result); + ClearFlag(Flags.Subtract); + ClearFlag(Flags.Carry); + + af.High = (byte)result; + } + + protected void Cp8(byte operand) + { + int result = (af.High - operand); + + SetClearFlagConditional(Flags.Sign, IsBitSet((byte)result, 7)); + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(operand, 5)); + SetClearFlagConditional(Flags.HalfCarry, (((af.High ^ result ^ operand) & 0x10) != 0)); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(operand, 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (((operand ^ af.High) & (af.High ^ result) & 0x80) != 0)); + SetFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, (af.High < operand)); + } + + protected void Increment8(ref byte register) + { + byte result = (byte)(register + 1); + + SetClearFlagConditional(Flags.Sign, IsBitSet(result, 7)); + SetClearFlagConditional(Flags.Zero, (result == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(result, 5)); + SetClearFlagConditional(Flags.HalfCarry, ((register & 0x0F) == 0x0F)); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(result, 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (register == 0x7F)); + ClearFlag(Flags.Subtract); + // C + + register = result; + } + + protected void IncrementMemory8(ushort address) + { + byte value = ReadMemory8(address); + Increment8(ref value); + WriteMemory8(address, value); + } + + protected void Decrement8(ref byte register) + { + byte result = (byte)(register - 1); + + SetClearFlagConditional(Flags.Sign, IsBitSet(result, 7)); + SetClearFlagConditional(Flags.Zero, (result == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(result, 5)); + SetClearFlagConditional(Flags.HalfCarry, ((register & 0x0F) == 0x00)); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(result, 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (register == 0x80)); + SetFlag(Flags.Subtract); + // C + + register = result; + } + + protected void DecrementMemory8(ushort address) + { + byte value = ReadMemory8(address); + Decrement8(ref value); + WriteMemory8(address, value); + } + + #endregion + + #region Opcodes: General-Purpose Arithmetic and CPU Control Group + + protected void DecimalAdjustAccumulator() + { + /* "The Undocumented Z80 Documented" by Sean Young, chapter 4.7, http://www.z80.info/zip/z80-documented.pdf */ + + byte before = af.High, diff = 0x00, result; + bool carry = IsFlagSet(Flags.Carry), halfCarry = IsFlagSet(Flags.HalfCarry); + byte highNibble = (byte)((before & 0xF0) >> 4), lowNibble = (byte)(before & 0x0F); + + if (carry) + { + diff |= 0x60; + if ((halfCarry && lowNibble <= 0x09) || lowNibble >= 0x0A) + diff |= 0x06; + } + else + { + if (lowNibble >= 0x0A && lowNibble <= 0x0F) + { + diff |= 0x06; + if (highNibble >= 0x09 && highNibble <= 0x0F) + diff |= 0x60; + } + else + { + if (highNibble >= 0x0A && highNibble <= 0x0F) + diff |= 0x60; + if (halfCarry) + diff |= 0x06; + } + + SetClearFlagConditional(Flags.Carry, ( + ((highNibble >= 0x09 && highNibble <= 0x0F) && (lowNibble >= 0x0A && lowNibble <= 0x0F)) || + ((highNibble >= 0x0A && highNibble <= 0x0F) && (lowNibble >= 0x00 && lowNibble <= 0x09)))); + } + + if (!IsFlagSet(Flags.Subtract)) + SetClearFlagConditional(Flags.HalfCarry, (lowNibble >= 0x0A && lowNibble <= 0x0F)); + else + SetClearFlagConditional(Flags.HalfCarry, (halfCarry && (lowNibble >= 0x00 && lowNibble <= 0x05))); + + if (!IsFlagSet(Flags.Subtract)) + result = (byte)(before + diff); + else + result = (byte)(before - diff); + + SetClearFlagConditional(Flags.Sign, IsBitSet(result, 7)); + SetClearFlagConditional(Flags.Zero, (result == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(result, 5)); + // H (set above) + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(result, 3)); + CalculateAndSetParity(result); + // N + // C (set above) + + af.High = result; + } + + protected void Negate() + { + int result = (0 - af.High); + + SetClearFlagConditional(Flags.Sign, ((result & 0xFF) >= 0x80)); + SetClearFlagConditional(Flags.Zero, ((result & 0xFF) == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)(result & 0xFF), 5)); + SetClearFlagConditional(Flags.HalfCarry, ((0 - (af.High & 0x0F)) < 0)); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)(result & 0xFF), 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (af.High == 0x80)); + SetFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, (af.High != 0x00)); + + af.High = (byte)result; + } + + #endregion + + #region Opcodes: 16-Bit Arithmetic Group + + protected void Add16(ref Register dest, ushort operand, bool withCarry) + { + int operandWithCarry = ((short)operand + (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0)); + int result = (dest.Word + operandWithCarry); + + // S + // Z + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)(result >> 8), 5)); + SetClearFlagConditional(Flags.HalfCarry, (((dest.Word & 0x0FFF) + (operandWithCarry & 0x0FFF)) > 0x0FFF)); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)(result >> 8), 3)); + // PV + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, (((dest.Word & 0xFFFF) + (operandWithCarry & 0xFFFF)) > 0xFFFF)); + + if (withCarry) + { + SetClearFlagConditional(Flags.Sign, ((result & 0x8000) != 0x0000)); + SetClearFlagConditional(Flags.Zero, ((result & 0xFFFF) == 0x0000)); + SetClearFlagConditional(Flags.ParityOrOverflow, (((dest.Word ^ operandWithCarry) & 0x8000) == 0 && ((dest.Word ^ (result & 0xFFFF)) & 0x8000) != 0)); + } + + dest.Word = (ushort)result; + } + + protected void Subtract16(ref Register dest, ushort operand, bool withCarry) + { + int result = (dest.Word - operand - (withCarry && IsFlagSet(Flags.Carry) ? 1 : 0)); + + SetClearFlagConditional(Flags.Sign, ((result & 0x8000) != 0x0000)); + SetClearFlagConditional(Flags.Zero, ((result & 0xFFFF) == 0x0000)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)(result >> 8), 5)); + SetClearFlagConditional(Flags.HalfCarry, ((((dest.Word ^ result ^ operand) >> 8) & 0x10) != 0)); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)(result >> 8), 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, (((operand ^ dest.Word) & (dest.Word ^ result) & 0x8000) != 0)); + SetFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, ((result & 0x10000) != 0)); + + dest.Word = (ushort)result; + } + + protected void Increment16(ref ushort register) + { + register++; + } + + protected void Decrement16(ref ushort register) + { + register--; + } + + #endregion + + #region Opcodes: Rotate and Shift Group + + protected byte RotateLeft(ushort address) + { + byte value = ReadMemory8(address); + RotateLeft(ref value); + WriteMemory8(address, value); + return value; + } + + protected void RotateLeft(ref byte value) + { + bool isCarrySet = IsFlagSet(Flags.Carry); + bool isMsbSet = IsBitSet(value, 7); + value <<= 1; + if (isCarrySet) SetBit(ref value, 0); + + SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7)); + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3)); + CalculateAndSetParity(value); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected byte RotateLeftCircular(ushort address) + { + byte value = ReadMemory8(address); + RotateLeftCircular(ref value); + WriteMemory8(address, value); + return value; + } + + protected void RotateLeftCircular(ref byte value) + { + bool isMsbSet = IsBitSet(value, 7); + value <<= 1; + if (isMsbSet) SetBit(ref value, 0); + + SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7)); + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3)); + CalculateAndSetParity(value); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected byte RotateRight(ushort address) + { + byte value = ReadMemory8(address); + RotateRight(ref value); + WriteMemory8(address, value); + return value; + } + + protected void RotateRight(ref byte value) + { + bool isCarrySet = IsFlagSet(Flags.Carry); + bool isLsbSet = IsBitSet(value, 0); + value >>= 1; + if (isCarrySet) SetBit(ref value, 7); + + SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7)); + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3)); + CalculateAndSetParity(value); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + protected byte RotateRightCircular(ushort address) + { + byte value = ReadMemory8(address); + RotateRightCircular(ref value); + WriteMemory8(address, value); + return value; + } + + protected void RotateRightCircular(ref byte value) + { + bool isLsbSet = IsBitSet(value, 0); + value >>= 1; + if (isLsbSet) SetBit(ref value, 7); + + SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7)); + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3)); + CalculateAndSetParity(value); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + protected void RotateLeftAccumulator() + { + bool isCarrySet = IsFlagSet(Flags.Carry); + bool isMsbSet = IsBitSet(af.High, 7); + af.High <<= 1; + if (isCarrySet) SetBit(ref af.High, 0); + + // S + // Z + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3)); + // PV + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected void RotateLeftAccumulatorCircular() + { + bool isMsbSet = IsBitSet(af.High, 7); + af.High <<= 1; + if (isMsbSet) SetBit(ref af.High, 0); + + // S + // Z + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3)); + // PV + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected void RotateRightAccumulator() + { + bool isCarrySet = IsFlagSet(Flags.Carry); + bool isLsbSet = IsBitSet(af.High, 0); + af.High >>= 1; + if (isCarrySet) SetBit(ref af.High, 7); + + // S + // Z + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3)); + // PV + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + protected void RotateRightAccumulatorCircular() + { + bool isLsbSet = IsBitSet(af.High, 0); + af.High >>= 1; + if (isLsbSet) SetBit(ref af.High, 7); + + // S + // Z + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3)); + // PV + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + protected void RotateRight4B() + { + byte hlValue = ReadMemory8(hl.Word); + + // A=WX (HL)=YZ + // A=WZ (HL)=XY + byte a1 = (byte)(af.High >> 4); //W + byte a2 = (byte)(af.High & 0xF); //X + byte hl1 = (byte)(hlValue >> 4); //Y + byte hl2 = (byte)(hlValue & 0xF); //Z + + af.High = (byte)((a1 << 4) | hl2); + hlValue = (byte)((a2 << 4) | hl1); + + WriteMemory8(hl.Word, hlValue); + + SetClearFlagConditional(Flags.Sign, IsBitSet(af.High, 7)); + SetClearFlagConditional(Flags.Zero, (af.High == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3)); + CalculateAndSetParity(af.High); + ClearFlag(Flags.Subtract); + // C + } + + protected void RotateLeft4B() + { + byte hlValue = ReadMemory8(hl.Word); + + // A=WX (HL)=YZ + // A=WY (HL)=ZX + byte a1 = (byte)(af.High >> 4); //W + byte a2 = (byte)(af.High & 0xF); //X + byte hl1 = (byte)(hlValue >> 4); //Y + byte hl2 = (byte)(hlValue & 0xF); //Z + + af.High = (byte)((a1 << 4) | hl1); + hlValue = (byte)((hl2 << 4) | a2); + + WriteMemory8(hl.Word, hlValue); + + SetClearFlagConditional(Flags.Sign, IsBitSet(af.High, 7)); + SetClearFlagConditional(Flags.Zero, (af.High == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(af.High, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(af.High, 3)); + CalculateAndSetParity(af.High); + ClearFlag(Flags.Subtract); + // C + } + + protected byte ShiftLeftArithmetic(ushort address) + { + byte value = ReadMemory8(address); + ShiftLeftArithmetic(ref value); + WriteMemory8(address, value); + return value; + } + + protected void ShiftLeftArithmetic(ref byte value) + { + bool isMsbSet = IsBitSet(value, 7); + value <<= 1; + + SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7)); + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3)); + CalculateAndSetParity(value); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected byte ShiftRightArithmetic(ushort address) + { + byte value = ReadMemory8(address); + ShiftRightArithmetic(ref value); + WriteMemory8(address, value); + return value; + } + + protected void ShiftRightArithmetic(ref byte value) + { + bool isLsbSet = IsBitSet(value, 0); + bool isMsbSet = IsBitSet(value, 7); + value >>= 1; + if (isMsbSet) SetBit(ref value, 7); + + SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7)); + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3)); + CalculateAndSetParity(value); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + protected byte ShiftLeftLogical(ushort address) + { + byte value = ReadMemory8(address); + ShiftLeftLogical(ref value); + WriteMemory8(address, value); + return value; + } + + protected void ShiftLeftLogical(ref byte value) + { + bool isMsbSet = IsBitSet(value, 7); + value <<= 1; + value |= 0x01; + + SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7)); + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3)); + CalculateAndSetParity(value); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isMsbSet); + } + + protected byte ShiftRightLogical(ushort address) + { + byte value = ReadMemory8(address); + ShiftRightLogical(ref value); + WriteMemory8(address, value); + return value; + } + + protected void ShiftRightLogical(ref byte value) + { + bool isLsbSet = IsBitSet(value, 0); + value >>= 1; + + SetClearFlagConditional(Flags.Sign, IsBitSet(value, 7)); + SetClearFlagConditional(Flags.Zero, (value == 0x00)); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5)); + ClearFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3)); + CalculateAndSetParity(value); + ClearFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, isLsbSet); + } + + #endregion + + #region Opcodes: Bit Set, Reset and Test Group + + protected byte SetBit(ushort address, int bit) + { + byte value = ReadMemory8(address); + SetBit(ref value, bit); + WriteMemory8(address, value); + return value; + } + + protected void SetBit(ref byte value, int bit) + { + value |= (byte)(1 << bit); + } + + protected byte ResetBit(ushort address, int bit) + { + byte value = ReadMemory8(address); + ResetBit(ref value, bit); + WriteMemory8(address, value); + return value; + } + + protected void ResetBit(ref byte value, int bit) + { + value &= (byte)~(1 << bit); + } + + protected void TestBit(ushort address, int bit) + { + byte value = ReadMemory8(address); + + TestBit(value, bit); + + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet((byte)(address >> 8), 5)); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet((byte)(address >> 8), 3)); + } + + protected void TestBit(byte value, int bit) + { + bool isBitSet = ((value & (1 << bit)) != 0); + + SetClearFlagConditional(Flags.Sign, (bit == 7 && isBitSet)); + SetClearFlagConditional(Flags.Zero, !isBitSet); + SetClearFlagConditional(Flags.UnusedBitY, IsBitSet(value, 5)); + SetFlag(Flags.HalfCarry); + SetClearFlagConditional(Flags.UnusedBitX, IsBitSet(value, 3)); + SetClearFlagConditional(Flags.ParityOrOverflow, !isBitSet); + ClearFlag(Flags.Subtract); + // C + } + + #endregion + + #region Opcodes: Jump Group + + protected void DecrementJumpNonZero() + { + bc.High--; + JumpConditional8(bc.High != 0); + } + + protected void Jump8() + { + pc += (ushort)(((sbyte)ReadMemory8(pc)) + 1); + } + + protected void JumpConditional8(bool condition) + { + if (condition) + { + Jump8(); + currentCycles += CycleCounts.AdditionalJumpCond8Taken; + } + else + pc++; + } + + protected void JumpConditional16(bool condition) + { + if (condition) + pc = ReadMemory16(pc); + else + pc += 2; + } + + #endregion + + #region Opcodes: Call and Return Group + + protected void Call16() + { + WriteMemory8(--sp, (byte)((pc + 2) >> 8)); + WriteMemory8(--sp, (byte)((pc + 2) & 0xFF)); + pc = ReadMemory16(pc); + } + + protected void CallConditional16(bool condition) + { + if (condition) + { + Call16(); + currentCycles += CycleCounts.AdditionalCallCondTaken; + } + else + pc += 2; + } + + protected void Return() + { + pc = ReadMemory16(sp); + sp += 2; + } + + protected void ReturnConditional(bool condition) + { + if (condition) + { + Return(); + currentCycles += CycleCounts.AdditionalRetCondTaken; + } + } + + protected void Restart(ushort address) + { + WriteMemory8(--sp, (byte)(pc >> 8)); + WriteMemory8(--sp, (byte)(pc & 0xFF)); + pc = address; + } + + #endregion + + #region Opcodes: Input and Output Group + + protected void PortInput(ref byte dest, byte port) + { + dest = ReadPort(port); + + SetClearFlagConditional(Flags.Sign, IsBitSet(dest, 7)); + SetClearFlagConditional(Flags.Zero, (dest == 0x00)); + ClearFlag(Flags.HalfCarry); + CalculateAndSetParity(dest); + ClearFlag(Flags.Subtract); + // C + } + + protected void PortInputFlagsOnly(byte port) + { + byte temp = 0; + + PortInput(ref temp, port); + } + + protected void PortInputIncrement() + { + WriteMemory8(hl.Word, ReadPort(bc.Low)); + Increment16(ref hl.Word); + Decrement8(ref bc.High); + + // S + SetClearFlagConditional(Flags.Zero, (bc.High == 0x00)); + // H + // PV + SetFlag(Flags.Subtract); + // C + } + + protected void PortInputIncrementRepeat() + { + PortInputIncrement(); + + if (bc.High != 0) + { + currentCycles += CycleCounts.AdditionalRepeatByteOps; + pc -= 2; + } + else + { + // S + SetFlag(Flags.Zero); + // H + // PV + SetFlag(Flags.Subtract); + // C + } + } + + protected void PortInputDecrement() + { + WriteMemory8(hl.Word, ReadPort(bc.Low)); + Decrement16(ref hl.Word); + Decrement8(ref bc.High); + + // S + SetClearFlagConditional(Flags.Zero, (bc.High == 0x00)); + // H + // PV + SetFlag(Flags.Subtract); + // C + } + + protected void PortInputDecrementRepeat() + { + PortInputDecrement(); + + if (bc.High != 0) + { + currentCycles += CycleCounts.AdditionalRepeatByteOps; + pc -= 2; + } + else + { + // S + SetFlag(Flags.Zero); + // H + // PV + SetFlag(Flags.Subtract); + // C + } + } + + protected void PortOutputIncrement() + { + byte value = ReadMemory8(hl.Word); + WritePort(bc.Low, value); + Increment16(ref hl.Word); + Decrement8(ref bc.High); + + bool setHC = ((value + hl.Low) > 255); + + // S + SetClearFlagConditional(Flags.Zero, (bc.High == 0x00)); + SetClearFlagConditional(Flags.HalfCarry, setHC); + CalculateAndSetParity((byte)(((value + hl.Low) & 0x07) ^ bc.High)); + SetFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, setHC); + } + + protected void PortOutputIncrementRepeat() + { + PortOutputIncrement(); + + if (bc.High != 0) + { + currentCycles += CycleCounts.AdditionalRepeatByteOps; + pc -= 2; + } + else + { + // S + SetFlag(Flags.Zero); + // H + // PV + SetFlag(Flags.Subtract); + // C + } + } + + protected void PortOutputDecrement() + { + byte value = ReadMemory8(hl.Word); + WritePort(bc.Low, value); + Decrement16(ref hl.Word); + Decrement8(ref bc.High); + + bool setHC = ((value + hl.Low) > 255); + + // S + SetClearFlagConditional(Flags.Zero, (bc.High == 0x00)); + SetClearFlagConditional(Flags.HalfCarry, setHC); + CalculateAndSetParity((byte)(((value + hl.Low) & 0x07) ^ bc.High)); + SetFlag(Flags.Subtract); + SetClearFlagConditional(Flags.Carry, setHC); + } + + protected void PortOutputDecrementRepeat() + { + PortOutputDecrement(); + + if (bc.High != 0) + { + currentCycles += CycleCounts.AdditionalRepeatByteOps; + pc -= 2; + } + else + { + // S + SetFlag(Flags.Zero); + // H + // PV + SetFlag(Flags.Subtract); + // C + } + } + + #endregion + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CPU/Z80A.cs.meta b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.cs.meta new file mode 100644 index 0000000..caf0cd9 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CPU/Z80A.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 02ea1ee0691d94f4ba12b23ecea75623 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/CartridgeLoader.cs b/Assets/Plugins/Essgee/Emulation/CartridgeLoader.cs new file mode 100644 index 0000000..b1ae198 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CartridgeLoader.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; +using System.IO; +using System.IO.Compression; + +using Essgee.Emulation.Machines; +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation +{ + public static class CartridgeLoader + { + static Dictionary<string, Type> fileExtensionSystemDictionary; + + static CartridgeLoader() + { + fileExtensionSystemDictionary = new Dictionary<string, Type>(); + foreach (var machineType in Assembly.GetExecutingAssembly().GetTypes().Where(x => typeof(IMachine).IsAssignableFrom(x) && !x.IsInterface).OrderBy(x => x.GetCustomAttribute<MachineIndexAttribute>()?.Index)) + { + if (machineType == null) continue; + + var instance = (IMachine)Activator.CreateInstance(machineType); + foreach (var extension in instance.FileFilter.Extension.Split(';')) + fileExtensionSystemDictionary.Add(extension, machineType); + } + } + + public static (Type, byte[]) Load(string fileName, string fileType) + { + Type machineType = null; + byte[] romData = null; + + if (!File.Exists(fileName)) + throw new CartridgeLoaderException($"{fileType} file not found."); + + try + { + var fileExtension = Path.GetExtension(fileName); + if (fileExtension == ".zip") + { + using (var zip = ZipFile.Open(fileName, ZipArchiveMode.Read)) + { + foreach (var entry in zip.Entries) + { + var entryExtension = Path.GetExtension(entry.Name); + if (fileExtensionSystemDictionary.ContainsKey(entryExtension)) + { + machineType = fileExtensionSystemDictionary[entryExtension]; + using (var stream = entry.Open()) + { + romData = new byte[entry.Length]; + stream.Read(romData, 0, romData.Length); + } + break; + } + } + } + } + else if (fileExtensionSystemDictionary.ContainsKey(fileExtension)) + { + machineType = fileExtensionSystemDictionary[fileExtension]; + romData = File.ReadAllBytes(fileName); + } + } + catch (Exception ex) when (!AppEnvironment.DebugMode) + { + throw new CartridgeLoaderException("File load error", ex); + } + + if (machineType == null) + throw new CartridgeLoaderException($"File could not be recognized as {fileType}."); + + return (machineType, romData); + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/CartridgeLoader.cs.meta b/Assets/Plugins/Essgee/Emulation/CartridgeLoader.cs.meta new file mode 100644 index 0000000..7f7ea3c --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/CartridgeLoader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f1b72196f7ea1bf41b9b580316878200 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges.meta b/Assets/Plugins/Essgee/Emulation/Cartridges.meta new file mode 100644 index 0000000..6b93e80 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fc75a07f5083eef408264c31457c5131 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Coleco.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Coleco.meta new file mode 100644 index 0000000..173df53 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Coleco.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1185c3d60bd11204ead23f542a5c6427 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Coleco/ColecoCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Coleco/ColecoCartridge.cs new file mode 100644 index 0000000..e90f519 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Coleco/ColecoCartridge.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; + +namespace Essgee.Emulation.Cartridges.Coleco +{ + public class ColecoCartridge : ICartridge + { + // TODO: http://atariage.com/forums/topic/210168-colecovision-bank-switching/ ? + + byte[] romData; + + public ColecoCartridge(int romSize, int ramSize) + { + romData = new byte[romSize]; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + } + + public void LoadRam(byte[] data) + { + // + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return null; + } + + public bool IsRamSaveNeeded() + { + return false; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return (ushort)(romData.Length - 1); + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + if (address <= 0x1FFF) + { + /* BIOS */ + return romData[address & 0x1FFF]; + } + else + { + /* Cartridge */ + address -= 0x8000; + if (address >= romData.Length) address -= (ushort)romData.Length; + return romData[address]; + } + } + + public void Write(ushort address, byte value) + { + /* Cannot write to cartridge */ + return; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Coleco/ColecoCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Coleco/ColecoCartridge.cs.meta new file mode 100644 index 0000000..d0a208b --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Coleco/ColecoCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9d70811b8aea410499364dc0485491e3 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/ICartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/ICartridge.cs new file mode 100644 index 0000000..11b8e71 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/ICartridge.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.Cartridges +{ + public interface ICartridge + { + void LoadRom(byte[] data); + void LoadRam(byte[] data); + + byte[] GetRomData(); + byte[] GetRamData(); + bool IsRamSaveNeeded(); + + ushort GetLowerBound(); + ushort GetUpperBound(); + + void Step(int clockCyclesInStep); + + byte Read(ushort address); + void Write(ushort address, byte value); + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/ICartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/ICartridge.cs.meta new file mode 100644 index 0000000..99393f8 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/ICartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 69d4af746d98c1b4ab29d5be6b2d9f2d \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo.meta new file mode 100644 index 0000000..3ecdcd2 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: eea6a30841878a34286f04f99571c050 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs new file mode 100644 index 0000000..942afc4 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs @@ -0,0 +1,425 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel; +using System.Drawing; + +namespace Essgee.Emulation.Cartridges.Nintendo +{ + /* Image processing, etc. based on https://github.com/AntonioND/gbcam-rev-engineer/blob/master/doc/gb_camera_doc_v1_1_1.pdf */ + + public class GBCameraCartridge : IGameBoyCartridge + { + const int camSensorExtraLines = 8; + const int camSensorWidth = 128; + const int camSensorHeight = 112 + camSensorExtraLines; + + const int camWidth = 128; + const int camHeight = 112; + + static readonly float[] edgeRatioLookUpTable = new float[] { 0.50f, 0.75f, 1.00f, 1.25f, 2.00f, 3.00f, 4.00f, 5.00f }; + + public enum ImageSources + { + [Description("Random Noise")] + Noise, + [Description("Image File")] + File + } + + ImageSources imageSourceType; + //Bitmap scaledImage; + + readonly int[,] webcamOutput, camRetinaOutput; + readonly byte[,,] tileBuffer; + + byte[] romData, ramData; + bool hasBattery; + + byte romBank, ramBank; + bool ramEnable; + + readonly byte[] camRegisters; + bool camSelected; + + int cameraCycles, camClocksLeft; + + public GBCameraCartridge(int romSize, int ramSize) + { + imageSourceType = ImageSources.Noise; + //scaledImage = new Bitmap(camSensorWidth, camSensorHeight); + + webcamOutput = new int[camSensorWidth, camSensorHeight]; + camRetinaOutput = new int[camSensorWidth, camSensorHeight]; + tileBuffer = new byte[14, 16, 16]; + + romData = new byte[romSize]; + ramData = new byte[ramSize]; + + romBank = 1; + ramBank = 0; + + ramEnable = false; + + camRegisters = new byte[0x80]; // 0x36 used + camSelected = false; + + hasBattery = false; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + } + + public void LoadRam(byte[] data) + { + Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length)); + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return ramData; + } + + public bool IsRamSaveNeeded() + { + return hasBattery; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0x7FFF; + } + + public void SetCartridgeConfig(bool battery, bool rtc, bool rumble) + { + hasBattery = battery; + } + + public void SetImageSource(ImageSources source, string filename) + { + imageSourceType = source; + + if (imageSourceType == ImageSources.File) + { + //TODO imageSourceType == ImageSources.File + + //using (var tempImage = new Bitmap(filename)) + //{ + // using (var g = System.Drawing.Graphics.FromImage(scaledImage)) + // { + // var ratio = Math.Min(tempImage.Width / (float)camSensorWidth, tempImage.Height / (float)camSensorHeight); + // var srcWidth = (int)(camSensorWidth * ratio); + // var srcHeight = (int)(camSensorHeight * ratio); + // var srcX = (tempImage.Width - srcWidth) / 2; + // var srcY = (tempImage.Height - srcHeight) / 2; + // var scaledRect = new Rectangle(0, 0, camSensorWidth, camSensorHeight); + + // g.FillRectangle(Brushes.White, scaledRect); + // g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High; + // g.DrawImage(tempImage, scaledRect, new Rectangle(srcX, srcY, srcWidth, srcHeight), GraphicsUnit.Pixel); + // } + //} + + //for (var x = 0; x < camSensorWidth; x++) + // for (var y = 0; y < camSensorHeight; y++) + // webcamOutput[x, y] = (int)(scaledImage.GetPixel(x, y).GetBrightness() * 255); + } + } + + public void Step(int clockCyclesInStep) + { + cameraCycles += clockCyclesInStep; + if (cameraCycles >= camClocksLeft) + { + camRegisters[0x00] &= 0xFE; + cameraCycles = 0; + } + } + + public byte Read(ushort address) + { + if (address >= 0x0000 && address <= 0x3FFF) + { + return romData[address & 0x3FFF]; + } + else if (address >= 0x4000 && address <= 0x7FFF) + { + return romData[(romBank << 14) | (address & 0x3FFF)]; + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + if (!camSelected) + { + if ((camRegisters[0x00] & 0b1) == 0) + return ramData[(ramBank << 13) | (address & 0x1FFF)]; + else + return 0xFF; + } + else + { + var reg = (byte)(address & 0x7F); + if (reg == 0x00) + return (byte)(camRegisters[reg] & 0x07); + else + return 0xFF; + } + } + else + return 0xFF; + } + + public void Write(ushort address, byte value) + { + if (address >= 0x0000 && address <= 0x1FFF) + { + ramEnable = (value & 0x0F) == 0x0A; + } + else if (address >= 0x2000 && address <= 0x3FFF) + { + romBank = (byte)((romBank & 0xC0) | (value & 0x3F)); + romBank &= (byte)((romData.Length >> 14) - 1); + if ((romBank & 0x3F) == 0x00) romBank |= 0x01; + } + else if (address >= 0x4000 && address <= 0x5FFF) + { + if ((value & 0x10) != 0) + { + camSelected = true; + } + else + { + camSelected = false; + ramBank = (byte)(value & 0x0F); + } + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + if (!camSelected) + { + if (ramEnable) + ramData[(ramBank << 13) | (address & 0x1FFF)] = value; + } + else + { + var reg = (byte)(address & 0x7F); + if (reg == 0x00 && (value & 0b1) != 0) + GenerateImage(); + + camRegisters[reg] = value; + } + } + } + + private int Clamp(int value, int min, int max) + { + if (value < min) value = min; + else if (value > max) value = max; + return value; + } + + private void GenerateImage() + { + /* Get configuration -- register 0 */ + var pBits = 0; + var mBits = 0; + + switch ((camRegisters[0x00] >> 1) & 0b11) + { + case 0: pBits = 0x00; mBits = 0x01; break; + case 1: pBits = 0x01; mBits = 0x00; break; + case 2: + case 3: pBits = 0x01; mBits = 0x02; break; + } + + /* Register 1 */ + var nBit = ((camRegisters[0x01] >> 7) & 0b1) != 0; + var vhBits = (camRegisters[0x01] >> 5) & 0b11; + + /* Registers 2 and 3 */ + var exposureBits = camRegisters[0x02] << 8 | camRegisters[0x03]; + + /* Register 4 */ + var edgeAlpha = edgeRatioLookUpTable[(camRegisters[0x04] >> 4) & 0b111]; + var e3Bit = ((camRegisters[0x04] >> 7) & 0b1) != 0; + var iBit = ((camRegisters[0x04] >> 3) & 0b1) != 0; + + /* Calculate timings */ + camClocksLeft = 4 * (32446 + (nBit ? 0 : 512) + 16 * exposureBits); + + /* Clear tile buffer */ + for (var j = 0; j < 14; j++) + for (var i = 0; i < 16; i++) + for (var k = 0; k < 16; k++) + tileBuffer[j, i, k] = 0x00; + + /* Sensor handling */ + /* Copy webcam buffer to sensor buffer, apply color correction & exposure time */ + for (var i = 0; i < camSensorWidth; i++) + { + for (var j = 0; j < camSensorHeight; j++) + { + var value = 0; + switch (imageSourceType) + { + case ImageSources.File: value = webcamOutput[i, j]; break; + case ImageSources.Noise: value = StandInfo.Random.Next(255); break; + } + + value = (value * exposureBits) / 0x0300; + value = 128 + (((value - 128) * 1) / 8); + camRetinaOutput[i, j] = Clamp(value, 0, 255); + + /* Invert */ + if (iBit) + camRetinaOutput[i, j] = 255 - camRetinaOutput[i, j]; + + /* Make signed */ + camRetinaOutput[i, j] = camRetinaOutput[i, j] - 128; + } + } + + var tempBuffer = new int[camSensorWidth, camSensorHeight]; + var filteringMode = (nBit ? 8 : 0) | (vhBits << 1) | (e3Bit ? 1 : 0); + switch (filteringMode) + { + case 0x00: + /* 1-D filtering */ + for (var i = 0; i < camSensorWidth; i++) + for (var j = 0; j < camSensorHeight; j++) + tempBuffer[i, j] = camRetinaOutput[i, j]; + + for (var i = 0; i < camSensorWidth; i++) + { + for (var j = 0; j < camSensorHeight; j++) + { + var ms = tempBuffer[i, Math.Min(j + 1, camSensorHeight - 1)]; + var px = tempBuffer[i, j]; + + var value = 0; + if ((pBits & 0b01) != 0) value += px; + if ((pBits & 0b10) != 0) value += ms; + if ((mBits & 0b01) != 0) value -= px; + if ((mBits & 0b10) != 0) value -= ms; + + camRetinaOutput[i, j] = Clamp(value, -128, 127); + } + } + break; + + case 0x02: + /* 1-D filtering + Horiz. enhancement : P + {2P-(MW+ME)} * alpha */ + for (var i = 0; i < camSensorWidth; i++) + { + for (var j = 0; j < camSensorHeight; j++) + { + var mw = camRetinaOutput[Math.Max(0, i - 1), j]; + var me = camRetinaOutput[Math.Min(i + 1, camSensorWidth - 1), j]; + var px = camRetinaOutput[i, j]; + + tempBuffer[i, j] = Clamp((int)(px + ((2 * px - mw - me) * edgeAlpha)), 0, 255); + } + } + + for (var i = 0; i < camSensorWidth; i++) + { + for (var j = 0; j < camSensorHeight; j++) + { + var ms = tempBuffer[i, Math.Min(j + 1, camSensorHeight - 1)]; + var px = tempBuffer[i, j]; + + var value = 0; + if ((pBits & 0b01) != 0) value += px; + if ((pBits & 0b10) != 0) value += ms; + if ((mBits & 0b01) != 0) value -= px; + if ((mBits & 0b10) != 0) value -= ms; + + camRetinaOutput[i, j] = Clamp(value, -128, 127); + } + } + break; + + case 0x0E: + /* 2D enhancement : P + {4P-(MN+MS+ME+MW)} * alpha */ + for (var i = 0; i < camSensorWidth; i++) + { + for (var j = 0; j < camSensorHeight; j++) + { + var ms = camRetinaOutput[i, Math.Min(j + 1, camSensorHeight - 1)]; + var mn = camRetinaOutput[i, Math.Max(0, j - 1)]; + var mw = camRetinaOutput[Math.Max(0, i - 1), j]; + var me = camRetinaOutput[Math.Min(i + 1, camSensorWidth - 1), j]; + var px = camRetinaOutput[i, j]; + + tempBuffer[i, j] = Clamp((int)(px + ((4 * px - mw - me - mn - ms) * edgeAlpha)), -128, 127); + } + } + + for (var i = 0; i < camSensorWidth; i++) + for (var j = 0; j < camSensorHeight; j++) + camRetinaOutput[i, j] = tempBuffer[i, j]; + break; + + case 0x01: + /* Unknown, always same color; sensor datasheet does not document this, maybe a bug? */ + for (var i = 0; i < camSensorWidth; i++) + for (var j = 0; j < camSensorHeight; j++) + camRetinaOutput[i, j] = 0; + break; + + default: + /* Unknown; write to log if enabled */ + if (AppEnvironment.EnableLogger) + { + EssgeeLogger.WriteLine($"Unsupported GB Camera mode 0x{filteringMode:X2}"); + EssgeeLogger.WriteLine(string.Join(" ", camRegisters.Take(6).Select(x => $"0x{x:X2}"))); + } + break; + } + + /* Make unsigned */ + for (var i = 0; i < camSensorWidth; i++) + for (var j = 0; j < camSensorHeight; j++) + camRetinaOutput[i, j] = camRetinaOutput[i, j] + 128; + + /* Convert output to GB tiles */ + for (var i = 0; i < camWidth; i++) + { + for (var j = 0; j < camHeight; j++) + { + var sensorValue = camRetinaOutput[i, j + (camSensorExtraLines / 2)]; + var matrixOffset = 0x06 + ((j % 4) * 12) + ((i % 4) * 3); + + var c = (byte)0; + if (sensorValue < camRegisters[matrixOffset + 0]) c = 3; + else if (sensorValue < camRegisters[matrixOffset + 1]) c = 2; + else if (sensorValue < camRegisters[matrixOffset + 2]) c = 1; + else c = 0; + + if ((c & 1) != 0) tileBuffer[j >> 3, i >> 3, ((j & 7) * 2) + 0] |= (byte)(1 << (7 - (7 & i))); + if ((c & 2) != 0) tileBuffer[j >> 3, i >> 3, ((j & 7) * 2) + 1] |= (byte)(1 << (7 - (7 & i))); + } + } + + /* Copy tiles to cartridge RAM */ + int outputOffset = 0x100; + for (var j = 0; j < 14; j++) + for (var i = 0; i < 16; i++) + for (var k = 0; k < 16; k++) + ramData[outputOffset++] = tileBuffer[j, i, k]; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs.meta new file mode 100644 index 0000000..e6b4a2c --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3887c3ff437835449bdf694c3e812afa \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs new file mode 100644 index 0000000..0195d20 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.Cartridges.Nintendo +{ + public interface IGameBoyCartridge : ICartridge + { + void SetCartridgeConfig(bool battery, bool rtc, bool rumble); + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs.meta new file mode 100644 index 0000000..9fee577 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8f215e1ba8582ab42b21a0dae809cb6e \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs new file mode 100644 index 0000000..2b7d5d6 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Cartridges.Nintendo +{ + public class MBC1Cartridge : IGameBoyCartridge + { + byte[] romData, ramData; + bool hasBattery; + + byte romBank, ramBank; + bool ramEnable; + + byte bankingMode; + + public MBC1Cartridge(int romSize, int ramSize) + { + romData = new byte[romSize]; + ramData = new byte[ramSize]; + + romBank = 1; + ramBank = 0; + + ramEnable = false; + + bankingMode = 0; + + hasBattery = false; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + } + + public void LoadRam(byte[] data) + { + Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length)); + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return ramData; + } + + public bool IsRamSaveNeeded() + { + return hasBattery; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0x7FFF; + } + + public void SetCartridgeConfig(bool battery, bool rtc, bool rumble) + { + hasBattery = battery; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + if (address >= 0x0000 && address <= 0x3FFF) + { + return romData[address & 0x3FFF]; + } + else if (address >= 0x4000 && address <= 0x7FFF) + { + return romData[(romBank << 14) | (address & 0x3FFF)]; + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + if (ramEnable && ramData.Length != 0) + return ramData[(ramBank << 13) | (address & 0x1FFF)]; + else + return 0xFF; + } + else + return 0xFF; + } + + public void Write(ushort address, byte value) + { + if (address >= 0x0000 && address <= 0x1FFF) + { + ramEnable = (value & 0x0F) == 0x0A; + } + else if (address >= 0x2000 && address <= 0x3FFF) + { + romBank = (byte)((romBank & 0xE0) | (value & 0x1F)); + romBank &= (byte)((romData.Length >> 14) - 1); + if ((romBank & 0x1F) == 0x00) romBank |= 0x01; + } + else if (address >= 0x4000 && address <= 0x5FFF) + { + if (bankingMode == 0) + { + romBank = (byte)((romBank & 0x9F) | ((value & 0x03) << 5)); + romBank &= (byte)((romData.Length >> 14) - 1); + if ((romBank & 0x1F) == 0x00) romBank |= 0x01; + } + else + { + ramBank = (byte)(value & 0x03); + } + } + else if (address >= 0x6000 && address <= 0x7FFF) + { + bankingMode = (byte)(value & 0b1); + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + if (ramEnable && ramData.Length != 0) + ramData[(ramBank << 13) | (address & 0x1FFF)] = value; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs.meta new file mode 100644 index 0000000..975cb25 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9e0eda098544f8e4aa894031f92b2b57 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs new file mode 100644 index 0000000..b8272ae --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs @@ -0,0 +1,130 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Cartridges.Nintendo +{ + public class MBC2Cartridge : IGameBoyCartridge + { + byte[] romData, ramData; + bool hasBattery; + + byte romBank; + bool ramEnable; + + public MBC2Cartridge(int romSize, int ramSize) + { + romData = new byte[romSize]; + ramData = new byte[ramSize]; + + romBank = 1; + + ramEnable = false; + + hasBattery = false; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + } + + public void LoadRam(byte[] data) + { + Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length)); + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return ramData; + } + + public bool IsRamSaveNeeded() + { + return hasBattery; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0x7FFF; + } + + public void SetCartridgeConfig(bool battery, bool rtc, bool rumble) + { + hasBattery = battery; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + if (address >= 0x0000 && address <= 0x3FFF) + { + return romData[address & 0x3FFF]; + } + else if (address >= 0x4000 && address <= 0x7FFF) + { + return romData[(romBank << 14) | (address & 0x3FFF)]; + } + else if (address >= 0xA000 && address <= 0xA1FF) + { + if (ramEnable) + { + var ramOffset = (address >> 1) & 0x00FF; + var valueShift = (address & 0x01) << 2; + return (byte)((ramData[ramOffset] >> valueShift) & 0x0F); + } + else + return 0xFF; + } + else + return 0xFF; + } + + public void Write(ushort address, byte value) + { + if (address >= 0x0000 && address <= 0x1FFF) + { + if ((address & 0x0100) == 0) + ramEnable = (value & 0x0F) == 0x0A; + } + else if (address >= 0x2000 && address <= 0x3FFF) + { + if ((address & 0x0100) != 0) + { + romBank = (byte)((romBank & 0xF0) | (value & 0x0F)); + romBank &= (byte)((romData.Length >> 14) - 1); + if ((romBank & 0x0F) == 0x00) romBank |= 0x01; + } + } + else if (address >= 0xA000 && address <= 0xA1FF) + { + if (ramEnable) + { + var ramOffset = (address >> 1) & 0x00FF; + var valueShift = (address & 0x01) << 2; + + ramData[ramOffset] = (byte)((ramData[ramOffset] & (0x0F << (valueShift ^ 0x04))) | (value << valueShift)); + } + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs.meta new file mode 100644 index 0000000..1b7cd17 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b5795dc3ee1b33746a64186a778e2bd6 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs new file mode 100644 index 0000000..bff75c8 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Cartridges.Nintendo +{ + public class MBC3Cartridge : IGameBoyCartridge + { + // https://thomas.spurden.name/gameboy/#mbc3-real-time-clock-rtc + + public class RTC + { + public const int NumRegisters = 0x05; + + public byte[] BaseRegisters { get; private set; } + public byte[] LatchedRegisters { get; private set; } + + public DateTime BaseTime { get; set; } + + public bool IsSelected { get; set; } + public byte SelectedRegister { get; set; } + public bool IsLatched { get; set; } + + public RTC() + { + BaseRegisters = new byte[NumRegisters]; + LatchedRegisters = new byte[NumRegisters]; + + BaseTime = DateTime.Now; + + IsSelected = false; + SelectedRegister = 0; + IsLatched = false; + } + + public void FromSaveData(byte[] ramData) + { + var rtcOffset = ramData.Length - 0x30; + + // Time + BaseRegisters[0x00] = ramData[rtcOffset + 0]; + BaseRegisters[0x01] = ramData[rtcOffset + 4]; + BaseRegisters[0x02] = ramData[rtcOffset + 8]; + BaseRegisters[0x03] = ramData[rtcOffset + 12]; + BaseRegisters[0x04] = ramData[rtcOffset + 16]; + + // Latched time + LatchedRegisters[0x00] = ramData[rtcOffset + 20]; + LatchedRegisters[0x01] = ramData[rtcOffset + 24]; + LatchedRegisters[0x02] = ramData[rtcOffset + 28]; + LatchedRegisters[0x03] = ramData[rtcOffset + 32]; + LatchedRegisters[0x04] = ramData[rtcOffset + 36]; + + // Timestamp + BaseTime = DateTimeOffset.FromUnixTimeSeconds((long)BitConverter.ToUInt64(ramData, rtcOffset + 40)).UtcDateTime; + } + + public byte[] ToSaveData() + { + var appendData = new byte[0x30]; + + // Time + appendData[0] = BaseRegisters[0x00]; + appendData[4] = BaseRegisters[0x01]; + appendData[8] = BaseRegisters[0x02]; + appendData[12] = BaseRegisters[0x03]; + appendData[16] = BaseRegisters[0x04]; + + // Latched time + appendData[20] = LatchedRegisters[0x00]; + appendData[24] = LatchedRegisters[0x01]; + appendData[28] = LatchedRegisters[0x02]; + appendData[32] = LatchedRegisters[0x03]; + appendData[36] = LatchedRegisters[0x04]; + + // Timestamp + var timestamp = BitConverter.GetBytes(((DateTimeOffset)BaseTime).ToUnixTimeSeconds()); + for (var i = 0; i < timestamp.Length; i++) appendData[40 + i] = timestamp[i]; + + return appendData; + } + + public void Update() + { + // GOLD,38695,3000 == 00931 + + + var currentTime = DateTime.Now; + var newTime = currentTime; + + if (((BaseRegisters[0x04] >> 6) & 0b1) == 0 && currentTime > BaseTime) + newTime.Add(currentTime - BaseTime); + + newTime.AddSeconds(BaseRegisters[0x00]); + newTime.AddMinutes(BaseRegisters[0x01]); + newTime.AddHours(BaseRegisters[0x02]); + newTime.AddDays(BaseRegisters[0x03]); + newTime.AddDays((BaseRegisters[0x04] & 0b1) << 8); + + BaseRegisters[0x00] = (byte)newTime.Second; + BaseRegisters[0x01] = (byte)newTime.Minute; + BaseRegisters[0x02] = (byte)newTime.Hour; + BaseRegisters[0x03] = (byte)(newTime.Day & 0xFF); + BaseRegisters[0x04] = (byte)((BaseRegisters[0x04] & 0xFE) | ((newTime.Day >> 8) & 0b1) | ((newTime.Day >> 8) & 0b1) << 7); + + BaseTime = currentTime; + } + } + + byte[] romData, ramData; + bool hasBattery, hasRTC; + + byte romBank, ramBank; + bool ramEnable; + + RTC rtc; + + public MBC3Cartridge(int romSize, int ramSize) + { + romData = new byte[romSize]; + ramData = new byte[ramSize]; + + hasBattery = false; + hasRTC = false; + + romBank = 1; + ramBank = 0; + ramEnable = false; + + rtc = new RTC(); + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + } + + public void LoadRam(byte[] data) + { + /* Has appended RTC state data? */ + if ((data.Length & 0x30) == 0x30) rtc.FromSaveData(data); + + Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length)); + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + if (hasRTC) + return ramData.Concat(rtc.ToSaveData()).ToArray(); + else + return ramData; + } + + public bool IsRamSaveNeeded() + { + return hasBattery; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0x7FFF; + } + + public void SetCartridgeConfig(bool battery, bool rtc, bool rumble) + { + hasBattery = battery; + hasRTC = rtc; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + if (address >= 0x0000 && address <= 0x3FFF) + { + return romData[address & 0x3FFF]; + } + else if (address >= 0x4000 && address <= 0x7FFF) + { + return romData[(romBank << 14) | (address & 0x3FFF)]; + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + if (rtc.IsSelected) + { + if (rtc.IsLatched) + return rtc.LatchedRegisters[rtc.SelectedRegister]; + else + return rtc.BaseRegisters[rtc.SelectedRegister]; + } + else if (ramEnable) + return ramData[(ramBank << 13) | (address & 0x1FFF)]; + else + return 0xFF; + } + else + return 0xFF; + } + + public void Write(ushort address, byte value) + { + if (address >= 0x0000 && address <= 0x1FFF) + { + ramEnable = (value & 0x0F) == 0x0A; + } + else if (address >= 0x2000 && address <= 0x3FFF) + { + romBank = (byte)((romBank & 0x80) | (value & 0x7F)); + romBank &= (byte)((romData.Length >> 14) - 1); + if (romBank == 0x00) romBank = 0x01; + } + else if (address >= 0x4000 && address <= 0x5FFF) + { + if (value >= 0x00 && value <= 0x07) + { + rtc.IsSelected = false; + ramBank = (byte)(value & 0x07); + } + else if (value >= 0x08 && value <= 0x0C) + { + rtc.IsSelected = true; + rtc.SelectedRegister = (byte)(value - 0x08); + } + } + else if (address >= 0x6000 && address <= 0x7FFF) + { + if (value == 0x00 && rtc.IsLatched) + rtc.IsLatched = false; + else if (value == 0x01 && !rtc.IsLatched) + { + rtc.Update(); + for (var i = 0; i < RTC.NumRegisters; i++) + rtc.LatchedRegisters[i] = rtc.BaseRegisters[i]; + rtc.IsLatched = true; + } + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + if (rtc.IsSelected) + { + rtc.Update(); + rtc.BaseRegisters[rtc.SelectedRegister] = value; + } + else if (ramEnable) + ramData[(ramBank << 13) | (address & 0x1FFF)] = value; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs.meta new file mode 100644 index 0000000..123814f --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 739832bbd2b7ce54e8658790d0d1b4ae \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs new file mode 100644 index 0000000..e6f52b9 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Cartridges.Nintendo +{ + // TODO: rumble? + + public class MBC5Cartridge : IGameBoyCartridge + { + public event EventHandler<EventArgs> EnableRumble; + protected virtual void OnEnableRumble(EventArgs e) { EnableRumble?.Invoke(this, EventArgs.Empty); } + + byte[] romData, ramData; + bool hasBattery, hasRumble; + + ushort romBank; + byte ramBank; + bool ramEnable; + + public MBC5Cartridge(int romSize, int ramSize) + { + romData = new byte[romSize]; + ramData = new byte[ramSize]; + + romBank = 1; + ramBank = 0; + + ramEnable = false; + + hasBattery = false; + hasRumble = false; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + } + + public void LoadRam(byte[] data) + { + Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length)); + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return ramData; + } + + public bool IsRamSaveNeeded() + { + return hasBattery; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0x7FFF; + } + + public void SetCartridgeConfig(bool battery, bool rtc, bool rumble) + { + hasBattery = battery; + hasRumble = rumble; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + if (address >= 0x0000 && address <= 0x3FFF) + { + return romData[address & 0x3FFF]; + } + else if (address >= 0x4000 && address <= 0x7FFF) + { + return romData[(romBank << 14) | (address & 0x3FFF)]; + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + if (ramEnable && ramData.Length != 0) + return ramData[(ramBank << 13) | (address & 0x1FFF)]; + else + return 0xFF; + } + else + return 0xFF; + } + + public void Write(ushort address, byte value) + { + if (address >= 0x0000 && address <= 0x1FFF) + { + ramEnable = (value & 0x0F) == 0x0A; + } + else if (address >= 0x2000 && address <= 0x2FFF) + { + romBank = (ushort)((romBank & 0x0100) | value); + romBank &= (ushort)((romData.Length >> 14) - 1); + } + else if (address >= 0x3000 && address <= 0x3FFF) + { + romBank = (ushort)((romBank & 0x00FF) | ((value & 0x01) << 8)); + romBank &= (ushort)((romData.Length >> 14) - 1); + } + else if (address >= 0x4000 && address <= 0x5FFF) + { + if (hasRumble) + { + if ((value & 0x08) == 0x08) OnEnableRumble(EventArgs.Empty); + ramBank = (byte)(value & 0x07); + ramBank %= (byte)(ramData.Length >> 13); + } + else + { + ramBank = (byte)(value & 0x0F); + ramBank %= (byte)(ramData.Length >> 13); + } + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + if (ramEnable && ramData.Length != 0) + ramData[(ramBank << 13) | (address & 0x1FFF)] = value; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs.meta new file mode 100644 index 0000000..5427293 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f083c0cfa9c1cf54ba90f19320c53c16 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs new file mode 100644 index 0000000..e2ddb08 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Cartridges.Nintendo +{ + public class NoMapperCartridge : IGameBoyCartridge + { + byte[] romData, ramData; + bool hasBattery; + + public NoMapperCartridge(int romSize, int ramSize) + { + romData = new byte[romSize]; + ramData = new byte[ramSize]; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + } + + public void LoadRam(byte[] data) + { + Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length)); + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return ramData; + } + + public bool IsRamSaveNeeded() + { + return hasBattery; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0x7FFF; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public void SetCartridgeConfig(bool battery, bool rtc, bool rumble) + { + hasBattery = battery; + } + + public byte Read(ushort address) + { + if (address >= 0x0000 && address <= 0x7FFF) + return romData[address & 0x7FFF]; + else if (address >= 0xA000 && address <= 0xBFFF) + return ramData[address & 0x1FFF]; + else + return 0xFF; + } + + public void Write(ushort address, byte value) + { + if (address >= 0xA000 && address <= 0xBFFF) + ramData[address & 0x1FFF] = value; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs.meta new file mode 100644 index 0000000..e426369 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 90768449a6b38a440b08bb61118b37b7 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/SpecializedLoader.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/SpecializedLoader.cs new file mode 100644 index 0000000..908d075 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/SpecializedLoader.cs @@ -0,0 +1,106 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; + +namespace Essgee.Emulation.Cartridges.Nintendo +{ + public static class SpecializedLoader + { + public static IGameBoyCartridge CreateCartridgeInstance(byte[] romData, byte[] ramData, Type mapperType) + { + var romSize = -1; + switch (romData[0x0148]) + { + case 0x00: romSize = 32 * 1024; break; + case 0x01: romSize = 64 * 1024; break; + case 0x02: romSize = 128 * 1024; break; + case 0x03: romSize = 256 * 1024; break; + case 0x04: romSize = 512 * 1024; break; + case 0x05: romSize = 1024 * 1024; break; + case 0x06: romSize = 2048 * 1024; break; + case 0x07: romSize = 4096 * 1024; break; + case 0x08: romSize = 8192 * 1024; break; + case 0x52: romSize = 1152 * 1024; break; + case 0x53: romSize = 1280 * 1024; break; + case 0x54: romSize = 1536 * 1024; break; + + default: romSize = romData.Length; break; + } + + var ramSize = -1; + switch (romData[0x0149]) + { + case 0x00: ramSize = 0 * 1024; break; + case 0x01: ramSize = 2 * 1024; break; + case 0x02: ramSize = 8 * 1024; break; + case 0x03: ramSize = 32 * 1024; break; + case 0x04: ramSize = 128 * 1024; break; + case 0x05: ramSize = 64 * 1024; break; + + default: ramSize = 0; break; + } + + /* NOTES: + * MBC2 internal RAM is not given in header, 512*4b == 256 bytes + * GB Camera internal RAM ~seems~ to not be given in header? 128 kbytes + */ + + var mapperTypeFromHeader = typeof(NoMapperCartridge); + var hasBattery = false; + var hasRtc = false; + var hasRumble = false; + switch (romData[0x0147]) + { + case 0x00: mapperType = typeof(NoMapperCartridge); break; + case 0x01: mapperType = typeof(MBC1Cartridge); break; + case 0x02: mapperType = typeof(MBC1Cartridge); break; + case 0x03: mapperType = typeof(MBC1Cartridge); hasBattery = true; break; + case 0x05: mapperType = typeof(MBC2Cartridge); ramSize = 0x100; break; + case 0x06: mapperType = typeof(MBC2Cartridge); ramSize = 0x100; hasBattery = true; break; + case 0x08: mapperType = typeof(NoMapperCartridge); break; + case 0x09: mapperType = typeof(NoMapperCartridge); hasBattery = true; break; + // 0B-0D, MMM01 + case 0x0F: mapperType = typeof(MBC3Cartridge); hasBattery = true; hasRtc = true; break; + case 0x10: mapperType = typeof(MBC3Cartridge); hasBattery = true; hasRtc = true; break; + case 0x11: mapperType = typeof(MBC3Cartridge); break; + case 0x12: mapperType = typeof(MBC3Cartridge); break; + case 0x13: mapperType = typeof(MBC3Cartridge); hasBattery = true; break; + case 0x19: mapperType = typeof(MBC5Cartridge); break; + case 0x1A: mapperType = typeof(MBC5Cartridge); break; + case 0x1B: mapperType = typeof(MBC5Cartridge); hasBattery = true; break; + case 0x1C: mapperType = typeof(MBC5Cartridge); hasRumble = true; break; + case 0x1D: mapperType = typeof(MBC5Cartridge); hasRumble = true; break; + case 0x1E: mapperType = typeof(MBC5Cartridge); hasBattery = true; hasRumble = true; break; + // 20, MBC6 + // 22, MBC7 + case 0xFC: mapperType = typeof(GBCameraCartridge); ramSize = 128 * 1024; break; + // FD, BANDAI TAMA5 + // FE, HuC3 + // FF, HuC1 + + default: throw new EmulationException($"Unimplemented cartridge type 0x{romData[0x0147]:X2}"); + } + + if (mapperType == null) + mapperType = mapperTypeFromHeader; + + if (romSize != romData.Length) + { + var romSizePadded = 1; + while (romSizePadded < romData.Length) romSizePadded <<= 1; + romSize = Math.Max(romSizePadded, romData.Length); + } + + var cartridge = (IGameBoyCartridge)Activator.CreateInstance(mapperType, new object[] { romSize, ramSize }); + cartridge.LoadRom(romData); + cartridge.LoadRam(ramData); + cartridge.SetCartridgeConfig(hasBattery, hasRtc, hasRumble); + + return cartridge; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/SpecializedLoader.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/SpecializedLoader.cs.meta new file mode 100644 index 0000000..93599c1 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Nintendo/SpecializedLoader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 40e53d5096e06264a829b803422f047e \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega.meta new file mode 100644 index 0000000..45cd0cd --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9dbf1dc8385034e4082468ce1f21dca0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/CodemastersCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/CodemastersCartridge.cs new file mode 100644 index 0000000..e4bc7eb --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/CodemastersCartridge.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Cartridges.Sega +{ + public class CodemastersCartridge : ICartridge + { + byte[] romData; + + [StateRequired] + byte[] ramData; + + [StateRequired] + readonly byte[] pagingRegisters; + [StateRequired] + readonly byte bankMask; + + [StateRequired] + bool isRamEnabled; + + public CodemastersCartridge(int romSize, int ramSize) + { + pagingRegisters = new byte[3]; + pagingRegisters[0] = 0x00; + pagingRegisters[1] = 0x01; + pagingRegisters[2] = 0x02; + + romData = new byte[romSize]; + ramData = new byte[ramSize]; + + bankMask = (byte)((romData.Length >> 14) - 1); + + isRamEnabled = false; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + } + + public void LoadRam(byte[] data) + { + Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length)); + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return ramData; + } + + public bool IsRamSaveNeeded() + { + return false; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0xBFFF; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + switch (address & 0xC000) + { + case 0x0000: + return romData[((pagingRegisters[0] << 14) | (address & 0x3FFF))]; + + case 0x4000: + return romData[((pagingRegisters[1] << 14) | (address & 0x3FFF))]; + + case 0x8000: + if (isRamEnabled && (address >= 0xA000 && address <= 0xBFFF)) + return ramData[address & 0x1FFF]; + else + return romData[((pagingRegisters[2] << 14) | (address & 0x3FFF))]; + + default: + throw new EmulationException(string.Format("Codemasters mapper: Cannot read from cartridge address 0x{0:X4}", address)); + } + } + + public void Write(ushort address, byte value) + { + switch (address) + { + case 0x0000: + pagingRegisters[0] = (byte)(value & bankMask); + break; + + case 0x4000: + pagingRegisters[1] = (byte)(value & bankMask); + isRamEnabled = ((value & 0x80) == 0x80); + break; + + case 0x8000: + pagingRegisters[2] = (byte)(value & bankMask); + break; + } + + if (isRamEnabled && ((address & 0xF000) == 0xA000 || (address & 0xF000) == 0xB000)) + ramData[address & 0x1FFF] = value; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/CodemastersCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/CodemastersCartridge.cs.meta new file mode 100644 index 0000000..465846d --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/CodemastersCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f7cca9db8813d3c45a1ce0cfb7792b71 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs new file mode 100644 index 0000000..219609f --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; + +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Cartridges.Sega +{ + public class KoreanMSX8kMapperCartridge : ICartridge + { + byte[] romData; + + [StateRequired] + readonly byte[] pagingRegisters; + + [StateRequired] + byte bankMask; + + public KoreanMSX8kMapperCartridge(int romSize, int ramSize) + { + pagingRegisters = new byte[4]; + + romData = new byte[romSize]; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + + var romSizeRounded = 1; + while (romSizeRounded < romData.Length) romSizeRounded <<= 1; + + bankMask = (byte)((romSizeRounded >> 13) - 1); + } + + public void LoadRam(byte[] data) + { + // + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return null; + } + + public bool IsRamSaveNeeded() + { + return false; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0xBFFF; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + switch (address & 0xE000) + { + case 0x0000: return romData[(0x00 << 13) | (address & 0x1FFF)]; + case 0x2000: return romData[(0x01 << 13) | (address & 0x1FFF)]; + case 0x4000: return romData[(pagingRegisters[2] << 13) | (address & 0x1FFF)]; + case 0x6000: return romData[(pagingRegisters[3] << 13) | (address & 0x1FFF)]; + case 0x8000: return romData[(pagingRegisters[0] << 13) | (address & 0x1FFF)]; + case 0xA000: return romData[(pagingRegisters[1] << 13) | (address & 0x1FFF)]; + default: throw new EmulationException(string.Format("Korean MSX 8k mapper: Cannot read from cartridge address 0x{0:X4}", address)); + } + } + + public void Write(ushort address, byte value) + { + if (address >= 0x0000 && address <= 0x0003) + { + pagingRegisters[address & 0x0003] = (byte)(value & bankMask); + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs.meta new file mode 100644 index 0000000..fa5e9fc --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ec1ba102183679945bca01f524bb1868 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs new file mode 100644 index 0000000..55204c4 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; + +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Cartridges.Sega +{ + public class KoreanMapperCartridge : ICartridge + { + byte[] romData; + + [StateRequired] + byte bankMask, pagingRegister; + + public KoreanMapperCartridge(int romSize, int ramSize) + { + pagingRegister = 0x02; + + romData = new byte[romSize]; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + + var romSizeRounded = 1; + while (romSizeRounded < romData.Length) romSizeRounded <<= 1; + + bankMask = (byte)((romSizeRounded >> 14) - 1); + } + + public void LoadRam(byte[] data) + { + // + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return null; + } + + public bool IsRamSaveNeeded() + { + return false; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0xBFFF; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + switch (address & 0xC000) + { + case 0x0000: + return romData[address & 0x3FFF]; + + case 0x4000: + return romData[(0x01 << 14) | (address & 0x3FFF)]; + + case 0x8000: + return romData[((pagingRegister << 14) | (address & 0x3FFF))]; + + default: + throw new EmulationException(string.Format("Korean mapper: Cannot read from cartridge address 0x{0:X4}", address)); + } + } + + public void Write(ushort address, byte value) + { + switch (address) + { + case 0xA000: + pagingRegister = (byte)(value & bankMask); + break; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs.meta new file mode 100644 index 0000000..221eb0d --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 510ae156eb8cf4e4fb7c5fa573062f07 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs new file mode 100644 index 0000000..5b09e17 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.Utilities; + +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Cartridges.Sega +{ + /* Mostly standard Sega mapper, but with bit-reversing functionality to flip sprites + * + * Mapper writes: https://github.com/ocornut/meka/blob/0f1bf8f876a99cb23c440043d2aadfd683c5c812/meka/srcs/mappers.cpp#L571 + * Bit-reversing logic: https://stackoverflow.com/a/3590938 */ + + public class KoreanSpriteMapperCartridge : ICartridge + { + byte[] romData; + + [StateRequired] + byte[] ramData; + + [StateRequired] + readonly byte[] pagingRegisters; + + [StateRequired] + byte romBankMask; + [StateRequired] + bool hasCartRam; + + bool isRamEnabled { get { return IsBitSet(pagingRegisters[0], 3); } } + bool isRomWriteEnable { get { return IsBitSet(pagingRegisters[0], 7); } } + int ramBank { get { return ((pagingRegisters[0] >> 2) & 0x01); } } + int romBank0 { get { return pagingRegisters[1]; } } + int romBank1 { get { return pagingRegisters[2]; } } + int romBank2 { get { return pagingRegisters[3]; } } + + [StateRequired] + bool isBitReverseBank1, isBitReverseBank2; + + public KoreanSpriteMapperCartridge(int romSize, int ramSize) + { + pagingRegisters = new byte[0x04]; + pagingRegisters[0] = 0x00; /* Mapper control */ + pagingRegisters[1] = 0x00; /* Page 0 ROM bank */ + pagingRegisters[2] = 0x01; /* Page 1 ROM bank */ + pagingRegisters[3] = 0x02; /* Page 2 ROM bank */ + + romData = new byte[romSize]; + ramData = new byte[ramSize]; + + romBankMask = 0xFF; + hasCartRam = false; + + isBitReverseBank1 = isBitReverseBank2 = false; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + + var romSizeRounded = 1; + while (romSizeRounded < romData.Length) romSizeRounded <<= 1; + + romBankMask = (byte)((romSizeRounded >> 14) - 1); + + /* Ensure startup banks are within ROM size */ + pagingRegisters[1] &= romBankMask; + pagingRegisters[2] &= romBankMask; + pagingRegisters[3] &= romBankMask; + } + + public void LoadRam(byte[] data) + { + Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length)); + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return ramData; + } + + public bool IsRamSaveNeeded() + { + return hasCartRam; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0xBFFF; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + switch (address & 0xC000) + { + case 0x0000: + if (address < 0x400) + /* First 1kb is constant to preserve interrupt vectors */ + return romData[address]; + else + return romData[((romBank0 << 14) | (address & 0x3FFF))]; + + case 0x4000: + { + /* If requested, reverse bits before return */ + var romAddress = ((romBank1 << 14) | (address & 0x3FFF)); + if (!isBitReverseBank1) + return romData[romAddress]; + else + return (byte)(((romData[romAddress] * 0x80200802ul) & 0x0884422110ul) * 0x0101010101ul >> 32); + } + + case 0x8000: + if (isRamEnabled) + return ramData[((ramBank << 14) | (address & 0x3FFF))]; + else + { + /* If requested, reverse bits before return */ + var romAddress = ((romBank2 << 14) | (address & 0x3FFF)); + if (!isBitReverseBank2) + return romData[romAddress]; + else + return (byte)(((romData[romAddress] * 0x80200802ul) & 0x0884422110ul) * 0x0101010101ul >> 32); + } + + default: + throw new EmulationException(string.Format("Korean sprite-flip mapper: Cannot read from cartridge address 0x{0:X4}", address)); + } + } + + public void Write(ushort address, byte value) + { + if (address >= 0xFFFC && address <= 0xFFFF) + { + /* Check for bit-reverse flags */ + if ((address & 0x0003) == 0x02) + isBitReverseBank1 = ((value & 0x40) == 0x40); + else if ((address & 0x0003) == 0x03) + isBitReverseBank2 = ((value & 0x40) == 0x40); + + /* Write to paging register */ + if ((address & 0x0003) != 0x00) value &= romBankMask; + pagingRegisters[address & 0x0003] = value; + + /* Check if RAM ever gets enabled; if it is, indicate that we'll need to save the RAM */ + if (!hasCartRam && isRamEnabled && (address & 0x0003) == 0x0000) + hasCartRam = true; + } + if (isRamEnabled && (address & 0xC000) == 0x8000) + { + /* Cartridge RAM */ + ramData[((ramBank << 14) | (address & 0x3FFF))] = value; + } + else if (isRomWriteEnable) + { + /* ROM write enabled...? */ + } + + /* Otherwise ignore writes to ROM, as some games seem to be doing that? (ex. Gunstar Heroes GG to 0000) */ + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs.meta new file mode 100644 index 0000000..d27b63e --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9ebf387a119ca924bac38d2244e917ec \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs new file mode 100644 index 0000000..2ec46f5 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; + +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Cartridges.Sega +{ + /* http://www.smspower.org/forums/post69724#69724 */ + + public class Multicart4PakAllActionCartridge : ICartridge + { + byte[] romData; + + [StateRequired] + readonly int romMask; + + [StateRequired] + int romBank0, romBank1, romBank2; + + public Multicart4PakAllActionCartridge(int romSize, int ramSize) + { + romData = new byte[romSize]; + + romMask = 1; + while (romMask < romSize) romMask <<= 1; + romMask -= 1; + + romBank0 = romBank1 = romBank2 = 0; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + } + + public void LoadRam(byte[] data) + { + // + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return null; + } + + public bool IsRamSaveNeeded() + { + return false; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0xBFFF; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + switch (address & 0xC000) + { + case 0x0000: + return romData[((romBank0 << 14) | (address & 0x3FFF))]; + + case 0x4000: + return romData[((romBank1 << 14) | (address & 0x3FFF))]; + + case 0x8000: + return romData[((((romBank0 & 0x30) + romBank2) << 14) | (address & 0x3FFF))]; + + default: + throw new EmulationException(string.Format("4 Pak mapper: Cannot read from cartridge address 0x{0:X4}", address)); + } + } + + public void Write(ushort address, byte value) + { + // TODO: really just these addresses? no mirroring? + if (address == 0x3FFE) + romBank0 = value; + else if (address == 0x7FFF) + romBank1 = value; + else if (address == 0xBFFF) + romBank2 = value; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs.meta new file mode 100644 index 0000000..4a3a9e8 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 92b6b8db8e7eac6469ed11bfe02b5428 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaMapperCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaMapperCartridge.cs new file mode 100644 index 0000000..40b4a19 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaMapperCartridge.cs @@ -0,0 +1,154 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; + +using Essgee.Exceptions; +using Essgee.Utilities; + +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Cartridges.Sega +{ + public class SegaMapperCartridge : ICartridge + { + byte[] romData; + + [StateRequired] + byte[] ramData; + + [StateRequired] + readonly byte[] pagingRegisters; + + [StateRequired] + byte romBankMask; + [StateRequired] + bool hasCartRam; + + bool isRamEnabled { get { return IsBitSet(pagingRegisters[0], 3); } } + bool isRomWriteEnable { get { return IsBitSet(pagingRegisters[0], 7); } } + int ramBank { get { return ((pagingRegisters[0] >> 2) & 0x01); } } + int romBank0 { get { return pagingRegisters[1]; } } + int romBank1 { get { return pagingRegisters[2]; } } + int romBank2 { get { return pagingRegisters[3]; } } + + public SegaMapperCartridge(int romSize, int ramSize) + { + pagingRegisters = new byte[0x04]; + pagingRegisters[0] = 0x00; /* Mapper control */ + pagingRegisters[1] = 0x00; /* Page 0 ROM bank */ + pagingRegisters[2] = 0x01; /* Page 1 ROM bank */ + pagingRegisters[3] = 0x02; /* Page 2 ROM bank */ + + romSize = Math.Max(romSize, 0xC000); + + romData = new byte[romSize]; + ramData = new byte[ramSize]; + + romBankMask = 0xFF; + hasCartRam = false; + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + + var romSizeRounded = 1; + while (romSizeRounded < romData.Length) romSizeRounded <<= 1; + + romBankMask = (byte)((romSizeRounded >> 14) - 1); + + /* Ensure startup banks are within ROM size */ + pagingRegisters[1] &= romBankMask; + pagingRegisters[2] &= romBankMask; + pagingRegisters[3] &= romBankMask; + } + + public void LoadRam(byte[] data) + { + Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length)); + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return ramData; + } + + public bool IsRamSaveNeeded() + { + return hasCartRam; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return 0xBFFF; + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + switch (address & 0xC000) + { + case 0x0000: + if (address < 0x400) + /* First 1kb is constant to preserve interrupt vectors */ + return romData[address]; + else + return romData[((romBank0 << 14) | (address & 0x3FFF))]; + + case 0x4000: + return romData[((romBank1 << 14) | (address & 0x3FFF))]; + + case 0x8000: + if (isRamEnabled) + return ramData[((ramBank << 14) | (address & 0x3FFF))]; + else + return romData[((romBank2 << 14) | (address & 0x3FFF))]; + + default: + throw new EmulationException(string.Format("Sega mapper: Cannot read from cartridge address 0x{0:X4}", address)); + } + } + + public void Write(ushort address, byte value) + { + if (address >= 0xFFFC && address <= 0xFFFF) + { + /* Write to paging register */ + if ((address & 0x0003) != 0x00) value &= romBankMask; + pagingRegisters[address & 0x0003] = value; + + /* Check if RAM ever gets enabled; if it is, indicate that we'll need to save the RAM */ + if (!hasCartRam && isRamEnabled && (address & 0x0003) == 0x0000) + hasCartRam = true; + } + if (isRamEnabled && (address & 0xC000) == 0x8000) + { + /* Cartridge RAM */ + ramData[((ramBank << 14) | (address & 0x3FFF))] = value; + } + else if (isRomWriteEnable) + { + /* ROM write enabled...? */ + } + + /* Otherwise ignore writes to ROM, as some games seem to be doing that? (ex. Gunstar Heroes GG to 0000) */ + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaMapperCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaMapperCartridge.cs.meta new file mode 100644 index 0000000..51d6ef2 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaMapperCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d86b15c129c010f4f911b9aba4e5670a \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaSGCartridge.cs b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaSGCartridge.cs new file mode 100644 index 0000000..4c3c742 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaSGCartridge.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; + +using Essgee.Utilities; + +namespace Essgee.Emulation.Cartridges.Sega +{ + public class SegaSGCartridge : ICartridge + { + byte[] romData; + + [StateRequired] + byte[] ramData; + + [StateRequired] + readonly int romMask, ramMask; + + public SegaSGCartridge(int romSize, int ramSize) + { + romData = new byte[romSize]; + ramData = new byte[ramSize]; + + romMask = 1; + while (romMask < romSize) romMask <<= 1; + romMask -= 1; + + ramMask = (ramSize - 1); + } + + public void LoadRom(byte[] data) + { + Buffer.BlockCopy(data, 0, romData, 0, Math.Min(data.Length, romData.Length)); + } + + public void LoadRam(byte[] data) + { + Buffer.BlockCopy(data, 0, ramData, 0, Math.Min(data.Length, ramData.Length)); + } + + public byte[] GetRomData() + { + return romData; + } + + public byte[] GetRamData() + { + return ramData; + } + + public bool IsRamSaveNeeded() + { + return false; + } + + public ushort GetLowerBound() + { + return 0x0000; + } + + public ushort GetUpperBound() + { + return (ushort)((romData.Length + ramData.Length) - 1); + } + + public void Step(int clockCyclesInStep) + { + /* Nothing to do */ + } + + public byte Read(ushort address) + { + if (ramData.Length > 0) + { + if (address < (romMask + 1)) + return romData[address & romMask]; + else + return ramData[address & ramMask]; + } + else + { + return romData[address & romMask]; + } + } + + public void Write(ushort address, byte value) + { + if (ramData.Length > 0 && address >= (romMask + 1)) + ramData[address & ramMask] = value; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaSGCartridge.cs.meta b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaSGCartridge.cs.meta new file mode 100644 index 0000000..cbd2155 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Cartridges/Sega/SegaSGCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a9ece9b21b502a2468df0428e956bbba \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Configuration.meta b/Assets/Plugins/Essgee/Emulation/Configuration.meta new file mode 100644 index 0000000..211444d --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9359d9b379638da42a314af0f095fe0c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/ColecoVision.cs b/Assets/Plugins/Essgee/Emulation/Configuration/ColecoVision.cs new file mode 100644 index 0000000..16b9067 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/ColecoVision.cs @@ -0,0 +1,95 @@ +using Essgee.Utilities; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Essgee.Emulation.Configuration +{ + ////todo Unity [ElementPriorityAttribute(4)] + public class ColecoVision : IConfiguration + { + [IsBootstrapRomPath] + //todo Unity [FileBrowserControl("General", "BIOS Path", "ColecoVision BIOS ROM (*.col;*.zip)|*.col;*.zip")] + public string BiosRom { get; set; } + + //todo Unity [DropDownControl("Controls", "Up", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsUp { get; set; } + //todo Unity [DropDownControl("Controls", "Down", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsDown { get; set; } + //todo Unity [DropDownControl("Controls", "Left", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsLeft { get; set; } + //todo Unity [DropDownControl("Controls", "Right", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsRight { get; set; } + //todo Unity [DropDownControl("Controls", "Left Button", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsButtonLeft { get; set; } + //todo Unity [DropDownControl("Controls", "Right Button", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsButtonRight { get; set; } + + //todo Unity [DropDownControl("Controls", "Keypad 1", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypad1 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 2", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypad2 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 3", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypad3 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 4", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypad4 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 5", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypad5 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 6", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypad6 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 7", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypad7 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 8", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypad8 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 9", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypad9 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 0", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypad0 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad *", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypadStar { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad #", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsKeypadNumberSign { get; set; } + + public ColecoVision() + { + BiosRom = string.Empty; + + ControlsUp = Keys.Up; + ControlsDown = Keys.Down; + ControlsLeft = Keys.Left; + ControlsRight = Keys.Right; + ControlsButtonLeft = Keys.A; + ControlsButtonRight = Keys.S; + + ControlsKeypad1 = Keys.NumPad1; + ControlsKeypad2 = Keys.NumPad2; + ControlsKeypad3 = Keys.NumPad3; + ControlsKeypad4 = Keys.NumPad4; + ControlsKeypad5 = Keys.NumPad5; + ControlsKeypad6 = Keys.NumPad6; + ControlsKeypad7 = Keys.NumPad7; + ControlsKeypad8 = Keys.NumPad8; + ControlsKeypad9 = Keys.NumPad9; + ControlsKeypad0 = Keys.NumPad0; + ControlsKeypadStar = Keys.Multiply; + ControlsKeypadNumberSign = Keys.Divide; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/ColecoVision.cs.meta b/Assets/Plugins/Essgee/Emulation/Configuration/ColecoVision.cs.meta new file mode 100644 index 0000000..ba580cf --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/ColecoVision.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4ae1eb77c5770f141917f1d7cb917ca1 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/GameBoy.cs b/Assets/Plugins/Essgee/Emulation/Configuration/GameBoy.cs new file mode 100644 index 0000000..596a2e3 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/GameBoy.cs @@ -0,0 +1,72 @@ +using Essgee.Emulation.Cartridges.Nintendo; +using Essgee.Emulation.ExtDevices.Nintendo; +using Essgee.Utilities; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; + +namespace Essgee.Emulation.Configuration +{ + //todo Unity [ElementPriority(5)] + public class GameBoy : IConfiguration + { + //todo Unity [CheckBoxControl("General", "Use Bootstrap ROM")] + public bool UseBootstrap { get; set; } + [IsBootstrapRomPath] + //todo Unity [FileBrowserControl("General", "Bootstrap Path", "Game Boy Bootstrap ROM (*.gb;*.bin;*.zip)|*.gb;*.bin;*.zip")] + public string BootstrapRom { get; set; } + //todo Unity [DropDownControl("General", "Serial Device", typeof(ISerialDevice))] + [JsonConverter(typeof(TypeNameJsonConverter), "Essgee.Emulation.ExtDevices.Nintendo")] + public Type SerialDevice { get; set; } + + //todo Unity [DropDownControl("GB Camera", "Camera Source", typeof(GBCameraCartridge.ImageSources))] + [JsonConverter(typeof(StringEnumConverter))] + public GBCameraCartridge.ImageSources CameraSource { get; set; } + //todo Unity [FileBrowserControl("GB Camera", "Camera Image", "Image Files (*.png;*.bmp)|*.png;*.bmp")] + public string CameraImageFile { get; set; } + + //todo Unity [DropDownControl("Controls", "Up", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsUp { get; set; } + //todo Unity [DropDownControl("Controls", "Down", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsDown { get; set; } + //todo Unity [DropDownControl("Controls", "Left", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsLeft { get; set; } + //todo Unity [DropDownControl("Controls", "Right", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsRight { get; set; } + //todo Unity [DropDownControl("Controls", "A", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsA { get; set; } + //todo Unity [DropDownControl("Controls", "B", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsB { get; set; } + //todo Unity [DropDownControl("Controls", "Select", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsSelect { get; set; } + //todo Unity [DropDownControl("Controls", "Start", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsStart { get; set; } + + public GameBoy() + { + UseBootstrap = false; + BootstrapRom = string.Empty; + + SerialDevice = typeof(DummyDevice); + CameraSource = GBCameraCartridge.ImageSources.Noise; + CameraImageFile = string.Empty; + + ControlsUp = Keys.Up; + ControlsDown = Keys.Down; + ControlsLeft = Keys.Left; + ControlsRight = Keys.Right; + ControlsA = Keys.S; + ControlsB = Keys.A; + ControlsSelect = Keys.Space; + ControlsStart = Keys.Enter; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/GameBoy.cs.meta b/Assets/Plugins/Essgee/Emulation/Configuration/GameBoy.cs.meta new file mode 100644 index 0000000..cfee7f8 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/GameBoy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0f330bdde80f4154a97f9d8e34402812 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/GameBoyColor.cs b/Assets/Plugins/Essgee/Emulation/Configuration/GameBoyColor.cs new file mode 100644 index 0000000..f0963ef --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/GameBoyColor.cs @@ -0,0 +1,85 @@ +using Essgee.Emulation.Cartridges.Nintendo; +using Essgee.Emulation.ExtDevices.Nintendo; +using Essgee.Utilities; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; + +namespace Essgee.Emulation.Configuration +{ + //todo Unity [ElementPriority(6)] + public class GameBoyColor : IConfiguration + { + //todo Unity [CheckBoxControl("General", "Use Bootstrap ROM")] + public bool UseBootstrap { get; set; } + [IsBootstrapRomPath] + //todo Unity [FileBrowserControl("General", "Bootstrap Path", "Game Boy Color Bootstrap ROM (*.gbc;*.bin;*.zip)|*.gbc;*.bin;*.zip")] + public string BootstrapRom { get; set; } + //todo Unity [DropDownControl("General", "Serial Device", typeof(ISerialDevice))] + [JsonConverter(typeof(TypeNameJsonConverter), "Essgee.Emulation.ExtDevices.Nintendo")] + public Type SerialDevice { get; set; } + + //todo Unity [DropDownControl("GB Camera", "Camera Source", typeof(GBCameraCartridge.ImageSources))] + [JsonConverter(typeof(StringEnumConverter))] + public GBCameraCartridge.ImageSources CameraSource { get; set; } + //todo Unity [FileBrowserControl("GB Camera", "Camera Image", "Image Files (*.png;*.bmp)|*.png;*.bmp")] + public string CameraImageFile { get; set; } + + //todo Unity [DropDownControl("Infrared", "Infrared Source", typeof(Machines.GameBoyColor.InfraredSources))] + [JsonConverter(typeof(StringEnumConverter))] + public Machines.GameBoyColor.InfraredSources InfraredSource { get; set; } + //todo Unity [FileBrowserControl("Infrared", "Pokemon Pikachu DB", "Database Binary (*.bin)|*.bin")] + public string InfraredDatabasePikachu { get; set; } + + //todo Unity [DropDownControl("Controls", "Up", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsUp { get; set; } + //todo Unity [DropDownControl("Controls", "Down", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsDown { get; set; } + //todo Unity [DropDownControl("Controls", "Left", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsLeft { get; set; } + //todo Unity [DropDownControl("Controls", "Right", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsRight { get; set; } + //todo Unity [DropDownControl("Controls", "A", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsA { get; set; } + //todo Unity [DropDownControl("Controls", "B", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsB { get; set; } + //todo Unity [DropDownControl("Controls", "Select", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsSelect { get; set; } + //todo Unity [DropDownControl("Controls", "Start", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsStart { get; set; } + //todo Unity [DropDownControl("Controls", "Send IR Signal", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsSendIR { get; set; } + + public GameBoyColor() + { + UseBootstrap = false; + BootstrapRom = string.Empty; + + SerialDevice = typeof(DummyDevice); + CameraSource = GBCameraCartridge.ImageSources.Noise; + CameraImageFile = string.Empty; + + InfraredSource = Machines.GameBoyColor.InfraredSources.None; + InfraredDatabasePikachu = string.Empty; + + ControlsUp = Keys.Up; + ControlsDown = Keys.Down; + ControlsLeft = Keys.Left; + ControlsRight = Keys.Right; + ControlsA = Keys.S; + ControlsB = Keys.A; + ControlsSelect = Keys.Space; + ControlsStart = Keys.Enter; + ControlsSendIR = Keys.Back; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/GameBoyColor.cs.meta b/Assets/Plugins/Essgee/Emulation/Configuration/GameBoyColor.cs.meta new file mode 100644 index 0000000..0af47d0 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/GameBoyColor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4e72130cc49f0324fa58c96d9aaa4771 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/GameGear.cs b/Assets/Plugins/Essgee/Emulation/Configuration/GameGear.cs new file mode 100644 index 0000000..e59e623 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/GameGear.cs @@ -0,0 +1,61 @@ +using Essgee.Utilities; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Essgee.Emulation.Configuration +{ + //todo Unity [ElementPriority(3)] + public class GameGear : IConfiguration + { + //todo Unity [DropDownControl("General", "Region", typeof(Region))] + [JsonConverter(typeof(StringEnumConverter))] + public Region Region { get; set; } + + //todo Unity [CheckBoxControl("General", "Use Bootstrap ROM")] + public bool UseBootstrap { get; set; } + [IsBootstrapRomPath] + //todo Unity [FileBrowserControl("General", "Bootstrap Path", "Game Gear Bootstrap ROM (*.gg;*.zip)|*.gg;*.zip")] + public string BootstrapRom { get; set; } + + //todo Unity [DropDownControl("Controls", "D-Pad Up", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsUp { get; set; } + //todo Unity [DropDownControl("Controls", "D-Pad Down", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsDown { get; set; } + //todo Unity [DropDownControl("Controls", "D-Pad Left", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsLeft { get; set; } + //todo Unity [DropDownControl("Controls", "D-Pad Right", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsRight { get; set; } + //todo Unity [DropDownControl("Controls", "Button 1", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsButton1 { get; set; } + //todo Unity [DropDownControl("Controls", "Button 2", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsButton2 { get; set; } + //todo Unity [DropDownControl("Controls", "Start", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys ControlsStart { get; set; } + + public bool AllowMemoryControl { get; set; } + + public GameGear() + { + BootstrapRom = string.Empty; + + Region = Region.Export; + + ControlsUp = Keys.Up; + ControlsDown = Keys.Down; + ControlsLeft = Keys.Left; + ControlsRight = Keys.Right; + ControlsButton1 = Keys.A; + ControlsButton2 = Keys.S; + ControlsStart = Keys.Return; + + AllowMemoryControl = true; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/GameGear.cs.meta b/Assets/Plugins/Essgee/Emulation/Configuration/GameGear.cs.meta new file mode 100644 index 0000000..c6c0a6a --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/GameGear.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f8c8deb0a7eac484e8b82758c20c92cd \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/IConfiguration.cs b/Assets/Plugins/Essgee/Emulation/Configuration/IConfiguration.cs new file mode 100644 index 0000000..0c21fc6 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/IConfiguration.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.Configuration +{ + public interface IConfiguration { } +} diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/IConfiguration.cs.meta b/Assets/Plugins/Essgee/Emulation/Configuration/IConfiguration.cs.meta new file mode 100644 index 0000000..700b26a --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/IConfiguration.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 12f32f85f1566d740bd12985ea4a9834 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/MasterSystem.cs b/Assets/Plugins/Essgee/Emulation/Configuration/MasterSystem.cs new file mode 100644 index 0000000..a76468c --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/MasterSystem.cs @@ -0,0 +1,109 @@ +using Essgee.Utilities; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Essgee.Emulation.Configuration +{ + ////todo Unity [ElementPriority(2)] + public class MasterSystem : IConfiguration + { + //todo Unity [DropDownControl("General", "TV Standard", typeof(TVStandard))] + [JsonConverter(typeof(StringEnumConverter))] + public TVStandard TVStandard { get; set; } + //todo Unity [DropDownControl("General", "Region", typeof(Region))] + [JsonConverter(typeof(StringEnumConverter))] + public Region Region { get; set; } + //todo Unity [DropDownControl("General", "Model", typeof(VDPTypes), Tooltip = "Selects which type of VDP chip is emulated. This is used by some software to detect which console model it is running on.")] + [JsonConverter(typeof(StringEnumConverter))] + public VDPTypes VDPType { get; set; } + + //todo Unity [CheckBoxControl("General", "Use Bootstrap ROM")] + public bool UseBootstrap { get; set; } + [IsBootstrapRomPath] + //todo Unity [FileBrowserControl("General", "Bootstrap Path", "SMS Bootstrap ROM (*.sms;*.zip)|*.sms;*.zip")] + public string BootstrapRom { get; set; } + + //todo Unity [DropDownControl("General", "Pause Button", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys InputPause { get; set; } + //todo Unity [DropDownControl("General", "Reset Button", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys InputReset { get; set; } + + //todo Unity [DropDownControl("Controller Port 1", "Device Type", typeof(InputDevice))] + [JsonConverter(typeof(StringEnumConverter))] + public InputDevice Joypad1DeviceType { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Up", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Up { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Down", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Down { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Left", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Left { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Right", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Right { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 1", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 2", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Button2 { get; set; } + + //todo Unity [DropDownControl("Controller Port 2", "Device Type", typeof(InputDevice))] + [JsonConverter(typeof(StringEnumConverter))] + public InputDevice Joypad2DeviceType { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Up", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Up { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Down", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Down { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Left", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Left { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Right", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Right { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 1", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 2", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Button2 { get; set; } + + public bool AllowMemoryControl { get; set; } + + public MasterSystem() + { + BootstrapRom = string.Empty; + + TVStandard = TVStandard.NTSC; + Region = Region.Export; + VDPType = VDPTypes.SMS2GG; + + InputPause = Keys.Space; + InputReset = Keys.Back; + + Joypad1DeviceType = InputDevice.Controller; + Joypad1Up = Keys.Up; + Joypad1Down = Keys.Down; + Joypad1Left = Keys.Left; + Joypad1Right = Keys.Right; + Joypad1Button1 = Keys.A; + Joypad1Button2 = Keys.S; + + Joypad2DeviceType = InputDevice.Controller; + Joypad2Up = Keys.NumPad8; + Joypad2Down = Keys.NumPad2; + Joypad2Left = Keys.NumPad4; + Joypad2Right = Keys.NumPad6; + Joypad2Button1 = Keys.NumPad1; + Joypad2Button2 = Keys.NumPad3; + + AllowMemoryControl = true; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/MasterSystem.cs.meta b/Assets/Plugins/Essgee/Emulation/Configuration/MasterSystem.cs.meta new file mode 100644 index 0000000..5262134 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/MasterSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 28c56adc13b701a4580ddd74b71da8f6 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/SC3000.cs b/Assets/Plugins/Essgee/Emulation/Configuration/SC3000.cs new file mode 100644 index 0000000..e8fdbc2 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/SC3000.cs @@ -0,0 +1,86 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Essgee.Emulation.Configuration +{ + //todo Unity [ElementPriority(1)] + public class SC3000 : IConfiguration + { + //todo Unity [DropDownControl("General", "TV Standard", typeof(TVStandard))] + [JsonConverter(typeof(StringEnumConverter))] + public TVStandard TVStandard { get; set; } + + //todo Unity [DropDownControl("General", "Reset Button", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys InputReset { get; set; } + + //todo Unity [DropDownControl("General", "Change Input Mode", typeof(Keys), Keys.F11, Tooltip = "Selects which PC keyboard key is used to switch between SC-3000 keyboard and controller input.")] + [JsonConverter(typeof(StringEnumConverter))] + public Keys InputChangeMode { get; set; } + + //todo Unity [DropDownControl("General", "Play Tape", typeof(Keys), Keys.F11, Tooltip = "Note that tape emulation is currently non-functional.")] + [JsonConverter(typeof(StringEnumConverter))] + public Keys InputPlayTape { get; set; } + + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Up", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Up { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Down", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Down { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Left", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Left { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Right", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Right { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 1", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 2", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Button2 { get; set; } + + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Up", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Up { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Down", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Down { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Left", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Left { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Right", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Right { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 1", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 2", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Button2 { get; set; } + + public SC3000() + { + TVStandard = TVStandard.NTSC; + + InputReset = Keys.F12; + InputChangeMode = Keys.F1; + InputPlayTape = Keys.F2; + + Joypad1Up = Keys.Up; + Joypad1Down = Keys.Down; + Joypad1Left = Keys.Left; + Joypad1Right = Keys.Right; + Joypad1Button1 = Keys.A; + Joypad1Button2 = Keys.S; + + Joypad2Up = Keys.NumPad8; + Joypad2Down = Keys.NumPad2; + Joypad2Left = Keys.NumPad4; + Joypad2Right = Keys.NumPad6; + Joypad2Button1 = Keys.NumPad1; + Joypad2Button2 = Keys.NumPad3; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/SC3000.cs.meta b/Assets/Plugins/Essgee/Emulation/Configuration/SC3000.cs.meta new file mode 100644 index 0000000..d6603af --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/SC3000.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 727a7522f4818ff44a2728f98cda2b66 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/SG1000.cs b/Assets/Plugins/Essgee/Emulation/Configuration/SG1000.cs new file mode 100644 index 0000000..d729d4b --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/SG1000.cs @@ -0,0 +1,76 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Essgee.Emulation.Configuration +{ + //todo Unity [ElementPriority(0)] + public class SG1000 : IConfiguration + { + //todo Unity [DropDownControl("General", "TV Standard", typeof(TVStandard))] + [JsonConverter(typeof(StringEnumConverter))] + public TVStandard TVStandard { get; set; } + + //todo Unity [DropDownControl("General", "Pause Button", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys InputPause { get; set; } + + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Up", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Up { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Down", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Down { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Left", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Left { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Right", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Right { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 1", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 2", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad1Button2 { get; set; } + + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Up", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Up { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Down", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Down { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Left", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Left { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Right", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Right { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 1", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 2", typeof(Keys), Keys.F11)] + [JsonConverter(typeof(StringEnumConverter))] + public Keys Joypad2Button2 { get; set; } + + public SG1000() + { + TVStandard = TVStandard.NTSC; + + InputPause = Keys.Space; + + Joypad1Up = Keys.Up; + Joypad1Down = Keys.Down; + Joypad1Left = Keys.Left; + Joypad1Right = Keys.Right; + Joypad1Button1 = Keys.A; + Joypad1Button2 = Keys.S; + + Joypad2Up = Keys.NumPad8; + Joypad2Down = Keys.NumPad2; + Joypad2Left = Keys.NumPad4; + Joypad2Right = Keys.NumPad6; + Joypad2Button1 = Keys.NumPad1; + Joypad2Button2 = Keys.NumPad3; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Configuration/SG1000.cs.meta b/Assets/Plugins/Essgee/Emulation/Configuration/SG1000.cs.meta new file mode 100644 index 0000000..75083d0 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Configuration/SG1000.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ff8803776ef43a6489bbe3909015e4af \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/EmulatorHandler.cs b/Assets/Plugins/Essgee/Emulation/EmulatorHandler.cs new file mode 100644 index 0000000..f8d3d86 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/EmulatorHandler.cs @@ -0,0 +1,339 @@ +using Essgee.Emulation.Configuration; +using Essgee.Emulation.Machines; +using Essgee.EventArguments; +using Essgee.Metadata; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; + +namespace Essgee.Emulation +{ + public class EmulatorHandler + { + readonly Action<Exception> exceptionHandler; + + IMachine emulator; + + Thread emulationThread; + volatile bool emulationThreadRunning; + + volatile bool limitFps; + volatile bool emulationThreadPaused; + + volatile bool configChangeRequested = false; + volatile IConfiguration newConfiguration = null; + + volatile bool stateLoadRequested = false, stateSaveRequested = false; + volatile int stateNumber = -1; + + volatile Queue<bool> pauseStateChangesRequested = new Queue<bool>(); + + public event EventHandler<SendLogMessageEventArgs> SendLogMessage + { + add { emulator.SendLogMessage += value; } + remove { emulator.SendLogMessage -= value; } + } + + public event EventHandler<EventArgs> EmulationReset + { + add { emulator.EmulationReset += value; } + remove { emulator.EmulationReset -= value; } + } + + public event EventHandler<RenderScreenEventArgs> RenderScreen + { + add { emulator.RenderScreen += value; } + remove { emulator.RenderScreen -= value; } + } + + public event EventHandler<SizeScreenEventArgs> SizeScreen + { + add { emulator.SizeScreen += value; } + remove { emulator.SizeScreen -= value; } + } + + public event EventHandler<ChangeViewportEventArgs> ChangeViewport + { + add { emulator.ChangeViewport += value; } + remove { emulator.ChangeViewport -= value; } + } + + public event EventHandler<PollInputEventArgs> PollInput + { + add { emulator.PollInput += value; } + remove { emulator.PollInput -= value; } + } + + public event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples + { + add { emulator.EnqueueSamples += value; } + remove { emulator.EnqueueSamples -= value; } + } + + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData + { + add { emulator.SaveExtraData += value; } + remove { emulator.SaveExtraData -= value; } + } + + public event EventHandler<EventArgs> EnableRumble + { + add { emulator.EnableRumble += value; } + remove { emulator.EnableRumble -= value; } + } + + public event EventHandler<EventArgs> PauseChanged; + + GameMetadata currentGameMetadata; + + public bool IsCartridgeLoaded { get; private set; } + + public bool IsRunning => emulationThreadRunning; + public bool IsPaused => emulationThreadPaused; + + public bool IsHandlingSaveState => (stateLoadRequested || stateSaveRequested); + + public (string Manufacturer, string Model, string DatFileName, double RefreshRate, double PixelAspectRatio, (string Name, string Description)[] RuntimeOptions) Information => + (emulator.ManufacturerName, emulator.ModelName, emulator.DatFilename, emulator.RefreshRate, emulator.PixelAspectRatio, emulator.RuntimeOptions); + + public EmulatorHandler(Type type, Action<Exception> exceptionHandler = null) + { + this.exceptionHandler = exceptionHandler; + + emulator = (IMachine)Activator.CreateInstance(type); + } + + public void SetConfiguration(IConfiguration config) + { + if (emulationThreadRunning) + { + configChangeRequested = true; + newConfiguration = config; + } + else + emulator.SetConfiguration(config); + } + + public void Initialize() + { + emulator.Initialize(); + } + + public void Startup() + { + emulationThreadRunning = true; + emulationThreadPaused = false; + + emulator.Startup(); + emulator.Reset(); + + emulationThread = new Thread(ThreadMainLoop) { Name = "EssgeeEmulation", Priority = ThreadPriority.Normal }; + emulationThread.Start(); + } + + public void Reset() + { + emulator.Reset(); + } + + public void Shutdown() + { + emulationThreadRunning = false; + + emulationThread?.Join(); + + emulator.Shutdown(); + } + + public void Pause(bool pauseState) + { + pauseStateChangesRequested.Enqueue(pauseState); + } + + public string GetSaveStateFilename(int number) + { + return Path.Combine(StandInfo.SaveStatePath, $"{Path.GetFileNameWithoutExtension(currentGameMetadata.FileName)} (State {number:D2}).est"); + } + + public void LoadState(int number) + { + stateLoadRequested = true; + stateNumber = number; + } + + public void SaveState(int number) + { + stateSaveRequested = true; + stateNumber = number; + } + + public void LoadCartridge(byte[] romData, GameMetadata gameMetadata) + { + currentGameMetadata = gameMetadata; + + byte[] ramData = new byte[currentGameMetadata.RamSize]; + + var savePath = Path.Combine(StandInfo.SaveDataPath, Path.ChangeExtension(currentGameMetadata.FileName, "sav")); + if (File.Exists(savePath)) + ramData = File.ReadAllBytes(savePath); + + emulator.Load(romData, ramData, currentGameMetadata.MapperType); + + IsCartridgeLoaded = true; + } + + public void SaveCartridge() + { + if (currentGameMetadata == null) return; + + var cartRamSaveNeeded = emulator.IsCartridgeRamSaveNeeded(); + if ((cartRamSaveNeeded && currentGameMetadata.MapperType != null && currentGameMetadata.HasNonVolatileRam) || + cartRamSaveNeeded) + { + var ramData = emulator.GetCartridgeRam(); + var savePath = Path.Combine(StandInfo.SaveDataPath, Path.ChangeExtension(currentGameMetadata.FileName, "sav")); + File.WriteAllBytes(savePath, ramData); + } + } + + public Dictionary<string, dynamic> GetDebugInformation() + { + return emulator.GetDebugInformation(); + } + + public Type GetMachineType() + { + return emulator.GetType(); + } + + public void SetFpsLimiting(bool value) + { + limitFps = value; + } + + public object GetRuntimeOption(string name) + { + return emulator.GetRuntimeOption(name); + } + + public void SetRuntimeOption(string name, object value) + { + emulator.SetRuntimeOption(name, value); + } + + public int FramesPerSecond { get; private set; } + + private void ThreadMainLoop() + { + // TODO: rework fps limiter/counter - AGAIN - because the counter is inaccurate at sampleTimespan=0.25 and the limiter CAN cause sound crackling at sampleTimespan>0.25 + // try this maybe? https://stackoverflow.com/a/34839411 + + var stopWatch = Stopwatch.StartNew(); + + TimeSpan accumulatedTime = TimeSpan.Zero, lastStartTime = TimeSpan.Zero, lastEndTime = TimeSpan.Zero; + + var frameCounter = 0; + var sampleTimespan = TimeSpan.FromSeconds(0.5); + + try + { + while (true) + { + if (!emulationThreadRunning) + break; + + if (stateLoadRequested && stateNumber != -1) + { + var statePath = GetSaveStateFilename(stateNumber); + if (File.Exists(statePath)) + { + using (var stream = new FileStream(statePath, FileMode.Open)) + { + emulator.SetState(SaveStateHandler.Load(stream, emulator.GetType().Name)); + } + } + + stateLoadRequested = false; + stateNumber = -1; + } + + var refreshRate = emulator.RefreshRate; + var targetElapsedTime = TimeSpan.FromTicks((long)Math.Round(TimeSpan.TicksPerSecond / refreshRate)); + + var startTime = stopWatch.Elapsed; + + while (pauseStateChangesRequested.Count > 0) + { + var newPauseState = pauseStateChangesRequested.Dequeue(); + emulationThreadPaused = newPauseState; + + PauseChanged?.Invoke(this, EventArgs.Empty); + } + + if (!emulationThreadPaused) + { + if (limitFps) + { + var elapsedTime = (startTime - lastStartTime); + lastStartTime = startTime; + + if (elapsedTime < targetElapsedTime) + { + accumulatedTime += elapsedTime; + + while (accumulatedTime >= targetElapsedTime) + { + emulator.RunFrame(); + frameCounter++; + + accumulatedTime -= targetElapsedTime; + } + } + } + else + { + emulator.RunFrame(); + frameCounter++; + } + + if ((stopWatch.Elapsed - lastEndTime) >= sampleTimespan) + { + FramesPerSecond = (int)((frameCounter * 1000.0) / sampleTimespan.TotalMilliseconds); + frameCounter = 0; + lastEndTime = stopWatch.Elapsed; + } + } + else + { + lastEndTime = stopWatch.Elapsed; + } + + if (configChangeRequested) + { + emulator.SetConfiguration(newConfiguration); + configChangeRequested = false; + } + + if (stateSaveRequested && stateNumber != -1) + { + var statePath = GetSaveStateFilename(stateNumber); + using (var stream = new FileStream(statePath, FileMode.OpenOrCreate)) + { + SaveStateHandler.Save(stream, emulator.GetType().Name, emulator.GetState()); + } + + stateSaveRequested = false; + stateNumber = -1; + } + } + } + catch (Exception ex) when (!AppEnvironment.DebugMode) + { + ex.Data.Add("Thread", Thread.CurrentThread.Name); + exceptionHandler(ex); + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/EmulatorHandler.cs.meta b/Assets/Plugins/Essgee/Emulation/EmulatorHandler.cs.meta new file mode 100644 index 0000000..21be897 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/EmulatorHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2a3dbee1ff1b11c4f82f85a480959bee \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Enumerations.cs b/Assets/Plugins/Essgee/Emulation/Enumerations.cs new file mode 100644 index 0000000..4adc8fa --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Enumerations.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel; + +namespace Essgee.Emulation +{ + // TODO change all these b/c gameboy is a thing now + + public enum TVStandard + { + //todo unity [ValueIgnored(true)] + Auto = -1, + [Description("NTSC (60 Hz)")] + NTSC = 0, + [Description("PAL (50 Hz)")] + PAL + } + public enum Region + { + //todo unity [ValueIgnored(true)] + Auto = -1, + [Description("Domestic (Japan)")] + Domestic = 0, + [Description("Export")] + Export + } + + public enum InputDevice + { + [Description("None")] + None = 0, + [Description("Standard Controller")] + Controller, + [Description("Light Phaser")] + Lightgun + } + + public enum VDPTypes + { + [Description("Mark III / Master System")] + Mk3SMS1 = 0, + [Description("Master System II / Game Gear")] + SMS2GG = 1 + } + + public enum InterruptType + { + Maskable, + NonMaskable + } + + public enum InterruptState + { + Clear, + Assert + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Enumerations.cs.meta b/Assets/Plugins/Essgee/Emulation/Enumerations.cs.meta new file mode 100644 index 0000000..b057d8b --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Enumerations.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3dcbfdb676ca9d943a2c4e7049a71487 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/ExtDevices.meta b/Assets/Plugins/Essgee/Emulation/ExtDevices.meta new file mode 100644 index 0000000..58e1490 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/ExtDevices.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 32a6b746a89ac5242be599fa472bab1b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo.meta b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo.meta new file mode 100644 index 0000000..a256439 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a09bb4257e29ab646b70aba3720660e1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/DummyDevice.cs b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/DummyDevice.cs new file mode 100644 index 0000000..0704c75 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/DummyDevice.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel; + +using Essgee.EventArguments; + +namespace Essgee.Emulation.ExtDevices.Nintendo +{ + [Description("None")] + //todo Unity [ElementPriority(0)] + public class DummyDevice : ISerialDevice + { + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public void Initialize() { } + public void Shutdown() { } + public byte ExchangeBit(int left, byte data) { return 0b1; } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/DummyDevice.cs.meta b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/DummyDevice.cs.meta new file mode 100644 index 0000000..1c152dc --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/DummyDevice.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0ac549cbc5bf99c48933ba2369c6838e \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GBPrinter.cs b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GBPrinter.cs new file mode 100644 index 0000000..6dac4db --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GBPrinter.cs @@ -0,0 +1,428 @@ +using Essgee.EventArguments; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Linq; + +namespace Essgee.Emulation.ExtDevices.Nintendo +{ + [Description("Game Boy Printer")] + //todo Unity [ElementPriority(2)] + public class GBPrinter : ISerialDevice + { + readonly Color[] defaultPalette = new Color[] + { + Color.FromArgb(0xF8, 0xF8, 0xF8), + Color.FromArgb(0x9B, 0x9B, 0x9B), + Color.FromArgb(0x3E, 0x3E, 0x3E), + Color.FromArgb(0x1F, 0x1F, 0x1F) + }; + + enum PrinterPacketBytes + { + MagicLSB, + MagicMSB, + CommandByte, + CompressionFlag, + DataLengthLSB, + DataLengthMSB, + DataByte, + ChecksumLSB, + ChecksumMSB, + Execute + } + + enum PrinterCommands : byte + { + Initialize = 0x01, + StartPrinting = 0x02, + Unknown = 0x03, + ImageTransfer = 0x04, + ReadStatus = 0x0F + } + + [Flags] + enum PrinterPresenceBits : byte + { + Unknown = (1 << 0), + Present = (1 << 7), + } + + [Flags] + enum PrinterStatusBits : byte + { + BadChecksum = (1 << 0), + PrintInProgress = (1 << 1), + PrintRequested = (1 << 2), + ReadyToPrint = (1 << 3), + LowVoltage = (1 << 4), + Unknown = (1 << 5), + PaperJam = (1 << 6), + ThermalProblem = (1 << 7) + }; + + (ushort magic, PrinterCommands command, bool isCompressed, ushort dataLen, byte[] data, ushort checksum) packet; + PrinterPacketBytes nextPacketByte; + int dataBytesLeft; + + PrinterStatusBits status; + PrinterPresenceBits presence; + + List<byte> imageData; + byte marginBefore, marginAfter, palette, exposure; + + int imageHeight; + int printDelay; + + byte serialData; + + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public GBPrinter() + { + imageData = new List<byte>(); + } + + public void Initialize() + { + ResetPacket(); + + nextPacketByte = PrinterPacketBytes.MagicLSB; + dataBytesLeft = 0; + + status = 0; + presence = 0; + + marginBefore = marginAfter = 0; + palette = exposure = 0; + + imageHeight = 0; + printDelay = 0; + + serialData = 0; + } + + public void Shutdown() + { + // + } + + private void ResetPacket() + { + packet = (0, 0, false, 0, new byte[0], 0); + } + + public byte ExchangeBit(int left, byte data) + { + var bitToSend = (byte)((serialData >> 7) & 0b1); + serialData = (byte)((serialData << 1) | (data & 0b1)); + if (left == 0) serialData = ProcessReceivedByte(serialData); + return bitToSend; + } + + public byte ProcessReceivedByte(byte data) + { + byte ret = 0; + + switch (nextPacketByte) + { + case PrinterPacketBytes.MagicLSB: + /* Received: First magic byte + * Action: Reset packet + * Send: Nothing + */ + if (data == 0x88) + { + ResetPacket(); + packet.magic |= (ushort)(data << 8); + nextPacketByte = PrinterPacketBytes.MagicMSB; + } + break; + + case PrinterPacketBytes.MagicMSB: + /* Received: Second magic byte + * Action: Nothing + * Send: Nothing + */ + if (data == 0x33) + { + packet.magic |= data; + nextPacketByte = PrinterPacketBytes.CommandByte; + } + break; + + case PrinterPacketBytes.CommandByte: + /* Received: Command byte + * Action: Nothing + * Send: Nothing + */ + packet.command = (PrinterCommands)data; + nextPacketByte = PrinterPacketBytes.CompressionFlag; + break; + + case PrinterPacketBytes.CompressionFlag: + /* Received: Compression flag + * Action: Nothing + * Send: Nothing + */ + packet.isCompressed = (data & 0x01) != 0; + nextPacketByte = PrinterPacketBytes.DataLengthLSB; + break; + + case PrinterPacketBytes.DataLengthLSB: + /* Received: Data length LSB + * Action: Nothing + * Send: Nothing + */ + packet.dataLen |= data; + nextPacketByte = PrinterPacketBytes.DataLengthMSB; + break; + + case PrinterPacketBytes.DataLengthMSB: + /* Received: Data length MSB + * Action: Prepare to receive data + * Send: Nothing + */ + packet.dataLen |= (ushort)(data << 8); + packet.data = new byte[packet.dataLen]; + dataBytesLeft = packet.dataLen; + if (dataBytesLeft > 0) + nextPacketByte = PrinterPacketBytes.DataByte; + else + nextPacketByte = PrinterPacketBytes.ChecksumLSB; + break; + + case PrinterPacketBytes.DataByte: + /* Received: Data byte + * Action: Nothing + * Send: Nothing + */ + if (dataBytesLeft > 0) + { + packet.data[--dataBytesLeft] = data; + if (dataBytesLeft == 0) + nextPacketByte = PrinterPacketBytes.ChecksumLSB; + } + break; + + case PrinterPacketBytes.ChecksumLSB: + /* Received: Checksum LSB + * Action: Nothing + * Send: Nothing + */ + packet.checksum |= data; + nextPacketByte = PrinterPacketBytes.ChecksumMSB; + break; + + case PrinterPacketBytes.ChecksumMSB: + /* Received: Checksum MSB + * Action: Nothing + * Send: Printer presence + */ + packet.checksum |= (ushort)(data << 8); + presence = PrinterPresenceBits.Present | PrinterPresenceBits.Unknown; + ret = (byte)presence; + nextPacketByte = PrinterPacketBytes.Execute; + break; + + case PrinterPacketBytes.Execute: + /* Received: Execute command + * Action: Nothing + * Send: Printer status + */ + + /* First, we're done with the packet, so check what we need to do now */ + packet.data = packet.data.Reverse().ToArray(); + switch (packet.command) + { + case PrinterCommands.Initialize: + /* Reset some data */ + status = 0; + imageData.Clear(); + imageHeight = 0; + printDelay = 0; + break; + + case PrinterCommands.ImageTransfer: + /* Copy packet data for drawing, increase image height & tell GB we're ready to print */ + if (packet.data.Length > 0) + { + if (packet.isCompressed) + { + /* Decompress RLE first! */ + List<byte> decomp = new List<byte>(); + int ofs = 0, numbytes = 0; + while (ofs < packet.dataLen) + { + if ((packet.data[ofs] & 0x80) != 0) + { + /* Compressed */ + numbytes = (packet.data[ofs] & 0x7F) + 2; + for (int i = 0; i < numbytes; i++) decomp.Add(packet.data[ofs + 1]); + ofs += 2; + } + else + { + /* Uncompressed */ + numbytes = (packet.data[ofs] & 0x7F) + 1; + for (int i = 0; i < numbytes; i++) decomp.Add(packet.data[ofs + 1 + i]); + ofs += (numbytes + 1); + } + } + packet.data = decomp.ToArray(); + packet.dataLen = (ushort)decomp.Count; + } + + imageData.AddRange(packet.data); + imageHeight += (packet.data.Length / 0x28); + + status |= PrinterStatusBits.ReadyToPrint; + } + break; + + case PrinterCommands.StartPrinting: + /* Fetch parameters from packet, tell GB that we're about to print & perform printing */ + marginBefore = (byte)((packet.data[1] >> 4) & 0x0F); + marginAfter = (byte)(packet.data[1] & 0x0F); + palette = packet.data[2]; + exposure = (byte)(packet.data[3] & 0x7F); + + status &= ~PrinterStatusBits.ReadyToPrint; + status |= PrinterStatusBits.PrintRequested; + PerformPrint(); + break; + + case PrinterCommands.ReadStatus: + if ((status & PrinterStatusBits.PrintRequested) != 0) + { + /* If we said printing has been requested, tell the GB it's in progress now */ + status &= ~PrinterStatusBits.PrintRequested; + status |= PrinterStatusBits.PrintInProgress; + } + else if ((status & PrinterStatusBits.PrintInProgress) != 0) + { + /* Delay the process a bit... */ + printDelay++; + if (printDelay >= 16) // TODO: figure out actual print duration/timing? + { + /* If we said printing is in progress, tell the GB we're finished with it */ + status &= ~PrinterStatusBits.PrintInProgress; + printDelay = 0; + } + } + break; + } + + /* End of packet */ + DumpPacket(); + + ret = (byte)status; + + nextPacketByte = PrinterPacketBytes.MagicLSB; + break; + } + + return ret; + } + + public byte DoMasterTransfer(byte data) + { + /* Not used */ + return 0xFF; + } + + private void PerformPrint() + { + if (imageHeight == 0) return; + + //TODO 需要实现 + + + + ///* Create bitmap for "printing" */ + //using (var image = new Bitmap(160, imageHeight)) + //{ + // /* Convert image tiles to pixels */ + // for (var y = 0; y < image.Height; y += 8) + // { + // for (var x = 0; x < image.Width; x += 8) + // { + // var tileAddress = ((y / 8) * 0x140) + ((x / 8) * 0x10); + + // for (var py = 0; py < 8; py++) + // { + // for (var px = 0; px < 8; px++) + // { + // var ba = (imageData[tileAddress + 0] >> (7 - (px % 8))) & 0b1; + // var bb = (imageData[tileAddress + 1] >> (7 - (px % 8))) & 0b1; + // var c = (byte)((bb << 1) | ba); + // image.SetPixel(x + px, y + py, defaultPalette[(byte)((palette >> (c << 1)) & 0x03)]); + // } + + // tileAddress += 2; + // } + // } + // } + + // /* Apply approximate exposure (i.e. mess with the brightness a bit) */ + // using (var adjustedImage = new Bitmap(image.Width, image.Height)) + // { + // using (var g = System.Drawing.Graphics.FromImage(adjustedImage)) + // { + // var scale = ((128 - exposure) / 128.0f) + 0.5f; + // var matrix = new float[][] + // { + // new float[] { scale, 0.0f, 0.0f, 0.0f, 0.0f }, + // new float[] { 0.0f, scale, 0.0f, 0.0f, 0.0f }, + // new float[] { 0.0f, 0.0f, scale, 0.0f, 0.0f }, + // new float[] { 0.0f, 0.0f, 0.0f, 1.0f, 0.0f }, + // new float[] { 0.0f, 0.0f, 0.0f, 0.0f, 1.0f } + // }; + + // var imageAttribs = new ImageAttributes(); + // imageAttribs.ClearColorMatrix(); + // imageAttribs.SetColorMatrix(new ColorMatrix(matrix), ColorMatrixFlag.Default, ColorAdjustType.Bitmap); + // g.DrawImage(image, new Rectangle(0, 0, adjustedImage.Width, adjustedImage.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imageAttribs); + + // /* Save the image */ + // OnSaveExtraData(new SaveExtraDataEventArgs(ExtraDataTypes.Image, ExtraDataOptions.IncludeDateTime, "Printout", adjustedImage)); + // } + // } + //} + + + } + + private void DumpPacket() + { + if (AppEnvironment.EnableLogger) + { + EssgeeLogger.WriteLine("[Received GB Printer Packet]"); + EssgeeLogger.WriteLine("- Magic bytes: 0x" + packet.magic.ToString("X4")); + EssgeeLogger.WriteLine("- Command: " + packet.command.ToString()); + EssgeeLogger.WriteLine("- Is data compressed? " + packet.isCompressed.ToString()); + EssgeeLogger.WriteLine("- Data length: 0x" + packet.dataLen.ToString("X4")); + if (packet.dataLen != 0) + { + EssgeeLogger.WriteLine("- Data (UNCOMPRESSED):"); + for (int line = 0; line < ((packet.dataLen / 16) == 0 ? 1 : (packet.dataLen / 16)); line++) + { + string msg = ""; + msg+=(" - 0x" + (line * 16).ToString("X4") + ": "); + for (int byteno = 0; byteno < ((packet.dataLen % 16) == 0 ? 0x10 : (packet.dataLen % 16)); byteno++) + msg += (packet.data[(line * 16) + byteno].ToString("X2") + " "); + EssgeeLogger.WriteLine(msg); + EssgeeLogger.WriteLine(); + } + } + EssgeeLogger.WriteLine("- Checksum: 0x" + packet.checksum.ToString("X4")); + EssgeeLogger.WriteLine("[Status Returned]"); + EssgeeLogger.WriteLine("- Presence: " + presence.ToString()); + EssgeeLogger.WriteLine("- Status: " + status.ToString()); + EssgeeLogger.WriteLine(); + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GBPrinter.cs.meta b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GBPrinter.cs.meta new file mode 100644 index 0000000..6770122 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GBPrinter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 46eed2246cd4ff74792eeedfbe26b3e6 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs new file mode 100644 index 0000000..56352de --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs @@ -0,0 +1,92 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel; +using System.IO; +using System.IO.MemoryMappedFiles; + +using Essgee.EventArguments; + +namespace Essgee.Emulation.ExtDevices.Nintendo +{ + [Description("Game Boy (Link Cable)")] + //todo Unity [ElementPriority(1)] + public class GameBoyIPC : ISerialDevice + { + // TODO: ensure correct mmf/accessor disposal? + + const string ipcName = "EssgeeGBLink"; + const int ipcLength = 16; + + const int ipcBaseOffsetSerialData = 0; + // + + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + MemoryMappedFile mmf; + MemoryMappedViewAccessor accessor; + + bool ipcConnectionExists; + int ipcOffsetSelf, ipcOffsetRemote; + + public GameBoyIPC() + { + mmf = null; + accessor = null; + + ipcConnectionExists = false; + ipcOffsetSelf = ipcOffsetRemote = 0; + } + + public void Initialize() + { + // + } + + public void Shutdown() + { + if (ipcConnectionExists) + { + accessor.Flush(); + accessor.Dispose(); + mmf.Dispose(); + + ipcConnectionExists = false; + } + } + + private void EstablishIPCConnection() + { + if (ipcConnectionExists) return; + + try + { + // Try to open existing mapped file; if it exists, assume other instance is first machine + mmf = MemoryMappedFile.OpenExisting(ipcName); + ipcOffsetSelf = ipcBaseOffsetSerialData + 1; + ipcOffsetRemote = ipcBaseOffsetSerialData + 0; + } + catch (FileNotFoundException) + { + // Mapped file does not yet exist, create file and assume this instance is first machine + mmf = MemoryMappedFile.CreateOrOpen(ipcName, ipcLength); + ipcOffsetSelf = ipcBaseOffsetSerialData + 0; + ipcOffsetRemote = ipcBaseOffsetSerialData + 1; + } + accessor = mmf.CreateViewAccessor(0, ipcLength, MemoryMappedFileAccess.ReadWrite); + + ipcConnectionExists = true; + } + + public byte ExchangeBit(int left, byte data) + { + if (!ipcConnectionExists) EstablishIPCConnection(); + + accessor.Write(ipcOffsetSelf, data); + return accessor.ReadByte(ipcOffsetRemote); + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs.meta b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs.meta new file mode 100644 index 0000000..66382cf --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ffe92e23b6dc04e4a9827a21262ccfe3 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/ISerialDevice.cs b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/ISerialDevice.cs new file mode 100644 index 0000000..a95a9c2 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/ISerialDevice.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.EventArguments; + +namespace Essgee.Emulation.ExtDevices.Nintendo +{ + public interface ISerialDevice + { + event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + + void Initialize(); + void Shutdown(); + + byte ExchangeBit(int left, byte data); + } +} diff --git a/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/ISerialDevice.cs.meta b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/ISerialDevice.cs.meta new file mode 100644 index 0000000..6f7fce5 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/ExtDevices/Nintendo/ISerialDevice.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b2db0a79b5ac7344990fd1e3711a8a4a \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Machines.meta b/Assets/Plugins/Essgee/Emulation/Machines.meta new file mode 100644 index 0000000..70899f1 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e77364449233a0e4b8f13621ff8ee825 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Machines/ColecoVision.cs b/Assets/Plugins/Essgee/Emulation/Machines/ColecoVision.cs new file mode 100644 index 0000000..6f4d42e --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/ColecoVision.cs @@ -0,0 +1,443 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.Video; +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges; +using Essgee.Emulation.Cartridges.Coleco; +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Machines +{ + [MachineIndex(4)] + public class ColecoVision : IMachine + { + // TODO: accuracy, bugfixes, PAL machines?? + + const double masterClock = 10738635; + const double refreshRate = 59.922743; + + const int ramSize = 1 * 1024; + + double vdpClock, psgClock; + + public event EventHandler<SendLogMessageEventArgs> SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler<EventArgs> EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler<RenderScreenEventArgs> RenderScreen + { + add { vdp.RenderScreen += value; } + remove { vdp.RenderScreen -= value; } + } + + public event EventHandler<SizeScreenEventArgs> SizeScreen + { + add { vdp.SizeScreen += value; } + remove { vdp.SizeScreen -= value; } + } + + public event EventHandler<ChangeViewportEventArgs> ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler<PollInputEventArgs> PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples + { + add { psg.EnqueueSamples += value; } + remove { psg.EnqueueSamples -= value; } + } + + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler<EventArgs> EnableRumble { add { } remove { } } + + public string ManufacturerName => "Coleco"; + public string ModelName => "ColecoVision"; + public string DatFilename => "Coleco - ColecoVision.dat"; + public (string Extension, string Description) FileFilter => (".col", "ColecoVision ROMs"); + public bool HasBootstrap => true; + public double RefreshRate => refreshRate; + public double PixelAspectRatio => 8.0 / 7.0; + public (string Name, string Description)[] RuntimeOptions => vdp.RuntimeOptions.Concat(psg.RuntimeOptions).ToArray(); + + ICartridge bios, cartridge; + byte[] wram; + Z80A cpu; + TMS99xxA vdp; + SN76489 psg; + + [Flags] + enum KeyJoyButtons : ushort + { + None = 0x0000, + KeyNumber6 = 0x0001, + KeyNumber1 = 0x0002, + KeyNumber3 = 0x0003, + KeyNumber9 = 0x0004, + KeyNumber0 = 0x0005, + KeyStarKey = 0x0006, + KeyInvalid8 = 0x0007, + KeyNumber2 = 0x0008, + KeyNumberSignKey = 0x0009, + KeyNumber7 = 0x000A, + KeyInvalid4 = 0x000B, + KeyNumber5 = 0x000C, + KeyNumber4 = 0x000D, + KeyNumber8 = 0x000E, + KeyMask = 0x000F, + JoyRightButton = 0x0040, + JoyUp = 0x0100, + JoyRight = 0x0200, + JoyDown = 0x0400, + JoyLeft = 0x0800, + JoyLeftButton = 0x4000, + JoyMask = 0x0F40, + } + + ushort portControls1, portControls2; + byte controlsReadMode; + + bool isNmi, isNmiPending; + + int currentMasterClockCyclesInFrame, totalMasterClockCyclesInFrame; + + Configuration.ColecoVision configuration; + + public ColecoVision() { } + + public void Initialize() + { + bios = null; + cartridge = null; + + wram = new byte[ramSize]; + cpu = new Z80A(ReadMemory, WriteMemory, ReadPort, WritePort); + vdp = new TMS99xxA(); + psg = new SN76489(); + + vdp.EndOfScanline += (s, e) => + { + PollInputEventArgs pollInputEventArgs = new PollInputEventArgs(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + }; + } + + public void SetConfiguration(IConfiguration config) + { + configuration = (Configuration.ColecoVision)config; + + ReconfigureSystem(); + } + + public object GetRuntimeOption(string name) + { + if (name.StartsWith("Graphics")) + return vdp.GetRuntimeOption(name); + else if (name.StartsWith("Audio")) + return psg.GetRuntimeOption(name); + else + return null; + } + + public void SetRuntimeOption(string name, object value) + { + if (name.StartsWith("Graphics")) + vdp.SetRuntimeOption(name, value); + else if (name.StartsWith("Audio")) + psg.SetRuntimeOption(name, value); + } + + private void ReconfigureSystem() + { + vdpClock = (masterClock / 1.0); + psgClock = (masterClock / 3.0); + + vdp?.SetClockRate(vdpClock); + vdp?.SetRefreshRate(refreshRate); + vdp?.SetRevision(0); + + psg?.SetSampleRate(StandInfo.Configuration.SampleRate); + psg?.SetOutputChannels(2); + psg?.SetClockRate(psgClock); + psg?.SetRefreshRate(refreshRate); + + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / refreshRate); + + OnChangeViewport(new ChangeViewportEventArgs(vdp.Viewport)); + } + + private void LoadBios() + { + var (type, bootstrapRomData) = CartridgeLoader.Load(configuration.BiosRom, "ColecoVision BIOS"); + bios = new ColecoCartridge(bootstrapRomData.Length, 0); + bios.LoadRom(bootstrapRomData); + } + + public void Startup() + { + LoadBios(); + + cpu.Startup(); + cpu.SetStackPointer(0xFFFF); + vdp.Startup(); + psg.Startup(); + } + + public void Reset() + { + cpu.Reset(); + vdp.Reset(); + psg.Reset(); + + portControls1 = portControls2 = 0xFFFF; + controlsReadMode = 0x00; + + isNmi = isNmiPending = false; + + OnEmulationReset(EventArgs.Empty); + } + + public void Shutdown() + { + cpu?.Shutdown(); + vdp?.Shutdown(); + psg?.Shutdown(); + } + + public void SetState(Dictionary<string, dynamic> state) + { + SaveStateHandler.PerformSetState(cartridge, state[nameof(cartridge)]); + wram = state[nameof(wram)]; + SaveStateHandler.PerformSetState(cpu, state[nameof(cpu)]); + SaveStateHandler.PerformSetState(vdp, state[nameof(vdp)]); + SaveStateHandler.PerformSetState(psg, state[nameof(psg)]); + + portControls1 = state[nameof(portControls1)]; + portControls2 = state[nameof(portControls2)]; + controlsReadMode = state[nameof(controlsReadMode)]; + isNmi = state[nameof(isNmi)]; + isNmiPending = state[nameof(isNmiPending)]; + + ReconfigureSystem(); + } + + public Dictionary<string, dynamic> GetState() + { + return new Dictionary<string, dynamic> + { + [nameof(cartridge)] = SaveStateHandler.PerformGetState(cartridge), + [nameof(wram)] = wram, + [nameof(cpu)] = SaveStateHandler.PerformGetState(cpu), + [nameof(vdp)] = SaveStateHandler.PerformGetState(vdp), + [nameof(psg)] = SaveStateHandler.PerformGetState(psg), + + [nameof(portControls1)] = portControls1, + [nameof(portControls2)] = portControls2, + [nameof(controlsReadMode)] = controlsReadMode, + [nameof(isNmi)] = isNmi, + [nameof(isNmiPending)] = isNmiPending + }; + } + + public Dictionary<string, dynamic> GetDebugInformation() + { + var dict = new Dictionary<string, dynamic> + { + { "CyclesInFrame", currentMasterClockCyclesInFrame }, + }; + + return dict; + } + + public void Load(byte[] romData, byte[] ramData, Type mapperType) + { + if (mapperType == null) + mapperType = typeof(ColecoCartridge); + + cartridge = (ICartridge)Activator.CreateInstance(mapperType, new object[] { romData.Length, 0 }); + cartridge.LoadRom(romData); + } + + public byte[] GetCartridgeRam() + { + return cartridge?.GetRamData(); + } + + public bool IsCartridgeRamSaveNeeded() + { + if (cartridge == null) return false; + return cartridge.IsRamSaveNeeded(); + } + + public virtual void RunFrame() + { + while (currentMasterClockCyclesInFrame < totalMasterClockCyclesInFrame) + RunStep(); + + currentMasterClockCyclesInFrame -= totalMasterClockCyclesInFrame; + } + + public void RunStep() + { + double currentCpuClockCycles = 0.0; + currentCpuClockCycles += cpu.Step(); + + double currentMasterClockCycles = (currentCpuClockCycles * 3.0); + + vdp.Step((int)Math.Round(currentMasterClockCycles)); + + if (vdp.InterruptLine == InterruptState.Assert && !isNmi) isNmiPending = true; + isNmi = (vdp.InterruptLine == InterruptState.Assert); + if (isNmiPending) + { + isNmiPending = false; + cpu.SetInterruptLine(InterruptType.NonMaskable, InterruptState.Assert); + } + + psg.Step((int)Math.Round(currentCpuClockCycles)); + + cartridge?.Step((int)Math.Round(currentCpuClockCycles)); + + currentMasterClockCyclesInFrame += (int)Math.Round(currentMasterClockCycles); + } + + private void ParseInput(PollInputEventArgs eventArgs) + { + var keysDown = eventArgs.Keyboard; + + ushort dataCtrl1 = 0x0000; + + if (keysDown.Contains(configuration.ControlsKeypad1)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumber1; + if (keysDown.Contains(configuration.ControlsKeypad2)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumber2; + if (keysDown.Contains(configuration.ControlsKeypad3)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumber3; + if (keysDown.Contains(configuration.ControlsKeypad4)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumber4; + if (keysDown.Contains(configuration.ControlsKeypad5)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumber5; + if (keysDown.Contains(configuration.ControlsKeypad6)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumber6; + if (keysDown.Contains(configuration.ControlsKeypad7)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumber7; + if (keysDown.Contains(configuration.ControlsKeypad8)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumber8; + if (keysDown.Contains(configuration.ControlsKeypad9)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumber9; + if (keysDown.Contains(configuration.ControlsKeypad0)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumber0; + if (keysDown.Contains(configuration.ControlsKeypadStar)) dataCtrl1 = (ushort)KeyJoyButtons.KeyStarKey; + if (keysDown.Contains(configuration.ControlsKeypadNumberSign)) dataCtrl1 = (ushort)KeyJoyButtons.KeyNumberSignKey; + + if (keysDown.Contains(configuration.ControlsButtonRight)) dataCtrl1 |= (ushort)KeyJoyButtons.JoyRightButton; + + if (keysDown.Contains(configuration.ControlsUp)) dataCtrl1 |= (ushort)KeyJoyButtons.JoyUp; + if (keysDown.Contains(configuration.ControlsDown)) dataCtrl1 |= (ushort)KeyJoyButtons.JoyDown; + if (keysDown.Contains(configuration.ControlsLeft)) dataCtrl1 |= (ushort)KeyJoyButtons.JoyLeft; + if (keysDown.Contains(configuration.ControlsRight)) dataCtrl1 |= (ushort)KeyJoyButtons.JoyRight; + if (keysDown.Contains(configuration.ControlsButtonLeft)) dataCtrl1 |= (ushort)KeyJoyButtons.JoyLeftButton; + + portControls1 = (ushort)~dataCtrl1; + + // TODO: controller 2 + + portControls2 = 0xFFFF; + } + + private byte ReadMemory(ushort address) + { + if (address >= 0x0000 && address <= 0x1FFF) + { + return (bios?.Read(address) ?? 0x00); + } + else if (address >= 0x2000 && address <= 0x3FFF) + { + /* Expansion port */ + } + else if (address >= 0x4000 && address <= 0x5FFF) + { + /* Expansion port */ + } + else if (address >= 0x6000 && address <= 0x7FFF) + { + return wram[address & (ramSize - 1)]; + } + else if (address >= 0x8000 && address <= 0xFFFF) + { + return (cartridge != null ? cartridge.Read(address) : (byte)0x00); + } + + /* Cannot read from address, return 0 */ + return 0x00; + } + + private void WriteMemory(ushort address, byte value) + { + if (address >= 0x0000 && address <= 0x1FFF) + { + /* Can't write to BIOS */ + } + else if (address >= 0x2000 && address <= 0x3FFF) + { + /* Expansion port */ + } + else if (address >= 0x4000 && address <= 0x5FFF) + { + /* Expansion port */ + } + else if (address >= 0x6000 && address <= 0x7FFF) + { + wram[address & (ramSize - 1)] = value; + } + else if (address >= 0x8000 && address <= 0xFFFF) + { + cartridge?.Write(address, value); + } + } + + private byte ReadPort(byte port) + { + switch (port & 0xE0) + { + case 0xA0: /* VDP ports */ + return vdp.ReadPort(port); + + case 0xE0: /* Controls */ + if ((port & 0x01) == 0) + return (controlsReadMode == 0x00 ? (byte)(portControls1 & 0xFF) : (byte)((portControls1 >> 8) & 0xFF)); + else + return (controlsReadMode == 0x00 ? (byte)(portControls2 & 0xFF) : (byte)((portControls2 >> 8) & 0xFF)); + + default: + return 0xFF; /* Not connected */ + } + } + + public void WritePort(byte port, byte value) + { + switch (port & 0xE0) + { + case 0x80: /* Control mode (keypad/right buttons) */ + controlsReadMode = 0x00; + break; + + case 0xA0: /* VDP */ + vdp.WritePort(port, value); + break; + + case 0xC0: /* Control mode (joystick/left buttons) */ + controlsReadMode = 0x01; + break; + + case 0xE0: /* PSG */ + psg.WritePort(port, value); + break; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Machines/ColecoVision.cs.meta b/Assets/Plugins/Essgee/Emulation/Machines/ColecoVision.cs.meta new file mode 100644 index 0000000..f3f14c5 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/ColecoVision.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6861fcd381d9ac84c9ec16b4d8eb2bce \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Machines/GameBoy.cs b/Assets/Plugins/Essgee/Emulation/Machines/GameBoy.cs new file mode 100644 index 0000000..5c39580 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/GameBoy.cs @@ -0,0 +1,704 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel; + +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.Video.Nintendo; +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges.Nintendo; +using Essgee.Emulation.ExtDevices.Nintendo; +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Machines +{ + [MachineIndex(5)] + public class GameBoy : IMachine + { + const double masterClock = 4194304; + const double refreshRate = 59.727500569606; + + const int wramSize = 8 * 1024; + const int hramSize = 0x7F; + + const int serialCycleCount = 512; + + public event EventHandler<SendLogMessageEventArgs> SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler<EventArgs> EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler<RenderScreenEventArgs> RenderScreen + { + add { video.RenderScreen += value; } + remove { video.RenderScreen -= value; } + } + + public event EventHandler<SizeScreenEventArgs> SizeScreen + { + add { video.SizeScreen += value; } + remove { video.SizeScreen -= value; } + } + + public event EventHandler<ChangeViewportEventArgs> ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler<PollInputEventArgs> PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples + { + add { audio.EnqueueSamples += value; } + remove { audio.EnqueueSamples -= value; } + } + + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler<EventArgs> EnableRumble; + protected virtual void OnEnableRumble(EventArgs e) { EnableRumble?.Invoke(this, EventArgs.Empty); } + + public string ManufacturerName => "Nintendo"; + public string ModelName => "Game Boy"; + public string DatFilename => "Nintendo - Game Boy.dat"; + public (string Extension, string Description) FileFilter => (".gb", "Game Boy ROMs"); + public bool HasBootstrap => true; + public double RefreshRate => refreshRate; + public double PixelAspectRatio => 1.0; + public (string Name, string Description)[] RuntimeOptions => video.RuntimeOptions.Concat(audio.RuntimeOptions).ToArray(); + + byte[] bootstrap; + IGameBoyCartridge cartridge; + byte[] wram, hram; + byte ie; + SM83 cpu; + DMGVideo video; + DMGAudio audio; + ISerialDevice serialDevice; + + // FF00 - P1/JOYP + byte joypadRegister; + + // FF01 - SB + byte serialData; + // FF02 - SC + bool serialUseInternalClock, serialTransferInProgress; + + // FF04 - DIV + byte divider; + // FF05 - TIMA + byte timerCounter; + // + ushort clockCycleCount; + + // FF06 - TMA + byte timerModulo; + + // FF07 - TAC + bool timerRunning; + byte timerInputClock; + // + bool timerOverflow, timerLoading; + + // FF0F - IF + bool irqVBlank, irqLCDCStatus, irqTimerOverflow, irqSerialIO, irqKeypad; + + // FF50 + bool bootstrapDisabled; + + [Flags] + enum JoypadInputs : byte + { + Right = (1 << 0), + Left = (1 << 1), + Up = (1 << 2), + Down = (1 << 3), + A = (1 << 4), + B = (1 << 5), + Select = (1 << 6), + Start = (1 << 7) + } + + JoypadInputs inputsPressed; + + int serialBitsCounter, serialCycles; + + int currentMasterClockCyclesInFrame, totalMasterClockCyclesInFrame; + + Configuration.GameBoy configuration; + + public GameBoy() { } + + public void Initialize() + { + bootstrap = null; + cartridge = null; + + wram = new byte[wramSize]; + hram = new byte[hramSize]; + cpu = new SM83(ReadMemory, WriteMemory); + video = new DMGVideo(ReadMemory, cpu.RequestInterrupt); + audio = new DMGAudio(); + + video.EndOfScanline += (s, e) => + { + PollInputEventArgs pollInputEventArgs = new PollInputEventArgs(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + }; + } + + public void SetConfiguration(IConfiguration config) + { + configuration = (Configuration.GameBoy)config; + + ReconfigureSystem(); + } + + public object GetRuntimeOption(string name) + { + if (name.StartsWith("Graphics")) + return video.GetRuntimeOption(name); + else if (name.StartsWith("Audio")) + return audio.GetRuntimeOption(name); + else + return null; + } + + public void SetRuntimeOption(string name, object value) + { + if (name.StartsWith("Graphics")) + video.SetRuntimeOption(name, value); + else if (name.StartsWith("Audio")) + audio.SetRuntimeOption(name, value); + } + + private void ReconfigureSystem() + { + /* Video */ + video?.SetClockRate(masterClock); + video?.SetRefreshRate(refreshRate); + video?.SetRevision(0); + + /* Audio */ + audio?.SetSampleRate(StandInfo.Configuration.SampleRate); + audio?.SetOutputChannels(2); + audio?.SetClockRate(masterClock); + audio?.SetRefreshRate(refreshRate); + + /* Cartridge */ + if (cartridge is GBCameraCartridge camCartridge) + camCartridge.SetImageSource(configuration.CameraSource, configuration.CameraImageFile); + + /* Serial */ + if (serialDevice != null) + { + serialDevice.SaveExtraData -= SaveExtraData; + serialDevice.Shutdown(); + } + + serialDevice = (ISerialDevice)Activator.CreateInstance(configuration.SerialDevice); + serialDevice.SaveExtraData += SaveExtraData; + serialDevice.Initialize(); + + /* Misc timing */ + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / refreshRate); + + /* Announce viewport */ + OnChangeViewport(new ChangeViewportEventArgs(video.Viewport)); + } + + private void LoadBootstrap() + { + if (configuration.UseBootstrap) + { + var (type, bootstrapRomData) = CartridgeLoader.Load(configuration.BootstrapRom, "Game Boy Bootstrap"); + bootstrap = new byte[bootstrapRomData.Length]; + Buffer.BlockCopy(bootstrapRomData, 0, bootstrap, 0, bootstrap.Length); + } + } + + public void Startup() + { + LoadBootstrap(); + + cpu.Startup(); + video.Startup(); + audio.Startup(); + } + + public void Reset() + { + cpu.Reset(); + video.Reset(); + audio.Reset(); + + if (configuration.UseBootstrap) + { + cpu.SetProgramCounter(0x0000); + cpu.SetStackPointer(0x0000); + } + else + { + cpu.SetProgramCounter(0x0100); + cpu.SetStackPointer(0xFFFE); + cpu.SetRegisterAF(0x01B0); + cpu.SetRegisterBC(0x0013); + cpu.SetRegisterDE(0x00D8); + cpu.SetRegisterHL(0x014D); + + video.WritePort(0x40, 0x91); + video.WritePort(0x42, 0x00); + video.WritePort(0x43, 0x00); + video.WritePort(0x45, 0x00); + video.WritePort(0x47, 0xFC); + video.WritePort(0x48, 0xFF); + video.WritePort(0x49, 0xFF); + video.WritePort(0x4A, 0x00); + video.WritePort(0x4B, 0x00); + } + + joypadRegister = 0x0F; + + serialData = 0xFF; + serialUseInternalClock = serialTransferInProgress = false; + + timerCounter = 0; + clockCycleCount = 0; + + timerModulo = 0; + + timerRunning = false; + timerInputClock = 0; + + timerOverflow = timerLoading = false; + + irqVBlank = irqLCDCStatus = irqTimerOverflow = irqSerialIO = irqKeypad = false; + + bootstrapDisabled = !configuration.UseBootstrap; + + inputsPressed = 0; + + serialBitsCounter = serialCycles = 0; + + OnEmulationReset(EventArgs.Empty); + } + + public void Shutdown() + { + if (serialDevice != null) + { + serialDevice.SaveExtraData -= SaveExtraData; + serialDevice.Shutdown(); + } + + if (cartridge is MBC5Cartridge mbc5Cartridge) + mbc5Cartridge.EnableRumble -= EnableRumble; + + cpu?.Shutdown(); + video?.Shutdown(); + audio?.Shutdown(); + } + + public void SetState(Dictionary<string, dynamic> state) + { + throw new NotImplementedException(); + } + + public Dictionary<string, dynamic> GetState() + { + throw new NotImplementedException(); + } + + public Dictionary<string, dynamic> GetDebugInformation() + { + var dict = new Dictionary<string, dynamic> + { + { "CyclesInFrame", currentMasterClockCyclesInFrame }, + }; + + return dict; + } + + public void Load(byte[] romData, byte[] ramData, Type mapperType) + { + cartridge = SpecializedLoader.CreateCartridgeInstance(romData, ramData, mapperType); + + if (cartridge is GBCameraCartridge camCartridge) + camCartridge.SetImageSource(configuration.CameraSource, configuration.CameraImageFile); + + if (cartridge is MBC5Cartridge mbc5Cartridge) + mbc5Cartridge.EnableRumble += EnableRumble; + } + + public byte[] GetCartridgeRam() + { + return cartridge?.GetRamData(); + } + + public bool IsCartridgeRamSaveNeeded() + { + if (cartridge == null) return false; + return cartridge.IsRamSaveNeeded(); + } + + public virtual void RunFrame() + { + while (currentMasterClockCyclesInFrame < totalMasterClockCyclesInFrame) + RunStep(); + + currentMasterClockCyclesInFrame = 0; + } + + public void RunStep() + { + var clockCyclesInStep = cpu.Step(); + + for (var s = 0; s < clockCyclesInStep / 4; s++) + { + HandleTimerOverflow(); + UpdateCycleCounter((ushort)(clockCycleCount + 4)); + + HandleSerialIO(4); + + video.Step(4); + audio.Step(4); + cartridge?.Step(4); + + currentMasterClockCyclesInFrame += 4; + } + } + + private void IncrementTimer() + { + timerCounter++; + if (timerCounter == 0) timerOverflow = true; + } + + private bool GetTimerBit(byte value, ushort cycles) + { + switch (value & 0b11) + { + case 0: return (cycles & (1 << 9)) != 0; + case 1: return (cycles & (1 << 3)) != 0; + case 2: return (cycles & (1 << 5)) != 0; + case 3: return (cycles & (1 << 7)) != 0; + default: throw new EmulationException("Unhandled timer state"); + } + } + + private void UpdateCycleCounter(ushort value) + { + if (timerRunning) + { + if (!GetTimerBit(timerInputClock, value) && GetTimerBit(timerInputClock, clockCycleCount)) + IncrementTimer(); + } + + clockCycleCount = value; + divider = (byte)(clockCycleCount >> 8); + } + + private void HandleTimerOverflow() + { + timerLoading = false; + + if (timerOverflow) + { + cpu.RequestInterrupt(SM83.InterruptSource.TimerOverflow); + timerOverflow = false; + + timerCounter = timerModulo; + timerLoading = true; + } + } + + private void HandleSerialIO(int clockCyclesInStep) + { + if (serialTransferInProgress) + { + if (serialUseInternalClock) + { + for (var c = 0; c < clockCyclesInStep; c++) + { + serialCycles++; + if (serialCycles == serialCycleCount) + { + serialCycles = 0; + + serialBitsCounter--; + + var bitToSend = (byte)((serialData >> 7) & 0b1); + var bitReceived = serialDevice.ExchangeBit(serialBitsCounter, bitToSend); + serialData = (byte)((serialData << 1) | (bitReceived & 0b1)); + + if (serialBitsCounter == 0) + { + cpu.RequestInterrupt(SM83.InterruptSource.SerialIO); + serialTransferInProgress = false; + } + } + } + } + } + } + + private void ParseInput(PollInputEventArgs eventArgs) + { + inputsPressed = 0; + + /* Keyboard */ + if (eventArgs.Keyboard.Contains(configuration.ControlsRight) && !eventArgs.Keyboard.Contains(configuration.ControlsLeft)) inputsPressed |= JoypadInputs.Right; + if (eventArgs.Keyboard.Contains(configuration.ControlsLeft) && !eventArgs.Keyboard.Contains(configuration.ControlsRight)) inputsPressed |= JoypadInputs.Left; + if (eventArgs.Keyboard.Contains(configuration.ControlsUp) && !eventArgs.Keyboard.Contains(configuration.ControlsDown)) inputsPressed |= JoypadInputs.Up; + if (eventArgs.Keyboard.Contains(configuration.ControlsDown) && !eventArgs.Keyboard.Contains(configuration.ControlsUp)) inputsPressed |= JoypadInputs.Down; + if (eventArgs.Keyboard.Contains(configuration.ControlsA)) inputsPressed |= JoypadInputs.A; + if (eventArgs.Keyboard.Contains(configuration.ControlsB)) inputsPressed |= JoypadInputs.B; + if (eventArgs.Keyboard.Contains(configuration.ControlsSelect)) inputsPressed |= JoypadInputs.Select; + if (eventArgs.Keyboard.Contains(configuration.ControlsStart)) inputsPressed |= JoypadInputs.Start; + + /* XInput controller */ + if (eventArgs.ControllerState.IsAnyRightDirectionPressed() && !eventArgs.ControllerState.IsAnyLeftDirectionPressed()) inputsPressed |= JoypadInputs.Right; + if (eventArgs.ControllerState.IsAnyLeftDirectionPressed() && !eventArgs.ControllerState.IsAnyRightDirectionPressed()) inputsPressed |= JoypadInputs.Left; + if (eventArgs.ControllerState.IsAnyUpDirectionPressed() && !eventArgs.ControllerState.IsAnyDownDirectionPressed()) inputsPressed |= JoypadInputs.Up; + if (eventArgs.ControllerState.IsAnyDownDirectionPressed() && !eventArgs.ControllerState.IsAnyUpDirectionPressed()) inputsPressed |= JoypadInputs.Down; + if (eventArgs.ControllerState.IsAPressed()) inputsPressed |= JoypadInputs.A; + if (eventArgs.ControllerState.IsXPressed() || eventArgs.ControllerState.IsBPressed()) inputsPressed |= JoypadInputs.B; + if (eventArgs.ControllerState.IsBackPressed()) inputsPressed |= JoypadInputs.Select; + if (eventArgs.ControllerState.IsStartPressed()) inputsPressed |= JoypadInputs.Start; + } + + private byte ReadMemory(ushort address) + { + if (address >= 0x0000 && address <= 0x7FFF) + { + if (configuration.UseBootstrap && address < 0x0100 && !bootstrapDisabled) + return bootstrap[address & 0x00FF]; + else + return (cartridge != null ? cartridge.Read(address) : (byte)0xFF); + } + else if (address >= 0x8000 && address <= 0x9FFF) + { + return video.ReadVram(address); + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + return (cartridge != null ? cartridge.Read(address) : (byte)0xFF); + } + else if (address >= 0xC000 && address <= 0xFDFF) + { + return wram[address & (wramSize - 1)]; + } + else if (address >= 0xFE00 && address <= 0xFE9F) + { + return video.ReadOam(address); + } + else if (address >= 0xFF00 && address <= 0xFF7F) + { + return ReadIo(address); + } + else if (address >= 0xFF80 && address <= 0xFFFE) + { + return hram[address - 0xFF80]; + } + else if (address == 0xFFFF) + { + return ie; + } + + /* Cannot read from address, return 0 */ + return 0x00; + } + + private byte ReadIo(ushort address) + { + if ((address & 0xFFF0) == 0xFF40) + return video.ReadPort((byte)(address & 0xFF)); + else if ((address & 0xFFF0) == 0xFF10 || (address & 0xFFF0) == 0xFF20 || (address & 0xFFF0) == 0xFF30) + return audio.ReadPort((byte)(address & 0xFF)); + else + { + switch (address) + { + case 0xFF00: + // P1/JOYP + return joypadRegister; + + case 0xFF01: + // SB + return serialData; + + case 0xFF02: + // SC + return (byte)( + 0x7E | + (serialUseInternalClock ? (1 << 0) : 0) | + (serialTransferInProgress ? (1 << 7) : 0)); + + case 0xFF04: + // DIV + return divider; + + case 0xFF05: + // TIMA + return timerCounter; + + case 0xFF06: + // TMA + return timerModulo; + + case 0xFF07: + // TAC + return (byte)( + 0xF8 | + (timerRunning ? (1 << 2) : 0) | + (timerInputClock & 0b11)); + + case 0xFF0F: + // IF + return (byte)( + 0xE0 | + (irqVBlank ? (1 << 0) : 0) | + (irqLCDCStatus ? (1 << 1) : 0) | + (irqTimerOverflow ? (1 << 2) : 0) | + (irqSerialIO ? (1 << 3) : 0) | + (irqKeypad ? (1 << 4) : 0)); + + case 0xFF50: + // Bootstrap disable + return (byte)( + 0xFE | + (bootstrapDisabled ? (1 << 0) : 0)); + + default: + return 0xFF;// throw new NotImplementedException(); + } + } + } + + private void WriteMemory(ushort address, byte value) + { + if (address >= 0x0000 && address <= 0x7FFF) + { + cartridge?.Write(address, value); + } + else if (address >= 0x8000 && address <= 0x9FFF) + { + video.WriteVram(address, value); + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + cartridge?.Write(address, value); + } + else if (address >= 0xC000 && address <= 0xFDFF) + { + wram[address & (wramSize - 1)] = value; + } + else if (address >= 0xFE00 && address <= 0xFE9F) + { + video.WriteOam(address, value); + } + else if (address >= 0xFF00 && address <= 0xFF7F) + { + WriteIo(address, value); + } + else if (address >= 0xFF80 && address <= 0xFFFE) + { + hram[address - 0xFF80] = value; + } + else if (address == 0xFFFF) + { + ie = value; + } + } + + private void WriteIo(ushort address, byte value) + { + if ((address & 0xFFF0) == 0xFF40) + video.WritePort((byte)(address & 0xFF), value); + else if ((address & 0xFFF0) == 0xFF10 || (address & 0xFFF0) == 0xFF20 || (address & 0xFFF0) == 0xFF30) + audio.WritePort((byte)(address & 0xFF), value); + else + { + switch (address) + { + case 0xFF00: + joypadRegister = (byte)((joypadRegister & 0xC0) | (value & 0x30)); + if ((joypadRegister & 0x30) == 0x20) + joypadRegister |= (byte)(((byte)inputsPressed & 0x0F) ^ 0x0F); + else if ((joypadRegister & 0x30) == 0x10) + joypadRegister |= (byte)((((byte)inputsPressed & 0xF0) >> 4) ^ 0x0F); + else + joypadRegister = 0xFF; + break; + + case 0xFF01: + serialData = value; + break; + + case 0xFF02: + serialUseInternalClock = (value & (1 << 0)) != 0; + serialTransferInProgress = (value & (1 << 7)) != 0; + + if (serialTransferInProgress) serialCycles = 0; + serialBitsCounter = 8; + break; + + case 0xFF04: + UpdateCycleCounter(0); + break; + + case 0xFF05: + if (!timerLoading) + { + timerCounter = value; + timerOverflow = false; + } + break; + + case 0xFF06: + timerModulo = value; + if (timerLoading) + timerCounter = value; + break; + + case 0xFF07: + { + var newTimerRunning = (value & (1 << 2)) != 0; + var newTimerInputClock = (byte)(value & 0b11); + + var oldBit = timerRunning && GetTimerBit(timerInputClock, clockCycleCount); + var newBit = newTimerRunning && GetTimerBit(newTimerInputClock, clockCycleCount); + + if (oldBit && !newBit) + IncrementTimer(); + + timerRunning = newTimerRunning; + timerInputClock = newTimerInputClock; + } + break; + + case 0xFF0F: + irqVBlank = (value & (1 << 0)) != 0; + irqLCDCStatus = (value & (1 << 1)) != 0; + irqTimerOverflow = (value & (1 << 2)) != 0; + irqSerialIO = (value & (1 << 3)) != 0; + irqKeypad = (value & (1 << 4)) != 0; + break; + + case 0xFF50: + if (!bootstrapDisabled) + bootstrapDisabled = (value & (1 << 0)) != 0; + break; + } + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Machines/GameBoy.cs.meta b/Assets/Plugins/Essgee/Emulation/Machines/GameBoy.cs.meta new file mode 100644 index 0000000..9399769 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/GameBoy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d24bc8f5b7f44b2439d6860b8d024aee \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Machines/GameBoyColor.cs b/Assets/Plugins/Essgee/Emulation/Machines/GameBoyColor.cs new file mode 100644 index 0000000..fee10c5 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/GameBoyColor.cs @@ -0,0 +1,877 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel; +using System.IO; + +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.Video.Nintendo; +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges.Nintendo; +using Essgee.Emulation.ExtDevices.Nintendo; +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Machines +{ + [MachineIndex(6)] + public class GameBoyColor : IMachine + { + const double masterClock = 4194304; + const double refreshRate = 59.727500569606; + + const int wramSize = 8 * 1024; + const int hramSize = 0x7F; + + const int serialCycleCountNormal = 512; + const int serialCycleCountFast = 16; + + public event EventHandler<SendLogMessageEventArgs> SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler<EventArgs> EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler<RenderScreenEventArgs> RenderScreen + { + add { video.RenderScreen += value; } + remove { video.RenderScreen -= value; } + } + + public event EventHandler<SizeScreenEventArgs> SizeScreen + { + add { video.SizeScreen += value; } + remove { video.SizeScreen -= value; } + } + + public event EventHandler<ChangeViewportEventArgs> ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler<PollInputEventArgs> PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples + { + add { audio.EnqueueSamples += value; } + remove { audio.EnqueueSamples -= value; } + } + + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler<EventArgs> EnableRumble; + protected virtual void OnEnableRumble(EventArgs e) { EnableRumble?.Invoke(this, EventArgs.Empty); } + + public string ManufacturerName => "Nintendo"; + public string ModelName => "Game Boy Color"; + public string DatFilename => "Nintendo - Game Boy Color.dat"; + public (string Extension, string Description) FileFilter => (".gbc", "Game Boy Color ROMs"); + public bool HasBootstrap => true; + public double RefreshRate => refreshRate; + public double PixelAspectRatio => 1.0; + public (string Name, string Description)[] RuntimeOptions => video.RuntimeOptions.Concat(audio.RuntimeOptions).ToArray(); + + byte[] bootstrap; + IGameBoyCartridge cartridge; + byte[,] wram; + byte[] hram; + byte ie; + SM83CGB cpu; + CGBVideo video; + CGBAudio audio; + ISerialDevice serialDevice; + + // FF00 - P1/JOYP + byte joypadRegister; + + // FF01 - SB + byte serialData; + // FF02 - SC + bool serialUseInternalClock, serialFastClockSpeed, serialTransferInProgress; + + // FF04 - DIV + byte divider; + // FF05 - TIMA + byte timerCounter; + // + ushort clockCycleCount; + + // FF06 - TMA + byte timerModulo; + + // FF07 - TAC + bool timerRunning; + byte timerInputClock; + // + bool timerOverflow, timerLoading; + + // FF0F - IF + bool irqVBlank, irqLCDCStatus, irqTimerOverflow, irqSerialIO, irqKeypad; + + // FF4C + + // FF4D - KEY1 + bool speedIsDouble, speedSwitchPending; + + // FF50 + bool bootstrapDisabled; + + // FF56 - RP + bool irSendingSignal, irNotReceivingSignal, irReadEnableA, irReadEnableB; + + // FF70 - SVBK + byte wramBank; + + public enum InfraredSources + { + [Description("None")] + None, + [Description("Random")] + Random, + [Description("Constant Light (Lamp)")] + ConstantOn, + [Description("Pocket Pikachu Color")] + PocketPikachuColor + } + ushort[] irDatabase; + int irDatabaseBaseIndex, irDatabaseStep; + int irDatabaseCurrentIndex, irCycles; + bool irExternalTransferActive; + + [Flags] + enum JoypadInputs : byte + { + Right = (1 << 0), + Left = (1 << 1), + Up = (1 << 2), + Down = (1 << 3), + A = (1 << 4), + B = (1 << 5), + Select = (1 << 6), + Start = (1 << 7) + } + + JoypadInputs inputsPressed; + + int serialBitsCounter, serialCycles, clockCyclesPerSerialBit; + + int currentMasterClockCyclesInFrame, totalMasterClockCyclesInFrame; + + Configuration.GameBoyColor configuration; + + public GameBoyColor() { } + + public void Initialize() + { + bootstrap = null; + cartridge = null; + + wram = new byte[8, wramSize]; + hram = new byte[hramSize]; + cpu = new SM83CGB(ReadMemory, WriteMemory); + video = new CGBVideo(ReadMemory, cpu.RequestInterrupt); + audio = new CGBAudio(); + serialDevice = null; + + video.EndOfScanline += (s, e) => + { + PollInputEventArgs pollInputEventArgs = new PollInputEventArgs(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + }; + } + + public void SetConfiguration(IConfiguration config) + { + configuration = (Configuration.GameBoyColor)config; + + ReconfigureSystem(); + } + + public object GetRuntimeOption(string name) + { + if (name.StartsWith("Graphics")) + return video.GetRuntimeOption(name); + else if (name.StartsWith("Audio")) + return audio.GetRuntimeOption(name); + else + return null; + } + + public void SetRuntimeOption(string name, object value) + { + if (name.StartsWith("Graphics")) + video.SetRuntimeOption(name, value); + else if (name.StartsWith("Audio")) + audio.SetRuntimeOption(name, value); + } + + private void ReconfigureSystem() + { + /* Video */ + video?.SetClockRate(masterClock); + video?.SetRefreshRate(refreshRate); + video?.SetRevision(0); + + /* Audio */ + audio?.SetSampleRate(StandInfo.Configuration.SampleRate); + audio?.SetOutputChannels(2); + audio?.SetClockRate(masterClock); + audio?.SetRefreshRate(refreshRate); + + /* Cartridge */ + if (cartridge is GBCameraCartridge camCartridge) + camCartridge.SetImageSource(configuration.CameraSource, configuration.CameraImageFile); + + /* Serial */ + if (serialDevice != null) + { + serialDevice.SaveExtraData -= SaveExtraData; + serialDevice.Shutdown(); + } + + serialDevice = (ISerialDevice)Activator.CreateInstance(configuration.SerialDevice); + serialDevice.SaveExtraData += SaveExtraData; + serialDevice.Initialize(); + + /* Infrared */ + irDatabaseBaseIndex = 0; + irDatabaseStep = 0; + irDatabaseCurrentIndex = irCycles = 0; + irExternalTransferActive = false; + + if (configuration.InfraredSource == InfraredSources.PocketPikachuColor && File.Exists(configuration.InfraredDatabasePikachu)) + { + using (var reader = new BinaryReader(new FileStream(configuration.InfraredDatabasePikachu, FileMode.Open, FileAccess.ReadWrite, FileShare.ReadWrite))) + { + irDatabase = new ushort[reader.BaseStream.Length / 2]; + for (var i = 0; i < irDatabase.Length; i++) + irDatabase[i] = reader.ReadUInt16(); + + irDatabaseStep = 2007; + if ((irDatabaseBaseIndex < 0) || (irDatabaseBaseIndex * irDatabaseStep >= irDatabase.Length)) + irDatabaseBaseIndex = 0; + } + } + + /* Misc timing */ + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / refreshRate); + + /* Announce viewport */ + OnChangeViewport(new ChangeViewportEventArgs(video.Viewport)); + } + + private void LoadBootstrap() + { + if (configuration.UseBootstrap) + { + var (type, bootstrapRomData) = CartridgeLoader.Load(configuration.BootstrapRom, "Game Boy Color Bootstrap"); + bootstrap = new byte[bootstrapRomData.Length]; + Buffer.BlockCopy(bootstrapRomData, 0, bootstrap, 0, bootstrap.Length); + } + } + + public void Startup() + { + LoadBootstrap(); + + cpu.Startup(); + video.Startup(); + audio.Startup(); + } + + public void Reset() + { + cpu.Reset(); + video.Reset(); + audio.Reset(); + + if (configuration.UseBootstrap) + { + cpu.SetProgramCounter(0x0000); + cpu.SetStackPointer(0x0000); + } + else + { + cpu.SetProgramCounter(0x0100); + cpu.SetStackPointer(0xFFFE); + cpu.SetRegisterAF(0x11B0); + cpu.SetRegisterBC(0x0000); + cpu.SetRegisterDE(0xFF56); + cpu.SetRegisterHL(0x000D); + + video.WritePort(0x40, 0x91); + video.WritePort(0x42, 0x00); + video.WritePort(0x43, 0x00); + video.WritePort(0x45, 0x00); + video.WritePort(0x47, 0xFC); + video.WritePort(0x48, 0xFF); + video.WritePort(0x49, 0xFF); + video.WritePort(0x4A, 0x00); + video.WritePort(0x4B, 0x00); + } + + joypadRegister = 0x0F; + + serialData = 0xFF; + serialUseInternalClock = serialFastClockSpeed = serialTransferInProgress = false; + + timerCounter = 0; + clockCycleCount = 0; + + timerModulo = 0; + + timerRunning = false; + timerInputClock = 0; + + timerOverflow = timerLoading = false; + + irqVBlank = irqLCDCStatus = irqTimerOverflow = irqSerialIO = irqKeypad = false; + + bootstrapDisabled = !configuration.UseBootstrap; + + irSendingSignal = irReadEnableA = irReadEnableB = false; + irNotReceivingSignal = true; + + wramBank = 0x01; + + inputsPressed = 0; + + serialBitsCounter = serialCycles = clockCyclesPerSerialBit = 0; + + OnEmulationReset(EventArgs.Empty); + } + + public void Shutdown() + { + if (serialDevice != null) + { + serialDevice.SaveExtraData -= SaveExtraData; + serialDevice.Shutdown(); + } + + if (cartridge is MBC5Cartridge mbc5Cartridge) + mbc5Cartridge.EnableRumble -= EnableRumble; + + cpu?.Shutdown(); + video?.Shutdown(); + audio?.Shutdown(); + } + + public void SetState(Dictionary<string, dynamic> state) + { + throw new NotImplementedException(); + } + + public Dictionary<string, dynamic> GetState() + { + throw new NotImplementedException(); + } + + public Dictionary<string, dynamic> GetDebugInformation() + { + var dict = new Dictionary<string, dynamic> + { + { "CyclesInFrame", currentMasterClockCyclesInFrame }, + }; + + return dict; + } + + public void Load(byte[] romData, byte[] ramData, Type mapperType) + { + cartridge = SpecializedLoader.CreateCartridgeInstance(romData, ramData, mapperType); + + if (cartridge is GBCameraCartridge camCartridge) + camCartridge.SetImageSource(configuration.CameraSource, configuration.CameraImageFile); + + if (cartridge is MBC5Cartridge mbc5Cartridge) + mbc5Cartridge.EnableRumble += EnableRumble; + } + + public byte[] GetCartridgeRam() + { + return cartridge?.GetRamData(); + } + + public bool IsCartridgeRamSaveNeeded() + { + if (cartridge == null) return false; + return cartridge.IsRamSaveNeeded(); + } + + public virtual void RunFrame() + { + while (currentMasterClockCyclesInFrame < totalMasterClockCyclesInFrame) + RunStep(); + + currentMasterClockCyclesInFrame = 0; + } + + public void RunStep() + { + // TODO: verify if current handling of CGB double speed mode is correct! seems to work but is probably wrong?? + // NOTES: + // https://github.com/LIJI32/GBVideoPlayer/blob/master/How%20It%20Works.md#hblank-and-sub-hblank-tricks + // https://gbdev.io/pandocs/#ff4d-key1-cgb-mode-only-prepare-speed-switch + + var clockCyclesInStep = RunCpuStep(); + var cycleLength = cpu.IsDoubleSpeed ? 2 : 4; + + video.IsDoubleSpeed = cpu.IsDoubleSpeed; + + for (var s = 0; s < clockCyclesInStep / 4; s++) + { + HandleTimerOverflow(); + UpdateCycleCounter((ushort)(clockCycleCount + 4)); + + HandleSerialIO(cycleLength); + + HandleIRCommunication(cycleLength); + + video.Step(cycleLength); + audio.Step(cycleLength); + cartridge?.Step(cycleLength); + + currentMasterClockCyclesInFrame += cycleLength; + } + } + + private int RunCpuStep() + { + if (video.GDMAWaitCycles > 0) + { + var cycleLength = cpu.IsDoubleSpeed ? 2 : 4; + video.GDMAWaitCycles -= cycleLength; + return cycleLength; + } + else + return cpu.Step(); + } + + private void IncrementTimer() + { + timerCounter++; + if (timerCounter == 0) timerOverflow = true; + } + + private bool GetTimerBit(byte value, ushort cycles) + { + switch (value & 0b11) + { + case 0: return (cycles & (1 << 9)) != 0; + case 1: return (cycles & (1 << 3)) != 0; + case 2: return (cycles & (1 << 5)) != 0; + case 3: return (cycles & (1 << 7)) != 0; + default: throw new EmulationException("Unhandled timer state"); + } + } + + private void UpdateCycleCounter(ushort value) + { + if (timerRunning) + { + if (!GetTimerBit(timerInputClock, value) && GetTimerBit(timerInputClock, clockCycleCount)) + IncrementTimer(); + } + + clockCycleCount = value; + divider = (byte)(clockCycleCount >> 8); + } + + private void HandleTimerOverflow() + { + timerLoading = false; + + if (timerOverflow) + { + cpu.RequestInterrupt(SM83.InterruptSource.TimerOverflow); + timerOverflow = false; + + timerCounter = timerModulo; + timerLoading = true; + } + } + + private void HandleSerialIO(int clockCyclesInStep) + { + if (serialTransferInProgress) + { + for (var c = 0; c < clockCyclesInStep; c++) + { + serialCycles++; + if (serialCycles == clockCyclesPerSerialBit) + { + serialCycles = 0; + + serialBitsCounter--; + + var bitToSend = (byte)((serialData >> 7) & 0b1); + var bitReceived = serialDevice.ExchangeBit(serialBitsCounter, bitToSend); + serialData = (byte)((serialData << 1) | (bitReceived & 0b1)); + + if (serialBitsCounter == 0) + { + cpu.RequestInterrupt(SM83.InterruptSource.SerialIO); + serialTransferInProgress = false; + } + } + } + } + } + + private void HandleIRCommunication(int clockCyclesInStep) + { + switch (configuration.InfraredSource) + { + case InfraredSources.None: + irNotReceivingSignal = true; + break; + + case InfraredSources.Random: + irNotReceivingSignal = (StandInfo.Random.Next(256) % 2) == 0; + break; + + case InfraredSources.ConstantOn: + irNotReceivingSignal = false; + break; + + case InfraredSources.PocketPikachuColor: + if (irExternalTransferActive) + { + for (var c = 0; c < clockCyclesInStep; c++) + { + irCycles++; + if (irCycles == irDatabase[(irDatabaseBaseIndex * irDatabaseStep) + irDatabaseCurrentIndex]) + { + irCycles = 0; + + irNotReceivingSignal = !irNotReceivingSignal; + + irDatabaseCurrentIndex++; + if (irDatabaseCurrentIndex >= irDatabaseStep) + { + irDatabaseCurrentIndex = 0; + irExternalTransferActive = false; + irNotReceivingSignal = true; + } + } + } + } + break; + } + } + + private void ParseInput(PollInputEventArgs eventArgs) + { + inputsPressed = 0; + + /* Keyboard */ + if (eventArgs.Keyboard.Contains(configuration.ControlsRight) && !eventArgs.Keyboard.Contains(configuration.ControlsLeft)) inputsPressed |= JoypadInputs.Right; + if (eventArgs.Keyboard.Contains(configuration.ControlsLeft) && !eventArgs.Keyboard.Contains(configuration.ControlsRight)) inputsPressed |= JoypadInputs.Left; + if (eventArgs.Keyboard.Contains(configuration.ControlsUp) && !eventArgs.Keyboard.Contains(configuration.ControlsDown)) inputsPressed |= JoypadInputs.Up; + if (eventArgs.Keyboard.Contains(configuration.ControlsDown) && !eventArgs.Keyboard.Contains(configuration.ControlsUp)) inputsPressed |= JoypadInputs.Down; + if (eventArgs.Keyboard.Contains(configuration.ControlsA)) inputsPressed |= JoypadInputs.A; + if (eventArgs.Keyboard.Contains(configuration.ControlsB)) inputsPressed |= JoypadInputs.B; + if (eventArgs.Keyboard.Contains(configuration.ControlsSelect)) inputsPressed |= JoypadInputs.Select; + if (eventArgs.Keyboard.Contains(configuration.ControlsStart)) inputsPressed |= JoypadInputs.Start; + + if (eventArgs.Keyboard.Contains(configuration.ControlsSendIR)) + { + irExternalTransferActive = true; + irDatabaseCurrentIndex = 0; + irCycles = 0; + + irNotReceivingSignal = false; + } + + /* XInput controller */ + if (eventArgs.ControllerState.IsAnyRightDirectionPressed() && !eventArgs.ControllerState.IsAnyLeftDirectionPressed()) inputsPressed |= JoypadInputs.Right; + if (eventArgs.ControllerState.IsAnyLeftDirectionPressed() && !eventArgs.ControllerState.IsAnyRightDirectionPressed()) inputsPressed |= JoypadInputs.Left; + if (eventArgs.ControllerState.IsAnyUpDirectionPressed() && !eventArgs.ControllerState.IsAnyDownDirectionPressed()) inputsPressed |= JoypadInputs.Up; + if (eventArgs.ControllerState.IsAnyDownDirectionPressed() && !eventArgs.ControllerState.IsAnyUpDirectionPressed()) inputsPressed |= JoypadInputs.Down; + if (eventArgs.ControllerState.IsAPressed()) inputsPressed |= JoypadInputs.A; + if (eventArgs.ControllerState.IsXPressed() || eventArgs.ControllerState.IsBPressed()) inputsPressed |= JoypadInputs.B; + if (eventArgs.ControllerState.IsBackPressed()) inputsPressed |= JoypadInputs.Select; + if (eventArgs.ControllerState.IsStartPressed()) inputsPressed |= JoypadInputs.Start; + } + + private byte ReadMemory(ushort address) + { + if (address >= 0x0000 && address <= 0x7FFF) + { + if (configuration.UseBootstrap && (address <= 0x00FF || (address >= 0x0200 && address <= 0x08FF)) && !bootstrapDisabled) + return bootstrap[address]; + else + return (cartridge != null ? cartridge.Read(address) : (byte)0xFF); + } + else if (address >= 0x8000 && address <= 0x9FFF) + { + return video.ReadVram(address); + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + return (cartridge != null ? cartridge.Read(address) : (byte)0xFF); + } + else if (address >= 0xC000 && address <= 0xFDFF) + { + if ((address & 0x1000) == 0) + return wram[0, address & 0x0FFF]; + else + return wram[wramBank, address & 0x0FFF]; + } + else if (address >= 0xFE00 && address <= 0xFE9F) + { + return video.ReadOam(address); + } + else if (address >= 0xFF00 && address <= 0xFF7F) + { + return ReadIo(address); + } + else if (address >= 0xFF80 && address <= 0xFFFE) + { + return hram[address - 0xFF80]; + } + else if (address == 0xFFFF) + { + return ie; + } + + /* Cannot read from address, return 0 */ + return 0x00; + } + + private byte ReadIo(ushort address) + { + if (((address & 0xFFF0) == 0xFF40 && address != 0xFF4C && address != 0xFF4D) || (address >= 0xFF51 && address <= 0xFF55) || (address >= 0xFF68 && address <= 0xFF6B)) + return video.ReadPort((byte)(address & 0xFF)); + else if ((address & 0xFFF0) == 0xFF10 || (address & 0xFFF0) == 0xFF20 || (address & 0xFFF0) == 0xFF30) + return audio.ReadPort((byte)(address & 0xFF)); + else + { + switch (address) + { + case 0xFF00: + // P1/JOYP + return joypadRegister; + + case 0xFF01: + // SB + return serialData; + + case 0xFF02: + // SC + return (byte)( + 0x7C | + (serialUseInternalClock ? (1 << 0) : 0) | + (serialFastClockSpeed ? (1 << 1) : 0) | + (serialTransferInProgress ? (1 << 7) : 0)); + + case 0xFF04: + // DIV + return divider; + + case 0xFF05: + // TIMA + return timerCounter; + + case 0xFF06: + // TMA + return timerModulo; + + case 0xFF07: + // TAC + return (byte)( + 0xF8 | + (timerRunning ? (1 << 2) : 0) | + (timerInputClock & 0b11)); + + case 0xFF0F: + // IF + return (byte)( + 0xE0 | + (irqVBlank ? (1 << 0) : 0) | + (irqLCDCStatus ? (1 << 1) : 0) | + (irqTimerOverflow ? (1 << 2) : 0) | + (irqSerialIO ? (1 << 3) : 0) | + (irqKeypad ? (1 << 4) : 0)); + + case 0xFF4D: + // KEY1 + return (byte)( + 0x7E | + (speedSwitchPending ? (1 << 0) : 0) | + ((speedIsDouble = cpu.IsDoubleSpeed) ? (1 << 7) : 0)); + + case 0xFF50: + // Bootstrap disable + return (byte)( + 0xFE | + (bootstrapDisabled ? (1 << 0) : 0)); + + case 0xFF56: + // RP + return (byte)( + 0x3C | + (irSendingSignal ? (1 << 0) : 0) | + (!irReadEnableA || !irReadEnableB || irNotReceivingSignal ? (1 << 1) : 0) | + (irReadEnableA ? (1 << 6) : 0) | + (irReadEnableB ? (1 << 7) : 0)); + + case 0xFF70: + // SVBK + return (byte)( + 0xF8 | + (wramBank & 0b111)); + + default: + return 0xFF;// throw new NotImplementedException(); + } + } + } + + private void WriteMemory(ushort address, byte value) + { + if (address >= 0x0000 && address <= 0x7FFF) + { + cartridge?.Write(address, value); + } + else if (address >= 0x8000 && address <= 0x9FFF) + { + video.WriteVram(address, value); + } + else if (address >= 0xA000 && address <= 0xBFFF) + { + cartridge?.Write(address, value); + } + else if (address >= 0xC000 && address <= 0xFDFF) + { + if ((address & 0x1000) == 0) + wram[0, address & 0x0FFF] = value; + else + wram[wramBank, address & 0x0FFF] = value; + } + else if (address >= 0xFE00 && address <= 0xFE9F) + { + video.WriteOam(address, value); + } + else if (address >= 0xFF00 && address <= 0xFF7F) + { + WriteIo(address, value); + } + else if (address >= 0xFF80 && address <= 0xFFFE) + { + hram[address - 0xFF80] = value; + } + else if (address == 0xFFFF) + { + ie = value; + } + } + + private void WriteIo(ushort address, byte value) + { + if (((address & 0xFFF0) == 0xFF40 && address != 0xFF4C && address != 0xFF4D) || (address >= 0xFF51 && address <= 0xFF55) || (address >= 0xFF68 && address <= 0xFF6B)) + video.WritePort((byte)(address & 0xFF), value); + else if ((address & 0xFFF0) == 0xFF10 || (address & 0xFFF0) == 0xFF20 || (address & 0xFFF0) == 0xFF30) + audio.WritePort((byte)(address & 0xFF), value); + else + { + switch (address) + { + case 0xFF00: + joypadRegister = (byte)((joypadRegister & 0xC0) | (value & 0x30)); + if ((joypadRegister & 0x30) == 0x20) + joypadRegister |= (byte)(((byte)inputsPressed & 0x0F) ^ 0x0F); + else if ((joypadRegister & 0x30) == 0x10) + joypadRegister |= (byte)((((byte)inputsPressed & 0xF0) >> 4) ^ 0x0F); + else + joypadRegister = 0xFF; + break; + + case 0xFF01: + serialData = value; + break; + + case 0xFF02: + serialUseInternalClock = (value & (1 << 0)) != 0; + serialFastClockSpeed = (value & (1 << 1)) != 0; + serialTransferInProgress = (value & (1 << 7)) != 0; + + clockCyclesPerSerialBit = (serialFastClockSpeed ? serialCycleCountFast : serialCycleCountNormal) >> (cpu.IsDoubleSpeed ? 1 : 0); + + if (serialTransferInProgress) serialCycles = 0; + serialBitsCounter = 8; + break; + + case 0xFF04: + UpdateCycleCounter(0); + break; + + case 0xFF05: + if (!timerLoading) + { + timerCounter = value; + timerOverflow = false; + } + break; + + case 0xFF06: + timerModulo = value; + if (timerLoading) + timerCounter = value; + break; + + case 0xFF07: + { + var newTimerRunning = (value & (1 << 2)) != 0; + var newTimerInputClock = (byte)(value & 0b11); + + var oldBit = timerRunning && GetTimerBit(timerInputClock, clockCycleCount); + var newBit = newTimerRunning && GetTimerBit(newTimerInputClock, clockCycleCount); + + if (oldBit && !newBit) + IncrementTimer(); + + timerRunning = newTimerRunning; + timerInputClock = newTimerInputClock; + } + break; + + case 0xFF0F: + irqVBlank = (value & (1 << 0)) != 0; + irqLCDCStatus = (value & (1 << 1)) != 0; + irqTimerOverflow = (value & (1 << 2)) != 0; + irqSerialIO = (value & (1 << 3)) != 0; + irqKeypad = (value & (1 << 4)) != 0; + break; + + case 0xFF4D: + speedSwitchPending = (value & (1 << 0)) != 0; + break; + + case 0xFF50: + if (!bootstrapDisabled) + bootstrapDisabled = (value & (1 << 0)) != 0; + break; + + case 0xFF56: + irSendingSignal = (value & (1 << 0)) != 0; + irReadEnableA = (value & (1 << 6)) != 0; + irReadEnableB = (value & (1 << 7)) != 0; + break; + + case 0xFF70: + wramBank = (byte)(value & 0b111); + if (wramBank == 0x00) wramBank = 0x01; + break; + } + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Machines/GameBoyColor.cs.meta b/Assets/Plugins/Essgee/Emulation/Machines/GameBoyColor.cs.meta new file mode 100644 index 0000000..39e580c --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/GameBoyColor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4840cc971763acf498a4729ba103aef2 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Machines/GameGear.cs b/Assets/Plugins/Essgee/Emulation/Machines/GameGear.cs new file mode 100644 index 0000000..d50a7c2 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/GameGear.cs @@ -0,0 +1,514 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.Video; +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges; +using Essgee.Emulation.Cartridges.Sega; +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; + +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Machines +{ + [MachineIndex(3)] + public class GameGear : IMachine + { + const double masterClock = 10738635; + const double refreshRate = 59.922743; + + const int ramSize = 1 * 8192; + + double vdpClock, psgClock; + + public event EventHandler<SendLogMessageEventArgs> SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler<EventArgs> EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler<RenderScreenEventArgs> RenderScreen + { + add { vdp.RenderScreen += value; } + remove { vdp.RenderScreen -= value; } + } + + public event EventHandler<SizeScreenEventArgs> SizeScreen + { + add { vdp.SizeScreen += value; } + remove { vdp.SizeScreen -= value; } + } + + public event EventHandler<ChangeViewportEventArgs> ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler<PollInputEventArgs> PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples + { + add { psg.EnqueueSamples += value; } + remove { psg.EnqueueSamples -= value; } + } + + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler<EventArgs> EnableRumble { add { } remove { } } + + public string ManufacturerName => "Sega"; + public string ModelName => "Game Gear"; + public string DatFilename => "Sega - Game Gear.dat"; + public (string Extension, string Description) FileFilter => (".gg", "Game Gear ROMs"); + public bool HasBootstrap => true; + public double RefreshRate => refreshRate; + public double PixelAspectRatio => 8.0 / 7.0; + public (string Name, string Description)[] RuntimeOptions => vdp.RuntimeOptions.Concat(psg.RuntimeOptions).ToArray(); + + ICartridge bootstrap, cartridge; + byte[] wram; + Z80A cpu; + SegaGGVDP vdp; + SegaGGPSG psg; + + [Flags] + enum IOPortABInputs : byte + { + PortAUp = (1 << 0), + PortADown = (1 << 1), + PortALeft = (1 << 2), + PortARight = (1 << 3), + PortATL = (1 << 4), + PortATR = (1 << 5), + PortBUp = (1 << 6), + PortBDown = (1 << 7) + } + + [Flags] + enum IOPortBMiscInputs : byte + { + PortBLeft = (1 << 0), + PortBRight = (1 << 1), + PortBTL = (1 << 2), + PortBTR = (1 << 3), + Reset = (1 << 4), + CartSlotCONTPin = (1 << 5), + PortATH = (1 << 6), + PortBTH = (1 << 7) + } + + [Flags] + enum IOPortCInputs : byte + { + Start = (1 << 7) + } + + IOPortABInputs portAInputsPressed; + IOPortBMiscInputs portBInputsPressed; + IOPortCInputs portCInputsPressed; + + byte portMemoryControl, portIoControl, hCounterLatched, portIoAB, portIoBMisc; + byte portIoC, portParallelData, portDataDirNMI, portTxBuffer, portRxBuffer, portSerialControl; + + bool isWorkRamEnabled { get { return !IsBitSet(portMemoryControl, 4); } } + bool isBootstrapRomEnabled { get { return !IsBitSet(portMemoryControl, 3); } } + + int currentMasterClockCyclesInFrame, totalMasterClockCyclesInFrame; + + Configuration.GameGear configuration; + + public GameGear() { } + + public void Initialize() + { + bootstrap = null; + cartridge = null; + + wram = new byte[ramSize]; + cpu = new Z80A(ReadMemory, WriteMemory, ReadPort, WritePort); + vdp = new SegaGGVDP(); + psg = new SegaGGPSG(); + + vdp.EndOfScanline += (s, e) => + { + PollInputEventArgs pollInputEventArgs = new PollInputEventArgs(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + }; + } + + public void SetConfiguration(IConfiguration config) + { + configuration = (Configuration.GameGear)config; + + ReconfigureSystem(); + } + + public object GetRuntimeOption(string name) + { + if (name.StartsWith("Graphics")) + return vdp.GetRuntimeOption(name); + else if (name.StartsWith("Audio")) + return psg.GetRuntimeOption(name); + else + return null; + } + + public void SetRuntimeOption(string name, object value) + { + if (name.StartsWith("Graphics")) + vdp.SetRuntimeOption(name, value); + else if (name.StartsWith("Audio")) + psg.SetRuntimeOption(name, value); + } + + private void ReconfigureSystem() + { + vdpClock = (masterClock / 1.0); + psgClock = (masterClock / 3.0); + + vdp?.SetClockRate(vdpClock); + vdp?.SetRefreshRate(refreshRate); + vdp?.SetRevision(1); + + psg?.SetSampleRate(StandInfo.Configuration.SampleRate); + psg?.SetOutputChannels(2); + psg?.SetClockRate(psgClock); + psg?.SetRefreshRate(refreshRate); + + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / refreshRate); + + OnChangeViewport(new ChangeViewportEventArgs(vdp.Viewport)); + } + + private void LoadBootstrap() + { + if (configuration.UseBootstrap) + { + var (type, bootstrapRomData) = CartridgeLoader.Load(configuration.BootstrapRom, "GameGear Bootstrap"); + bootstrap = new SegaMapperCartridge(bootstrapRomData.Length, 0); + bootstrap.LoadRom(bootstrapRomData); + } + } + + public void Startup() + { + LoadBootstrap(); + + cpu.Startup(); + vdp.Startup(); + psg.Startup(); + } + + public void Reset() + { + cpu.Reset(); + cpu.SetStackPointer(0xDFF0); + vdp.Reset(); + psg.Reset(); + + portAInputsPressed = 0; + portBInputsPressed = 0; + portCInputsPressed = 0; + + portMemoryControl = (byte)(bootstrap != null ? 0xA3 : 0x00); + portIoControl = 0x0F; + hCounterLatched = 0x00; + portIoAB = portIoBMisc = 0xFF; + + portIoC = (byte)(0x80 | (configuration.Region == Region.Export ? 0x40 : 0x00)); + portParallelData = 0x00; + portDataDirNMI = 0xFF; + portTxBuffer = 0x00; + portRxBuffer = 0xFF; + portSerialControl = 0x00; + psg.WritePort(SegaGGPSG.PortStereoControl, 0xFF); + + OnEmulationReset(EventArgs.Empty); + } + + public void Shutdown() + { + cpu?.Shutdown(); + vdp?.Shutdown(); + psg?.Shutdown(); + } + + public void SetState(Dictionary<string, dynamic> state) + { + configuration.Region = state[nameof(configuration.Region)]; + + SaveStateHandler.PerformSetState(bootstrap, state[nameof(bootstrap)]); + SaveStateHandler.PerformSetState(cartridge, state[nameof(cartridge)]); + wram = state[nameof(wram)]; + SaveStateHandler.PerformSetState(cpu, state[nameof(cpu)]); + SaveStateHandler.PerformSetState(vdp, state[nameof(vdp)]); + SaveStateHandler.PerformSetState(psg, state[nameof(psg)]); + + portMemoryControl = state[nameof(portMemoryControl)]; + portIoControl = state[nameof(portIoControl)]; + hCounterLatched = state[nameof(hCounterLatched)]; + portIoAB = state[nameof(portIoAB)]; + portIoBMisc = state[nameof(portIoBMisc)]; + + portIoC = state[nameof(portIoC)]; + portParallelData = state[nameof(portParallelData)]; + portDataDirNMI = state[nameof(portDataDirNMI)]; + portTxBuffer = state[nameof(portTxBuffer)]; + portRxBuffer = state[nameof(portRxBuffer)]; + portSerialControl = state[nameof(portSerialControl)]; + + ReconfigureSystem(); + } + + public Dictionary<string, dynamic> GetState() + { + return new Dictionary<string, dynamic> + { + [nameof(configuration.Region)] = configuration.Region, + + [nameof(bootstrap)] = SaveStateHandler.PerformGetState(bootstrap), + [nameof(cartridge)] = SaveStateHandler.PerformGetState(cartridge), + [nameof(wram)] = wram, + [nameof(cpu)] = SaveStateHandler.PerformGetState(cpu), + [nameof(vdp)] = SaveStateHandler.PerformGetState(vdp), + [nameof(psg)] = SaveStateHandler.PerformGetState(psg), + + [nameof(portMemoryControl)] = portMemoryControl, + [nameof(portIoControl)] = portIoControl, + [nameof(hCounterLatched)] = hCounterLatched, + [nameof(portIoAB)] = portIoAB, + [nameof(portIoBMisc)] = portIoBMisc, + + [nameof(portIoC)] = portIoC, + [nameof(portParallelData)] = portParallelData, + [nameof(portDataDirNMI)] = portDataDirNMI, + [nameof(portTxBuffer)] = portTxBuffer, + [nameof(portRxBuffer)] = portRxBuffer, + [nameof(portSerialControl)] = portSerialControl + }; + } + + public Dictionary<string, dynamic> GetDebugInformation() + { + var dict = new Dictionary<string, dynamic> + { + { "CyclesInFrame", currentMasterClockCyclesInFrame }, + }; + + return dict; + } + + public void Load(byte[] romData, byte[] ramData, Type mapperType) + { + if (mapperType == null) + mapperType = typeof(SegaMapperCartridge); + if (ramData.Length == 0) + ramData = new byte[32768]; + + cartridge = (ICartridge)Activator.CreateInstance(mapperType, new object[] { romData.Length, ramData.Length }); + cartridge.LoadRom(romData); + cartridge.LoadRam(ramData); + } + + public byte[] GetCartridgeRam() + { + return cartridge?.GetRamData(); + } + + public bool IsCartridgeRamSaveNeeded() + { + if (cartridge == null) return false; + return cartridge.IsRamSaveNeeded(); + } + + public virtual void RunFrame() + { + while (currentMasterClockCyclesInFrame < totalMasterClockCyclesInFrame) + RunStep(); + + currentMasterClockCyclesInFrame -= totalMasterClockCyclesInFrame; + } + + public void RunStep() + { + double currentCpuClockCycles = 0.0; + currentCpuClockCycles += cpu.Step(); + + double currentMasterClockCycles = (currentCpuClockCycles * 3.0); + + vdp.Step((int)Math.Round(currentMasterClockCycles)); + + cpu.SetInterruptLine(InterruptType.Maskable, vdp.InterruptLine); + + psg.Step((int)Math.Round(currentCpuClockCycles)); + + cartridge?.Step((int)Math.Round(currentCpuClockCycles)); + + currentMasterClockCyclesInFrame += (int)Math.Round(currentMasterClockCycles); + } + + private void ParseInput(PollInputEventArgs eventArgs) + { + portAInputsPressed = 0; + portBInputsPressed = 0; + portCInputsPressed = 0; + + /* Keyboard */ + if (eventArgs.Keyboard.Contains(configuration.ControlsUp)) portAInputsPressed |= IOPortABInputs.PortAUp; + if (eventArgs.Keyboard.Contains(configuration.ControlsDown)) portAInputsPressed |= IOPortABInputs.PortADown; + if (eventArgs.Keyboard.Contains(configuration.ControlsLeft)) portAInputsPressed |= IOPortABInputs.PortALeft; + if (eventArgs.Keyboard.Contains(configuration.ControlsRight)) portAInputsPressed |= IOPortABInputs.PortARight; + if (eventArgs.Keyboard.Contains(configuration.ControlsButton1)) portAInputsPressed |= IOPortABInputs.PortATL; + if (eventArgs.Keyboard.Contains(configuration.ControlsButton2)) portAInputsPressed |= IOPortABInputs.PortATR; + if (eventArgs.Keyboard.Contains(configuration.ControlsStart)) portCInputsPressed |= IOPortCInputs.Start; + + /* XInput controller */ + if (eventArgs.ControllerState.IsAnyUpDirectionPressed() && !eventArgs.ControllerState.IsAnyDownDirectionPressed()) portAInputsPressed |= IOPortABInputs.PortAUp; + if (eventArgs.ControllerState.IsAnyDownDirectionPressed() && !eventArgs.ControllerState.IsAnyUpDirectionPressed()) portAInputsPressed |= IOPortABInputs.PortADown; + if (eventArgs.ControllerState.IsAnyLeftDirectionPressed() && !eventArgs.ControllerState.IsAnyRightDirectionPressed()) portAInputsPressed |= IOPortABInputs.PortALeft; + if (eventArgs.ControllerState.IsAnyRightDirectionPressed() && !eventArgs.ControllerState.IsAnyLeftDirectionPressed()) portAInputsPressed |= IOPortABInputs.PortARight; + if (eventArgs.ControllerState.IsAPressed()) portAInputsPressed |= IOPortABInputs.PortATL; + if (eventArgs.ControllerState.IsXPressed() || eventArgs.ControllerState.IsBPressed()) portAInputsPressed |= IOPortABInputs.PortATR; + if (eventArgs.ControllerState.IsStartPressed()) portCInputsPressed |= IOPortCInputs.Start; + + portIoAB |= (byte)(IOPortABInputs.PortAUp | IOPortABInputs.PortADown | IOPortABInputs.PortALeft | IOPortABInputs.PortARight | IOPortABInputs.PortATL | IOPortABInputs.PortATR | IOPortABInputs.PortBUp | IOPortABInputs.PortBDown); + portIoBMisc |= (byte)(IOPortBMiscInputs.PortBLeft | IOPortBMiscInputs.PortBRight | IOPortBMiscInputs.PortBTL | IOPortBMiscInputs.PortBTR | IOPortBMiscInputs.Reset | IOPortBMiscInputs.CartSlotCONTPin | IOPortBMiscInputs.PortATH | IOPortBMiscInputs.PortBTH); + portIoC |= (byte)IOPortCInputs.Start; + + portIoAB &= (byte)~portAInputsPressed; + portIoBMisc &= (byte)~portBInputsPressed; + portIoC &= (byte)~portCInputsPressed; + } + + private byte ReadMemory(ushort address) + { + if (address >= 0x0000 && address <= 0xBFFF) + { + if (address <= 0x0400 && isBootstrapRomEnabled && bootstrap != null) + return bootstrap.Read(address); + + if (cartridge != null) + return cartridge.Read(address); + } + else if (address >= 0xC000 && address <= 0xFFFF) + { + if (isWorkRamEnabled) + return wram[address & (ramSize - 1)]; + } + + /* Cannot read from address, return 0 */ + return 0x00; + } + + private void WriteMemory(ushort address, byte value) + { + if (isBootstrapRomEnabled) bootstrap?.Write(address, value); + cartridge?.Write(address, value); + + if (isWorkRamEnabled && address >= 0xC000 && address <= 0xFFFF) + wram[address & (ramSize - 1)] = value; + } + + private byte ReadPort(byte port) + { + var maskedPort = (byte)(port & 0xC1); + + switch (maskedPort & 0xF0) + { + case 0x00: + /* GG-specific ports */ + switch (port) + { + case 0x00: return (byte)((portIoC & 0xBF) | (configuration.Region == Region.Export ? 0x40 : 0x00)); + case 0x01: return portParallelData; + case 0x02: return portDataDirNMI; + case 0x03: return portTxBuffer; + case 0x04: return portRxBuffer; + case 0x05: return portSerialControl; + case 0x06: return 0xFF; + } + return 0xFF; + + case 0x40: /* Counters */ + case 0x80: /* VDP ports */ + return vdp.ReadPort(maskedPort); + + case 0xC0: + if (port == 0xC0 || port == 0xDC) + return portIoAB; /* IO port A/B register */ + else if (port == 0xC1 || port == 0xDD) + return portIoBMisc; /* IO port B/misc register */ + else + return 0xFF; + + default: + // TODO: handle properly + return 0x00; + } + } + + public void WritePort(byte port, byte value) + { + var maskedPort = (byte)(port & 0xC1); + + switch (maskedPort & 0xF0) + { + case 0x00: + switch (port) + { + case 0x00: /* Read-only */ break; + case 0x01: portParallelData = value; break; + case 0x02: portDataDirNMI = value; break; + case 0x03: portTxBuffer = value; break; + case 0x04: /* Read-only? */; break; + case 0x05: portSerialControl = (byte)(value & 0xF8); break; + case 0x06: psg.WritePort(port, value); break; + default: + /* System stuff */ + if ((maskedPort & 0x01) == 0) + { + /* Memory control */ + if (configuration.AllowMemoryControl) + portMemoryControl = value; + } + else + { + /* I/O control */ + if ((portIoControl & 0x0A) == 0x00 && ((value & 0x02) == 0x02 || (value & 0x08) == 0x08)) + hCounterLatched = vdp.ReadPort(SegaSMSVDP.PortHCounter); + portIoControl = value; + } + break; + } + break; + + case 0x40: + /* PSG */ + psg.WritePort(maskedPort, value); + break; + + case 0x80: + /* VDP */ + vdp.WritePort(maskedPort, value); + break; + + case 0xC0: + /* No effect */ + break; + + default: + // TODO: handle properly + break; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Machines/GameGear.cs.meta b/Assets/Plugins/Essgee/Emulation/Machines/GameGear.cs.meta new file mode 100644 index 0000000..9925a15 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/GameGear.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: de9201bc81725c14e8ce6987146f89d8 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Machines/IMachine.cs b/Assets/Plugins/Essgee/Emulation/Machines/IMachine.cs new file mode 100644 index 0000000..bd6f806 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/IMachine.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.EventArguments; +using Essgee.Emulation.Configuration; + +namespace Essgee.Emulation.Machines +{ + public interface IMachine + { + event EventHandler<SendLogMessageEventArgs> SendLogMessage; + event EventHandler<EventArgs> EmulationReset; + event EventHandler<RenderScreenEventArgs> RenderScreen; + event EventHandler<SizeScreenEventArgs> SizeScreen; + event EventHandler<ChangeViewportEventArgs> ChangeViewport; + event EventHandler<PollInputEventArgs> PollInput; + event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples; + event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + event EventHandler<EventArgs> EnableRumble; + + string ManufacturerName { get; } + string ModelName { get; } + string DatFilename { get; } + (string Extension, string Description) FileFilter { get; } + bool HasBootstrap { get; } + double RefreshRate { get; } + double PixelAspectRatio { get; } + (string Name, string Description)[] RuntimeOptions { get; } + + Dictionary<string, dynamic> GetDebugInformation(); + + void SetConfiguration(IConfiguration config); + + object GetRuntimeOption(string name); + void SetRuntimeOption(string name, object value); + + void Initialize(); + void Startup(); + void Reset(); + void Shutdown(); + + void SetState(Dictionary<string, dynamic> state); + Dictionary<string, dynamic> GetState(); + + void Load(byte[] romData, byte[] ramData, Type mapperType); + byte[] GetCartridgeRam(); + bool IsCartridgeRamSaveNeeded(); + + void RunFrame(); + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Machines/IMachine.cs.meta b/Assets/Plugins/Essgee/Emulation/Machines/IMachine.cs.meta new file mode 100644 index 0000000..29f53d9 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/IMachine.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e3358260543f04a48b308b485fdc8380 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Machines/MasterSystem.cs b/Assets/Plugins/Essgee/Emulation/Machines/MasterSystem.cs new file mode 100644 index 0000000..22d676c --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/MasterSystem.cs @@ -0,0 +1,632 @@ +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges; +using Essgee.Emulation.Cartridges.Sega; +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.Video; +using Essgee.EventArguments; +using Essgee.Utilities; +using Essgee.Utilities.XInput; +using System; +using System.Collections.Generic; +using System.Linq; +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Machines +{ + [MachineIndex(2)] + public class MasterSystem : IMachine + { + const double masterClockNtsc = 10738635; + const double masterClockPal = 10640684; + const double refreshRateNtsc = 59.922743; + const double refreshRatePal = 49.701459; + + const int ramSize = 1 * 8192; + + double masterClock; + double vdpClock, psgClock; + + public event EventHandler<SendLogMessageEventArgs> SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler<EventArgs> EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler<RenderScreenEventArgs> RenderScreen + { + add { vdp.RenderScreen += value; } + remove { vdp.RenderScreen -= value; } + } + + public event EventHandler<SizeScreenEventArgs> SizeScreen + { + add { vdp.SizeScreen += value; } + remove { vdp.SizeScreen -= value; } + } + + public event EventHandler<ChangeViewportEventArgs> ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler<PollInputEventArgs> PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples + { + add { psg.EnqueueSamples += value; } + remove { psg.EnqueueSamples -= value; } + } + + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler<EventArgs> EnableRumble { add { } remove { } } + + public string ManufacturerName => "Sega"; + public string ModelName => "Master System"; + public string DatFilename => "Sega - Master System - Mark III.dat"; + public (string Extension, string Description) FileFilter => (".sms", "Master System ROMs"); + public bool HasBootstrap => true; + public double RefreshRate { get; private set; } + public double PixelAspectRatio => 8.0 / 7.0; + public (string Name, string Description)[] RuntimeOptions => vdp.RuntimeOptions.Concat(psg.RuntimeOptions).ToArray(); + + ICartridge bootstrap, cartridge; + byte[] wram; + Z80A cpu; + SegaSMSVDP vdp; + SegaSMSPSG psg; + + InputDevice[] inputDevices; + + [Flags] + enum ControllerInputs : byte + { + Up = 0b00000001, + Down = 0b00000010, + Left = 0b00000100, + Right = 0b00001000, + TL = 0b00010000, + TR = 0b00100000, + TH = 0b01000000 + } + const byte inputResetButton = 0x10; + + bool lightgunLatched; + + const string pauseInputName = "Pause"; + bool pauseButtonPressed, pauseButtonToggle; + + byte portMemoryControl, portIoControl, hCounterLatched; + + bool isExpansionSlotEnabled { get { return !IsBitSet(portMemoryControl, 7); } } + bool isCartridgeSlotEnabled { get { return !IsBitSet(portMemoryControl, 6); } } + bool isCardSlotEnabled { get { return !IsBitSet(portMemoryControl, 5); } } + bool isWorkRamEnabled { get { return !IsBitSet(portMemoryControl, 4); } } + bool isBootstrapRomEnabled { get { return !IsBitSet(portMemoryControl, 3); } } + bool isIoChipEnabled { get { return !IsBitSet(portMemoryControl, 2); } } + + enum IOControlDirection { Output = 0, Input = 1 } + enum IOControlOutputLevel { Low = 0, High = 1 } + enum IOControlPort { A = 0, B = 1 } + enum IOControlPin { TR = 0, TH = 1 }; + + IOControlDirection portAPinTRDirection { get { return (IOControlDirection)((portIoControl >> 0) & 0x01); } } + IOControlDirection portAPinTHDirection { get { return (IOControlDirection)((portIoControl >> 1) & 0x01); } } + IOControlDirection portBPinTRDirection { get { return (IOControlDirection)((portIoControl >> 2) & 0x01); } } + IOControlDirection portBPinTHDirection { get { return (IOControlDirection)((portIoControl >> 3) & 0x01); } } + IOControlOutputLevel portAPinTROutputLevel { get { return (IOControlOutputLevel)((portIoControl >> 4) & 0x01); } } + IOControlOutputLevel portAPinTHOutputLevel { get { return (IOControlOutputLevel)((portIoControl >> 5) & 0x01); } } + IOControlOutputLevel portBPinTROutputLevel { get { return (IOControlOutputLevel)((portIoControl >> 6) & 0x01); } } + IOControlOutputLevel portBPinTHOutputLevel { get { return (IOControlOutputLevel)((portIoControl >> 7) & 0x01); } } + + int currentMasterClockCyclesInFrame, totalMasterClockCyclesInFrame; + + Configuration.MasterSystem configuration; + + IEnumerable<Keys> lastKeysDown; + ControllerState lastControllerState; + MouseButtons lastMouseButtons; + (int x, int y) lastMousePosition; + + public MasterSystem() { } + + public void Initialize() + { + bootstrap = null; + cartridge = null; + + wram = new byte[ramSize]; + cpu = new Z80A(ReadMemory, WriteMemory, ReadPort, WritePort); + vdp = new SegaSMSVDP(); + psg = new SegaSMSPSG(); + + inputDevices = new InputDevice[2]; + inputDevices[0] = InputDevice.None; + inputDevices[1] = InputDevice.None; + + lastKeysDown = new List<Keys>(); + lastControllerState = new ControllerState(); + + vdp.EndOfScanline += (s, e) => + { + PollInputEventArgs pollInputEventArgs = new PollInputEventArgs(); + OnPollInput(pollInputEventArgs); + + lastKeysDown = pollInputEventArgs.Keyboard; + lastControllerState = pollInputEventArgs.ControllerState; + lastMouseButtons = pollInputEventArgs.MouseButtons; + lastMousePosition = pollInputEventArgs.MousePosition; + + HandlePauseButton(); + }; + } + + public void SetConfiguration(IConfiguration config) + { + configuration = (Configuration.MasterSystem)config; + + ReconfigureSystem(); + } + + public object GetRuntimeOption(string name) + { + if (name.StartsWith("Graphics")) + return vdp.GetRuntimeOption(name); + else if (name.StartsWith("Audio")) + return psg.GetRuntimeOption(name); + else + return null; + } + + public void SetRuntimeOption(string name, object value) + { + if (name.StartsWith("Graphics")) + vdp.SetRuntimeOption(name, value); + else if (name.StartsWith("Audio")) + psg.SetRuntimeOption(name, value); + } + + private void ReconfigureSystem() + { + if (configuration.TVStandard == TVStandard.NTSC) + { + masterClock = masterClockNtsc; + RefreshRate = refreshRateNtsc; + } + else + { + masterClock = masterClockPal; + RefreshRate = refreshRatePal; + } + + vdpClock = (masterClock / 1.0); + psgClock = (masterClock / 3.0); + + vdp?.SetClockRate(vdpClock); + vdp?.SetRefreshRate(RefreshRate); + vdp?.SetRevision((int)configuration.VDPType); + + psg?.SetSampleRate(StandInfo.Configuration.SampleRate); + psg?.SetOutputChannels(2); + psg?.SetClockRate(psgClock); + psg?.SetRefreshRate(RefreshRate); + + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / RefreshRate); + + OnChangeViewport(new ChangeViewportEventArgs(vdp.Viewport)); + + inputDevices[0] = configuration.Joypad1DeviceType; + inputDevices[1] = configuration.Joypad2DeviceType; + } + + private void LoadBootstrap() + { + if (configuration.UseBootstrap) + { + var (type, bootstrapRomData) = CartridgeLoader.Load(configuration.BootstrapRom, "Master System Bootstrap"); + bootstrap = new SegaMapperCartridge(bootstrapRomData.Length, 0); + bootstrap.LoadRom(bootstrapRomData); + } + } + + public void Startup() + { + LoadBootstrap(); + + cpu.Startup(); + vdp.Startup(); + psg.Startup(); + } + + public void Reset() + { + cpu.Reset(); + cpu.SetStackPointer(0xDFF0); + vdp.Reset(); + psg.Reset(); + + pauseButtonPressed = pauseButtonToggle = false; + + portMemoryControl = (byte)(bootstrap != null ? 0xE3 : 0x00); + portIoControl = 0x0F; + hCounterLatched = 0x00; + + OnEmulationReset(EventArgs.Empty); + } + + public void Shutdown() + { + cpu?.Shutdown(); + vdp?.Shutdown(); + psg?.Shutdown(); + } + + public void SetState(Dictionary<string, dynamic> state) + { + configuration.TVStandard = state[nameof(configuration.TVStandard)]; + configuration.Region = state[nameof(configuration.Region)]; + + SaveStateHandler.PerformSetState(bootstrap, state[nameof(bootstrap)]); + SaveStateHandler.PerformSetState(cartridge, state[nameof(cartridge)]); + wram = state[nameof(wram)]; + SaveStateHandler.PerformSetState(cpu, state[nameof(cpu)]); + SaveStateHandler.PerformSetState(vdp, state[nameof(vdp)]); + SaveStateHandler.PerformSetState(psg, state[nameof(psg)]); + + inputDevices = state[nameof(inputDevices)]; + lightgunLatched = state[nameof(lightgunLatched)]; + + portMemoryControl = state[nameof(portMemoryControl)]; + portIoControl = state[nameof(portIoControl)]; + hCounterLatched = state[nameof(hCounterLatched)]; + + ReconfigureSystem(); + } + + public Dictionary<string, dynamic> GetState() + { + return new Dictionary<string, dynamic> + { + [nameof(configuration.TVStandard)] = configuration.TVStandard, + [nameof(configuration.Region)] = configuration.Region, + + [nameof(bootstrap)] = SaveStateHandler.PerformGetState(bootstrap), + [nameof(cartridge)] = SaveStateHandler.PerformGetState(cartridge), + [nameof(wram)] = wram, + [nameof(cpu)] = SaveStateHandler.PerformGetState(cpu), + [nameof(vdp)] = SaveStateHandler.PerformGetState(vdp), + [nameof(psg)] = SaveStateHandler.PerformGetState(psg), + + [nameof(inputDevices)] = inputDevices, + [nameof(lightgunLatched)] = lightgunLatched, + + [nameof(portMemoryControl)] = portMemoryControl, + [nameof(portIoControl)] = portIoControl, + [nameof(hCounterLatched)] = hCounterLatched + }; + } + + public Dictionary<string, dynamic> GetDebugInformation() + { + var dict = new Dictionary<string, dynamic> + { + { "CyclesInFrame", currentMasterClockCyclesInFrame }, + }; + + return dict; + } + + public void Load(byte[] romData, byte[] ramData, Type mapperType) + { + if (mapperType == null) + mapperType = typeof(SegaMapperCartridge); + if (ramData.Length == 0) + ramData = new byte[32768]; + + cartridge = (ICartridge)Activator.CreateInstance(mapperType, new object[] { romData.Length, ramData.Length }); + cartridge.LoadRom(romData); + cartridge.LoadRam(ramData); + } + + public byte[] GetCartridgeRam() + { + return cartridge?.GetRamData(); + } + + public bool IsCartridgeRamSaveNeeded() + { + if (cartridge == null) return false; + return cartridge.IsRamSaveNeeded(); + } + + public virtual void RunFrame() + { + while (currentMasterClockCyclesInFrame < totalMasterClockCyclesInFrame) + RunStep(); + + currentMasterClockCyclesInFrame -= totalMasterClockCyclesInFrame; + } + + public void RunStep() + { + double currentCpuClockCycles = 0.0; + currentCpuClockCycles += cpu.Step(); + + double currentMasterClockCycles = (currentCpuClockCycles * 3.0); + + vdp.Step((int)Math.Round(currentMasterClockCycles)); + + if (pauseButtonPressed) + { + pauseButtonPressed = false; + cpu.SetInterruptLine(InterruptType.NonMaskable, InterruptState.Assert); + } + + cpu.SetInterruptLine(InterruptType.Maskable, vdp.InterruptLine); + + psg.Step((int)Math.Round(currentCpuClockCycles)); + + cartridge?.Step((int)Math.Round(currentCpuClockCycles)); + + currentMasterClockCyclesInFrame += (int)Math.Round(currentMasterClockCycles); + } + + private void HandlePauseButton() + { + var pausePressed = lastKeysDown.Contains(configuration.InputPause) || lastControllerState.IsStartPressed(); + var pauseButtonHeld = pauseButtonToggle && pausePressed; + if (pausePressed) + { + if (!pauseButtonHeld) pauseButtonPressed = true; + pauseButtonToggle = true; + } + else if (pauseButtonToggle) + pauseButtonToggle = false; + } + + private byte ReadInput(int port) + { + var state = (byte)0xFF; + + switch (inputDevices[port]) + { + case InputDevice.None: + /* Do nothing */ + break; + + case InputDevice.Controller: + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Up : configuration.Joypad2Up) || (port == 0 && lastControllerState.IsAnyUpDirectionPressed() && !lastControllerState.IsAnyDownDirectionPressed())) + state &= (byte)~ControllerInputs.Up; + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Down : configuration.Joypad2Down) || (port == 0 && lastControllerState.IsAnyDownDirectionPressed() && !lastControllerState.IsAnyUpDirectionPressed())) + state &= (byte)~ControllerInputs.Down; + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Left : configuration.Joypad2Left) || (port == 0 && lastControllerState.IsAnyLeftDirectionPressed() && !lastControllerState.IsAnyRightDirectionPressed())) + state &= (byte)~ControllerInputs.Left; + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Right : configuration.Joypad2Right) || (port == 0 && lastControllerState.IsAnyRightDirectionPressed() && !lastControllerState.IsAnyLeftDirectionPressed())) + state &= (byte)~ControllerInputs.Right; + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Button1 : configuration.Joypad2Button1) || (port == 0 && lastControllerState.IsAPressed())) + state &= (byte)~ControllerInputs.TL; + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Button2 : configuration.Joypad2Button2) || (port == 0 && (lastControllerState.IsXPressed() || lastControllerState.IsBPressed()))) + state &= (byte)~ControllerInputs.TR; + break; + + case InputDevice.Lightgun: + if (GetIOControlDirection(port == 0 ? IOControlPort.A : IOControlPort.B, IOControlPin.TH, portIoControl) == IOControlDirection.Input) + { + var diffX = Math.Abs(lastMousePosition.x - (vdp.ReadPort(SegaSMSVDP.PortHCounter) << 1)); + var diffY = Math.Abs(lastMousePosition.y - vdp.ReadPort(SegaSMSVDP.PortVCounter)); + + if ((diffY <= 5) && (diffX <= 60)) + { + state &= (byte)~ControllerInputs.TH; + if (!lightgunLatched) + { + hCounterLatched = (byte)(lastMousePosition.x >> 1); + lightgunLatched = true; + } + } + else + lightgunLatched = false; + } + + var mouseButton = port == 0 ? MouseButtons.Left : MouseButtons.Right; + if ((lastMouseButtons & mouseButton) == mouseButton) + state &= (byte)~ControllerInputs.TL; + break; + } + + return state; + } + + private byte ReadResetButton() + { + return (!lastKeysDown.Contains(configuration.InputReset) ? inputResetButton : (byte)0x00); + } + + private IOControlDirection GetIOControlDirection(IOControlPort port, IOControlPin pin, byte data) + { + return (IOControlDirection)((data >> (0 | ((byte)port << 1) | (byte)pin)) & 0x01); + } + + private IOControlOutputLevel GetIOControlOutputLevel(IOControlPort port, IOControlPin pin, byte data) + { + return (IOControlOutputLevel)((data >> (4 | ((byte)port << 1) | (byte)pin)) & 0x01); + } + + private byte ReadIoPort(byte port) + { + if ((port & 0x01) == 0) + { + /* IO port A/B register */ + var inputCtrlA = ReadInput(0); /* Read controller port A */ + var inputCtrlB = ReadInput(1); /* Read controller port B */ + + if (configuration.Region == Region.Export) + { + /* Adjust TR according to direction/level */ + if (GetIOControlDirection(IOControlPort.A, IOControlPin.TR, portIoControl) == IOControlDirection.Output) + { + inputCtrlA &= (byte)~ControllerInputs.TR; + if (GetIOControlOutputLevel(IOControlPort.A, IOControlPin.TR, portIoControl) == IOControlOutputLevel.High) + inputCtrlA |= (byte)ControllerInputs.TR; + } + } + + var portState = (byte)(inputCtrlA & 0x3F); /* Controller port A (bits 0-5, into bits 0-5) */ + portState |= (byte)((inputCtrlB & 0x03) << 6); /* Controller port B (bits 0-1, into bits 6-7) */ + + return portState; + } + else + { + /* IO port B/misc register */ + var inputCtrlA = ReadInput(0); /* Read controller port A */ + var inputCtrlB = ReadInput(1); /* Read controller port B */ + + if (configuration.Region == Region.Export) + { + /* Adjust TR and THx according to direction/level */ + if (GetIOControlDirection(IOControlPort.B, IOControlPin.TR, portIoControl) == IOControlDirection.Output) + { + inputCtrlB &= (byte)~ControllerInputs.TR; + if (GetIOControlOutputLevel(IOControlPort.B, IOControlPin.TR, portIoControl) == IOControlOutputLevel.High) + inputCtrlB |= (byte)ControllerInputs.TR; + } + if (GetIOControlDirection(IOControlPort.A, IOControlPin.TH, portIoControl) == IOControlDirection.Output) + { + inputCtrlA &= (byte)~ControllerInputs.TH; + if (GetIOControlOutputLevel(IOControlPort.A, IOControlPin.TH, portIoControl) == IOControlOutputLevel.High) + inputCtrlA |= (byte)ControllerInputs.TH; + } + if (GetIOControlDirection(IOControlPort.B, IOControlPin.TH, portIoControl) == IOControlDirection.Output) + { + inputCtrlB &= (byte)~ControllerInputs.TH; + if (GetIOControlOutputLevel(IOControlPort.B, IOControlPin.TH, portIoControl) == IOControlOutputLevel.High) + inputCtrlB |= (byte)ControllerInputs.TH; + } + } + + var portState = (byte)((inputCtrlB & 0x3F) >> 2); /* Controller port B (bits 2-5, into bits 0-3) */ + portState |= ReadResetButton(); /* Reset button (bit 4, into bit 4) */ + portState |= 0b00100000; /* Cartridge slot CONT pin (bit 5, into bit 5) */ + portState |= (byte)(((inputCtrlA >> 6) & 0x01) << 6); /* Controller port A TH pin (bit 6, into bit 6) */ + portState |= (byte)(((inputCtrlB >> 6) & 0x01) << 7); /* Controller port B TH pin (bit 6, into bit 7) */ + + return portState; + } + } + + private byte ReadMemory(ushort address) + { + if (address >= 0x0000 && address <= 0xBFFF) + { + if (isBootstrapRomEnabled && bootstrap != null) + return bootstrap.Read(address); + + if (isCartridgeSlotEnabled && cartridge != null) + return cartridge.Read(address); + } + else if (address >= 0xC000 && address <= 0xFFFF) + { + if (isWorkRamEnabled) + return wram[address & (ramSize - 1)]; + } + + /* Cannot read from address, return 0 */ + return 0x00; + } + + private void WriteMemory(ushort address, byte value) + { + if (isBootstrapRomEnabled) bootstrap?.Write(address, value); + if (isCartridgeSlotEnabled) cartridge?.Write(address, value); + + if (isWorkRamEnabled && address >= 0xC000 && address <= 0xFFFF) + wram[address & (ramSize - 1)] = value; + } + + private byte ReadPort(byte port) + { + port = (byte)(port & 0xC1); + + switch (port & 0xF0) + { + case 0x00: + /* Behave like SMS2 */ + return 0xFF; + + case 0x40: + /* Counters */ + if ((port & 0x01) == 0) + return vdp.ReadPort(port); /* V counter */ + else + return hCounterLatched; /* H counter */ + + case 0x80: + return vdp.ReadPort(port); /* VDP ports */ + + case 0xC0: + return ReadIoPort(port); /* IO ports */ + + default: + // TODO: handle properly + return 0x00; + } + } + + public void WritePort(byte port, byte value) + { + port = (byte)(port & 0xC1); + + switch (port & 0xF0) + { + case 0x00: + /* System stuff */ + if ((port & 0x01) == 0) + { + /* Memory control */ + + // NOTE: Sonic Chaos June 30 prototype writes 0xFF to port 0x06; mirroring causes write to memory control, which causes the game to disable all memory access + if (configuration.AllowMemoryControl) + portMemoryControl = value; + } + else + { + /* I/O control */ + if ((GetIOControlDirection(IOControlPort.A, IOControlPin.TH, value) == IOControlDirection.Input && + GetIOControlOutputLevel(IOControlPort.A, IOControlPin.TH, value) == IOControlOutputLevel.High && + GetIOControlOutputLevel(IOControlPort.A, IOControlPin.TH, portIoControl) == IOControlOutputLevel.Low) || + (GetIOControlDirection(IOControlPort.B, IOControlPin.TH, value) == IOControlDirection.Input && + GetIOControlOutputLevel(IOControlPort.B, IOControlPin.TH, value) == IOControlOutputLevel.High && + GetIOControlOutputLevel(IOControlPort.B, IOControlPin.TH, portIoControl) == IOControlOutputLevel.Low)) + { + /* TH is input and transition is Low->High, latch HCounter */ + hCounterLatched = vdp.ReadPort(SegaSMSVDP.PortHCounter); + } + + portIoControl = value; + } + break; + + case 0x40: + /* PSG */ + psg.WritePort(port, value); + break; + + case 0x80: + /* VDP */ + vdp.WritePort(port, value); + break; + + case 0xC0: + /* No effect */ + break; + + default: + // TODO: handle properly + break; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Machines/MasterSystem.cs.meta b/Assets/Plugins/Essgee/Emulation/Machines/MasterSystem.cs.meta new file mode 100644 index 0000000..f4de8b9 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/MasterSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3c4298b2c8d87b948bca1a380584a60f \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Machines/SC3000.cs b/Assets/Plugins/Essgee/Emulation/Machines/SC3000.cs new file mode 100644 index 0000000..53917b2 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/SC3000.cs @@ -0,0 +1,613 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.Video; +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges; +using Essgee.Emulation.Cartridges.Sega; +using Essgee.Emulation.Peripherals; +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Machines +{ + /* TODO: verify everything, the SC-3000 isn't that well-documented... + * + * Tape cassette notes: PPI port B bit 7 == input from cassette, port C bit 4 == output to cassette + */ + + [MachineIndex(1)] + public class SC3000 : IMachine + { + const double masterClockNtsc = 10738635; + const double masterClockPal = 10640684; + const double refreshRateNtsc = 59.922743; + const double refreshRatePal = 49.701459; + + const int ramSize = 1 * 2048; + + double masterClock; + double vdpClock, psgClock; + + public event EventHandler<SendLogMessageEventArgs> SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler<EventArgs> EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler<RenderScreenEventArgs> RenderScreen + { + add { vdp.RenderScreen += value; } + remove { vdp.RenderScreen -= value; } + } + + public event EventHandler<SizeScreenEventArgs> SizeScreen + { + add { vdp.SizeScreen += value; } + remove { vdp.SizeScreen -= value; } + } + + public event EventHandler<ChangeViewportEventArgs> ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler<PollInputEventArgs> PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples + { + add { psg.EnqueueSamples += value; } + remove { psg.EnqueueSamples -= value; } + } + + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler<EventArgs> EnableRumble { add { } remove { } } + + public string ManufacturerName => "Sega"; + public string ModelName => "SC-3000"; + public string DatFilename => "Sega - SG-1000.dat"; // TODO: SC-3000 .dat does not exist? + public (string Extension, string Description) FileFilter => (".sc", "SC-3000 ROMs"); + public bool HasBootstrap => false; + public double RefreshRate { get; private set; } + public double PixelAspectRatio => 8.0 / 7.0; + public (string Name, string Description)[] RuntimeOptions => vdp.RuntimeOptions.Concat(psg.RuntimeOptions).ToArray(); + + ICartridge cartridge; + byte[] wram; + Z80A cpu; + TMS99xxA vdp; + SN76489 psg; + Intel8255 ppi; + bool[,] keyboard; + + [Flags] + enum PortAInputs : byte + { + P1Up = (1 << 0), + P1Down = (1 << 1), + P1Left = (1 << 2), + P1Right = (1 << 3), + P1Button1 = (1 << 4), + P1Button2 = (1 << 5), + P2Up = (1 << 6), + P2Down = (1 << 7), + } + + [Flags] + enum PortBInputs : byte + { + P2Left = (1 << 0), + P2Right = (1 << 1), + P2Button1 = (1 << 2), + P2Button2 = (1 << 3), + } + + PortAInputs portAInputsPressed; + PortBInputs portBInputsPressed; + + bool resetButtonPressed, resetButtonToggle; + + bool keyboardMode; + bool changeInputButtonPressed; + + enum TapeUpdateModes + { + Reading, + Writing + } + + bool isTapePlaying; + bool tapePlayButtonPressed; + + int currentMasterClockCyclesInFrame, totalMasterClockCyclesInFrame; + + Configuration.SC3000 configuration; + + public SC3000() { } + + public void Initialize() + { + cartridge = null; + wram = new byte[ramSize]; + cpu = new Z80A(ReadMemory, WriteMemory, ReadPort, WritePort); + vdp = new TMS99xxA(); + psg = new SN76489(); + ppi = new Intel8255(); + keyboard = new bool[12, 8]; + + vdp.EndOfScanline += (s, e) => + { + PollInputEventArgs pollInputEventArgs = new PollInputEventArgs(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + }; + } + + public void SetConfiguration(IConfiguration config) + { + configuration = (Configuration.SC3000)config; + + ReconfigureSystem(); + } + + public object GetRuntimeOption(string name) + { + if (name.StartsWith("Graphics")) + return vdp.GetRuntimeOption(name); + else if (name.StartsWith("Audio")) + return psg.GetRuntimeOption(name); + else + return null; + } + + public void SetRuntimeOption(string name, object value) + { + if (name.StartsWith("Graphics")) + vdp.SetRuntimeOption(name, value); + else if (name.StartsWith("Audio")) + psg.SetRuntimeOption(name, value); + } + + private void ReconfigureSystem() + { + if (configuration.TVStandard == TVStandard.NTSC) + { + masterClock = masterClockNtsc; + RefreshRate = refreshRateNtsc; + } + else + { + masterClock = masterClockPal; + RefreshRate = refreshRatePal; + } + + vdpClock = (masterClock / 1.0); + psgClock = (masterClock / 3.0); + + vdp?.SetClockRate(vdpClock); + vdp?.SetRefreshRate(RefreshRate); + vdp?.SetRevision(0); + + psg?.SetSampleRate(StandInfo.Configuration.SampleRate); + psg?.SetOutputChannels(2); + psg?.SetClockRate(psgClock); + psg?.SetRefreshRate(RefreshRate); + + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / RefreshRate); + + OnChangeViewport(new ChangeViewportEventArgs(vdp.Viewport)); + } + + public void Startup() + { + cpu.Startup(); + vdp.Startup(); + psg.Startup(); + } + + public void Reset() + { + cpu.Reset(); + cpu.SetStackPointer(0xDFF0); + vdp.Reset(); + psg.Reset(); + ppi.Reset(); + + for (int i = 0; i < keyboard.GetLength(0); i++) + for (int j = 0; j < keyboard.GetLength(1); j++) + keyboard[i, j] = false; + + portAInputsPressed = 0; + portBInputsPressed = 0; + + resetButtonPressed = resetButtonToggle = false; + + keyboardMode = true; + changeInputButtonPressed = false; + + isTapePlaying = false; + tapePlayButtonPressed = false; + + OnEmulationReset(EventArgs.Empty); + } + + public void Shutdown() + { + cpu?.Shutdown(); + vdp?.Shutdown(); + psg?.Shutdown(); + } + + public void SetState(Dictionary<string, dynamic> state) + { + configuration.TVStandard = state[nameof(configuration.TVStandard)]; + + SaveStateHandler.PerformSetState(cartridge, state[nameof(cartridge)]); + wram = state[nameof(wram)]; + SaveStateHandler.PerformSetState(cpu, state[nameof(cpu)]); + SaveStateHandler.PerformSetState(vdp, state[nameof(vdp)]); + SaveStateHandler.PerformSetState(psg, state[nameof(psg)]); + SaveStateHandler.PerformSetState(ppi, state[nameof(ppi)]); + keyboard = state[nameof(keyboard)]; + + ReconfigureSystem(); + } + + public Dictionary<string, dynamic> GetState() + { + return new Dictionary<string, dynamic> + { + [nameof(configuration.TVStandard)] = configuration.TVStandard, + + [nameof(cartridge)] = SaveStateHandler.PerformGetState(cartridge), + [nameof(wram)] = wram, + [nameof(cpu)] = SaveStateHandler.PerformGetState(cpu), + [nameof(vdp)] = SaveStateHandler.PerformGetState(vdp), + [nameof(psg)] = SaveStateHandler.PerformGetState(psg), + [nameof(ppi)] = SaveStateHandler.PerformGetState(ppi), + [nameof(keyboard)] = keyboard + }; + } + + public Dictionary<string, dynamic> GetDebugInformation() + { + var dict = new Dictionary<string, dynamic> + { + { "CyclesInFrame", currentMasterClockCyclesInFrame }, + }; + + return dict; + } + + public void Load(byte[] romData, byte[] ramData, Type mapperType) + { + if (mapperType == null) + mapperType = typeof(SegaSGCartridge); + + cartridge = (ICartridge)Activator.CreateInstance(mapperType, new object[] { romData.Length, ramData.Length }); + cartridge.LoadRom(romData); + cartridge.LoadRam(ramData); + } + + public byte[] GetCartridgeRam() + { + return cartridge.GetRamData(); + } + + public bool IsCartridgeRamSaveNeeded() + { + return cartridge.IsRamSaveNeeded(); + } + + public virtual void RunFrame() + { + while (currentMasterClockCyclesInFrame < totalMasterClockCyclesInFrame) + RunStep(); + + currentMasterClockCyclesInFrame -= totalMasterClockCyclesInFrame; + } + + public void RunStep() + { + double currentCpuClockCycles = 0.0; + currentCpuClockCycles += cpu.Step(); + + double currentMasterClockCycles = (currentCpuClockCycles * 3.0); + + vdp.Step((int)Math.Round(currentMasterClockCycles)); + + if (resetButtonPressed) + { + resetButtonPressed = false; + cpu.SetInterruptLine(InterruptType.NonMaskable, InterruptState.Assert); + } + + cpu.SetInterruptLine(InterruptType.Maskable, vdp.InterruptLine); + + psg.Step((int)Math.Round(currentCpuClockCycles)); + + cartridge?.Step((int)Math.Round(currentCpuClockCycles)); + + currentMasterClockCyclesInFrame += (int)Math.Round(currentMasterClockCycles); + } + + private void ParseInput(PollInputEventArgs eventArgs) + { + /* Get variables */ + var keysDown = eventArgs.Keyboard; + + /* Handle reset button */ + var resetPressed = keysDown.Contains(configuration.InputReset); + var resetButtonHeld = (resetButtonToggle && resetPressed); + if (resetPressed) + { + if (!resetButtonHeld) resetButtonPressed = true; + resetButtonToggle = true; + } + else if (resetButtonToggle) + resetButtonToggle = false; + + /* Toggle input mode (keyboard or controllers) */ + if (keysDown.Contains(configuration.InputChangeMode) && !changeInputButtonPressed) + { + keyboardMode = !keyboardMode; + var modeString = (keyboardMode ? "keyboard" : "controller"); + SendLogMessage(this, new SendLogMessageEventArgs($"Selected {modeString} mode.")); + } + changeInputButtonPressed = keysDown.Contains(configuration.InputChangeMode); + + /* Toggle tape playback */ + if (keysDown.Contains(configuration.InputPlayTape) && !tapePlayButtonPressed) + { + isTapePlaying = !isTapePlaying; + var playString = (isTapePlaying ? "playing" : "stopped"); + SendLogMessage(this, new SendLogMessageEventArgs($"Tape is {playString}.")); + } + tapePlayButtonPressed = keysDown.Contains(configuration.InputPlayTape); + + if (keyboardMode) + { + /* Handle keyboard */ + + // TODO: Replace hardcoded English layout w/ user-configurable settings + keyboard[0, 0] = keysDown.Contains(Keys.D1); + keyboard[0, 1] = keysDown.Contains(Keys.D2); + keyboard[0, 2] = keysDown.Contains(Keys.D3); + keyboard[0, 3] = keysDown.Contains(Keys.D4); + keyboard[0, 4] = keysDown.Contains(Keys.D5); + keyboard[0, 5] = keysDown.Contains(Keys.D6); + keyboard[0, 6] = keysDown.Contains(Keys.D7); + + keyboard[1, 0] = keysDown.Contains(Keys.Q); + keyboard[1, 1] = keysDown.Contains(Keys.W); + keyboard[1, 2] = keysDown.Contains(Keys.E); + keyboard[1, 3] = keysDown.Contains(Keys.R); + keyboard[1, 4] = keysDown.Contains(Keys.T); + keyboard[1, 5] = keysDown.Contains(Keys.Y); + keyboard[1, 6] = keysDown.Contains(Keys.U); + + keyboard[2, 0] = keysDown.Contains(Keys.A); + keyboard[2, 1] = keysDown.Contains(Keys.S); + keyboard[2, 2] = keysDown.Contains(Keys.D); + keyboard[2, 3] = keysDown.Contains(Keys.F); + keyboard[2, 4] = keysDown.Contains(Keys.G); + keyboard[2, 5] = keysDown.Contains(Keys.H); + keyboard[2, 6] = keysDown.Contains(Keys.J); + + keyboard[3, 0] = keysDown.Contains(Keys.Z); + keyboard[3, 1] = keysDown.Contains(Keys.X); + keyboard[3, 2] = keysDown.Contains(Keys.C); + keyboard[3, 3] = keysDown.Contains(Keys.V); + keyboard[3, 4] = keysDown.Contains(Keys.B); + keyboard[3, 5] = keysDown.Contains(Keys.N); + keyboard[3, 6] = keysDown.Contains(Keys.M); + + keyboard[4, 0] = keysDown.Contains(Keys.None); // Alphanumerics, Eng Dier's + keyboard[4, 1] = keysDown.Contains(Keys.Space); + keyboard[4, 2] = keysDown.Contains(Keys.Home); // Clr, Home + keyboard[4, 3] = keysDown.Contains(Keys.Back); // Del, Ins + keyboard[4, 4] = keysDown.Contains(Keys.None); // Not on English keyboard? + keyboard[4, 5] = keysDown.Contains(Keys.None); // "" + keyboard[4, 6] = keysDown.Contains(Keys.None); // "" + + keyboard[5, 0] = keysDown.Contains(Keys.Oemcomma); + keyboard[5, 1] = keysDown.Contains(Keys.OemPeriod); + keyboard[5, 2] = keysDown.Contains(Keys.OemQuestion); // Forward slash + keyboard[5, 3] = keysDown.Contains(Keys.None); // Pi + keyboard[5, 4] = keysDown.Contains(Keys.Down); + keyboard[5, 5] = keysDown.Contains(Keys.Left); + keyboard[5, 6] = keysDown.Contains(Keys.Right); + + keyboard[6, 0] = keysDown.Contains(Keys.K); + keyboard[6, 1] = keysDown.Contains(Keys.L); + keyboard[6, 2] = keysDown.Contains(Keys.Oemplus); // Semicolon + keyboard[6, 3] = keysDown.Contains(Keys.OemSemicolon); // Colon + keyboard[6, 4] = keysDown.Contains(Keys.OemCloseBrackets); + keyboard[6, 5] = keysDown.Contains(Keys.Enter); + keyboard[6, 6] = keysDown.Contains(Keys.Up); + + keyboard[7, 0] = keysDown.Contains(Keys.I); + keyboard[7, 1] = keysDown.Contains(Keys.O); + keyboard[7, 2] = keysDown.Contains(Keys.P); + keyboard[7, 3] = keysDown.Contains(Keys.PageUp); // @ + keyboard[7, 4] = keysDown.Contains(Keys.OemOpenBrackets); + keyboard[7, 5] = keysDown.Contains(Keys.None); // Not on English keyboard? + keyboard[7, 6] = keysDown.Contains(Keys.None); // "" + + keyboard[8, 0] = keysDown.Contains(Keys.D8); + keyboard[8, 1] = keysDown.Contains(Keys.D9); + keyboard[8, 2] = keysDown.Contains(Keys.D0); + keyboard[8, 3] = keysDown.Contains(Keys.OemMinus); + keyboard[8, 4] = keysDown.Contains(Keys.Oemtilde); // ^, ~ + keyboard[8, 5] = keysDown.Contains(Keys.OemPipe); // Yen, Pipe, Pound? + keyboard[8, 6] = keysDown.Contains(Keys.PageDown); // Break + + keyboard[9, 6] = keysDown.Contains(Keys.RControlKey); // Graph + + keyboard[10, 6] = keysDown.Contains(Keys.LControlKey); // Ctrl + + keyboard[11, 5] = keysDown.Contains(Keys.Tab); // Func + keyboard[11, 6] = keysDown.Contains(Keys.ShiftKey); // Shift + } + else + { + /* Handle controllers */ + portAInputsPressed = 0; + portBInputsPressed = 0; + + /* Keyboard */ + if (keysDown.Contains(configuration.Joypad1Up)) portAInputsPressed |= PortAInputs.P1Up; + if (keysDown.Contains(configuration.Joypad1Down)) portAInputsPressed |= PortAInputs.P1Down; + if (keysDown.Contains(configuration.Joypad1Left)) portAInputsPressed |= PortAInputs.P1Left; + if (keysDown.Contains(configuration.Joypad1Right)) portAInputsPressed |= PortAInputs.P1Right; + if (keysDown.Contains(configuration.Joypad1Button1)) portAInputsPressed |= PortAInputs.P1Button1; + if (keysDown.Contains(configuration.Joypad1Button2)) portAInputsPressed |= PortAInputs.P1Button2; + if (keysDown.Contains(configuration.Joypad2Up)) portAInputsPressed |= PortAInputs.P2Up; + if (keysDown.Contains(configuration.Joypad2Down)) portAInputsPressed |= PortAInputs.P2Down; + if (keysDown.Contains(configuration.Joypad2Left)) portBInputsPressed |= PortBInputs.P2Left; + if (keysDown.Contains(configuration.Joypad2Right)) portBInputsPressed |= PortBInputs.P2Right; + if (keysDown.Contains(configuration.Joypad2Button1)) portBInputsPressed |= PortBInputs.P2Button1; + if (keysDown.Contains(configuration.Joypad2Button2)) portBInputsPressed |= PortBInputs.P2Button2; + + /* XInput controller */ + if (eventArgs.ControllerState.IsAnyUpDirectionPressed() && !eventArgs.ControllerState.IsAnyDownDirectionPressed()) portAInputsPressed |= PortAInputs.P1Up; + if (eventArgs.ControllerState.IsAnyDownDirectionPressed() && !eventArgs.ControllerState.IsAnyUpDirectionPressed()) portAInputsPressed |= PortAInputs.P1Down; + if (eventArgs.ControllerState.IsAnyLeftDirectionPressed() && !eventArgs.ControllerState.IsAnyRightDirectionPressed()) portAInputsPressed |= PortAInputs.P1Left; + if (eventArgs.ControllerState.IsAnyRightDirectionPressed() && !eventArgs.ControllerState.IsAnyLeftDirectionPressed()) portAInputsPressed |= PortAInputs.P1Right; + if (eventArgs.ControllerState.IsAPressed()) portAInputsPressed |= PortAInputs.P1Button1; + if (eventArgs.ControllerState.IsXPressed() || eventArgs.ControllerState.IsBPressed()) portAInputsPressed |= PortAInputs.P1Button2; + } + } + + private void UpdateInput() + { + byte portA = 0xFF, portB = 0xFF; + byte row = (byte)(ppi.PortCOutput & 0x07); + + if (row == 0x07) + { + /* Controller ports */ + portA &= (byte)~portAInputsPressed; + portB &= (byte)~portBInputsPressed; + } + else + { + /* Keyboard matrix */ + for (int i = 0; i < 8; i++) + if (keyboard[i, row]) portA &= (byte)~(1 << i); + + for (int i = 0; i < 4; i++) + if (keyboard[8 + i, row]) portB &= (byte)~(1 << i); + } + ppi.PortAInput = portA; + ppi.PortBInput = (byte)((ppi.PortBInput & 0xF0) | (portB & 0x0F)); + } + + private void UpdateTape(TapeUpdateModes updateMode) + { + if (!isTapePlaying) return; + + // TODO: errr, try to actually emulate this? so far just seems to write repeating bit patterns, no ex. recognizable basic program data... + + switch (updateMode) + { + case TapeUpdateModes.Reading: + var read = ((ppi.PortBInput >> 7) & 0b1); // TODO: correct? + + // + break; + + case TapeUpdateModes.Writing: + var write = ((ppi.PortCOutput >> 4) & 0b1); // TODO: correct? + + // + break; + } + } + + /* Basic memory maps (via SC-3000 Service Manual, chp 2-8) + * + * IIa IIb IIIa IIIb + * 8000 -- CartRAM CartRAM CartRAM + * 8800 -- -- CartRAM CartRAM + * C000 WRAM WRAM WRAM CartRAM + * C800 -- -- -- CartRAM + */ + + private byte ReadMemory(ushort address) + { + if (cartridge != null && address >= cartridge.GetLowerBound() && address <= cartridge.GetUpperBound()) + { + return cartridge.Read(address); + } + else if (address >= 0xC000 && address <= 0xFFFF) + { + return wram[address & (ramSize - 1)]; + } + + /* Cannot read from address, return 0 */ + return 0x00; + } + + private void WriteMemory(ushort address, byte value) + { + if (cartridge != null && address >= cartridge.GetLowerBound() && address <= cartridge.GetUpperBound()) + { + cartridge.Write(address, value); + } + else if (address >= 0xC000 && address <= 0xFFFF) + { + wram[address & (ramSize - 1)] = value; + } + } + + private byte ReadPort(byte port) + { + switch (port & 0xC0) + { + case 0x80: + return vdp.ReadPort(port); + + case 0xC0: + UpdateTape(TapeUpdateModes.Reading); + UpdateInput(); + return ppi.ReadPort(port); + + default: + // TODO: handle properly + return 0x00; + } + } + + public void WritePort(byte port, byte value) + { + switch (port & 0xC0) + { + case 0x40: + psg.WritePort(port, value); + break; + + case 0x80: + vdp.WritePort(port, value); + break; + + case 0xC0: + ppi.WritePort(port, value); + UpdateTape(TapeUpdateModes.Writing); + break; + + default: + // TODO: handle properly + break; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Machines/SC3000.cs.meta b/Assets/Plugins/Essgee/Emulation/Machines/SC3000.cs.meta new file mode 100644 index 0000000..2208159 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/SC3000.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2df7595ac4e8eff45a21dab6630ec56c \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Machines/SG1000.cs b/Assets/Plugins/Essgee/Emulation/Machines/SG1000.cs new file mode 100644 index 0000000..5e83208 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/SG1000.cs @@ -0,0 +1,414 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.Video; +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges; +using Essgee.Emulation.Cartridges.Sega; +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Machines +{ + [MachineIndex(0)] + public class SG1000 : IMachine + { + // TODO: verify port 0xC0-0xFF behavior wrt the lack of a PPI and the SG-1000 Test Cartridge Extension Port test? + + const double masterClockNtsc = 10738635; + const double masterClockPal = 10640684; + const double refreshRateNtsc = 59.922743; + const double refreshRatePal = 49.701459; + + const int ramSize = 1 * 1024; + + double masterClock; + double vdpClock, psgClock; + + public event EventHandler<SendLogMessageEventArgs> SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler<EventArgs> EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler<RenderScreenEventArgs> RenderScreen + { + add { vdp.RenderScreen += value; } + remove { vdp.RenderScreen -= value; } + } + + public event EventHandler<SizeScreenEventArgs> SizeScreen + { + add { vdp.SizeScreen += value; } + remove { vdp.SizeScreen -= value; } + } + + public event EventHandler<ChangeViewportEventArgs> ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler<PollInputEventArgs> PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler<EnqueueSamplesEventArgs> EnqueueSamples + { + add { psg.EnqueueSamples += value; } + remove { psg.EnqueueSamples -= value; } + } + + public event EventHandler<SaveExtraDataEventArgs> SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler<EventArgs> EnableRumble { add { } remove { } } + + public string ManufacturerName => "Sega"; + public string ModelName => "SG-1000"; + public string DatFilename => "Sega - SG-1000.dat"; + public (string Extension, string Description) FileFilter => (".sg", "SG-1000 ROMs"); + public bool HasBootstrap => false; + public double RefreshRate { get; private set; } + public double PixelAspectRatio => 8.0 / 7.0; + public (string Name, string Description)[] RuntimeOptions => vdp.RuntimeOptions.Concat(psg.RuntimeOptions).ToArray(); + + ICartridge cartridge; + byte[] wram; + Z80A cpu; + TMS99xxA vdp; + SN76489 psg; + + [Flags] + enum PortIoABValues : byte + { + P1Up = 0b00000001, + P1Down = 0b00000010, + P1Left = 0b00000100, + P1Right = 0b00001000, + P1Button1 = 0b00010000, + P1Button2 = 0b00100000, + P2Up = 0b01000000, + P2Down = 0b10000000, + Mask = 0b11111111 + } + + [Flags] + enum PortIoBMiscValues : byte + { + P2Left = 0b00000001, + P2Right = 0b00000010, + P2Button1 = 0b00000100, + P2Button2 = 0b00001000, + CON = 0b00010000, + IC21Pin6 = 0b00100000, + IC21Pin10 = 0b01000000, + IC21Pin13 = 0b10000000, + Mask = 0b11111111 + } + + PortIoABValues portIoABPressed; + PortIoBMiscValues portIoBMiscPressed; + + bool pauseButtonPressed, pauseButtonToggle; + + int currentMasterClockCyclesInFrame, totalMasterClockCyclesInFrame; + + Configuration.SG1000 configuration; + + public SG1000() { } + + public void Initialize() + { + cartridge = null; + wram = new byte[ramSize]; + cpu = new Z80A(ReadMemory, WriteMemory, ReadPort, WritePort); + vdp = new TMS99xxA(); + psg = new SN76489(); + + vdp.EndOfScanline += (s, e) => + { + PollInputEventArgs pollInputEventArgs = new PollInputEventArgs(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + }; + } + + public void SetConfiguration(IConfiguration config) + { + configuration = (Configuration.SG1000)config; + + ReconfigureSystem(); + } + + public object GetRuntimeOption(string name) + { + if (name.StartsWith("Graphics")) + return vdp.GetRuntimeOption(name); + else if (name.StartsWith("Audio")) + return psg.GetRuntimeOption(name); + else + return null; + } + + public void SetRuntimeOption(string name, object value) + { + if (name.StartsWith("Graphics")) + vdp.SetRuntimeOption(name, value); + else if (name.StartsWith("Audio")) + psg.SetRuntimeOption(name, value); + } + + private void ReconfigureSystem() + { + if (configuration.TVStandard == TVStandard.NTSC) + { + masterClock = masterClockNtsc; + RefreshRate = refreshRateNtsc; + } + else + { + masterClock = masterClockPal; + RefreshRate = refreshRatePal; + } + + vdpClock = (masterClock / 1.0); + psgClock = (masterClock / 3.0); + + vdp?.SetClockRate(vdpClock); + vdp?.SetRefreshRate(RefreshRate); + vdp?.SetRevision(0); + + psg?.SetSampleRate(StandInfo.Configuration.SampleRate); + psg?.SetOutputChannels(2); + psg?.SetClockRate(psgClock); + psg?.SetRefreshRate(RefreshRate); + + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / RefreshRate); + + OnChangeViewport(new ChangeViewportEventArgs(vdp.Viewport)); + } + + public void Startup() + { + cpu.Startup(); + vdp.Startup(); + psg.Startup(); + } + + public void Reset() + { + cpu.Reset(); + cpu.SetStackPointer(0xDFF0); + vdp.Reset(); + psg.Reset(); + + portIoABPressed = 0; + portIoBMiscPressed = 0; + + pauseButtonPressed = pauseButtonToggle = false; + + OnEmulationReset(EventArgs.Empty); + } + + public void Shutdown() + { + cpu?.Shutdown(); + vdp?.Shutdown(); + psg?.Shutdown(); + } + + public void SetState(Dictionary<string, dynamic> state) + { + configuration.TVStandard = state[nameof(configuration.TVStandard)]; + + SaveStateHandler.PerformSetState(cartridge, state[nameof(cartridge)]); + wram = state[nameof(wram)]; + SaveStateHandler.PerformSetState(cpu, state[nameof(cpu)]); + SaveStateHandler.PerformSetState(vdp, state[nameof(vdp)]); + SaveStateHandler.PerformSetState(psg, state[nameof(psg)]); + + ReconfigureSystem(); + } + + public Dictionary<string, dynamic> GetState() + { + return new Dictionary<string, dynamic> + { + [nameof(configuration.TVStandard)] = configuration.TVStandard, + + [nameof(cartridge)] = SaveStateHandler.PerformGetState(cartridge), + [nameof(wram)] = wram, + [nameof(cpu)] = SaveStateHandler.PerformGetState(cpu), + [nameof(vdp)] = SaveStateHandler.PerformGetState(vdp), + [nameof(psg)] = SaveStateHandler.PerformGetState(psg) + }; + } + + public Dictionary<string, dynamic> GetDebugInformation() + { + var dict = new Dictionary<string, dynamic> + { + { "CyclesInFrame", currentMasterClockCyclesInFrame }, + }; + + return dict; + } + + public void Load(byte[] romData, byte[] ramData, Type mapperType) + { + if (mapperType == null) + mapperType = typeof(SegaSGCartridge); + + cartridge = (ICartridge)Activator.CreateInstance(mapperType, new object[] { romData.Length, ramData.Length }); + cartridge.LoadRom(romData); + cartridge.LoadRam(ramData); + } + + public byte[] GetCartridgeRam() + { + return cartridge.GetRamData(); + } + + public bool IsCartridgeRamSaveNeeded() + { + return cartridge.IsRamSaveNeeded(); + } + + public virtual void RunFrame() + { + while (currentMasterClockCyclesInFrame < totalMasterClockCyclesInFrame) + RunStep(); + + currentMasterClockCyclesInFrame -= totalMasterClockCyclesInFrame; + } + + public void RunStep() + { + double currentCpuClockCycles = 0.0; + currentCpuClockCycles += cpu.Step(); + + double currentMasterClockCycles = (currentCpuClockCycles * 3.0); + + vdp.Step((int)Math.Round(currentMasterClockCycles)); + + if (pauseButtonPressed) + { + pauseButtonPressed = false; + cpu.SetInterruptLine(InterruptType.NonMaskable, InterruptState.Assert); + } + + cpu.SetInterruptLine(InterruptType.Maskable, vdp.InterruptLine); + + psg.Step((int)Math.Round(currentCpuClockCycles)); + + cartridge?.Step((int)Math.Round(currentCpuClockCycles)); + + currentMasterClockCyclesInFrame += (int)Math.Round(currentMasterClockCycles); + } + + private void ParseInput(PollInputEventArgs eventArgs) + { + /* Get variables */ + var keysDown = eventArgs.Keyboard; + + /* Handle Pause button */ + var pausePressed = keysDown.Contains(configuration.InputPause); + var pauseButtonHeld = (pauseButtonToggle && pausePressed); + if (pausePressed) + { + if (!pauseButtonHeld) pauseButtonPressed = true; + pauseButtonToggle = true; + } + else if (pauseButtonToggle) + pauseButtonToggle = false; + + /* Handle controllers */ + portIoABPressed = 0; + portIoBMiscPressed = 0; + + /* Keyboard */ + if (keysDown.Contains(configuration.Joypad1Up)) portIoABPressed |= PortIoABValues.P1Up; + if (keysDown.Contains(configuration.Joypad1Down)) portIoABPressed |= PortIoABValues.P1Down; + if (keysDown.Contains(configuration.Joypad1Left)) portIoABPressed |= PortIoABValues.P1Left; + if (keysDown.Contains(configuration.Joypad1Right)) portIoABPressed |= PortIoABValues.P1Right; + if (keysDown.Contains(configuration.Joypad1Button1)) portIoABPressed |= PortIoABValues.P1Button1; + if (keysDown.Contains(configuration.Joypad1Button2)) portIoABPressed |= PortIoABValues.P1Button2; + if (keysDown.Contains(configuration.Joypad2Up)) portIoABPressed |= PortIoABValues.P2Up; + if (keysDown.Contains(configuration.Joypad2Down)) portIoABPressed |= PortIoABValues.P2Down; + if (keysDown.Contains(configuration.Joypad2Left)) portIoBMiscPressed |= PortIoBMiscValues.P2Left; + if (keysDown.Contains(configuration.Joypad2Right)) portIoBMiscPressed |= PortIoBMiscValues.P2Right; + if (keysDown.Contains(configuration.Joypad2Button1)) portIoBMiscPressed |= PortIoBMiscValues.P2Button1; + if (keysDown.Contains(configuration.Joypad2Button2)) portIoBMiscPressed |= PortIoBMiscValues.P2Button2; + + /* XInput controller */ + if (eventArgs.ControllerState.IsAnyUpDirectionPressed() && !eventArgs.ControllerState.IsAnyDownDirectionPressed()) portIoABPressed |= PortIoABValues.P1Up; + if (eventArgs.ControllerState.IsAnyDownDirectionPressed() && !eventArgs.ControllerState.IsAnyUpDirectionPressed()) portIoABPressed |= PortIoABValues.P1Down; + if (eventArgs.ControllerState.IsAnyLeftDirectionPressed() && !eventArgs.ControllerState.IsAnyRightDirectionPressed()) portIoABPressed |= PortIoABValues.P1Left; + if (eventArgs.ControllerState.IsAnyRightDirectionPressed() && !eventArgs.ControllerState.IsAnyLeftDirectionPressed()) portIoABPressed |= PortIoABValues.P1Right; + if (eventArgs.ControllerState.IsAPressed()) portIoABPressed |= PortIoABValues.P1Button1; + if (eventArgs.ControllerState.IsXPressed() || eventArgs.ControllerState.IsBPressed()) portIoABPressed |= PortIoABValues.P1Button2; + + portIoBMiscPressed |= (PortIoBMiscValues.IC21Pin6 | PortIoBMiscValues.IC21Pin10 | PortIoBMiscValues.IC21Pin13); /* Unused, always 1 */ + } + + private byte ReadMemory(ushort address) + { + if (address >= 0x0000 && address <= 0xBFFF) + { + return (cartridge != null ? cartridge.Read(address) : (byte)0x00); + } + else if (address >= 0xC000 && address <= 0xFFFF) + { + return wram[address & (ramSize - 1)]; + } + + /* Cannot read from address, return 0 */ + return 0x00; + } + + private void WriteMemory(ushort address, byte value) + { + if (address >= 0x0000 && address <= 0xBFFF) + { + cartridge?.Write(address, value); + } + else if (address >= 0xC000 && address <= 0xFFFF) + { + wram[address & (ramSize - 1)] = value; + } + } + + private byte ReadPort(byte port) + { + switch (port & 0xC0) + { + case 0x80: + return vdp.ReadPort(port); + + case 0xC0: + if ((port & 0x01) == 0) + return (byte)(PortIoABValues.Mask & ~portIoABPressed); + else + return (byte)(PortIoBMiscValues.Mask & ~portIoBMiscPressed); + + default: + // TODO: handle properly + return 0x00; + } + } + + public void WritePort(byte port, byte value) + { + switch (port & 0xC0) + { + case 0x40: psg.WritePort(port, value); break; + case 0x80: vdp.WritePort(port, value); break; + default: break; // TODO: handle properly + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Machines/SG1000.cs.meta b/Assets/Plugins/Essgee/Emulation/Machines/SG1000.cs.meta new file mode 100644 index 0000000..2014a6e --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Machines/SG1000.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cfe23eacb3173be4a94a9775b3289053 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Peripherals.meta b/Assets/Plugins/Essgee/Emulation/Peripherals.meta new file mode 100644 index 0000000..5d5f4a8 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Peripherals.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 53ebe1049b81852448c0a7671f0d499f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Peripherals/IPeripheral.cs b/Assets/Plugins/Essgee/Emulation/Peripherals/IPeripheral.cs new file mode 100644 index 0000000..238a5df --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Peripherals/IPeripheral.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation.Peripherals +{ + interface IPeripheral + { + void Startup(); + void Shutdown(); + void Reset(); + + byte ReadPort(byte port); + void WritePort(byte port, byte value); + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Peripherals/IPeripheral.cs.meta b/Assets/Plugins/Essgee/Emulation/Peripherals/IPeripheral.cs.meta new file mode 100644 index 0000000..064436b --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Peripherals/IPeripheral.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 399a15f4c8bd5824fac256665037afad \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Peripherals/Intel8255.cs b/Assets/Plugins/Essgee/Emulation/Peripherals/Intel8255.cs new file mode 100644 index 0000000..2ec7dd3 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Peripherals/Intel8255.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.Exceptions; +using Essgee.Utilities; + +namespace Essgee.Emulation.Peripherals +{ + public class Intel8255 : IPeripheral + { + [StateRequired] + public byte PortAInput { get; set; } + [StateRequired] + public byte PortBInput { get; set; } + [StateRequired] + public byte PortCInput { get; set; } + [StateRequired] + public byte PortAOutput { get; set; } + [StateRequired] + public byte PortBOutput { get; set; } + [StateRequired] + public byte PortCOutput { get; set; } + + [StateRequired] + byte configByte, setResetControlByte; + + int operatingModeGroupA => ((configByte >> 5) & 0x03); + bool isPortAInput => ((configByte & 0x10) == 0x10); + bool isPortCUInput => ((configByte & 0x08) == 0x08); + int operatingModeGroupB => ((configByte >> 2) & 0x01); + bool isPortBInput => ((configByte & 0x02) == 0x02); + bool isPortCLInput => ((configByte & 0x01) == 0x01); + int bitToChange => ((setResetControlByte >> 1) & 0x07); + bool isSetBitOperation => ((setResetControlByte & 0x01) == 0x01); + + public Intel8255() { } + + public void Startup() + { + // + } + + public void Shutdown() + { + // + } + + public void Reset() + { + PortAInput = PortAOutput = 0x00; + PortBInput = PortBOutput = 0x00; + PortCInput = PortCOutput = 0x00; + + WritePort(0x03, 0x9B); + } + + public byte ReadPort(byte port) + { + switch (port & 0x03) + { + case 0x00: return (isPortAInput ? PortAInput : PortAOutput); + case 0x01: return (isPortBInput ? PortBInput : PortBOutput); + case 0x02: return (byte)(((isPortCUInput ? PortCInput : PortCOutput) & 0xF0) | (isPortCLInput ? PortCInput : PortCOutput) & 0x0F); + case 0x03: return 0xFF; /* Cannot read control port */ + + default: throw new EmulationException(string.Format("i8255: Unsupported read from port 0x{0:X2}", port)); + } + } + + public void WritePort(byte port, byte value) + { + switch (port & 0x03) + { + case 0x00: PortAOutput = value; break; + case 0x01: PortBOutput = value; break; + case 0x02: PortCOutput = value; break; + + case 0x03: + /* Control port */ + if ((value & 0x80) == 0x80) + { + configByte = value; + } + else + { + setResetControlByte = value; + + byte mask = (byte)(1 << bitToChange); + if (isSetBitOperation) PortCOutput |= mask; + else PortCOutput &= (byte)~mask; + } + break; + + default: throw new EmulationException(string.Format("i8255: Unsupported write to port 0x{0:X2}, value 0x{1:X2}", port, value)); + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Peripherals/Intel8255.cs.meta b/Assets/Plugins/Essgee/Emulation/Peripherals/Intel8255.cs.meta new file mode 100644 index 0000000..a3b9b2d --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Peripherals/Intel8255.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5d39dd8d77215c64e981c2bebaac1901 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/SaveStateHandler.cs b/Assets/Plugins/Essgee/Emulation/SaveStateHandler.cs new file mode 100644 index 0000000..88444be --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/SaveStateHandler.cs @@ -0,0 +1,145 @@ +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization.Formatters.Binary; +using System.Text; + +namespace Essgee.Emulation +{ + public static class SaveStateHandler + { + public static string ExpectedVersion = $"ESGST{new Version(StandInfo.ProductVersion).Major:D3}"; + + public static Dictionary<string, dynamic> Load(Stream stream, string machineName) + { + stream.Position = 0; + + using (var reader = new BinaryReader(stream)) + { + /* Read and check version string */ + var version = Encoding.ASCII.GetString(reader.ReadBytes(ExpectedVersion.Length)); + if (version != ExpectedVersion) throw new EmulationException("Unsupported savestate version"); + + /* Read and check filesize */ + var filesize = reader.ReadUInt32(); + if (filesize != reader.BaseStream.Length) throw new EmulationException("Savestate filesize mismatch"); + + /* Read CRC32 */ + var crc32 = reader.ReadUInt32(); + + /* Read and check machine ID */ + var machineId = Encoding.ASCII.GetString(reader.ReadBytes(16)); + if (machineId != GenerateMachineIdString(machineName)) throw new EmulationException("Savestate machine mismatch"); + + /* Check CRC32 */ + using (var stateStream = new MemoryStream()) + { + reader.BaseStream.CopyTo(stateStream); + stateStream.Position = 0; + var expectedCrc32 = Crc32.Calculate(stateStream); + if (crc32 != expectedCrc32) throw new EmulationException("Savestate checksum error"); + + /* Read state data */ + var binaryFormatter = new BinaryFormatter(); + return (binaryFormatter.Deserialize(stateStream) as Dictionary<string, dynamic>); + } + } + } + + public static void Save(Stream stream, string machineName, Dictionary<string, dynamic> state) + { + using (var writer = new BinaryWriter(new MemoryStream())) + { + /* Write version string */ + writer.Write(Encoding.ASCII.GetBytes(ExpectedVersion)); + + /* Write filesize placeholder */ + var filesizePosition = writer.BaseStream.Position; + writer.Write(uint.MaxValue); + + /* Write CRC32 placeholder */ + var crc32Position = writer.BaseStream.Position; + writer.Write(uint.MaxValue); + + /* Write machine ID */ + writer.Write(Encoding.ASCII.GetBytes(GenerateMachineIdString(machineName))); + + /* Current position is end of header, store for later */ + var headerSize = writer.BaseStream.Position; + + /* Write state data */ + var binaryFormatter = new BinaryFormatter(); + binaryFormatter.Serialize(writer.BaseStream, state); + + /* Write filesize */ + var lastOffset = writer.BaseStream.Position; + writer.BaseStream.Position = filesizePosition; + writer.Write((uint)writer.BaseStream.Length); + writer.BaseStream.Position = lastOffset; + + /* Calculate CRC32 for state data, then write CRC32 */ + lastOffset = writer.BaseStream.Position; + + writer.BaseStream.Position = 0; + var crc32 = Crc32.Calculate(writer.BaseStream, (int)headerSize, (int)(writer.BaseStream.Length - headerSize)); + + writer.BaseStream.Position = crc32Position; + writer.Write(crc32); + writer.BaseStream.Position = lastOffset; + + /* Copy to file */ + writer.BaseStream.Position = 0; + writer.BaseStream.CopyTo(stream); + } + } + + private static string GenerateMachineIdString(string machineId) + { + return machineId.Substring(0, Math.Min(machineId.Length, 16)).PadRight(16); + } + + public static void PerformSetState(object obj, Dictionary<string, dynamic> state) + { + if (obj != null) + { + /* Restore property values from state */ + foreach (var prop in obj.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(x => x.GetCustomAttributes(typeof(StateRequiredAttribute), false).Length != 0)) + { + prop.SetValue(obj, state[prop.Name]); + } + + /* Restore field values from state */ + foreach (var field in obj.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(x => x.GetCustomAttributes(typeof(StateRequiredAttribute), false).Length != 0)) + { + field.SetValue(obj, state[field.Name]); + } + } + } + + public static Dictionary<string, dynamic> PerformGetState(object obj) + { + var state = new Dictionary<string, dynamic>(); + + if (obj != null) + { + /* Copy property values to state */ + foreach (var prop in obj.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(x => x.GetCustomAttributes(typeof(StateRequiredAttribute), false).Length != 0)) + { + state.Add(prop.Name, prop.GetValue(obj)); + } + + /* Copy field values to state */ + foreach (var field in obj.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(x => x.GetCustomAttributes(typeof(StateRequiredAttribute), false).Length != 0)) + { + state.Add(field.Name, field.GetValue(obj)); + } + } + + return state; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/SaveStateHandler.cs.meta b/Assets/Plugins/Essgee/Emulation/SaveStateHandler.cs.meta new file mode 100644 index 0000000..36a4413 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/SaveStateHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 174dc0116b075184494eb514aea3ad36 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Utilities.cs b/Assets/Plugins/Essgee/Emulation/Utilities.cs new file mode 100644 index 0000000..80ee2e0 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Utilities.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Emulation +{ + public static class Utilities + { + public static bool IsBitSet(byte value, int bit) + { + return ((value & (1 << bit)) != 0); + } + + public static void RGB222toBGRA8888(int color, ref byte[] buffer, int address) + { + byte r = (byte)((color >> 0) & 0x3), g = (byte)((color >> 2) & 0x3), b = (byte)((color >> 4) & 0x3); + buffer[address + 0] = (byte)((b << 6) | (b << 4) | (b << 2) | b); + buffer[address + 1] = (byte)((g << 6) | (g << 4) | (g << 2) | g); + buffer[address + 2] = (byte)((r << 6) | (r << 4) | (r << 2) | r); + buffer[address + 3] = 0xFF; + } + + public static void RGB444toBGRA8888(int color, ref byte[] buffer, int address) + { + byte r = (byte)((color >> 0) & 0xF), g = (byte)((color >> 4) & 0xF), b = (byte)((color >> 8) & 0xF); + buffer[address + 0] = (byte)((b << 4) | b); + buffer[address + 1] = (byte)((g << 4) | g); + buffer[address + 2] = (byte)((r << 4) | r); + buffer[address + 3] = 0xFF; + } + + public static void RGBCGBtoBGRA8888(int color, ref byte[] buffer, int address) + { + /* https://byuu.net/video/color-emulation -- "LCD emulation: Game Boy Color" */ + byte r = (byte)((color >> 0) & 0x1F), g = (byte)((color >> 5) & 0x1F), b = (byte)((color >> 10) & 0x1F); + buffer[address + 0] = (byte)(Math.Min(960, (r * 6) + (g * 4) + (b * 22)) >> 2); + buffer[address + 1] = (byte)(Math.Min(960, (g * 24) + (b * 8)) >> 2); + buffer[address + 2] = (byte)(Math.Min(960, (r * 26) + (g * 4) + (b * 2)) >> 2); + buffer[address + 3] = 0xFF; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Utilities.cs.meta b/Assets/Plugins/Essgee/Emulation/Utilities.cs.meta new file mode 100644 index 0000000..96ee3cf --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Utilities.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 09a14347df3fa794380f049632d22821 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Video.meta b/Assets/Plugins/Essgee/Emulation/Video.meta new file mode 100644 index 0000000..eed8189 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 38fa5b2e38bbb7b4695789da71a3506b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Video/IVideo.cs b/Assets/Plugins/Essgee/Emulation/Video/IVideo.cs new file mode 100644 index 0000000..51e3c5c --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/IVideo.cs @@ -0,0 +1,33 @@ +using System; + +using Essgee.EventArguments; + +namespace Essgee.Emulation.Video +{ + interface IVideo + { + (int X, int Y, int Width, int Height) Viewport { get; } + + event EventHandler<RenderScreenEventArgs> RenderScreen; + void OnRenderScreen(RenderScreenEventArgs e); + + event EventHandler<EventArgs> EndOfScanline; + void OnEndOfScanline(EventArgs e); + + event EventHandler<SizeScreenEventArgs> SizeScreen; + void OnSizeScreen(SizeScreenEventArgs e); + + (string Name, string Description)[] RuntimeOptions { get; } + + object GetRuntimeOption(string name); + void SetRuntimeOption(string name, object value); + + void Startup(); + void Shutdown(); + void Reset(); + void Step(int clockCyclesInStep); + + void SetClockRate(double clock); + void SetRefreshRate(double refresh); + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Video/IVideo.cs.meta b/Assets/Plugins/Essgee/Emulation/Video/IVideo.cs.meta new file mode 100644 index 0000000..a3fdf61 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/IVideo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4992318d978fee542b3b9cc4011f00d7 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Video/Nintendo.meta b/Assets/Plugins/Essgee/Emulation/Video/Nintendo.meta new file mode 100644 index 0000000..192c4e4 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/Nintendo.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1ddba906f8855dd40bb9666a7106c3b9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Emulation/Video/Nintendo/CGBVideo.cs b/Assets/Plugins/Essgee/Emulation/Video/Nintendo/CGBVideo.cs new file mode 100644 index 0000000..a2b25ca --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/Nintendo/CGBVideo.cs @@ -0,0 +1,539 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; + +using Essgee.Emulation.CPU; +using Essgee.Exceptions; +using Essgee.EventArguments; +using Essgee.Utilities; + +using static Essgee.Emulation.CPU.SM83; +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Video.Nintendo +{ + public class CGBVideo : DMGVideo + { + protected override int numSkippedFramesLcdOn => 1; + + // FF4F - VBK + byte vramBank; + + // FF51 - HDMA1 + byte dmaSourceHi; + + // FF52 - HDMA2 + byte dmaSourceLo; + + // FF53 - HDMA3 + byte dmaDestinationHi; + + // FF54 - HDMA4 + byte dmaDestinationLo; + + // FF55 - HDMA5 + byte dmaTransferBlockLength; + bool dmaTransferIsHDMA; + + // FF68 - BCPS + byte bgPaletteIndex; + bool bgPaletteAutoIncrement; + + // FF69 - BCPD + + // FF6A - OCPS + byte objPaletteIndex; + bool objPaletteAutoIncrement; + + // FF6B - OCPD + + // + + byte[] bgPaletteData, objPaletteData; + + ushort dmaSourceAddress, dmaDestinationAddress; + int dmaTransferByteLength; + + bool hdmaIsActive; + byte hdmaBytesLeft; + + public int GDMAWaitCycles { get; set; } + + protected const byte screenUsageBackgroundHighPriority = (1 << 3); + + public CGBVideo(MemoryReadDelegate memoryRead, RequestInterruptDelegate requestInterrupt) : base(memoryRead, requestInterrupt) + { + vram = new byte[2, 0x2000]; + + // + + bgPaletteData = new byte[64]; + objPaletteData = new byte[64]; + } + + public override void Reset() + { + base.Reset(); + + vramBank = 0; + + dmaTransferBlockLength = 0; + dmaTransferIsHDMA = false; + + bgPaletteAutoIncrement = true; + objPaletteAutoIncrement = true; + + for (var i = 0; i < bgPaletteData.Length; i += 2) + { + bgPaletteData[i + 1] = 0x7F; + bgPaletteData[i + 0] = 0xFF; + } + for (var i = 0; i < objPaletteData.Length; i += 2) + { + objPaletteData[i + 1] = 0x7F; + objPaletteData[i + 0] = 0xFF; + } + + dmaSourceAddress = dmaDestinationAddress = 0; + dmaTransferByteLength = 0; + + hdmaIsActive = false; + hdmaBytesLeft = 0; + } + + // + + protected override void StepHBlank() + { + /* Check and perform HDMA */ + if (hdmaIsActive && dmaTransferIsHDMA) + { + if (hdmaBytesLeft > 0 && (cycleCount % 2) == 0) + { + WriteVram(dmaDestinationAddress, memoryReadDelegate(dmaSourceAddress)); + dmaDestinationAddress++; + dmaSourceAddress++; + + dmaDestinationAddress &= 0x1FFF; + + dmaTransferByteLength--; + hdmaBytesLeft--; + + if (dmaTransferByteLength == 0) + hdmaIsActive = false; + + UpdateDMAStatus(); + } + } + + /* Increment cycle count & check for next LCD mode */ + cycleCount++; + if (cycleCount == clockCyclesPerLine) + { + if (hdmaIsActive) + hdmaBytesLeft = (byte)Math.Min(0x10, 0x10 - (dmaTransferByteLength % 16)); + + EndHBlank(); + } + } + + // + + protected override void RenderPixel(int y, int x) + { + if (x < 0 || x >= displayActiveWidth || y < 0 || y >= displayActiveHeight) return; + + if (skipFrames > 0) + { + SetPixel(y, x, 0xFF, 0xFF, 0xFF); + return; + } + + screenUsageFlags[x, y] = screenUsageEmpty; + + RenderBackground(y, x); + + if (wndEnable) RenderWindow(y, x); + if (objEnable) RenderSprites(y, x); + } + + protected override void RenderBackground(int y, int x) + { + // Get base addresses + var tileBase = (ushort)(bgWndTileSelect ? 0x0000 : 0x0800); + var mapBase = (ushort)(bgMapSelect ? 0x1C00 : 0x1800); + + // Calculate tilemap address & get tile + var yTransformed = (byte)(scrollY + y); + var xTransformed = (byte)(scrollX + x); + var mapAddress = mapBase + ((yTransformed >> 3) << 5) + (xTransformed >> 3); + var tileNumber = vram[0, mapAddress]; + if (!bgWndTileSelect) + tileNumber = (byte)(tileNumber ^ 0x80); + + // Get & extract tile attributes + var tileAttribs = vram[1, mapAddress]; + var tileBgPalette = tileAttribs & 0b111; + var tileVramBank = (tileAttribs >> 3) & 0b1; + var tileHorizontalFlip = ((tileAttribs >> 5) & 0b1) == 0b1; + var tileVerticalFlip = ((tileAttribs >> 6) & 0b1) == 0b1; + var tileBgHasPriority = ((tileAttribs >> 7) & 0b1) == 0b1; + + // Calculate tile address & get pixel color index + var xShift = tileHorizontalFlip ? (xTransformed % 8) : (7 - (xTransformed % 8)); + var yShift = tileVerticalFlip ? (7 - (yTransformed & 7)) : (yTransformed & 7); + var tileAddress = tileBase + (tileNumber << 4) + (yShift << 1); + var ba = (vram[tileVramBank, tileAddress + 0] >> xShift) & 0b1; + var bb = (vram[tileVramBank, tileAddress + 1] >> xShift) & 0b1; + var c = (byte)((bb << 1) | ba); + + // If color is not 0, note that a BG pixel (normal or high-priority) exists here + if (c != 0) + screenUsageFlags[x, y] |= tileBgHasPriority ? screenUsageBackgroundHighPriority : screenUsageBackground; + + // Calculate color address in palette & draw pixel + if (layerBackgroundForceEnable) + { + var paletteAddress = (tileBgPalette << 3) + ((c & 0b11) << 1); + SetPixel(y, x, (ushort)((bgPaletteData[paletteAddress + 1] << 8) | bgPaletteData[paletteAddress + 0])); + } + else + SetPixel(y, x, (ushort)((bgPaletteData[1] << 8) | bgPaletteData[0])); + } + + protected override void RenderWindow(int y, int x) + { + // Check if current coords are inside window + if (y < windowY) return; + if (x < (windowX - 7)) return; + + // Get base addresses + var tileBase = (ushort)(bgWndTileSelect ? 0x0000 : 0x0800); + var mapBase = (ushort)(wndMapSelect ? 0x1C00 : 0x1800); + + // Calculate tilemap address & get tile + var yTransformed = (byte)(y - windowY); + var xTransformed = (byte)((7 - windowX) + x); + var mapAddress = mapBase + ((yTransformed >> 3) << 5) + (xTransformed >> 3); + var tileNumber = vram[0, mapAddress]; + if (!bgWndTileSelect) + tileNumber = (byte)(tileNumber ^ 0x80); + + // Get & extract tile attributes + var tileAttribs = vram[1, mapAddress]; + var tileBgPalette = tileAttribs & 0b111; + var tileVramBank = (tileAttribs >> 3) & 0b1; + var tileHorizontalFlip = ((tileAttribs >> 5) & 0b1) == 0b1; + var tileVerticalFlip = ((tileAttribs >> 6) & 0b1) == 0b1; + var tileBgHasPriority = ((tileAttribs >> 7) & 0b1) == 0b1; + + // Calculate tile address & get pixel color index + var xShift = tileHorizontalFlip ? (xTransformed % 8) : (7 - (xTransformed % 8)); + var yShift = tileVerticalFlip ? (7 - (yTransformed & 7)) : (yTransformed & 7); + var tileAddress = tileBase + (tileNumber << 4) + (yShift << 1); + var ba = (vram[tileVramBank, tileAddress + 0] >> xShift) & 0b1; + var bb = (vram[tileVramBank, tileAddress + 1] >> xShift) & 0b1; + var c = (byte)((bb << 1) | ba); + + // If color is not 0, note that a Window pixel (normal or high-priority) exists here + if (c != 0) + screenUsageFlags[x, y] |= tileBgHasPriority ? screenUsageBackgroundHighPriority : screenUsageWindow; // TODO correct? + + // Calculate color address in palette & draw pixel + if (layerWindowForceEnable) + { + var paletteAddress = (tileBgPalette << 3) + ((c & 0b11) << 1); + SetPixel(y, x, (ushort)((bgPaletteData[paletteAddress + 1] << 8) | bgPaletteData[paletteAddress + 0])); + } + else + SetPixel(y, x, (ushort)((bgPaletteData[1] << 8) | bgPaletteData[0])); + } + + protected override void RenderSprites(int y, int x) + { + var objHeight = objSize ? 16 : 8; + + // Iterate over sprite on line backwards + for (var s = numSpritesOnLine - 1; s >= 0; s--) + { + var i = spritesOnLine[s]; + + // Get sprite Y coord & if sprite is not on current scanline, continue to next slot + var objY = (short)(oam[(i * 4) + 0] - 16); + if (y < objY || y >= (objY + objHeight)) continue; + + // Get sprite X coord, tile number & attributes + var objX = (byte)(oam[(i * 4) + 1] - 8); + var objTileNumber = oam[(i * 4) + 2]; + var objAttributes = oam[(i * 4) + 3]; + + // Extract attributes + var objFlipY = ((objAttributes >> 6) & 0b1) == 0b1; + var objFlipX = ((objAttributes >> 5) & 0b1) == 0b1; + var objVramBank = (objAttributes >> 3) & 0b1; + var objPalNumber = (objAttributes >> 0) & 0b111; + + // Iterate over pixels + for (var px = 0; px < 8; px++) + { + // If sprite pixel X coord does not equal current rendering X coord, continue to next pixel + if (x != (byte)(objX + px)) continue; + + // Calculate tile address + var xShift = objFlipX ? (px % 8) : (7 - (px % 8)); + var yShift = objFlipY ? (7 - ((y - objY) % 8)) : ((y - objY) % 8); + if (objSize) + { + objTileNumber &= 0xFE; + if ((objFlipY && y < (objY + 8)) || (!objFlipY && y >= (objY + 8))) + objTileNumber |= 0x01; + } + var tileAddress = (objTileNumber << 4) + (yShift << 1); + + // Get palette & bitplanes + var ba = (vram[objVramBank, tileAddress + 0] >> xShift) & 0b1; + var bb = (vram[objVramBank, tileAddress + 1] >> xShift) & 0b1; + + // Combine to color index, continue drawing if color is not 0 + var c = (byte)((bb << 1) | ba); + if (c != 0) + { + // If sprite does not have priority i.e. if sprite should not be drawn, continue to next pixel + if (!HasSpritePriority(y, x, i)) continue; + + screenUsageFlags[x, y] |= screenUsageSprite; + screenUsageSpriteSlots[x, y] = (byte)i; + screenUsageSpriteXCoords[x, y] = objX; + + // Calculate color address in palette & draw pixel + if (layerSpritesForceEnable) + { + var paletteAddress = (objPalNumber << 3) + ((c & 0b11) << 1); + SetPixel(y, x, (ushort)((objPaletteData[paletteAddress + 1] << 8) | objPaletteData[paletteAddress + 0])); + } + } + } + } + } + + protected override bool HasSpritePriority(int y, int x, int objSlot) + { + // If BG and window have priority, check further conditions + if (bgEnable) + { + // Get new sprite OBJ-to-BG priority attribute + var objIsBehindBg = ((oam[(objSlot * 4) + 3] >> 7) & 0b1) == 0b1; + + // If high-priority BG pixel has already been drawn, -or- new sprite is shown behind BG/Window -and- a BG/Window pixel has already been drawn, new sprite does not have priority + if (IsScreenUsageFlagSet(y, x, screenUsageBackgroundHighPriority) || + (objIsBehindBg && (IsScreenUsageFlagSet(y, x, screenUsageBackground) || IsScreenUsageFlagSet(y, x, screenUsageWindow)))) return false; + } + + // New sprite has priority + return true; + } + + protected void SetPixel(int y, int x, ushort c) + { + WriteColorToFramebuffer(c, ((y * displayActiveWidth) + (x % displayActiveWidth)) * 4); + } + + private void WriteColorToFramebuffer(ushort c, int address) + { + RGBCGBtoBGRA8888(c, ref outputFramebuffer, address); + } + + // + + private void RunGDMA() + { + while (--dmaTransferByteLength >= 0) + { + if ((dmaSourceAddress >= 0x0000 && dmaSourceAddress <= 0x7FFF) || (dmaSourceAddress >= 0xA000 && dmaSourceAddress <= 0xDFFF)) + WriteVram(dmaDestinationAddress, memoryReadDelegate(dmaSourceAddress)); + + dmaDestinationAddress++; + dmaSourceAddress++; + + dmaDestinationAddress &= 0x1FFF; + } + + UpdateDMAStatus(); + } + + private void UpdateDMAStatus() + { + dmaTransferBlockLength = (byte)((dmaTransferByteLength >> 4) & 0xFF); + + dmaSourceHi = (byte)((dmaSourceAddress >> 8) & 0x1F); + dmaSourceLo = (byte)((dmaSourceAddress >> 0) & 0xF0); + dmaDestinationHi = (byte)((dmaDestinationAddress >> 8) & 0xFF); + dmaDestinationLo = (byte)((dmaDestinationAddress >> 0) & 0xF0); + } + + // + + public override byte ReadVram(ushort address) + { + if (modeNumber != 3) + return vram[vramBank, address & 0x1FFF]; + else + return 0xFF; + } + + public override void WriteVram(ushort address, byte value) + { + if (modeNumber != 3) + vram[vramBank, address & 0x1FFF] = value; + } + + public override byte ReadPort(byte port) + { + switch (port) + { + case 0x4F: + // VBK + return (byte)( + 0xFE | + (vramBank & 0b1)); + + case 0x55: + // HDMA5 + return dmaTransferBlockLength; + + case 0x68: + // BCPS + return (byte)( + 0x40 | + (bgPaletteIndex & 0x3F) | + (bgPaletteAutoIncrement ? (1 << 7) : 0)); + + case 0x69: + // BCPD + if (modeNumber != 3) return bgPaletteData[bgPaletteIndex]; + else return 0xFF; + + case 0x6A: + // OCPS + return (byte)( + 0x40 | + (objPaletteIndex & 0x3F) | + (objPaletteAutoIncrement ? (1 << 7) : 0)); + + case 0x6B: + // OCPD + if (modeNumber != 3) return objPaletteData[objPaletteIndex]; + else return 0xFF; + + default: + return base.ReadPort(port); + } + } + + public override void WritePort(byte port, byte value) + { + switch (port) + { + case 0x41: + // STAT + lycLyInterrupt = ((value >> 6) & 0b1) == 0b1; + m2OamInterrupt = ((value >> 5) & 0b1) == 0b1; + m1VBlankInterrupt = ((value >> 4) & 0b1) == 0b1; + m0HBlankInterrupt = ((value >> 3) & 0b1) == 0b1; + + CheckAndRequestStatInterupt(); + break; + + case 0x4F: + // VBK + vramBank = (byte)(value & 0b1); + break; + + case 0x51: + // HDMA1 + dmaSourceHi = value; + break; + + case 0x52: + // HDMA2 + dmaSourceLo = (byte)(value & 0xF0); + break; + + case 0x53: + // HDMA3 + dmaDestinationHi = (byte)(value & 0x1F); + break; + + case 0x54: + // HDMA4 + dmaDestinationLo = (byte)(value & 0xF0); + break; + + case 0x55: + // HDMA5 + dmaTransferBlockLength = (byte)(value & 0x7F); + dmaTransferIsHDMA = ((value >> 7) & 0b1) == 0b1; + + // Check for HDMA cancellation + if (!dmaTransferIsHDMA && hdmaIsActive) + hdmaIsActive = false; + else + { + // Calculate DMA addresses & length + dmaTransferByteLength = (dmaTransferBlockLength + 1) << 4; + dmaSourceAddress = (ushort)((dmaSourceHi << 8 | dmaSourceLo) & 0xFFF0); + dmaDestinationAddress = (ushort)((dmaDestinationHi << 8 | dmaDestinationLo) & 0x1FF0); + + // Run General-Purpose DMA + if (!dmaTransferIsHDMA) + { + GDMAWaitCycles = 8 * (dmaTransferBlockLength + 1); + RunGDMA(); + } + else + hdmaIsActive = true; + } + break; + + case 0x68: + // BCPS + bgPaletteIndex = (byte)(value & 0x3F); + bgPaletteAutoIncrement = ((value >> 7) & 0b1) == 0b1; + break; + + case 0x69: + // BCPD + if (modeNumber != 3) bgPaletteData[bgPaletteIndex] = value; + if (bgPaletteAutoIncrement) + { + bgPaletteIndex++; + bgPaletteIndex &= 0x3F; + } + break; + + case 0x6A: + // OCPS + objPaletteIndex = (byte)(value & 0x3F); + objPaletteAutoIncrement = ((value >> 7) & 0b1) == 0b1; + break; + + case 0x6B: + // OCPD + if (modeNumber != 3) objPaletteData[objPaletteIndex] = value; + if (objPaletteAutoIncrement) + { + objPaletteIndex++; + objPaletteIndex &= 0x3F; + } + break; + + default: + base.WritePort(port, value); + break; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Video/Nintendo/CGBVideo.cs.meta b/Assets/Plugins/Essgee/Emulation/Video/Nintendo/CGBVideo.cs.meta new file mode 100644 index 0000000..874fe8d --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/Nintendo/CGBVideo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4025ca74baf3418409354b5129430a4f \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Video/Nintendo/DMGVideo.cs b/Assets/Plugins/Essgee/Emulation/Video/Nintendo/DMGVideo.cs new file mode 100644 index 0000000..c91b629 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/Nintendo/DMGVideo.cs @@ -0,0 +1,874 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; + +using Essgee.Exceptions; +using Essgee.EventArguments; +using Essgee.Utilities; + +using static Essgee.Emulation.CPU.SM83; + +namespace Essgee.Emulation.Video.Nintendo +{ + public class DMGVideo : IVideo + { + protected const int displayActiveWidth = 160; + protected const int displayActiveHeight = 144; + protected const int numDisplayPixels = displayActiveWidth * displayActiveHeight; + + protected const int displayTotalHeight = 154; + + protected const int numOamSlots = 40; + protected const int maxSpritesPerLine = 10; + + protected const int mode2Boundary = 80; + protected const int mode3Boundary = mode2Boundary + 168; + + protected const string layerBackgroundOptionName = "GraphicsLayersShowBackground"; + protected const string layerWindowOptionName = "GraphicsLayersShowWindow"; + protected const string layerSpritesOptionName = "GraphicsLayersShowSprites"; + + protected virtual int numSkippedFramesLcdOn => 4; + + protected Action[] modeFunctions; + + protected readonly MemoryReadDelegate memoryReadDelegate; + protected readonly RequestInterruptDelegate requestInterruptDelegate; + + public virtual (int X, int Y, int Width, int Height) Viewport => (0, 0, displayActiveWidth, displayActiveHeight); + + public virtual event EventHandler<SizeScreenEventArgs> SizeScreen; + public virtual void OnSizeScreen(SizeScreenEventArgs e) { SizeScreen?.Invoke(this, e); } + + public virtual event EventHandler<RenderScreenEventArgs> RenderScreen; + public virtual void OnRenderScreen(RenderScreenEventArgs e) { RenderScreen?.Invoke(this, e); } + + public virtual event EventHandler<EventArgs> EndOfScanline; + public virtual void OnEndOfScanline(EventArgs e) { EndOfScanline?.Invoke(this, e); } + + // + + protected double clockRate, refreshRate; + + // + + [StateRequired] + protected byte[,] vram; + [StateRequired] + protected byte[] oam; + + // FF40 - LCDC + protected bool lcdEnable, wndMapSelect, wndEnable, bgWndTileSelect, bgMapSelect, objSize, objEnable, bgEnable; + + // FF41 - STAT + protected bool lycLyInterrupt, m2OamInterrupt, m1VBlankInterrupt, m0HBlankInterrupt, coincidenceFlag; + protected byte modeNumber; + + // FF42 - SCY + protected byte scrollY; + // FF43 - SCX + protected byte scrollX; + + // FF44 - LY + protected byte ly; + // FF45 - LYC + protected byte lyCompare; + + // FF46 - DMA + protected byte oamDmaStart; + + // FF47 - BGP + protected byte bgPalette; + // FF48 - OBP0 + protected byte obPalette0; + // FF49 - OBP1 + protected byte obPalette1; + + // FF4A - WY + protected byte windowY; + // FF4B - WX + protected byte windowX; + + // + + protected int numSpritesOnLine, skipFrames; + protected bool statIrqSignal, vBlankReady; + protected int[] spritesOnLine; + + readonly byte[][] colorValuesBgr = new byte[][] + { + /* B G R */ + new byte[] { 0xF8, 0xF8, 0xF8 }, /* White */ + new byte[] { 0x9B, 0x9B, 0x9B }, /* Light gray */ + new byte[] { 0x3E, 0x3E, 0x3E }, /* Dark gray */ + new byte[] { 0x1F, 0x1F, 0x1F }, /* Black */ + }; + + protected const byte screenUsageEmpty = 0; + protected const byte screenUsageBackground = 1 << 0; + protected const byte screenUsageWindow = 1 << 1; + protected const byte screenUsageSprite = 1 << 2; + protected byte[,] screenUsageFlags, screenUsageSpriteXCoords, screenUsageSpriteSlots; + + protected int cycleCount, cycleDotPause, currentScanline; + protected byte[] outputFramebuffer; + + protected int clockCyclesPerLine; + + public bool IsDoubleSpeed { get; set; } + + // + + public (string Name, string Description)[] RuntimeOptions => new (string name, string description)[] + { + (layerBackgroundOptionName, "Background"), + (layerWindowOptionName, "Window"), + (layerSpritesOptionName, "Sprites"), + }; + + protected bool layerBackgroundForceEnable, layerWindowForceEnable, layerSpritesForceEnable; + + public DMGVideo(MemoryReadDelegate memoryRead, RequestInterruptDelegate requestInterrupt) + { + vram = new byte[1, 0x2000]; + oam = new byte[0xA0]; + + // + + modeFunctions = new Action[] { StepHBlank, StepVBlank, StepOAMSearch, StepLCDTransfer }; + + spritesOnLine = new int[maxSpritesPerLine]; + + memoryReadDelegate = memoryRead; + requestInterruptDelegate = requestInterrupt; + + layerBackgroundForceEnable = true; + layerWindowForceEnable = true; + layerSpritesForceEnable = true; + } + + public object GetRuntimeOption(string name) + { + switch (name) + { + case layerBackgroundOptionName: return layerBackgroundForceEnable; + case layerWindowOptionName: return layerWindowForceEnable; + case layerSpritesOptionName: return layerSpritesForceEnable; + default: return null; + } + } + + public void SetRuntimeOption(string name, object value) + { + switch (name) + { + case layerBackgroundOptionName: layerBackgroundForceEnable = (bool)value; break; + case layerWindowOptionName: layerWindowForceEnable = (bool)value; break; + case layerSpritesOptionName: layerSpritesForceEnable = (bool)value; break; + } + } + + public virtual void Startup() + { + Reset(); + + if (memoryReadDelegate == null) throw new EmulationException("DMGVideo: Memory read delegate is null"); + if (requestInterruptDelegate == null) throw new EmulationException("DMGVideo: Request interrupt delegate is null"); + + Debug.Assert(clockRate != 0.0, "Clock rate is zero", "{0} clock rate is not configured", GetType().FullName); + Debug.Assert(refreshRate != 0.0, "Refresh rate is zero", "{0} refresh rate is not configured", GetType().FullName); + } + + public virtual void Shutdown() + { + // + } + + public virtual void Reset() + { + for (var i = 0; i < vram.GetLength(0); i++) vram[0, i] = 0; + for (var i = 0; i < oam.Length; i++) oam[i] = 0; + + for (var i = (byte)0x40; i < 0x4C; i++) + { + // skip OAM dma + if (i != 0x46) WritePort(i, 0x00); + } + + numSpritesOnLine = skipFrames = 0; + statIrqSignal = vBlankReady = false; + + for (var i = 0; i < spritesOnLine.Length; i++) spritesOnLine[i] = -1; + + ClearScreenUsage(); + + cycleCount = cycleDotPause = currentScanline = 0; + } + + public void SetClockRate(double clock) + { + clockRate = clock; + + ReconfigureTimings(); + } + + public void SetRefreshRate(double refresh) + { + refreshRate = refresh; + + ReconfigureTimings(); + } + + public virtual void SetRevision(int rev) + { + Debug.Assert(rev == 0, "Invalid revision", "{0} revision is invalid; only rev 0 is valid", GetType().FullName); + } + + protected virtual void ReconfigureTimings() + { + /* Calculate cycles/line */ + clockCyclesPerLine = (int)Math.Round((clockRate / refreshRate) / displayTotalHeight); + + /* Create arrays */ + screenUsageFlags = new byte[displayActiveWidth, displayActiveHeight]; + screenUsageSpriteXCoords = new byte[displayActiveWidth, displayActiveHeight]; + screenUsageSpriteSlots = new byte[displayActiveWidth, displayActiveHeight]; + outputFramebuffer = new byte[numDisplayPixels * 4]; + + for (var y = 0; y < displayActiveHeight; y++) + SetLine(y, 0xFF, 0xFF, 0xFF); + } + + public virtual void Step(int clockCyclesInStep) + { + for (var c = 0; c < clockCyclesInStep; c++) + { + if (lcdEnable) + { + /* LCD enabled, handle LCD modes */ + modeFunctions[modeNumber](); + } + else + { + /* LCD disabled */ + modeNumber = 0; + cycleCount = 0; + cycleDotPause = 0; + + currentScanline = 0; + ly = 0; + } + } + } + + /* TODO: dot clock pause! -- https://gbdev.io/pandocs/#properties-of-stat-modes + * - non-zero (SCX % 8) causes pause of that many dots + * - active window causes pause of *at least* 6 dots? + * - sprite dot pauses?? + * - influence of CGB double-speed mode on this? + * Required for: + * - GBVideoPlayer2, pixel column alignment & garbage on left screen edge + * - snorpung/pocket demo, vertical scroller right edge of scroll area + * - Prehistorik Man, scroller alignment (ex. level start "START" text should be centered on screen) + * - ...probably more? + */ + + protected virtual void StepVBlank() + { + // TODO: *should* be 4, but Altered Space hangs w/ any value lower than 8? + if (cycleCount == 8 && vBlankReady) + { + requestInterruptDelegate(InterruptSource.VBlank); + vBlankReady = false; + } + + /* V-blank */ + cycleCount++; + if (cycleCount == clockCyclesPerLine) EndVBlank(); + } + + protected virtual void EndVBlank() + { + /* End of scanline reached */ + OnEndOfScanline(EventArgs.Empty); + currentScanline++; + ly = (byte)currentScanline; + + /* Check for & request STAT interrupts */ + CheckAndRequestStatInterupt(); + + if (currentScanline == displayTotalHeight - 1) + { + // TODO: specific cycle this happens? + + /* LY reports as 0 on line 153 */ + ly = 0; + + // TODO: verify if STAT/LYC interrupt is supposed to happen here? currently breaks Shantae's sprites if done + //CheckAndRequestStatInterupt(); + } + else if (currentScanline == displayTotalHeight) + { + /* End of V-blank reached */ + modeNumber = 2; + currentScanline = 0; + ly = 0; + + CheckAndRequestStatInterupt(); + + ClearScreenUsage(); + } + + cycleCount = 0; + } + + protected virtual void StepOAMSearch() + { + /* OAM search */ + + if ((cycleCount % 2) == 0) + { + /* Get object Y coord */ + var objIndex = cycleCount >> 1; + var objY = oam[(objIndex << 2) + 0] - 16; + + /* Check if object is on current scanline & maximum number of objects was not exceeded, then increment counter */ + if (currentScanline >= objY && currentScanline < (objY + (objSize ? 16 : 8)) && numSpritesOnLine < maxSpritesPerLine) + { + var objX = oam[(objIndex << 2) + 1] - 8; + cycleDotPause += 11 - Math.Min(5, (objX + (objX >= windowX ? (255 - windowX) : scrollX)) % 8); // TODO: correct? + spritesOnLine[numSpritesOnLine++] = objIndex; + } + } + + /* Increment cycle count & check for next LCD mode */ + cycleCount++; + if (cycleCount == mode2Boundary) EndOAMSearch(); + } + + protected virtual void EndOAMSearch() + { + // 1) GBVideoPlayer2 & Prehistorik Man alignment, 2) GBVideoPlayer2 alignment + cycleDotPause += (6 << (IsDoubleSpeed ? 1 : 0)) + (scrollX % 8); + + modeNumber = 3; + CheckAndRequestStatInterupt(); + } + + protected virtual void StepLCDTransfer() + { + /* Data transfer to LCD */ + + /* Render pixels */ + RenderPixel(currentScanline, cycleCount - mode2Boundary - cycleDotPause); + + /* Increment cycle count & check for next LCD mode */ + cycleCount++; + // 3) snorpung/pocket scroller width + if (cycleCount == mode3Boundary + cycleDotPause + (wndEnable ? 12 : 0)) EndLCDTransfer(); + } + + protected virtual void EndLCDTransfer() + { + modeNumber = 0; + CheckAndRequestStatInterupt(); + } + + protected virtual void StepHBlank() + { + /* H-blank */ + + /* Increment cycle count & check for next LCD mode */ + cycleCount++; + if (cycleCount == clockCyclesPerLine) EndHBlank(); + } + + protected virtual void EndHBlank() + { + /* End of scanline reached */ + OnEndOfScanline(EventArgs.Empty); + currentScanline++; + ly = (byte)currentScanline; + + CheckAndRequestStatInterupt(); + + for (var i = 0; i < spritesOnLine.Length; i++) spritesOnLine[i] = -1; + numSpritesOnLine = 0; + + if (currentScanline == displayActiveHeight) + { + modeNumber = 1; + CheckAndRequestStatInterupt(); + + /* Reached V-blank, request V-blank interrupt */ + vBlankReady = true; + + if (skipFrames > 0) skipFrames--; + + /* Submit screen for rendering */ + OnRenderScreen(new RenderScreenEventArgs(displayActiveWidth, displayActiveHeight, outputFramebuffer.Clone() as byte[])); + } + else + { + modeNumber = 2; + CheckAndRequestStatInterupt(); + } + + cycleCount = 0; + cycleDotPause = 0; + } + + protected void CheckAndRequestStatInterupt() + { + if (!lcdEnable) return; + + var oldSignal = statIrqSignal; + statIrqSignal = false; + + if (modeNumber == 0 && m0HBlankInterrupt) statIrqSignal = true; + if (modeNumber == 1 && m1VBlankInterrupt) statIrqSignal = true; + if (modeNumber == 2 && m2OamInterrupt) statIrqSignal = true; + + coincidenceFlag = (ly == lyCompare); + if (coincidenceFlag && lycLyInterrupt) statIrqSignal = true; + + if (!oldSignal && statIrqSignal) + requestInterruptDelegate(InterruptSource.LCDCStatus); + } + + protected virtual void RenderPixel(int y, int x) + { + if (x < 0 || x >= displayActiveWidth || y < 0 || y >= displayActiveHeight) return; + + if (skipFrames > 0) + { + SetPixel(y, x, 0xFF, 0xFF, 0xFF); + return; + } + + screenUsageFlags[x, y] = screenUsageEmpty; + + if (bgEnable) + { + RenderBackground(y, x); + if (wndEnable) RenderWindow(y, x); + } + else + SetPixel(y, x, 0xFF, 0xFF, 0xFF); + + if (objEnable) RenderSprites(y, x); + } + + protected virtual void RenderBackground(int y, int x) + { + // Get base addresses + var tileBase = (ushort)(bgWndTileSelect ? 0x0000 : 0x0800); + var mapBase = (ushort)(bgMapSelect ? 0x1C00 : 0x1800); + + // Calculate tilemap address & get tile + var yTransformed = (byte)(scrollY + y); + var xTransformed = (byte)(scrollX + x); + var mapAddress = mapBase + ((yTransformed >> 3) << 5) + (xTransformed >> 3); + var tileNumber = vram[0, mapAddress]; + if (!bgWndTileSelect) + tileNumber = (byte)(tileNumber ^ 0x80); + + // Calculate tile address & get pixel color index + var tileAddress = tileBase + (tileNumber << 4) + ((yTransformed & 7) << 1); + var ba = (vram[0, tileAddress + 0] >> (7 - (xTransformed % 8))) & 0b1; + var bb = (vram[0, tileAddress + 1] >> (7 - (xTransformed % 8))) & 0b1; + var c = (byte)((bb << 1) | ba); + + // If color is not 0, note that a BG pixel exists here + if (c != 0) + screenUsageFlags[x, y] |= screenUsageBackground; + + // Draw pixel + if (layerBackgroundForceEnable) + SetPixel(y, x, (byte)((bgPalette >> (c << 1)) & 0x03)); + else + SetPixel(y, x, (byte)(bgPalette & 0x03)); + } + + protected virtual void RenderWindow(int y, int x) + { + // Check if current coords are inside window + if (y < windowY) return; + if (x < (windowX - 7)) return; + + // Get base addresses + var tileBase = (ushort)(bgWndTileSelect ? 0x0000 : 0x0800); + var mapBase = (ushort)(wndMapSelect ? 0x1C00 : 0x1800); + + // Calculate tilemap address & get tile + var yTransformed = (byte)(y - windowY); + var xTransformed = (byte)((7 - windowX) + x); + var mapAddress = mapBase + ((yTransformed >> 3) << 5) + (xTransformed >> 3); + var tileNumber = vram[0, mapAddress]; + if (!bgWndTileSelect) + tileNumber = (byte)(tileNumber ^ 0x80); + + // Calculate tile address & get pixel color index + var tileAddress = tileBase + (tileNumber << 4) + ((yTransformed & 7) << 1); + var ba = (vram[0, tileAddress + 0] >> (7 - (xTransformed % 8))) & 0b1; + var bb = (vram[0, tileAddress + 1] >> (7 - (xTransformed % 8))) & 0b1; + var c = (byte)((bb << 1) | ba); + + // If color is not 0, note that a Window pixel exists here + if (c != 0) + screenUsageFlags[x, y] |= screenUsageWindow; + + // Draw pixel + if (layerWindowForceEnable) + SetPixel(y, x, (byte)((bgPalette >> (c << 1)) & 0x03)); + else + SetPixel(y, x, (byte)(bgPalette & 0x03)); + } + + protected virtual void RenderSprites(int y, int x) + { + var objHeight = objSize ? 16 : 8; + + // Iterate over sprite on line + for (var s = 0; s < numSpritesOnLine; s++) + { + var i = spritesOnLine[s]; + + // Get sprite Y coord & if sprite is not on current scanline, continue to next slot + var objY = (short)(oam[(i * 4) + 0] - 16); + if (y < objY || y >= (objY + objHeight)) continue; + + // Get sprite X coord, tile number & attributes + var objX = (byte)(oam[(i * 4) + 1] - 8); + var objTileNumber = oam[(i * 4) + 2]; + var objAttributes = oam[(i * 4) + 3]; + + // Extract attributes + var objFlipY = ((objAttributes >> 6) & 0b1) == 0b1; + var objFlipX = ((objAttributes >> 5) & 0b1) == 0b1; + var objPalNumber = (objAttributes >> 4) & 0b1; + + // Iterate over pixels + for (var px = 0; px < 8; px++) + { + // If sprite pixel X coord does not equal current rendering X coord, continue to next pixel + if (x != (byte)(objX + px)) continue; + + // Calculate tile address + var xShift = objFlipX ? (px % 8) : (7 - (px % 8)); + var yShift = objFlipY ? (7 - ((y - objY) % 8)) : ((y - objY) % 8); + if (objSize) + { + objTileNumber &= 0xFE; + if ((objFlipY && y < (objY + 8)) || (!objFlipY && y >= (objY + 8))) + objTileNumber |= 0x01; + } + var tileAddress = (objTileNumber << 4) + (yShift << 1); + + // Get palette & bitplanes + var pal = objPalNumber == 0 ? obPalette0 : obPalette1; + var ba = (vram[0, tileAddress + 0] >> xShift) & 0b1; + var bb = (vram[0, tileAddress + 1] >> xShift) & 0b1; + + // Combine to color index, continue drawing if color is not 0 + var c = (byte)((bb << 1) | ba); + if (c != 0) + { + // If sprite does not have priority i.e. if sprite should not be drawn, continue to next pixel + if (!HasSpritePriority(y, x, i)) continue; + + screenUsageFlags[x, y] |= screenUsageSprite; + screenUsageSpriteSlots[x, y] = (byte)i; + screenUsageSpriteXCoords[x, y] = objX; + + // Draw pixel + if (layerSpritesForceEnable) + SetPixel(y, x, (byte)((pal >> (c << 1)) & 0x03)); + } + } + } + } + + protected virtual bool HasSpritePriority(int y, int x, int objSlot) + { + // Get new sprite X coord + var objX = (byte)(oam[(objSlot * 4) + 1] - 8); + + // Get potentially existing sprite X coord and slot + var prevX = screenUsageSpriteXCoords[x, y]; + var prevSlot = screenUsageSpriteSlots[x, y]; + + // If existing sprite has lower X coord -or- both sprites have same X coord BUT existing sprite has lower slot, new sprite does not have priority + if (prevX < objX || (prevX == objX && prevSlot < objSlot)) + return false; + + // Get new sprite OBJ-to-BG priority attribute + var objIsBehindBg = ((oam[(objSlot * 4) + 3] >> 7) & 0b1) == 0b1; + + // If new sprite is shown behind BG/Window -and- a BG/Window pixel has already been drawn, new sprite does not have priority + if (objIsBehindBg && + (IsScreenUsageFlagSet(y, x, screenUsageBackground) || IsScreenUsageFlagSet(y, x, screenUsageWindow))) return false; + + // New sprite has priority + return true; + } + + protected void SetLine(int y, byte c) + { + for (int x = 0; x < displayActiveWidth; x++) + SetPixel(y, x, c); + } + + protected void SetLine(int y, byte b, byte g, byte r) + { + for (int x = 0; x < displayActiveWidth; x++) + SetPixel(y, x, b, g, r); + } + + protected void SetPixel(int y, int x, byte c) + { + WriteColorToFramebuffer(c, ((y * displayActiveWidth) + (x % displayActiveWidth)) * 4); + } + + protected void SetPixel(int y, int x, byte b, byte g, byte r) + { + WriteColorToFramebuffer(b, g, r, ((y * displayActiveWidth) + (x % displayActiveWidth)) * 4); + } + + protected virtual void WriteColorToFramebuffer(byte c, int address) + { + outputFramebuffer[address + 0] = colorValuesBgr[c & 0x03][0]; + outputFramebuffer[address + 1] = colorValuesBgr[c & 0x03][1]; + outputFramebuffer[address + 2] = colorValuesBgr[c & 0x03][2]; + outputFramebuffer[address + 3] = 0xFF; + } + + protected virtual void WriteColorToFramebuffer(byte b, byte g, byte r, int address) + { + outputFramebuffer[address + 0] = b; + outputFramebuffer[address + 1] = g; + outputFramebuffer[address + 2] = r; + outputFramebuffer[address + 3] = 0xFF; + } + + protected virtual void ClearScreenUsage() + { + for (var y = 0; y < displayActiveHeight; y++) + { + for (var x = 0; x < displayActiveWidth; x++) + { + screenUsageFlags[x, y] = screenUsageEmpty; + screenUsageSpriteXCoords[x, y] = 255; + screenUsageSpriteSlots[x, y] = numOamSlots; + } + } + } + + protected bool IsScreenUsageFlagSet(int y, int x, byte flag) + { + return (screenUsageFlags[x, y] & flag) == flag; + } + + public virtual byte ReadVram(ushort address) + { + if (modeNumber != 3) + return vram[0, address & (vram.Length - 1)]; + else + return 0xFF; + } + + public virtual void WriteVram(ushort address, byte value) + { + if (modeNumber != 3) + vram[0, address & (vram.Length - 1)] = value; + } + + public virtual byte ReadOam(ushort address) + { + if (modeNumber != 2 && modeNumber != 3) + return oam[address - 0xFE00]; + else + return 0xFF; + } + + public virtual void WriteOam(ushort address, byte value) + { + if (modeNumber != 2 && modeNumber != 3) + oam[address - 0xFE00] = value; + } + + public virtual byte ReadPort(byte port) + { + switch (port) + { + case 0x40: + // LCDC + return (byte)( + (lcdEnable ? (1 << 7) : 0) | + (wndMapSelect ? (1 << 6) : 0) | + (wndEnable ? (1 << 5) : 0) | + (bgWndTileSelect ? (1 << 4) : 0) | + (bgMapSelect ? (1 << 3) : 0) | + (objSize ? (1 << 2) : 0) | + (objEnable ? (1 << 1) : 0) | + (bgEnable ? (1 << 0) : 0)); + + case 0x41: + // STAT + return (byte)( + 0x80 | + (lycLyInterrupt ? (1 << 6) : 0) | + (m2OamInterrupt ? (1 << 5) : 0) | + (m1VBlankInterrupt ? (1 << 4) : 0) | + (m0HBlankInterrupt ? (1 << 3) : 0) | + (coincidenceFlag ? (1 << 2) : 0) | + ((modeNumber & 0b11) << 0)); + + case 0x42: + // SCY + return scrollY; + + case 0x43: + // SCX + return scrollX; + + case 0x44: + // LY + return ly; + + case 0x45: + // LYC + return lyCompare; + + case 0x46: + // DMA + return oamDmaStart; + + case 0x47: + // BGP + return bgPalette; + + case 0x48: + // OBP0 + return obPalette0; + + case 0x49: + // OBP1 + return obPalette1; + + case 0x4A: + // WY + return windowY; + + case 0x4B: + //WX + return windowX; + + default: + return 0xFF; + } + } + + public virtual void WritePort(byte port, byte value) + { + switch (port) + { + case 0x40: + // LCDC + { + var newLcdEnable = ((value >> 7) & 0b1) == 0b1; + if (lcdEnable != newLcdEnable) + { + modeNumber = 2; + currentScanline = 0; + ly = 0; + + CheckAndRequestStatInterupt(); + + if (newLcdEnable) + skipFrames = numSkippedFramesLcdOn; + } + + lcdEnable = newLcdEnable; + wndMapSelect = ((value >> 6) & 0b1) == 0b1; + wndEnable = ((value >> 5) & 0b1) == 0b1; + bgWndTileSelect = ((value >> 4) & 0b1) == 0b1; + bgMapSelect = ((value >> 3) & 0b1) == 0b1; + objSize = ((value >> 2) & 0b1) == 0b1; + objEnable = ((value >> 1) & 0b1) == 0b1; + bgEnable = ((value >> 0) & 0b1) == 0b1; + } + break; + + case 0x41: + // STAT + lycLyInterrupt = ((value >> 6) & 0b1) == 0b1; + m2OamInterrupt = ((value >> 5) & 0b1) == 0b1; + m1VBlankInterrupt = ((value >> 4) & 0b1) == 0b1; + m0HBlankInterrupt = ((value >> 3) & 0b1) == 0b1; + + CheckAndRequestStatInterupt(); + + // TODO: correct? + if (lcdEnable && modeNumber == 1 && currentScanline != 0) + requestInterruptDelegate(InterruptSource.LCDCStatus); + break; + + case 0x42: + // SCY + scrollY = value; + break; + + case 0x43: + // SCX + scrollX = value; + break; + + case 0x44: + // LY + break; + + case 0x45: + // LYC + lyCompare = value; + CheckAndRequestStatInterupt(); + break; + + case 0x46: + // DMA + oamDmaStart = value; + for (int src = 0, dst = oamDmaStart << 8; src < 0xA0; src++, dst++) + oam[src] = memoryReadDelegate((ushort)dst); + break; + + case 0x47: + // BGP + bgPalette = value; + break; + + case 0x48: + // OBP0 + obPalette0 = value; + break; + + case 0x49: + // OBP1 + obPalette1 = value; + break; + + case 0x4A: + // WY + windowY = value; + break; + + case 0x4B: + // WX + windowX = value; + break; + } + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Video/Nintendo/DMGVideo.cs.meta b/Assets/Plugins/Essgee/Emulation/Video/Nintendo/DMGVideo.cs.meta new file mode 100644 index 0000000..93163d6 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/Nintendo/DMGVideo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 42b7849ed9e0ba741a3d7cdfb174512d \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Video/SegaGGVDP.cs b/Assets/Plugins/Essgee/Emulation/Video/SegaGGVDP.cs new file mode 100644 index 0000000..892ec28 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/SegaGGVDP.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Essgee.EventArguments; +using Essgee.Utilities; + +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Video +{ + /* Sega 315-5378, Game Gear */ + public class SegaGGVDP : SegaSMSVDP + { + protected override int numTotalScanlines => NumTotalScanlinesNtsc; + + public override (int X, int Y, int Width, int Height) Viewport => (0, 0, 160, 144); + + [StateRequired] + ushort cramLatch; + + public SegaGGVDP() : base() + { + cram = new byte[0x40]; + } + + public override void Reset() + { + base.Reset(); + + cramLatch = 0x0000; + } + + public override void SetRevision(int rev) + { + // TODO: can GG VDP be detected by software? if so, implement diffs as revision + base.SetRevision(rev); + } + + protected override void ReconfigureTimings() + { + /* Calculate cycles/line */ + clockCyclesPerLine = (int)Math.Round((clockRate / refreshRate) / numTotalScanlines); + + /* Create arrays */ + screenUsage = new byte[numVisiblePixels * numVisibleScanlines]; + outputFramebuffer = new byte[Viewport.Width * Viewport.Height * 4]; + + /* Update resolution/display timing */ + UpdateResolution(); + } + + protected override void PrepareRenderScreen() + { + OnRenderScreen(new RenderScreenEventArgs(Viewport.Width, Viewport.Height, outputFramebuffer.Clone() as byte[])); + } + + private bool ModifyAndVerifyCoordinates(ref int x, ref int y) + { + // TODO: correctly derive from timing/resolution values + x -= 61; + y -= 51; + + return x >= 0 && x < Viewport.Width && y >= 0 && y < Viewport.Height; + } + + protected override void SetPixel(int y, int x, int palette, int color) + { + if (!ModifyAndVerifyCoordinates(ref x, ref y)) return; + WriteColorToFramebuffer(palette, color, ((y * Viewport.Width) + (x % Viewport.Width)) * 4); + } + + protected override void SetPixel(int y, int x, byte b, byte g, byte r) + { + if (!ModifyAndVerifyCoordinates(ref x, ref y)) return; + WriteColorToFramebuffer(b, g, r, ((y * Viewport.Width) + (x % Viewport.Width)) * 4); + } + + protected override void WriteColorToFramebuffer(int palette, int color, int address) + { + int cramAddress = ((palette * 32) + (color * 2)); + WriteColorToFramebuffer((ushort)(cram[cramAddress + 1] << 8 | cram[cramAddress]), address); + } + + protected override void WriteColorToFramebuffer(ushort colorValue, int address) + { + RGB444toBGRA8888(colorValue, ref outputFramebuffer, address); + } + + protected override void WriteDataPort(byte value) + { + isSecondControlWrite = false; + + readBuffer = value; + + switch (codeRegister) + { + case 0x00: + case 0x01: + case 0x02: + vram[addressRegister] = value; + break; + case 0x03: + if ((addressRegister & 0x0001) != 0) + { + cramLatch = (ushort)((cramLatch & 0x00FF) | (value << 8)); + cram[(addressRegister & 0x003E) | 0x0000] = (byte)((cramLatch >> 0) & 0xFF); + cram[(addressRegister & 0x003E) | 0x0001] = (byte)((cramLatch >> 8) & 0xFF); + } + else + cramLatch = (ushort)((cramLatch & 0xFF00) | (value << 0)); + break; + } + + addressRegister++; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Video/SegaGGVDP.cs.meta b/Assets/Plugins/Essgee/Emulation/Video/SegaGGVDP.cs.meta new file mode 100644 index 0000000..0754e4f --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/SegaGGVDP.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f75093c71287a3b45b7fe57be15d49b8 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Video/SegaSMSVDP.cs b/Assets/Plugins/Essgee/Emulation/Video/SegaSMSVDP.cs new file mode 100644 index 0000000..ef3c275 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/SegaSMSVDP.cs @@ -0,0 +1,927 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; + +using Essgee.EventArguments; +using Essgee.Utilities; + +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Video +{ + /* Sega 315-5124 (Mark III, SMS) and 315-5246 (SMS 2); differences see 'VDPDIFF' comments */ + public class SegaSMSVDP : TMS99xxA + { + /* VDPDIFF: switch for Mk3/SMS1 vs SMS2/GG behavior; configurable via SetRevision, maybe still split into separate classes instead? */ + protected VDPTypes vdpType = VDPTypes.Mk3SMS1; + + public const int NumActiveScanlinesLow = 192; + public const int NumActiveScanlinesMed = 224; + public const int NumActiveScanlinesHigh = 240; + + protected const int NumSpritesMode4 = 64; + protected const int NumSpritesPerLineMode4 = 8; + + public const int PortVCounter = 0x40; // 0x7E canonically, but mirrored across bus + public const int PortHCounter = 0x41; // 0x7F canonically, but mirrored across bus + + [StateRequired] + protected byte[] cram; + + [StateRequired] + protected int vCounter, hCounter; + protected int nametableHeight, vCounterTableIndex; + [StateRequired] + protected int lineInterruptCounter; + protected int screenHeight; + + bool isLineInterruptEnabled => IsBitSet(registers[0x00], 4); + [StateRequired] + bool isLineInterruptPending; + + bool isColumn0MaskEnabled => IsBitSet(registers[0x00], 5); + bool isVScrollPartiallyDisabled => IsBitSet(registers[0x00], 7); /* Columns 24-31, i.e. pixels 192-255 */ + bool isHScrollPartiallyDisabled => IsBitSet(registers[0x00], 6); /* Rows 0-1, i.e. pixels 0-15 */ + + bool isBitM4Set => IsBitSet(registers[0x00], 2); + + protected override bool isModeGraphics1 => !(isBitM1Set || isBitM2Set || isBitM3Set || isBitM4Set); + protected override bool isModeText => (isBitM1Set && !(isBitM2Set || isBitM3Set || isBitM4Set)); + protected override bool isModeGraphics2 => (isBitM2Set && !(isBitM1Set || isBitM3Set || isBitM4Set)); + protected override bool isModeMulticolor => (isBitM3Set && !(isBitM1Set || isBitM2Set || isBitM4Set)); + + protected bool isSMS240LineMode => (!isBitM1Set && isBitM2Set && isBitM3Set && isBitM4Set); + protected bool isSMS224LineMode => (isBitM1Set && isBitM2Set && !isBitM3Set && isBitM4Set); + + bool isSpriteShiftLeft8 => IsBitSet(registers[0x00], 3); + + protected override ushort nametableBaseAddress + { + get + { + if (isBitM4Set) + { + if (isSMS224LineMode || isSMS240LineMode) + return (ushort)(((registers[0x02] & 0x0C) << 10) | 0x700); + else + return (ushort)((registers[0x02] & 0x0E) << 10); + } + else + return (ushort)((registers[0x02] & 0x0F) << 10); + } + } + protected override ushort spriteAttribTableBaseAddress => (ushort)((registers[0x05] & (isBitM4Set ? 0x7E : 0x7F)) << 7); + protected override ushort spritePatternGenBaseAddress => (ushort)((registers[0x06] & (isBitM4Set ? 0x04 : 0x07)) << 11); + + /* http://www.smspower.org/Development/Palette */ + // TODO: verify these, SMSPower has some mistakes (RGB approx correct, palette value wrong) + // (not that we'll really use this, aside from for F-16 Fighting Falcon, as SG1000 games should always be loaded into the SG1000 core...) + readonly byte[] legacyColorMap = new byte[] + { + 0x00, /* Transparent */ + 0x00, /* Black */ + 0x08, /* Medium green */ + 0x0C, /* Light green */ + 0x10, /* Dark blue */ + 0x30, /* Light blue */ + 0x01, /* Dark red */ + 0x3C, /* Cyan */ + 0x02, /* Medium red */ + 0x03, /* Light red */ + 0x05, /* Dark yellow */ + 0x0F, /* Light yellow */ + 0x04, /* Dark green */ + 0x33, /* Magenta */ + 0x15, /* Gray */ + 0x3F /* White */ + }; + + readonly byte[][] vCounterTables = new byte[][] + { + /* NTSC, 192 lines */ + new byte[] + { + /* Top blanking */ + 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, + + /* Top border */ + 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, + 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + + /* Active display */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + + /* Bottom border */ + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, + + /* Bottom blanking */ + 0xD8, 0xD9, 0xDA, + + /* Vertical blanking */ + 0xD5, 0xD6, 0xD7 + }, + /* NTSC, 224 lines */ + new byte[] + { + /* Top blanking */ + 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, + + /* Top border */ + 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + + /* Active display */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + + /* Bottom border */ + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, + + /* Bottom blanking */ + 0xE8, 0xE9, 0xEA, + + /* Vertical blanking */ + 0xE5, 0xE6, 0xE7, + }, + /* NTSC, 240 lines (invalid) */ + new byte[] + { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 + }, + /* PAL, 192 lines */ + new byte[] + { + /* Top blanking */ + 0xBD, 0xBE, 0xBF, 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, + + /* Top border */ + 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, + 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, + 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, + 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + + /* Active display */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + + /* Bottom border */ + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + + /* Bottom blanking */ + 0xF0, 0xF1, 0xF2, + + /* Vertical blanking */ + 0xBA, 0xBB, 0xBC + }, + /* PAL, 224 lines */ + new byte[] + { + /* Top blanking */ + 0xCD, 0xCE, 0xCF, 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, + + /* Top border */ + 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, + 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, + 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + + /* Active display */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + + /* Bottom border */ + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + + /* Bottom blanking */ + 0x00, 0x01, 0x02, + + /* Vertical blanking */ + 0xCA, 0xCB, 0xCC, + }, + /* PAL, 240 lines */ + new byte[] + { + /* Top blanking */ + 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, 0xE0, 0xE1, + + /* Top border */ + 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, 0xF0, 0xF1, + 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + + /* Active display */ + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + + /* Bottom border */ + 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF, + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + + /* Bottom blanking */ + 0x08, 0x09, 0x0A, + + /* Vertical blanking */ + 0xD2, 0xD3, 0xD4, + } + }; + + /* For H-counter emulation */ + readonly byte[] hCounterTable = new byte[] + { + 0x00, 0x01, 0x02, 0x02, 0x03, 0x04, 0x05, 0x05, 0x06, 0x07, 0x08, 0x08, 0x09, 0x0A, 0x0B, 0x0B, + 0x0C, 0x0D, 0x0E, 0x0E, 0x0F, 0x10, 0x11, 0x11, 0x12, 0x13, 0x14, 0x14, 0x15, 0x16, 0x17, 0x17, + 0x18, 0x19, 0x1A, 0x1A, 0x1B, 0x1C, 0x1D, 0x1D, 0x1E, 0x1F, 0x20, 0x20, 0x21, 0x22, 0x23, 0x23, + 0x24, 0x25, 0x26, 0x26, 0x27, 0x28, 0x29, 0x29, 0x2A, 0x2B, 0x2C, 0x2C, 0x2D, 0x2E, 0x2F, 0x2F, + 0x30, 0x31, 0x32, 0x32, 0x33, 0x34, 0x35, 0x35, 0x36, 0x37, 0x38, 0x38, 0x39, 0x3A, 0x3B, 0x3B, + 0x3C, 0x3D, 0x3E, 0x3E, 0x3F, 0x40, 0x41, 0x41, 0x42, 0x43, 0x44, 0x44, 0x45, 0x46, 0x47, 0x47, + 0x48, 0x49, 0x4A, 0x4A, 0x4B, 0x4C, 0x4D, 0x4D, 0x4E, 0x4F, 0x50, 0x50, 0x51, 0x52, 0x53, 0x53, + 0x54, 0x55, 0x56, 0x56, 0x57, 0x58, 0x59, 0x59, 0x5A, 0x5B, 0x5C, 0x5C, 0x5D, 0x5E, 0x5F, 0x5F, + 0x60, 0x61, 0x62, 0x62, 0x63, 0x64, 0x65, 0x65, 0x66, 0x67, 0x68, 0x68, 0x69, 0x6A, 0x6B, 0x6B, + 0x6C, 0x6D, 0x6E, 0x6E, 0x6F, 0x70, 0x71, 0x71, 0x72, 0x73, 0x74, 0x74, 0x75, 0x76, 0x77, 0x77, + 0x78, 0x79, 0x7A, 0x7A, 0x7B, 0x7C, 0x7D, 0x7D, 0x7E, 0x7F, 0x80, 0x80, 0x81, 0x82, 0x83, 0x83, + 0x84, 0x85, 0x86, 0x86, 0x87, 0x88, 0x89, 0x89, 0x8A, 0x8B, 0x8C, 0x8C, 0x8D, 0x8E, 0x8F, 0x8F, + 0x90, 0x91, 0x92, 0x92, 0x93, + + 0xE9, 0xEA, 0xEA, 0xEB, 0xEC, 0xED, 0xED, 0xEE, 0xEF, 0xF0, 0xF0, 0xF1, 0xF2, 0xF3, 0xF3, 0xF4, + 0xF5, 0xF6, 0xF6, 0xF7, 0xF8, 0xF9, 0xF9, 0xFA, 0xFB, 0xFC, 0xFC, 0xFD, 0xFE, 0xFF, 0xFF + }; + + [StateRequired] + byte horizontalScrollLatched, verticalScrollLatched; + + const byte screenUsageBgLowPriority = screenUsageBackground; + const byte screenUsageBgHighPriority = (1 << 2); + + public int ScreenHeight => screenHeight; + public int CurrentScanline => currentScanline; + + public SegaSMSVDP() : base() + { + registers = new byte[0x0B]; + cram = new byte[0x20]; + + spriteBuffer = new (int Number, int Y, int X, int Pattern, int Attribute)[NumActiveScanlinesHigh][]; + for (int i = 0; i < spriteBuffer.Length; i++) spriteBuffer[i] = new (int Number, int Y, int X, int Pattern, int Attribute)[NumSpritesPerLineMode4]; + } + + public override void Reset() + { + base.Reset(); + + WriteRegister(0x00, 0x36); + WriteRegister(0x01, 0x80); + WriteRegister(0x02, 0xFF); + WriteRegister(0x03, 0xFF); + WriteRegister(0x04, 0xFF); + WriteRegister(0x05, 0xFF); + WriteRegister(0x06, 0xFB); + WriteRegister(0x07, 0x00); + WriteRegister(0x08, 0x00); + WriteRegister(0x09, 0x00); + WriteRegister(0x0A, 0xFF); + + for (int i = 0; i < cram.Length; i++) cram[i] = 0; + + vCounter = hCounter = 0; + lineInterruptCounter = registers[0x0A]; + + isLineInterruptPending = false; + + horizontalScrollLatched = verticalScrollLatched = 0; + + UpdateResolution(); + } + + public override void SetRevision(int rev) + { + VDPTypes type = (VDPTypes)rev; + Debug.Assert(Enum.IsDefined(typeof(VDPTypes), type), "Invalid revision", "{0} revision is invalid; only rev 0 (MK3/SMS1) or 1 (SMS2/GG) is valid", GetType().FullName); + vdpType = type; + } + + protected override void ReconfigureTimings() + { + /* Calculate cycles/line */ + clockCyclesPerLine = (int)Math.Round((clockRate / refreshRate) / numTotalScanlines); + + /* Create arrays */ + screenUsage = new byte[numVisiblePixels * numVisibleScanlines]; + outputFramebuffer = new byte[(numVisiblePixels * numVisibleScanlines) * 4]; + + /* Update resolution/display timing */ + UpdateResolution(); + } + + public override void Step(int clockCyclesInStep) + { + InterruptLine = (((isFrameInterruptEnabled && isFrameInterruptPending) || (isLineInterruptEnabled && isLineInterruptPending)) ? InterruptState.Assert : InterruptState.Clear); + + cycleCount += clockCyclesInStep; + + hCounter = hCounterTable[(int)Math.Round((cycleCount + 578) / 3.0) % hCounterTable.Length]; + + if (cycleCount >= clockCyclesPerLine) + { + OnEndOfScanline(EventArgs.Empty); + + horizontalScrollLatched = registers[0x08]; + + if (currentScanline == scanlineActiveDisplay) + verticalScrollLatched = registers[0x09]; + + CheckSpriteOverflow(currentScanline); + + RenderLine(currentScanline); + + if (currentScanline >= scanlineActiveDisplay && currentScanline <= scanlineBottomBorder) + { + lineInterruptCounter--; + if (lineInterruptCounter < 0) + { + lineInterruptCounter = registers[0x0A]; + isLineInterruptPending = true; + } + } + else + lineInterruptCounter = registers[0x0A]; + + if (currentScanline == (scanlineBottomBorder + 1)) + isFrameInterruptPending = true; + + vCounter = vCounterTables[vCounterTableIndex][currentScanline]; + + currentScanline++; + if (currentScanline == numTotalScanlines) + { + currentScanline = 0; + ClearScreenUsage(); + + PrepareRenderScreen(); + } + + ParseSpriteTable(currentScanline); + + cycleCount -= clockCyclesPerLine; + if (cycleCount <= -clockCyclesPerLine) cycleCount = 0; + } + } + + protected override void PrepareRenderScreen() + { + OnRenderScreen(new RenderScreenEventArgs(numVisiblePixels, numVisibleScanlines, outputFramebuffer.Clone() as byte[])); + } + + protected override byte ReadVram(ushort address) + { + return vram[address & vramMask16k]; + } + + protected override void WriteVram(ushort address, byte value) + { + vram[address & vramMask16k] = value; + } + + protected override void RenderLine(int y) + { + if (y >= scanlineTopBorder && y < scanlineActiveDisplay) + { + if (layerBordersForceEnable) SetLine(y, 1, backgroundColor); + else SetLine(y, 0x00, 0x00, 0x00); + } + else if (y >= scanlineActiveDisplay && y < scanlineBottomBorder) + { + if (layerBackgroundForceEnable) + { + if (isBitM4Set) + RenderLineMode4Background(y); + else if (isModeGraphics1) + RenderLineGraphics1Background(y); + else if (isModeGraphics2) + RenderLineGraphics2Background(y); + else if (isModeMulticolor) + RenderLineMulticolorBackground(y); + else if (isModeText) + RenderLineTextBackground(y); + } + else + SetLine(y, 0x00, 0x00, 0x00); + + if (layerSpritesForceEnable) + { + if (isBitM4Set) + RenderLineMode4Sprites(y); + else if (!isModeText && !isBitM4Set) + RenderLineSprites(y); + } + + RenderBorders(y); + } + else if (y >= scanlineBottomBorder && y < numVisibleScanlines) + { + if (layerBordersForceEnable) SetLine(y, 1, backgroundColor); + else SetLine(y, 0x00, 0x00, 0x00); + } + } + + protected override void RenderBorders(int y) + { + for (int x = pixelLeftBorder; x < pixelActiveDisplay; x++) + { + if (layerBordersForceEnable) SetPixel(y, x, 1, backgroundColor); + else SetPixel(y, x, 0x00, 0x00, 0x00); + } + for (int x = pixelRightBorder; x < numVisiblePixels; x++) + { + if (layerBordersForceEnable) SetPixel(y, x, 1, backgroundColor); + else SetPixel(y, x, 0x00, 0x00, 0x00); + } + } + + protected void SetLine(int y, int palette, int color) + { + for (int x = 0; x < numVisiblePixels; x++) + SetPixel(y, x, palette, color); + } + + protected virtual void SetPixel(int y, int x, int palette, int color) + { + WriteColorToFramebuffer(palette, color, ((y * numVisiblePixels) + (x % numVisiblePixels)) * 4); + } + + private void RenderLineMode4Background(int y) + { + /* Determine coordinates in active display */ + int activeDisplayY = (y - scanlineActiveDisplay); + + /* Determine H scrolling parameters */ + int currentHorizontalScroll = ((isHScrollPartiallyDisabled && activeDisplayY < 16) ? 0 : horizontalScrollLatched); + int horizontalScrollCoarse = (currentHorizontalScroll >> 3); + int horizontalScrollFine = (currentHorizontalScroll & 0x07); + + ushort currentNametableBaseAddress = nametableBaseAddress; + bool currentIsVScrollPartiallyDisabled = isVScrollPartiallyDisabled; + bool currentIsColumn0MaskEnabled = isColumn0MaskEnabled; + + for (int x = 0; x < numTotalPixelsPerScanline; x++) + { + int activeDisplayX = (x - pixelActiveDisplay); + if (activeDisplayX < 0 || activeDisplayX >= NumActivePixelsPerScanline) continue; + + /* Determine V scrolling parameters */ + int currentVerticalScroll = ((currentIsVScrollPartiallyDisabled && activeDisplayX >= 192) ? 0 : verticalScrollLatched); + int verticalScrollCoarse = (currentVerticalScroll >> 3); + int verticalScrollFine = (currentVerticalScroll & 0x07); + + /* Calculate current scrolled column and row */ + int numColumns = 32; + int currentColumn = (((activeDisplayX - horizontalScrollFine) / 8) - horizontalScrollCoarse) & (numColumns - 1); + int currentRow = (((activeDisplayY + verticalScrollFine) / 8) + verticalScrollCoarse) % (nametableHeight / 8); + + /* VDPDIFF: Mk3/SMS1 VDP only, adjust current row according to mask bit; http://www.smspower.org/Development/TilemapMirroring + * NOTE: Emulating this breaks 224/240-line mode games (ex. Fantastic Dizzy, Cosmic Spacehead, Micro Machines)? + */ + if (vdpType == VDPTypes.Mk3SMS1) + currentRow &= (((registers[0x02] & 0x01) << 4) | 0x0F); + + /* Fetch data from nametable & extract properties */ + ushort nametableAddress = (ushort)(currentNametableBaseAddress + (currentRow * (numColumns * 2)) + (currentColumn * 2)); + ushort nametableData = (ushort)(ReadVram((ushort)(nametableAddress + 1)) << 8 | ReadVram(nametableAddress)); + + int tileIndex = (nametableData & 0x01FF); + bool hFlip = ((nametableData & 0x0200) == 0x0200); + bool vFlip = ((nametableData & 0x0400) == 0x0400); + int palette = (((nametableData & 0x0800) >> 11) & 0x0001); + bool priority = ((nametableData & 0x1000) == 0x1000); + + /* Fetch pixel data for current pixel line */ + int hPixel = ((activeDisplayX - horizontalScrollFine) % 8); + hPixel = (hFlip ? hPixel : (7 - hPixel)); + int vPixel = ((activeDisplayY + verticalScrollFine) % 8); + vPixel = (!vFlip ? vPixel : (7 - vPixel)); + + ushort tileAddress = (ushort)((tileIndex << 5) + (vPixel << 2)); + int c = (((ReadVram((ushort)(tileAddress + 0)) >> hPixel) & 0x01) << 0); + c |= (((ReadVram((ushort)(tileAddress + 1)) >> hPixel) & 0x01) << 1); + c |= (((ReadVram((ushort)(tileAddress + 2)) >> hPixel) & 0x01) << 2); + c |= (((ReadVram((ushort)(tileAddress + 3)) >> hPixel) & 0x01) << 3); + + /* Record screen usage, write to framebuffer */ + if (GetScreenUsageFlag(y, x) == screenUsageEmpty) + { + if ((currentIsColumn0MaskEnabled && (activeDisplayX / 8) == 0) || isDisplayBlanked) + SetPixel(y, x, 1, backgroundColor); + else + SetPixel(y, x, palette, c); + + SetScreenUsageFlag(y, x, (c != 0 && priority) ? screenUsageBgHighPriority : screenUsageBgLowPriority); + } + } + } + + protected override void CheckSpriteOverflow(int y) + { + if (!isBitM4Set) + { + /* Not in Master System video mode */ + base.CheckSpriteOverflow(y); + } + else + { + /* Ensure current scanline is within active display */ + if (y >= scanlineActiveDisplay && y < scanlineBottomBorder) + { + int activeDisplayY = (y - scanlineActiveDisplay); + + /* If last sprite in buffer is valid, sprite overflow occured */ + int lastSpriteInBuffer = spriteBuffer[activeDisplayY][NumSpritesPerLineMode4 - 1].Number; + if (lastSpriteInBuffer != -1) + { + isSpriteOverflow = true; + + /* Store sprite number in status register */ + /* NOTE: the last illegal sprite is *technically* only stored here in TMS99xxA modes, but still emulating it in Mode 4 should be fine */ + WriteSpriteNumberToStatus(lastSpriteInBuffer); + } + } + } + } + + protected override void ParseSpriteTable(int y) + { + if (!isBitM4Set) + { + /* Not in Master System video mode */ + base.ParseSpriteTable(y); + } + else + { + if (y < scanlineActiveDisplay || y >= scanlineBottomBorder) return; + + /* Determine coordinates in active display */ + int activeDisplayY = (y - scanlineActiveDisplay); + + /* Clear sprite list for current line */ + for (int i = 0; i < spriteBuffer[activeDisplayY].Length; i++) spriteBuffer[activeDisplayY][i] = (-1, 0, 0, 0, 0); + + /* Determine sprite size & get zoomed sprites adjustment */ + int zoomShift = (isZoomedSprites ? 1 : 0); + int spriteHeight = ((isLargeSprites ? 16 : 8) << zoomShift); + + int numValidSprites = 0; + for (int sprite = 0; sprite < NumSpritesMode4; sprite++) + { + int yCoordinate = ReadVram((ushort)(spriteAttribTableBaseAddress + sprite)); + + /* Ignore following if Y coord is 208 in 192-line mode */ + if (yCoordinate == 208 && screenHeight == NumActiveScanlinesLow) + { + /* Store first "illegal sprite" number in status register */ + WriteSpriteNumberToStatus(sprite); + return; + } + + /* Modify Y coord as needed */ + yCoordinate++; + if (yCoordinate > screenHeight + 32) yCoordinate -= 256; + + /* Ignore this sprite if on incorrect lines */ + if (activeDisplayY < yCoordinate || activeDisplayY >= (yCoordinate + spriteHeight)) continue; + + /* Check if maximum number of sprites per line is reached */ + numValidSprites++; + if (numValidSprites > NumSpritesPerLineMode4) return; + + /* Mark sprite for rendering */ + int xCoordinate = ReadVram((ushort)(spriteAttribTableBaseAddress + 0x80 + (sprite * 2))); + int patternNumber = ReadVram((ushort)(spriteAttribTableBaseAddress + 0x80 + (sprite * 2) + 1)); + int unusedData = ReadVram((ushort)(spriteAttribTableBaseAddress + 0x40 + (sprite * 2))); + + spriteBuffer[activeDisplayY][numValidSprites - 1] = (sprite, yCoordinate, xCoordinate, patternNumber, unusedData); + } + + /* Because we didn't bow out before already, store total number of sprites in status register */ + WriteSpriteNumberToStatus(NumSprites - 1); + } + } + + private void RenderLineMode4Sprites(int y) + { + if (y < scanlineActiveDisplay || y >= scanlineBottomBorder) return; + + /* Determine coordinates in active display */ + int activeDisplayY = (y - scanlineActiveDisplay); + + /* Determine sprite size & get zoomed sprites adjustment */ + int zoomShift = (isZoomedSprites ? 1 : 0); + int spriteHeight = ((isLargeSprites ? 16 : 8) << zoomShift); + + for (int s = 0; s < spriteBuffer[activeDisplayY].Length; s++) + { + var sprite = spriteBuffer[activeDisplayY][s]; + + if (sprite.Number == -1) continue; + + /* VDPDIFF: Mk3/SMS1 VDP zoomed sprites bug, only first four sprites (same as max sprites/line on TMS99xxA) can be zoomed horizontally + * Zoom works normally on SMS2/GG */ + int spriteWidth = (8 << zoomShift); + if (vdpType == VDPTypes.Mk3SMS1 && s >= NumSpritesPerLine) spriteWidth = 8; + + if (!isDisplayBlanked) + { + int yCoordinate = sprite.Y; + int xCoordinate = sprite.X; + int patternNumber = sprite.Pattern; + int unusedData = sprite.Attribute; + + if (isSpriteShiftLeft8) xCoordinate -= 8; + + for (int pixel = 0; pixel < spriteWidth; pixel++) + { + /* Ignore pixel if column 0 masking is enabled and sprite pixel is in column 0 */ + if (isColumn0MaskEnabled && (xCoordinate + pixel) < 8) continue; + + /* Check if sprite is outside active display, else continue to next sprite */ + if ((xCoordinate + pixel) < 0 || (xCoordinate + pixel) >= NumActivePixelsPerScanline) continue; + + /* Determine coordinate inside sprite */ + int inSpriteXCoord = (pixel >> zoomShift) % spriteWidth; + int inSpriteYCoord = ((activeDisplayY - yCoordinate) >> zoomShift) % spriteHeight; + + /* Calculate address and fetch pixel data */ + int tileIndex = patternNumber; + if (isLargeSprites) tileIndex &= ~0x01; + ushort tileAddress = (ushort)(spritePatternGenBaseAddress + (tileIndex << 5) + (inSpriteYCoord << 2)); + + /* Get color & check transparency and position */ + int c = (((ReadVram((ushort)(tileAddress + 0)) >> (7 - inSpriteXCoord)) & 0x1) << 0); + c |= (((ReadVram((ushort)(tileAddress + 1)) >> (7 - inSpriteXCoord)) & 0x1) << 1); + c |= (((ReadVram((ushort)(tileAddress + 2)) >> (7 - inSpriteXCoord)) & 0x1) << 2); + c |= (((ReadVram((ushort)(tileAddress + 3)) >> (7 - inSpriteXCoord)) & 0x1) << 3); + + if (c == 0) continue; + + int x = pixelActiveDisplay + (xCoordinate + pixel); + if (IsScreenUsageFlagSet(y, x, screenUsageSprite)) + { + /* If sprite was already at this location, set sprite collision flag */ + isSpriteCollision = true; + } + else if (!IsScreenUsageFlagSet(y, x, screenUsageBgHighPriority)) + { + /* Draw if pixel isn't occupied by high-priority BG */ + SetPixel(y, x, 1, c); + } + + /* Note that there is a sprite here regardless */ + + /* VDPDIFF: Mk3 / SMS1 VDP zoomed sprites bug, horizontally zoomed area of sprite (i.e. pixels 9-16) is ignored by collision; do not mark location as containing a sprite + * https://www.smspower.org/forums/post109677#109677 + * TODO: verify behavior somehow? + */ + if ((vdpType == VDPTypes.Mk3SMS1 && isZoomedSprites && pixel < 8) || (vdpType == VDPTypes.Mk3SMS1 && !isZoomedSprites) || vdpType == VDPTypes.SMS2GG) + SetScreenUsageFlag(y, x, screenUsageSprite); + } + } + } + } + + protected void UpdateResolution() + { + /* Check screenmode */ + if (isSMS240LineMode) + { + screenHeight = NumActiveScanlinesHigh; + nametableHeight = 256; + vCounterTableIndex = (isPalChip ? 5 : 2); + } + else if (isSMS224LineMode) + { + screenHeight = NumActiveScanlinesMed; + nametableHeight = 256; + vCounterTableIndex = (isPalChip ? 4 : 1); + } + else + { + screenHeight = NumActiveScanlinesLow; + nametableHeight = 224; + vCounterTableIndex = (isPalChip ? 3 : 0); + } + + /* Scanline parameters */ + if (!isPalChip) + { + /* NTSC */ + if (screenHeight == NumActiveScanlinesHigh) + { + /* 240 active lines, invalid on NTSC (dummy values); "Line Interrupt Test #1" mode 1 will show blue screen */ + topBorderSize = 0; + verticalActiveDisplaySize = 0; + bottomBorderSize = 0; + } + else if (screenHeight == NumActiveScanlinesMed) + { + /* 224 active lines */ + topBorderSize = 11; + verticalActiveDisplaySize = 224; + bottomBorderSize = 8; + } + else + { + /* 192 active lines */ + topBorderSize = 27; + verticalActiveDisplaySize = 192; + bottomBorderSize = 24; + } + } + else + { + /* PAL */ + if (screenHeight == NumActiveScanlinesHigh) + { + /* 240 active lines */ + topBorderSize = 30; + verticalActiveDisplaySize = 240; + bottomBorderSize = 24; + } + else if (screenHeight == NumActiveScanlinesMed) + { + /* 224 active lines */ + topBorderSize = 38; + verticalActiveDisplaySize = 224; + bottomBorderSize = 32; + } + else + { + /* 192 active lines */ + topBorderSize = 54; + verticalActiveDisplaySize = 192; + bottomBorderSize = 48; + } + } + + scanlineTopBorder = 0; + scanlineActiveDisplay = (scanlineTopBorder + topBorderSize); + scanlineBottomBorder = (scanlineActiveDisplay + verticalActiveDisplaySize); + + numVisibleScanlines = (topBorderSize + verticalActiveDisplaySize + bottomBorderSize); + + /* Pixel parameters */ + leftBorderSize = 13; + horizontalActiveDisplaySize = 256; + rightBorderSize = 15; + + pixelLeftBorder = 0; + pixelActiveDisplay = (pixelLeftBorder + leftBorderSize); + pixelRightBorder = (pixelActiveDisplay + horizontalActiveDisplaySize); + + numVisiblePixels = (leftBorderSize + horizontalActiveDisplaySize + rightBorderSize); + } + + protected virtual void WriteColorToFramebuffer(int palette, int color, int address) + { + WriteColorToFramebuffer(cram[((palette * 16) + color)], address); + } + + protected override void WriteColorToFramebuffer(ushort colorValue, int address) + { + /* If not in Master System video mode, color value is index into legacy colormap */ + if (!isBitM4Set) + colorValue = (legacyColorMap[colorValue & 0x000F]); + + RGB222toBGRA8888(colorValue, ref outputFramebuffer, address); + } + + protected override void WriteDataPort(byte value) + { + isSecondControlWrite = false; + + readBuffer = value; + + switch (codeRegister) + { + case 0x00: + case 0x01: + case 0x02: + WriteVram(addressRegister, value); + break; + case 0x03: + cram[(addressRegister & 0x001F)] = value; + break; + } + + addressRegister++; + } + + protected override byte ReadControlPort() + { + byte statusCurrent = (byte)statusFlags; + + statusFlags = StatusFlags.None; + isSecondControlWrite = false; + + isLineInterruptPending = false; + + InterruptLine = InterruptState.Clear; + + return statusCurrent; + } + + public override byte ReadPort(byte port) + { + if ((port & 0x40) == 0x40) + if ((port & 0x01) == 0) + return (byte)vCounter; /* V counter */ + else + return (byte)hCounter; /* H counter */ + else + return base.ReadPort(port); + } + + protected override void WriteRegister(byte register, byte value) + { + if (register < registers.Length) + registers[register] = value; + + if (register == 0x00 || register == 0x01) + UpdateResolution(); + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Video/SegaSMSVDP.cs.meta b/Assets/Plugins/Essgee/Emulation/Video/SegaSMSVDP.cs.meta new file mode 100644 index 0000000..0d3a6b4 --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/SegaSMSVDP.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b2208cc9d471f2340aa4019b18607dda \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Emulation/Video/TMS99xxA.cs b/Assets/Plugins/Essgee/Emulation/Video/TMS99xxA.cs new file mode 100644 index 0000000..f356b8d --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/TMS99xxA.cs @@ -0,0 +1,881 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Diagnostics; + +using Essgee.EventArguments; +using Essgee.Utilities; + +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Video +{ + /* Texas Instruments TMS99xxA family */ + public class TMS99xxA : IVideo + { + public const int NumTotalScanlinesPal = 313; + public const int NumTotalScanlinesNtsc = 262; + + public const int NumActiveScanlines = 192; + public const int NumActivePixelsPerScanline = 256; + + protected const string layerBackgroundOptionName = "GraphicsLayersShowBackground"; + protected const string layerSpritesOptionName = "GraphicsLayersShowSprites"; + protected const string layerBordersOptionName = "GraphicsLayersShowBorders"; + + protected readonly int numTotalPixelsPerScanline = 342; + protected virtual int numTotalScanlines => (isPalChip ? NumTotalScanlinesPal : NumTotalScanlinesNtsc); + + protected int topBorderSize, verticalActiveDisplaySize, bottomBorderSize; + protected int scanlineTopBorder, scanlineActiveDisplay, scanlineBottomBorder; + protected int numVisibleScanlines; + + protected int leftBorderSize, horizontalActiveDisplaySize, rightBorderSize; + protected int pixelLeftBorder, pixelActiveDisplay, pixelRightBorder; + protected int numVisiblePixels; + + public virtual (int X, int Y, int Width, int Height) Viewport => (0, 0, numVisiblePixels, numVisibleScanlines); + + public virtual event EventHandler<SizeScreenEventArgs> SizeScreen; + public virtual void OnSizeScreen(SizeScreenEventArgs e) { SizeScreen?.Invoke(this, e); } + + public virtual event EventHandler<RenderScreenEventArgs> RenderScreen; + public virtual void OnRenderScreen(RenderScreenEventArgs e) { RenderScreen?.Invoke(this, e); } + + public virtual event EventHandler<EventArgs> EndOfScanline; + public virtual void OnEndOfScanline(EventArgs e) { EndOfScanline?.Invoke(this, e); } + + protected const int NumSprites = 32; + protected const int NumSpritesPerLine = 4; + + protected double clockRate, refreshRate; + protected bool isPalChip; + + [StateRequired] + protected byte[] registers, vram; + [StateRequired] + protected (int Number, int Y, int X, int Pattern, int Attribute)[][] spriteBuffer; + + protected ushort vramMask16k => 0x3FFF; + protected ushort vramMask4k => 0x0FFF; + + [StateRequired] + protected bool isSecondControlWrite; + [StateRequired] + protected ushort controlWord; + [StateRequired] + protected byte readBuffer; + + protected byte codeRegister => (byte)((controlWord >> 14) & 0x03); + protected ushort addressRegister + { + get { return (ushort)(controlWord & 0x3FFF); } + set { controlWord = (ushort)((controlWord & 0xC000) | (value & 0x3FFF)); } + } + + [Flags] + protected enum StatusFlags : byte + { + None = 0, + SpriteCollision = (1 << 5), + SpriteOverflow = (1 << 6), + FrameInterruptPending = (1 << 7) + } + [StateRequired] + protected StatusFlags statusFlags; + protected bool isSpriteCollision + { + get { return ((statusFlags & StatusFlags.SpriteCollision) == StatusFlags.SpriteCollision); } + set { statusFlags = ((statusFlags & ~StatusFlags.SpriteCollision) | (value ? StatusFlags.SpriteCollision : StatusFlags.None)); } + } + protected bool isSpriteOverflow + { + get { return ((statusFlags & StatusFlags.SpriteOverflow) == StatusFlags.SpriteOverflow); } + set { statusFlags = ((statusFlags & ~StatusFlags.SpriteOverflow) | (value ? StatusFlags.SpriteOverflow : StatusFlags.None)); } + } + protected bool isFrameInterruptPending + { + get { return ((statusFlags & StatusFlags.FrameInterruptPending) == StatusFlags.FrameInterruptPending); } + set { statusFlags = ((statusFlags & ~StatusFlags.FrameInterruptPending) | (value ? StatusFlags.FrameInterruptPending : StatusFlags.None)); } + } + protected bool isFrameInterruptEnabled => IsBitSet(registers[0x01], 5); + + [StateRequired] + public InterruptState InterruptLine { get; set; } + + [StateRequired] + protected int currentScanline; + + protected bool isDisplayBlanked => !IsBitSet(registers[0x01], 6); + + protected bool is16kVRAMEnabled => IsBitSet(registers[0x01], 7); + + protected bool isBitM1Set => IsBitSet(registers[0x01], 4); + protected bool isBitM2Set => IsBitSet(registers[0x00], 1); + protected bool isBitM3Set => IsBitSet(registers[0x01], 3); + + protected virtual bool isModeGraphics1 => !(isBitM1Set || isBitM2Set || isBitM3Set); + protected virtual bool isModeText => (isBitM1Set && !(isBitM2Set || isBitM3Set)); + protected virtual bool isModeGraphics2 => (isBitM2Set && !(isBitM1Set || isBitM3Set)); + protected virtual bool isModeMulticolor => (isBitM3Set && !(isBitM1Set || isBitM2Set)); + + protected bool isLargeSprites => IsBitSet(registers[0x01], 1); + protected bool isZoomedSprites => IsBitSet(registers[0x01], 0); + + protected virtual ushort nametableBaseAddress => (ushort)((registers[0x02] & 0x0F) << 10); + protected virtual ushort spriteAttribTableBaseAddress => (ushort)((registers[0x05] & 0x7F) << 7); + protected virtual ushort spritePatternGenBaseAddress => (ushort)((registers[0x06] & 0x07) << 11); + + protected byte backgroundColor => (byte)(registers[0x07] & 0x0F); + protected byte textColor => (byte)((registers[0x07] >> 4) & 0x0F); + + /* http://www.smspower.org/Development/Palette */ + readonly byte[][] colorValuesBgr = new byte[][] + { + /* B G R */ + new byte[] { 0x00, 0x00, 0x00 }, /* Transparent */ + new byte[] { 0x00, 0x00, 0x00 }, /* Black */ + new byte[] { 0x3B, 0xB7, 0x47 }, /* Medium green */ + new byte[] { 0x6F, 0xCF, 0x7C }, /* Light green */ + new byte[] { 0xFF, 0x4E, 0x5D }, /* Dark blue */ + new byte[] { 0xFF, 0x72, 0x80 }, /* Light blue */ + new byte[] { 0x47, 0x62, 0xB6 }, /* Dark red */ + new byte[] { 0xED, 0xC8, 0x5D }, /* Cyan */ + new byte[] { 0x48, 0x6B, 0xD7 }, /* Medium red */ + new byte[] { 0x6C, 0x8F, 0xFB }, /* Light red */ + new byte[] { 0x41, 0xCD, 0xC3 }, /* Dark yellow */ + new byte[] { 0x76, 0xDA, 0xD3 }, /* Light yellow */ + new byte[] { 0x2F, 0x9F, 0x3E }, /* Dark green */ + new byte[] { 0xC7, 0x64, 0xB6 }, /* Magenta */ + new byte[] { 0xCC, 0xCC, 0xCC }, /* Gray */ + new byte[] { 0xFF, 0xFF, 0xFF } /* White */ + }; + + protected const byte screenUsageEmpty = 0; + protected const byte screenUsageSprite = (1 << 0); + protected const byte screenUsageBackground = (1 << 1); + [StateRequired] + protected byte[] screenUsage; + + [StateRequired] + protected int cycleCount; + protected byte[] outputFramebuffer; + + protected int clockCyclesPerLine; + + public (string Name, string Description)[] RuntimeOptions => new (string name, string description)[] + { + (layerBackgroundOptionName, "Background"), + (layerSpritesOptionName, "Sprites"), + (layerBordersOptionName, "Borders"), + }; + + protected bool layerBackgroundForceEnable, layerSpritesForceEnable, layerBordersForceEnable; + + public TMS99xxA() + { + registers = new byte[0x08]; + vram = new byte[0x4000]; + + spriteBuffer = new (int Number, int Y, int X, int Pattern, int Attribute)[NumActiveScanlines][]; + for (int i = 0; i < spriteBuffer.Length; i++) spriteBuffer[i] = new (int Number, int Y, int X, int Pattern, int Attribute)[NumSpritesPerLine]; + + layerBackgroundForceEnable = true; + layerSpritesForceEnable = true; + layerBordersForceEnable = true; + } + + public object GetRuntimeOption(string name) + { + switch (name) + { + case layerBackgroundOptionName: return layerBackgroundForceEnable; + case layerSpritesOptionName: return layerSpritesForceEnable; + case layerBordersOptionName: return layerBordersForceEnable; + default: return null; + } + } + + public void SetRuntimeOption(string name, object value) + { + switch (name) + { + case layerBackgroundOptionName: layerBackgroundForceEnable = (bool)value; break; + case layerSpritesOptionName: layerSpritesForceEnable = (bool)value; break; + case layerBordersOptionName: layerBordersForceEnable = (bool)value; break; + } + } + + public virtual void Startup() + { + Reset(); + + Debug.Assert(clockRate != 0.0, "Clock rate is zero", "{0} clock rate is not configured", GetType().FullName); + Debug.Assert(refreshRate != 0.0, "Refresh rate is zero", "{0} refresh rate is not configured", GetType().FullName); + } + + public virtual void Shutdown() + { + // + } + + public virtual void Reset() + { + for (int i = 0; i < registers.Length; i++) registers[i] = 0; + for (int i = 0; i < vram.Length; i++) vram[i] = 0; + + for (int i = 0; i < spriteBuffer.Length; i++) + for (int j = 0; j < spriteBuffer[i].Length; j++) + spriteBuffer[i][j] = (-1, 0, 0, 0, 0); + + isSecondControlWrite = false; + controlWord = 0x0000; + readBuffer = 0; + + statusFlags = StatusFlags.None; + + // TODO/FIXME: begin on random scanline (i.e. Vcounter for SMS/GG) on reset, http://www.smspower.org/forums/post62735#62735 + currentScanline = new Random().Next(scanlineTopBorder, numVisibleScanlines); + + ClearScreenUsage(); + + cycleCount = 0; + } + + public void SetClockRate(double clock) + { + clockRate = clock; + + ReconfigureTimings(); + } + + public void SetRefreshRate(double refresh) + { + refreshRate = refresh; + isPalChip = (refreshRate <= 50.0); + + ReconfigureTimings(); + } + + public virtual void SetRevision(int rev) + { + Debug.Assert(rev == 0, "Invalid revision", "{0} revision is invalid; only rev 0 is valid", GetType().FullName); + } + + protected virtual void ReconfigureTimings() + { + /* Calculate cycles/line */ + clockCyclesPerLine = (int)Math.Round((clockRate / refreshRate) / numTotalScanlines); + + /* Create arrays */ + screenUsage = new byte[numVisiblePixels * numVisibleScanlines]; + outputFramebuffer = new byte[(numVisiblePixels * numVisibleScanlines) * 4]; + + /* Scanline parameters */ + if (!isPalChip) + { + topBorderSize = 27; + verticalActiveDisplaySize = 192; + bottomBorderSize = 24; + } + else + { + topBorderSize = 54; + verticalActiveDisplaySize = 192; + bottomBorderSize = 48; + } + + scanlineTopBorder = 0; + scanlineActiveDisplay = (scanlineTopBorder + topBorderSize); + scanlineBottomBorder = (scanlineActiveDisplay + verticalActiveDisplaySize); + + numVisibleScanlines = (topBorderSize + verticalActiveDisplaySize + bottomBorderSize); + + /* Pixel parameters */ + leftBorderSize = 13; + horizontalActiveDisplaySize = 256; + rightBorderSize = 15; + + pixelLeftBorder = 0; + pixelActiveDisplay = (pixelLeftBorder + leftBorderSize); + pixelRightBorder = (pixelActiveDisplay + horizontalActiveDisplaySize); + + numVisiblePixels = (leftBorderSize + horizontalActiveDisplaySize + rightBorderSize); + + OnSizeScreen(new SizeScreenEventArgs(numVisiblePixels, numVisibleScanlines)); + } + + public virtual void Step(int clockCyclesInStep) + { + InterruptLine = ((isFrameInterruptEnabled && isFrameInterruptPending) ? InterruptState.Assert : InterruptState.Clear); + + cycleCount += clockCyclesInStep; + + if (cycleCount >= clockCyclesPerLine) + { + OnEndOfScanline(EventArgs.Empty); + + CheckSpriteOverflow(currentScanline); + + RenderLine(currentScanline); + + if (currentScanline == (scanlineBottomBorder + 1)) + isFrameInterruptPending = true; + + currentScanline++; + if (currentScanline == numTotalScanlines) + { + currentScanline = 0; + ClearScreenUsage(); + + PrepareRenderScreen(); + } + + ParseSpriteTable(currentScanline); + + cycleCount -= clockCyclesPerLine; + if (cycleCount <= -clockCyclesPerLine) cycleCount = 0; + } + } + + protected virtual void PrepareRenderScreen() + { + OnRenderScreen(new RenderScreenEventArgs(numVisiblePixels, numVisibleScanlines, outputFramebuffer.Clone() as byte[])); + } + + protected virtual void ClearScreenUsage() + { + for (int i = 0; i < screenUsage.Length; i++) + screenUsage[i] = screenUsageEmpty; + } + + protected virtual void RenderLine(int y) + { + if (y >= scanlineTopBorder && y < scanlineActiveDisplay) + { + if (layerBordersForceEnable) SetLine(y, backgroundColor); + else SetLine(y, 0x00, 0x00, 0x00); + } + else if (y >= scanlineActiveDisplay && y < scanlineBottomBorder) + { + if (layerBackgroundForceEnable) + { + if (isModeGraphics1) + RenderLineGraphics1Background(y); + else if (isModeGraphics2) + RenderLineGraphics2Background(y); + else if (isModeMulticolor) + RenderLineMulticolorBackground(y); + else if (isModeText) + RenderLineTextBackground(y); + } + else + SetLine(y, 0x00, 0x00, 0x00); + + if (layerSpritesForceEnable) + { + if (!isModeText) + RenderLineSprites(y); + } + + RenderBorders(y); + } + else if (y >= scanlineBottomBorder && y < numVisibleScanlines) + { + if (layerBordersForceEnable) SetLine(y, backgroundColor); + else SetLine(y, 0x00, 0x00, 0x00); + } + } + + protected virtual void RenderBorders(int y) + { + for (int x = pixelLeftBorder; x < pixelActiveDisplay; x++) + { + if (layerBordersForceEnable) SetPixel(y, x, backgroundColor); + else SetPixel(y, x, 0x00, 0x00, 0x00); + } + for (int x = pixelRightBorder; x < numVisiblePixels; x++) + { + if (layerBordersForceEnable) SetPixel(y, x, backgroundColor); + else SetPixel(y, x, 0x00, 0x00, 0x00); + } + } + + protected void SetLine(int y, ushort colorValue) + { + for (int x = 0; x < numVisiblePixels; x++) + SetPixel(y, x, colorValue); + } + + protected virtual void SetLine(int y, byte b, byte g, byte r) + { + for (int x = 0; x < numVisiblePixels; x++) + SetPixel(y, x, b, g, r); + } + + protected void SetPixel(int y, int x, ushort colorValue) + { + WriteColorToFramebuffer(colorValue, ((y * numVisiblePixels) + (x % numVisiblePixels)) * 4); + } + + protected virtual void SetPixel(int y, int x, byte b, byte g, byte r) + { + WriteColorToFramebuffer(b, g, r, ((y * numVisiblePixels) + (x % numVisiblePixels)) * 4); + } + + protected byte GetScreenUsageFlag(int y, int x) + { + return screenUsage[(y * numVisiblePixels) + (x % numVisiblePixels)]; + } + + protected bool IsScreenUsageFlagSet(int y, int x, byte flag) + { + return ((GetScreenUsageFlag(y, x) & flag) == flag); + } + + protected void SetScreenUsageFlag(int y, int x, byte flag) + { + screenUsage[(y * numVisiblePixels) + (x % numVisiblePixels)] |= flag; + } + + protected void ClearScreenUsageFlag(int y, int x, byte flag) + { + screenUsage[(y * numVisiblePixels) + (x % numVisiblePixels)] &= (byte)~flag; + } + + protected void RenderLineGraphics1Background(int y) + { + /* Determine coordinates in active display */ + int activeDisplayY = (y - scanlineActiveDisplay); + + int numColumns = 32; + ushort patternGeneratorBaseAddress = (ushort)((registers[0x04] & 0x07) << 11); + ushort colorTableBaseAddress = (ushort)(registers[0x03] << 6); + + for (int column = 0; column < numColumns; column++) + { + /* Calculate nametable address, fetch character number */ + ushort nametableAddress = (ushort)(nametableBaseAddress + ((activeDisplayY / 8) * numColumns) + column); + byte characterNumber = ReadVram(nametableAddress); + + /* Fetch pixel and color data for current pixel line (1 byte, 8 pixels) */ + byte pixelLineData = ReadVram((ushort)(patternGeneratorBaseAddress + (characterNumber * 8) + (activeDisplayY % 8))); + byte pixelLineColor = ReadVram((ushort)(colorTableBaseAddress + (characterNumber / 8))); + + /* Extract background and foreground color indices */ + byte[] colorIndicesBackgroundForeground = new byte[2]; + colorIndicesBackgroundForeground[0] = (byte)(pixelLineColor & 0x0F); + colorIndicesBackgroundForeground[1] = (byte)(pixelLineColor >> 4); + + for (int pixel = 0; pixel < 8; pixel++) + { + /* Fetch color index for current pixel (bit clear means background, bit set means foreground color) */ + byte c = colorIndicesBackgroundForeground[((pixelLineData >> (7 - pixel)) & 0x01)]; + /* Color index 0 is transparent, use background color */ + if (c == 0 || isDisplayBlanked) c = backgroundColor; + + /* Record screen usage, write to framebuffer */ + int x = pixelActiveDisplay + (column * 8) + pixel; + if (GetScreenUsageFlag(y, x) == screenUsageEmpty) + { + SetPixel(y, x, c); + SetScreenUsageFlag(y, x, screenUsageBackground); + } + } + } + } + + protected void RenderLineGraphics2Background(int y) + { + /* Determine coordinates in active display */ + int activeDisplayY = (y - scanlineActiveDisplay); + + int numColumns = (NumActivePixelsPerScanline / 8); + + /* Calculate some base addresses */ + ushort patternGeneratorBaseAddress = (ushort)((registers[0x04] & 0x04) << 11); + ushort colorTableBaseAddress = (ushort)((registers[0x03] & 0x80) << 6); + + for (int column = 0; column < numColumns; column++) + { + /* Calculate nametable address */ + ushort nametableAddress = (ushort)(nametableBaseAddress + ((activeDisplayY / 8) * numColumns) + column); + + /* Calculate character number and masks */ + ushort characterNumber = (ushort)(((activeDisplayY / 64) << 8) | ReadVram(nametableAddress)); + ushort characterNumberDataMask = (ushort)(((registers[0x04] & 0x03) << 8) | 0xFF); + ushort characterNumberColorMask = (ushort)(((registers[0x03] & 0x7F) << 3) | 0x07); + + /* Fetch pixel and color data for current pixel line (1 byte, 8 pixels) */ + byte pixelLineData = ReadVram((ushort)(patternGeneratorBaseAddress + ((characterNumber & characterNumberDataMask) * 8) + (activeDisplayY % 8))); + byte pixelLineColor = ReadVram((ushort)(colorTableBaseAddress + ((characterNumber & characterNumberColorMask) * 8) + (activeDisplayY % 8))); + + /* Extract background and foreground color indices */ + byte[] colorIndicesBackgroundForeground = new byte[2]; + colorIndicesBackgroundForeground[0] = (byte)(pixelLineColor & 0x0F); + colorIndicesBackgroundForeground[1] = (byte)(pixelLineColor >> 4); + + for (int pixel = 0; pixel < 8; pixel++) + { + /* Fetch color index for current pixel (bit clear means background, bit set means foreground color) */ + byte c = colorIndicesBackgroundForeground[((pixelLineData >> (7 - pixel)) & 0x01)]; + /* Color index 0 is transparent, use background color */ + if (c == 0 || isDisplayBlanked) c = backgroundColor; + + /* Record screen usage, write to framebuffer */ + int x = pixelActiveDisplay + (column * 8) + pixel; + if (GetScreenUsageFlag(y, x) == screenUsageEmpty) + { + SetPixel(y, x, c); + SetScreenUsageFlag(y, x, screenUsageBackground); + } + } + } + } + + protected void RenderLineMulticolorBackground(int y) + { + // TODO: check accuracy w/ games besides Smurfs Paint & Play Workshop (if there are any) + + /* Determine coordinates in active display */ + int activeDisplayY = (y - scanlineActiveDisplay); + + int numColumns = 32; + int row = (activeDisplayY / 8); + ushort patternGeneratorBaseAddress = (ushort)((registers[0x04] & 0x07) << 11); + + for (int column = 0; column < numColumns; column++) + { + /* Calculate nametable address, fetch character number */ + ushort nametableAddress = (ushort)(nametableBaseAddress + (row * numColumns) + column); + byte characterNumber = ReadVram(nametableAddress); + + /* Calculate pattern generator address, fetch pattern data */ + ushort patternGeneratorAddress = (ushort)(patternGeneratorBaseAddress + (characterNumber * 8) + ((row & 0x03) * 2) + ((activeDisplayY / 4) % 2)); + byte patternData = ReadVram(patternGeneratorAddress); + + for (int block = 0; block < 8; block += 4) + { + /* Fetch color index for current 4x4 block */ + byte c = (byte)((patternData >> (4 - block)) & 0x0F); + + /* Color index 0 is transparent, use background color */ + if (c == 0 || isDisplayBlanked) c = backgroundColor; + + for (int pixel = 0; pixel < 4; pixel++) + { + /* Record screen usage, write to framebuffer */ + int x = pixelActiveDisplay + (column * 8) + (block + pixel); + if (GetScreenUsageFlag(y, x) == screenUsageEmpty) + { + SetPixel(y, x, c); + SetScreenUsageFlag(y, x, screenUsageBackground); + } + } + } + } + } + + protected void RenderLineTextBackground(int y) + { + /* Determine coordinates in active display */ + int activeDisplayY = (y - scanlineActiveDisplay); + + int numColumns = 40; + ushort patternGeneratorBaseAddress = (ushort)((registers[0x04] & 0x07) << 11); + + /* Get background and text color indices */ + byte[] colorIndicesBackgroundForeground = new byte[2]; + colorIndicesBackgroundForeground[0] = backgroundColor; + colorIndicesBackgroundForeground[1] = textColor; + + /* Draw left and right 8px borders */ + for (int pixel = 0; pixel < 8; pixel++) + { + int x = pixelActiveDisplay + pixel; + SetPixel(y, x + 0, backgroundColor); + SetPixel(y, x + 8 + (numColumns * 6), backgroundColor); + } + + /* Draw text columns */ + for (int column = 0; column < numColumns; column++) + { + /* Calculate nametable address, fetch character number */ + ushort nametableAddress = (ushort)(nametableBaseAddress + ((activeDisplayY / 8) * numColumns) + column); + byte characterNumber = ReadVram(nametableAddress); + + /* Fetch pixel data for current pixel line (1 byte, 8 pixels) */ + byte pixelLineData = ReadVram((ushort)(patternGeneratorBaseAddress + (characterNumber * 8) + (activeDisplayY % 8))); + + for (int pixel = 0; pixel < 6; pixel++) + { + /* Fetch color index for current pixel (bit clear means background, bit set means text color) */ + byte c = (isDisplayBlanked ? backgroundColor : colorIndicesBackgroundForeground[((pixelLineData >> (7 - pixel)) & 0x01)]); + + /* Record screen usage, write to framebuffer */ + int x = pixelActiveDisplay + 8 + (column * 6) + pixel; + if (GetScreenUsageFlag(y, x) == screenUsageEmpty) + { + SetPixel(y, x, c); + SetScreenUsageFlag(y, x, screenUsageBackground); + } + } + } + } + + protected virtual void WriteSpriteNumberToStatus(int spriteNumber) + { + statusFlags &= (StatusFlags.FrameInterruptPending | StatusFlags.SpriteOverflow | StatusFlags.SpriteCollision); + statusFlags |= (StatusFlags)spriteNumber; + } + + protected virtual void CheckSpriteOverflow(int y) + { + /* Ensure current scanline is within active display */ + if (y >= scanlineActiveDisplay && y < scanlineBottomBorder) + { + int activeDisplayY = (y - scanlineActiveDisplay); + + /* If last sprite in buffer is valid, sprite overflow occured */ + int lastSpriteInBuffer = spriteBuffer[activeDisplayY][NumSpritesPerLine - 1].Number; + if (lastSpriteInBuffer != -1) + { + isSpriteOverflow = true; + + /* Store sprite number in status register */ + WriteSpriteNumberToStatus(lastSpriteInBuffer); + } + } + } + + protected virtual void ParseSpriteTable(int y) + { + if (y < scanlineActiveDisplay || y >= scanlineBottomBorder) return; + + /* Determine coordinates in active display */ + int activeDisplayY = (y - scanlineActiveDisplay); + + /* Clear sprite list for current line */ + for (int i = 0; i < spriteBuffer[activeDisplayY].Length; i++) spriteBuffer[activeDisplayY][i] = (-1, 0, 0, 0, 0); + + /* Determine sprite size & get zoomed sprites adjustment */ + int zoomShift = (isZoomedSprites ? 1 : 0); + int spriteHeight = ((isLargeSprites ? 16 : 8) << zoomShift); + + int numValidSprites = 0; + for (int sprite = 0; sprite < NumSprites; sprite++) + { + int yCoordinate = ReadVram((ushort)(spriteAttribTableBaseAddress + (sprite * 4))); + + /* Ignore following if Y coord is 208 */ + if (yCoordinate == 208) + { + /* Store first "illegal sprite" number in status register */ + WriteSpriteNumberToStatus(sprite); + return; + } + + /* Modify Y coord as needed */ + yCoordinate++; + if (yCoordinate > NumActiveScanlines + 32) yCoordinate -= 256; + + /* Ignore this sprite if on incorrect lines */ + if (activeDisplayY < yCoordinate || activeDisplayY >= (yCoordinate + spriteHeight)) continue; + + /* Check if maximum number of sprites per line is reached */ + numValidSprites++; + if (numValidSprites > NumSpritesPerLine) return; + + /* Mark sprite for rendering */ + int xCoordinate = ReadVram((ushort)(spriteAttribTableBaseAddress + (sprite * 4) + 1)); + int patternNumber = ReadVram((ushort)(spriteAttribTableBaseAddress + (sprite * 4) + 2)); + int attributes = ReadVram((ushort)(spriteAttribTableBaseAddress + (sprite * 4) + 3)); + + spriteBuffer[activeDisplayY][numValidSprites - 1] = (sprite, yCoordinate, xCoordinate, patternNumber, attributes); + } + + /* Because we didn't bow out before already, store total number of sprites in status register */ + WriteSpriteNumberToStatus(NumSprites - 1); + } + + protected void RenderLineSprites(int y) + { + if (y < scanlineActiveDisplay || y >= scanlineBottomBorder) return; + + /* Determine coordinates in active display */ + int activeDisplayY = (y - scanlineActiveDisplay); + + /* Determine sprite size & get zoomed sprites adjustment */ + int spriteSize = (isLargeSprites ? 16 : 8); + int zoomShift = (isZoomedSprites ? 1 : 0); + int numSpritePixels = (spriteSize << zoomShift); + + foreach (var sprite in spriteBuffer[activeDisplayY]) + { + if (sprite.Number == -1) continue; + + if (!isDisplayBlanked) + { + int yCoordinate = sprite.Y; + int xCoordinate = sprite.X; + int patternNumber = sprite.Pattern; + int attributes = sprite.Attribute; + + /* Fetch sprite information, extract attributes */ + bool earlyClock = ((attributes & 0x80) == 0x80); + int spriteColor = (attributes & 0x0F); + + /* Adjust according to registers/attributes */ + if (earlyClock) xCoordinate -= 32; + if (isLargeSprites) patternNumber &= 0xFC; + + for (int pixel = 0; pixel < numSpritePixels; pixel++) + { + /* Check if sprite is outside active display, else continue to next sprite */ + if ((xCoordinate + pixel) < 0 || (xCoordinate + pixel) >= NumActivePixelsPerScanline) continue; + + /* Determine coordinate inside sprite */ + int inSpriteXCoord = (pixel >> zoomShift) % spriteSize; + int inSpriteYCoord = ((activeDisplayY - yCoordinate) >> zoomShift) % spriteSize; + + /* Calculate address and fetch pixel data */ + ushort spritePatternAddress = spritePatternGenBaseAddress; + spritePatternAddress += (ushort)(patternNumber << 3); + spritePatternAddress += (ushort)inSpriteYCoord; + if (inSpriteXCoord >= 8) spritePatternAddress += 16; + + byte pixelLineData = ReadVram(spritePatternAddress); + + /* Check if pixel from pattern needs to be drawn, else continue to next sprite */ + if (((pixelLineData >> (7 - (inSpriteXCoord % 8))) & 0x01) == 0x00) continue; + + int x = pixelActiveDisplay + (xCoordinate + pixel); + if (IsScreenUsageFlagSet(y, x, screenUsageSprite)) + { + /* If sprite was already at this location, set sprite collision flag */ + isSpriteCollision = true; + } + else + { + /* If color isn't transparent, draw pixel to framebuffer */ + if (spriteColor != 0) + SetPixel(y, x, (ushort)spriteColor); + } + + /* Note that there is a sprite here regardless */ + SetScreenUsageFlag(y, x, screenUsageSprite); + } + } + } + } + + protected virtual void WriteColorToFramebuffer(byte b, byte g, byte r, int address) + { + outputFramebuffer[address + 0] = b; + outputFramebuffer[address + 1] = g; + outputFramebuffer[address + 2] = r; + outputFramebuffer[address + 3] = 0xFF; + } + + protected virtual void WriteColorToFramebuffer(ushort colorValue, int address) + { + outputFramebuffer[address + 0] = colorValuesBgr[colorValue & 0x0F][0]; + outputFramebuffer[address + 1] = colorValuesBgr[colorValue & 0x0F][1]; + outputFramebuffer[address + 2] = colorValuesBgr[colorValue & 0x0F][2]; + outputFramebuffer[address + 3] = 0xFF; + } + + protected virtual byte ReadVram(ushort address) + { + if (is16kVRAMEnabled) + return vram[address & vramMask16k]; + else + return vram[address & vramMask4k]; + } + + protected virtual void WriteVram(ushort address, byte value) + { + if (is16kVRAMEnabled) + vram[address & vramMask16k] = value; + else + vram[address & vramMask4k] = value; + } + + protected virtual byte ReadDataPort() + { + isSecondControlWrite = false; + statusFlags = StatusFlags.None; + + byte data = readBuffer; + readBuffer = ReadVram(addressRegister); + addressRegister++; + + return data; + } + + protected virtual byte ReadControlPort() + { + byte statusCurrent = (byte)statusFlags; + + statusFlags = StatusFlags.None; + isSecondControlWrite = false; + + InterruptLine = InterruptState.Clear; + + return statusCurrent; + } + + public virtual byte ReadPort(byte port) + { + if ((port & 0x01) == 0x00) + return ReadDataPort(); + else + return ReadControlPort(); + } + + protected virtual void WriteDataPort(byte value) + { + isSecondControlWrite = false; + + readBuffer = value; + WriteVram(addressRegister, value); + addressRegister++; + } + + protected virtual void WriteControlPort(byte value) + { + if (!isSecondControlWrite) + controlWord = (ushort)((controlWord & 0xFF00) | (value << 0)); + else + { + controlWord = (ushort)((controlWord & 0x00FF) | (value << 8)); + + switch (codeRegister) + { + case 0x00: readBuffer = ReadVram(addressRegister); addressRegister++; break; + case 0x01: break; + case 0x02: WriteRegister((byte)((controlWord >> 8) & 0x0F), (byte)(controlWord & 0x00FF)); break; + case 0x03: break; + } + } + + isSecondControlWrite = !isSecondControlWrite; + } + + public virtual void WritePort(byte port, byte value) + { + if ((port & 0x01) == 0x00) + WriteDataPort(value); + else + WriteControlPort(value); + } + + protected virtual void WriteRegister(byte register, byte value) + { + // TODO: confirm register mirroring + registers[register & 0x07] = value; + } + } +} diff --git a/Assets/Plugins/Essgee/Emulation/Video/TMS99xxA.cs.meta b/Assets/Plugins/Essgee/Emulation/Video/TMS99xxA.cs.meta new file mode 100644 index 0000000..7baf48f --- /dev/null +++ b/Assets/Plugins/Essgee/Emulation/Video/TMS99xxA.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a235296639e8898418d408c99ebe70fc \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Enumerations.cs b/Assets/Plugins/Essgee/Enumerations.cs new file mode 100644 index 0000000..3f4b685 --- /dev/null +++ b/Assets/Plugins/Essgee/Enumerations.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee +{ + public enum ScreenSizeMode { Stretch, Scale, Integer } + + public enum ExceptionResult { Continue, StopEmulation, ExitApplication } + + public enum ExtraDataTypes { Raw, Image } + + [Flags] + public enum ExtraDataOptions + { + IncludeDateTime = (1 << 0), + AllowOverwrite = (1 << 1) + } +} diff --git a/Assets/Plugins/Essgee/Enumerations.cs.meta b/Assets/Plugins/Essgee/Enumerations.cs.meta new file mode 100644 index 0000000..cfecbcd --- /dev/null +++ b/Assets/Plugins/Essgee/Enumerations.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2f9f52ebdd4aee949bf1ee9b137c9ec7 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/EssgeeLogger.cs b/Assets/Plugins/Essgee/EssgeeLogger.cs new file mode 100644 index 0000000..d4bd5cd --- /dev/null +++ b/Assets/Plugins/Essgee/EssgeeLogger.cs @@ -0,0 +1,14 @@ +using UnityEngine; + +public class EssgeeLogger +{ + public static void WriteLine(string message = null) + { + + } + + public static void EnqueueMessageSuccess(string message = null) + { + + } +} diff --git a/Assets/Plugins/Essgee/EssgeeLogger.cs.meta b/Assets/Plugins/Essgee/EssgeeLogger.cs.meta new file mode 100644 index 0000000..35abea2 --- /dev/null +++ b/Assets/Plugins/Essgee/EssgeeLogger.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cdcc61f43394b604d9ff021fce4eeace \ No newline at end of file diff --git a/Assets/Plugins/Essgee/EventArguments.meta b/Assets/Plugins/Essgee/EventArguments.meta new file mode 100644 index 0000000..b85ebf7 --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 281db18b9e0f1054e8b4abae8427ba41 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/EventArguments/ChangeViewportEventArgs.cs b/Assets/Plugins/Essgee/EventArguments/ChangeViewportEventArgs.cs new file mode 100644 index 0000000..4428cdf --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/ChangeViewportEventArgs.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.EventArguments +{ + public class ChangeViewportEventArgs : EventArgs + { + public (int X, int Y, int Width, int Height) Viewport { get; private set; } + + public ChangeViewportEventArgs((int, int, int, int) viewport) + { + Viewport = viewport; + } + } +} diff --git a/Assets/Plugins/Essgee/EventArguments/ChangeViewportEventArgs.cs.meta b/Assets/Plugins/Essgee/EventArguments/ChangeViewportEventArgs.cs.meta new file mode 100644 index 0000000..7094092 --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/ChangeViewportEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a5fdb88b89c3ee84b861d3156f877eec \ No newline at end of file diff --git a/Assets/Plugins/Essgee/EventArguments/EnqueueSamplesEventArgs.cs b/Assets/Plugins/Essgee/EventArguments/EnqueueSamplesEventArgs.cs new file mode 100644 index 0000000..e485702 --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/EnqueueSamplesEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.EventArguments +{ + public class EnqueueSamplesEventArgs : EventArgs + { + public int NumChannels { get; set; } + public short[][] ChannelSamples { get; set; } + public bool[] IsChannelMuted { get; set; } + public short[] MixedSamples { get; set; } + + public EnqueueSamplesEventArgs(int numChannels, short[][] channelSamples, bool[] isMuted, short[] mixedSamples) + { + NumChannels = numChannels; + ChannelSamples = channelSamples; + IsChannelMuted = isMuted; + MixedSamples = mixedSamples; + } + } +} diff --git a/Assets/Plugins/Essgee/EventArguments/EnqueueSamplesEventArgs.cs.meta b/Assets/Plugins/Essgee/EventArguments/EnqueueSamplesEventArgs.cs.meta new file mode 100644 index 0000000..83facde --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/EnqueueSamplesEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 43ad2fa85569796468e3bd2923aaf5db \ No newline at end of file diff --git a/Assets/Plugins/Essgee/EventArguments/PollInputEventArgs.cs b/Assets/Plugins/Essgee/EventArguments/PollInputEventArgs.cs new file mode 100644 index 0000000..e8072dc --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/PollInputEventArgs.cs @@ -0,0 +1,26 @@ +using Essgee.Utilities.XInput; +using System; +using System.Collections.Generic; + +namespace Essgee.EventArguments +{ + public class PollInputEventArgs : EventArgs + { + public IEnumerable<Keys> Keyboard { get; set; } + + public MouseButtons MouseButtons { get; set; } + public (int X, int Y) MousePosition { get; set; } + + public ControllerState ControllerState { get; set; } + + public PollInputEventArgs() + { + Keyboard = new List<Keys>(); + + MouseButtons = MouseButtons.None; + MousePosition = (0, 0); + + ControllerState = new ControllerState(); + } + } +} diff --git a/Assets/Plugins/Essgee/EventArguments/PollInputEventArgs.cs.meta b/Assets/Plugins/Essgee/EventArguments/PollInputEventArgs.cs.meta new file mode 100644 index 0000000..62f987e --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/PollInputEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ada5c874b28f2c24f8622fd7dce81592 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/EventArguments/RenderScreenEventArgs.cs b/Assets/Plugins/Essgee/EventArguments/RenderScreenEventArgs.cs new file mode 100644 index 0000000..49835fc --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/RenderScreenEventArgs.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.EventArguments +{ + public class RenderScreenEventArgs : EventArgs + { + public int Width { get; private set; } + public int Height { get; private set; } + public byte[] FrameData { get; private set; } + + public RenderScreenEventArgs(int width, int height, byte[] data) + { + Width = width; + Height = height; + FrameData = data; + } + } +} diff --git a/Assets/Plugins/Essgee/EventArguments/RenderScreenEventArgs.cs.meta b/Assets/Plugins/Essgee/EventArguments/RenderScreenEventArgs.cs.meta new file mode 100644 index 0000000..bbe7faf --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/RenderScreenEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: effb4e50cecefc94194ff7943b6d9bf9 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/EventArguments/SaveExtraDataEventArgs.cs b/Assets/Plugins/Essgee/EventArguments/SaveExtraDataEventArgs.cs new file mode 100644 index 0000000..50f3fa0 --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/SaveExtraDataEventArgs.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.EventArguments +{ + public class SaveExtraDataEventArgs : EventArgs + { + public ExtraDataTypes DataType { get; private set; } + public ExtraDataOptions Options { get; private set; } + + public string Description { get; private set; } + public object Data { get; private set; } + + public SaveExtraDataEventArgs(ExtraDataTypes type, ExtraDataOptions option, string desc, object data) + { + DataType = type; + Options = option; + Description = desc; + Data = data; + } + } +} diff --git a/Assets/Plugins/Essgee/EventArguments/SaveExtraDataEventArgs.cs.meta b/Assets/Plugins/Essgee/EventArguments/SaveExtraDataEventArgs.cs.meta new file mode 100644 index 0000000..66b734e --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/SaveExtraDataEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 239da568cc1e77a4c8cf9692726fbd37 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/EventArguments/SendLogMessageEventArgs.cs b/Assets/Plugins/Essgee/EventArguments/SendLogMessageEventArgs.cs new file mode 100644 index 0000000..00c664c --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/SendLogMessageEventArgs.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.EventArguments +{ + public class SendLogMessageEventArgs : EventArgs + { + public string Message { get; private set; } + + public SendLogMessageEventArgs(string message) + { + Message = message; + } + } +} diff --git a/Assets/Plugins/Essgee/EventArguments/SendLogMessageEventArgs.cs.meta b/Assets/Plugins/Essgee/EventArguments/SendLogMessageEventArgs.cs.meta new file mode 100644 index 0000000..50355e8 --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/SendLogMessageEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 73ff88c017bde9147946020156991f15 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/EventArguments/SizeScreenEventArgs.cs b/Assets/Plugins/Essgee/EventArguments/SizeScreenEventArgs.cs new file mode 100644 index 0000000..25f36a3 --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/SizeScreenEventArgs.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.EventArguments +{ + public class SizeScreenEventArgs : EventArgs + { + public int Width { get; private set; } + public int Height { get; private set; } + + public SizeScreenEventArgs(int width, int height) + { + Width = width; + Height = height; + } + } +} diff --git a/Assets/Plugins/Essgee/EventArguments/SizeScreenEventArgs.cs.meta b/Assets/Plugins/Essgee/EventArguments/SizeScreenEventArgs.cs.meta new file mode 100644 index 0000000..3a61f86 --- /dev/null +++ b/Assets/Plugins/Essgee/EventArguments/SizeScreenEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9ab5875b41ee4fa4790e97a80748fcbd \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Exceptions.meta b/Assets/Plugins/Essgee/Exceptions.meta new file mode 100644 index 0000000..afa813c --- /dev/null +++ b/Assets/Plugins/Essgee/Exceptions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f1e895bd77f41874fad666b2f480f482 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Exceptions/CartridgeLoaderException.cs b/Assets/Plugins/Essgee/Exceptions/CartridgeLoaderException.cs new file mode 100644 index 0000000..ecd42b9 --- /dev/null +++ b/Assets/Plugins/Essgee/Exceptions/CartridgeLoaderException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Essgee.Exceptions +{ + [Serializable] + public class CartridgeLoaderException : Exception + { + public CartridgeLoaderException() : base() { } + public CartridgeLoaderException(string message) : base(message) { } + public CartridgeLoaderException(string message, Exception innerException) : base(message, innerException) { } + public CartridgeLoaderException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/Assets/Plugins/Essgee/Exceptions/CartridgeLoaderException.cs.meta b/Assets/Plugins/Essgee/Exceptions/CartridgeLoaderException.cs.meta new file mode 100644 index 0000000..2907438 --- /dev/null +++ b/Assets/Plugins/Essgee/Exceptions/CartridgeLoaderException.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d5e871a4c155d6343910ef62bb192f5a \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Exceptions/EmulationException.cs b/Assets/Plugins/Essgee/Exceptions/EmulationException.cs new file mode 100644 index 0000000..263de32 --- /dev/null +++ b/Assets/Plugins/Essgee/Exceptions/EmulationException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Essgee.Exceptions +{ + [Serializable] + public class EmulationException : Exception + { + public EmulationException() : base() { } + public EmulationException(string message) : base(message) { } + public EmulationException(string message, Exception innerException) : base(message, innerException) { } + public EmulationException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/Assets/Plugins/Essgee/Exceptions/EmulationException.cs.meta b/Assets/Plugins/Essgee/Exceptions/EmulationException.cs.meta new file mode 100644 index 0000000..3292cff --- /dev/null +++ b/Assets/Plugins/Essgee/Exceptions/EmulationException.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d29afc05220518e41b9c5ebb04cec0a6 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Exceptions/GraphicsException.cs b/Assets/Plugins/Essgee/Exceptions/GraphicsException.cs new file mode 100644 index 0000000..9f3a8f4 --- /dev/null +++ b/Assets/Plugins/Essgee/Exceptions/GraphicsException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Essgee.Exceptions +{ + [Serializable] + public class GraphicsException : Exception + { + public GraphicsException() : base() { } + public GraphicsException(string message) : base(message) { } + public GraphicsException(string message, Exception innerException) : base(message, innerException) { } + public GraphicsException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/Assets/Plugins/Essgee/Exceptions/GraphicsException.cs.meta b/Assets/Plugins/Essgee/Exceptions/GraphicsException.cs.meta new file mode 100644 index 0000000..2d7a3d1 --- /dev/null +++ b/Assets/Plugins/Essgee/Exceptions/GraphicsException.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 292f826ddc00e6f41b4f4ba46a9a6ece \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Exceptions/HandlerException.cs b/Assets/Plugins/Essgee/Exceptions/HandlerException.cs new file mode 100644 index 0000000..0114f29 --- /dev/null +++ b/Assets/Plugins/Essgee/Exceptions/HandlerException.cs @@ -0,0 +1,13 @@ +using System; + +namespace Essgee.Exceptions +{ + [Serializable] + public class HandlerException : Exception + { + public HandlerException() : base() { } + public HandlerException(string message) : base(message) { } + public HandlerException(string message, Exception innerException) : base(message, innerException) { } + public HandlerException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/Assets/Plugins/Essgee/Exceptions/HandlerException.cs.meta b/Assets/Plugins/Essgee/Exceptions/HandlerException.cs.meta new file mode 100644 index 0000000..4b03e09 --- /dev/null +++ b/Assets/Plugins/Essgee/Exceptions/HandlerException.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6c6b7a048bc880a449b2b81ae303f70d \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Extensions.meta b/Assets/Plugins/Essgee/Extensions.meta new file mode 100644 index 0000000..01847d1 --- /dev/null +++ b/Assets/Plugins/Essgee/Extensions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1be95ab09a576dd4db60392be7d5f9f0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Extensions/AssemblyExtensionMethods.cs b/Assets/Plugins/Essgee/Extensions/AssemblyExtensionMethods.cs new file mode 100644 index 0000000..3f38165 --- /dev/null +++ b/Assets/Plugins/Essgee/Extensions/AssemblyExtensionMethods.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; +using System.IO; +using System.Drawing; + +namespace Essgee.Extensions +{ + public static class AssemblyExtensionMethods + { + public static T GetAttribute<T>(this ICustomAttributeProvider assembly, bool inherit = false) where T : Attribute + { + return assembly.GetCustomAttributes(typeof(T), inherit).OfType<T>().FirstOrDefault(); + } + + public static bool IsEmbeddedResourceAvailable(this Assembly assembly, string resourceName) + { + using (var stream = assembly.GetManifestResourceStream(resourceName)) + return (stream != null); + } + + public static string ReadEmbeddedTextFile(this Assembly assembly, string resourceName) + { + using (var reader = new StreamReader(assembly.GetManifestResourceStream(resourceName))) + return reader.ReadToEnd(); + } + + //public static Bitmap ReadEmbeddedImageFile(this Assembly assembly, string resourceName) + //{ + // using (var stream = assembly.GetManifestResourceStream(resourceName)) + // return new Bitmap(stream); + //} + } +} diff --git a/Assets/Plugins/Essgee/Extensions/AssemblyExtensionMethods.cs.meta b/Assets/Plugins/Essgee/Extensions/AssemblyExtensionMethods.cs.meta new file mode 100644 index 0000000..d971fb3 --- /dev/null +++ b/Assets/Plugins/Essgee/Extensions/AssemblyExtensionMethods.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2a919f96fbccdab4f98631971a5ad641 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Extensions/FormExtensionMethods.cs b/Assets/Plugins/Essgee/Extensions/FormExtensionMethods.cs new file mode 100644 index 0000000..423ffb4 --- /dev/null +++ b/Assets/Plugins/Essgee/Extensions/FormExtensionMethods.cs @@ -0,0 +1,11 @@ +//namespace Essgee.Extensions +//{ +// public static class FormExtensionMethods +// { +// public static void CheckInvokeMethod(this Form form, MethodInvoker methodInvoker) +// { +// if (form.InvokeRequired) form.BeginInvoke(methodInvoker); +// else methodInvoker(); +// } +// } +//} diff --git a/Assets/Plugins/Essgee/Extensions/FormExtensionMethods.cs.meta b/Assets/Plugins/Essgee/Extensions/FormExtensionMethods.cs.meta new file mode 100644 index 0000000..29c7235 --- /dev/null +++ b/Assets/Plugins/Essgee/Extensions/FormExtensionMethods.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6f4a1f0be012aa141a62c9f7259b614c \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Extensions/GeneralExtensionMethods.cs b/Assets/Plugins/Essgee/Extensions/GeneralExtensionMethods.cs new file mode 100644 index 0000000..a2b5e62 --- /dev/null +++ b/Assets/Plugins/Essgee/Extensions/GeneralExtensionMethods.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; + +namespace Essgee.Extensions +{ + public static class GeneralExtensionMethods + { + // https://www.c-sharpcorner.com/UploadFile/ff2f08/deep-copy-of-object-in-C-Sharp/ + public static T CloneObject<T>(this T source) + { + var type = source.GetType(); + var target = (T)Activator.CreateInstance(type); + + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)) + { + if (property.CanWrite) + { + if (property.PropertyType.IsValueType || property.PropertyType.IsEnum || property.PropertyType.Equals(typeof(string)) || property.PropertyType.Equals(typeof(Type))) + property.SetValue(target, property.GetValue(source, null), null); + else + { + object objPropertyValue = property.GetValue(source, null); + if (objPropertyValue == null) + property.SetValue(target, null, null); + else + property.SetValue(target, objPropertyValue.CloneObject(), null); + } + } + } + return target; + } + } +} diff --git a/Assets/Plugins/Essgee/Extensions/GeneralExtensionMethods.cs.meta b/Assets/Plugins/Essgee/Extensions/GeneralExtensionMethods.cs.meta new file mode 100644 index 0000000..29f74bd --- /dev/null +++ b/Assets/Plugins/Essgee/Extensions/GeneralExtensionMethods.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b82a62a18635b33479a83fdcf68428e5 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Extensions/SerializationExtensionMethods.cs b/Assets/Plugins/Essgee/Extensions/SerializationExtensionMethods.cs new file mode 100644 index 0000000..cc0f697 --- /dev/null +++ b/Assets/Plugins/Essgee/Extensions/SerializationExtensionMethods.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; + +using Newtonsoft.Json; + +namespace Essgee.Extensions +{ + public static class SerializationExtensionMethods + { + public static void SerializeToFile(this object obj, string jsonFileName) + { + SerializeToFile(obj, jsonFileName, new JsonSerializerSettings()); + } + + public static void SerializeToFile(this object obj, string jsonFileName, JsonSerializerSettings serializerSettings) + { + using (var writer = new StreamWriter(jsonFileName)) + { + writer.Write(JsonConvert.SerializeObject(obj, Formatting.Indented, serializerSettings)); + } + } + + public static T DeserializeFromFile<T>(this string jsonFileName) + { + using (var reader = new StreamReader(jsonFileName)) + { + return (T)JsonConvert.DeserializeObject(reader.ReadToEnd(), typeof(T), new JsonSerializerSettings() { Formatting = Formatting.Indented }); + } + } + + public static T DeserializeObject<T>(this string jsonString) + { + return (T)JsonConvert.DeserializeObject(jsonString, typeof(T), new JsonSerializerSettings() { Formatting = Formatting.Indented }); + } + } +} diff --git a/Assets/Plugins/Essgee/Extensions/SerializationExtensionMethods.cs.meta b/Assets/Plugins/Essgee/Extensions/SerializationExtensionMethods.cs.meta new file mode 100644 index 0000000..f489bc1 --- /dev/null +++ b/Assets/Plugins/Essgee/Extensions/SerializationExtensionMethods.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 016c6214586d0ed44a4181b198587385 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics.meta b/Assets/Plugins/Essgee/Graphics.meta new file mode 100644 index 0000000..e68fab0 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 414f2e9f45929a04a9c56ae02bd0ff9e +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Graphics/Enumerations.cs b/Assets/Plugins/Essgee/Graphics/Enumerations.cs new file mode 100644 index 0000000..b8e3fab --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Enumerations.cs @@ -0,0 +1,28 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; + +//namespace Essgee.Graphics +//{ +// public enum FilterMode +// { +// Linear, +// Nearest +// } + +// public enum WrapMode +// { +// Repeat, +// Edge, +// Border, +// Mirror +// } + +// public enum PixelFormat +// { +// Rgba8888, +// Rgb888 +// } +//} diff --git a/Assets/Plugins/Essgee/Graphics/Enumerations.cs.meta b/Assets/Plugins/Essgee/Graphics/Enumerations.cs.meta new file mode 100644 index 0000000..7fbe9a2 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Enumerations.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eedf5bf3a0ae6eb41acf730b466dabc9 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics/FastMethodInvoker.cs b/Assets/Plugins/Essgee/Graphics/FastMethodInvoker.cs new file mode 100644 index 0000000..29cc4cb --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/FastMethodInvoker.cs @@ -0,0 +1,140 @@ +using System; +using System.Reflection; +using System.Reflection.Emit; + +namespace Essgee.Graphics +{ + /* http://www.codeproject.com/Articles/14593/A-General-Fast-Method-Invoker */ + + internal delegate object FastInvokeHandler(object target, object[] paramters); + + internal class FastMethodInvoker + { + public static FastInvokeHandler GetMethodInvoker(MethodInfo methodInfo) + { + DynamicMethod dynamicMethod = new DynamicMethod(string.Empty, typeof(object), new Type[] { typeof(object), typeof(object[]) }, methodInfo.DeclaringType.Module); + + ILGenerator il = dynamicMethod.GetILGenerator(); + ParameterInfo[] ps = methodInfo.GetParameters(); + + Type[] paramTypes = new Type[ps.Length]; + for (int i = 0; i < paramTypes.Length; i++) + { + if (ps[i].ParameterType.IsByRef) + paramTypes[i] = ps[i].ParameterType.GetElementType(); + else + paramTypes[i] = ps[i].ParameterType; + } + + LocalBuilder[] locals = new LocalBuilder[paramTypes.Length]; + for (int i = 0; i < paramTypes.Length; i++) + locals[i] = il.DeclareLocal(paramTypes[i], true); + + for (int i = 0; i < paramTypes.Length; i++) + { + il.Emit(OpCodes.Ldarg_1); + EmitFastInt(il, i); + il.Emit(OpCodes.Ldelem_Ref); + EmitCastToReference(il, paramTypes[i]); + il.Emit(OpCodes.Stloc, locals[i]); + } + + if (!methodInfo.IsStatic) + il.Emit(OpCodes.Ldarg_0); + + for (int i = 0; i < paramTypes.Length; i++) + { + if (ps[i].ParameterType.IsByRef) + il.Emit(OpCodes.Ldloca_S, locals[i]); + else + il.Emit(OpCodes.Ldloc, locals[i]); + } + + if (methodInfo.IsStatic) + il.EmitCall(OpCodes.Call, methodInfo, null); + else + il.EmitCall(OpCodes.Callvirt, methodInfo, null); + + if (methodInfo.ReturnType == typeof(void)) + il.Emit(OpCodes.Ldnull); + else + EmitBoxIfNeeded(il, methodInfo.ReturnType); + + for (int i = 0; i < paramTypes.Length; i++) + { + if (ps[i].ParameterType.IsByRef) + { + il.Emit(OpCodes.Ldarg_1); + EmitFastInt(il, i); + il.Emit(OpCodes.Ldloc, locals[i]); + + if (locals[i].LocalType.IsValueType) + il.Emit(OpCodes.Box, locals[i].LocalType); + + il.Emit(OpCodes.Stelem_Ref); + } + } + + il.Emit(OpCodes.Ret); + + FastInvokeHandler invoder = (FastInvokeHandler)dynamicMethod.CreateDelegate(typeof(FastInvokeHandler)); + return invoder; + } + + private static void EmitCastToReference(ILGenerator il, Type type) + { + if (type.IsValueType) + il.Emit(OpCodes.Unbox_Any, type); + else + il.Emit(OpCodes.Castclass, type); + } + + private static void EmitBoxIfNeeded(ILGenerator il, Type type) + { + if (type.IsValueType) + il.Emit(OpCodes.Box, type); + } + + private static void EmitFastInt(ILGenerator il, int value) + { + switch (value) + { + case -1: + il.Emit(OpCodes.Ldc_I4_M1); + return; + case 0: + il.Emit(OpCodes.Ldc_I4_0); + return; + case 1: + il.Emit(OpCodes.Ldc_I4_1); + return; + case 2: + il.Emit(OpCodes.Ldc_I4_2); + return; + case 3: + il.Emit(OpCodes.Ldc_I4_3); + return; + case 4: + il.Emit(OpCodes.Ldc_I4_4); + return; + case 5: + il.Emit(OpCodes.Ldc_I4_5); + return; + case 6: + il.Emit(OpCodes.Ldc_I4_6); + return; + case 7: + il.Emit(OpCodes.Ldc_I4_7); + return; + case 8: + il.Emit(OpCodes.Ldc_I4_8); + return; + } + + if (value > -129 && value < 128) + il.Emit(OpCodes.Ldc_I4_S, (sbyte)value); + else + il.Emit(OpCodes.Ldc_I4, value); + } + } +} diff --git a/Assets/Plugins/Essgee/Graphics/FastMethodInvoker.cs.meta b/Assets/Plugins/Essgee/Graphics/FastMethodInvoker.cs.meta new file mode 100644 index 0000000..d297deb --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/FastMethodInvoker.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d46aa7b243f39584b93c79dc84079999 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics/GraphicsHandler.cs b/Assets/Plugins/Essgee/Graphics/GraphicsHandler.cs new file mode 100644 index 0000000..df5b3e2 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/GraphicsHandler.cs @@ -0,0 +1,247 @@ +//using Essgee.Graphics.Shaders; +//using System; +//using System.ComponentModel; +//using System.Diagnostics; +//using System.Drawing; + +//namespace Essgee.Graphics +//{ +// public class GraphicsHandler : IDisposable +// { +// static readonly CommonVertex[] vertices = new CommonVertex[] +// { +// new CommonVertex() { Position = new Vector3(0.0f, 1.0f, 0.0f), TexCoord = new Vector2(0.0f, 1.0f), Color = Color4.White }, +// new CommonVertex() { Position = new Vector3(0.0f, 0.0f, 0.0f), TexCoord = new Vector2(0.0f, 0.0f), Color = Color4.White }, +// new CommonVertex() { Position = new Vector3(1.0f, 0.0f, 0.0f), TexCoord = new Vector2(1.0f, 0.0f), Color = Color4.White }, +// new CommonVertex() { Position = new Vector3(1.0f, 1.0f, 0.0f), TexCoord = new Vector2(1.0f, 1.0f), Color = Color4.White } +// }; + +// static readonly byte[] indices = new byte[] { 0, 1, 2, 2, 3, 0 }; + +// public string GLRenderer { get; private set; } +// public string GLVersion { get; private set; } + +// VertexBuffer vertexBuffer; +// ShaderBundle shaderBundle; + +// Texture[] textures; +// int lastTextureUpdate; + +// OnScreenDisplayHandler onScreenDisplayHandler; + +// (int Width, int Height) textureSize; +// (int X, int Y, int Width, int Height) inputViewport, outputViewport; +// Matrix4 projectionMatrix, modelviewMatrix; + +// bool refreshRendererAndShader; + +// Stopwatch stopwatch; +// float deltaTime; + +// bool disposed = false; + +// public GraphicsHandler(OnScreenDisplayHandler osdHandler) +// { +// onScreenDisplayHandler = osdHandler; + +// if (AppEnvironment.EnableOpenGLDebug) +// GL.Enable(EnableCap.DebugOutput); + +// GLRenderer = GL.GetString(StringName.Renderer); +// GLVersion = GL.GetString(StringName.Version); + +// GL.Enable(EnableCap.Blend); +// GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha); + +// vertexBuffer = new VertexBuffer(); +// vertexBuffer.SetVertexData(vertices); +// vertexBuffer.SetIndices(indices); + +// textures = new Texture[ShaderBundle.MaxNumSourceSamplers]; + +// lastTextureUpdate = 0; + +// projectionMatrix = Matrix4.Identity; +// modelviewMatrix = Matrix4.Identity; + +// stopwatch = Stopwatch.StartNew(); + +// Application.Idle += (s, e) => +// { +// if (LicenseManager.UsageMode == LicenseUsageMode.Designtime || GraphicsContext.CurrentContext == null) return; + +// stopwatch.Stop(); +// deltaTime = (float)stopwatch.Elapsed.TotalMilliseconds / 10.0f; +// stopwatch.Reset(); +// stopwatch.Start(); +// }; + +// onScreenDisplayHandler.EnqueueMessageSuccess($"Graphics initialized; {GLRenderer}, {GLVersion}."); +// } + +// ~GraphicsHandler() +// { +// Dispose(false); +// } + +// public void Dispose() +// { +// Dispose(true); +// GC.SuppressFinalize(this); +// } + +// protected virtual void Dispose(bool disposing) +// { +// if (disposed) return; + +// if (disposing) +// { +// if (vertexBuffer != null) vertexBuffer.Dispose(); +// if (shaderBundle != null) shaderBundle.Dispose(); +// } + +// disposed = true; +// } + +// public void LoadShaderBundle(string shaderName) +// { +// var lastShaderFilter = shaderBundle?.Manifest.Filter; +// var lastShaderWrap = shaderBundle?.Manifest.Wrap; + +// shaderBundle = new ShaderBundle(shaderName, typeof(CommonVertex)); + +// if ((lastShaderFilter != null && lastShaderFilter != shaderBundle.Manifest.Filter) || (lastShaderWrap != null && lastShaderWrap != shaderBundle.Manifest.Wrap)) +// CreateTextures(); + +// FlushTextures(); + +// refreshRendererAndShader = true; + +// onScreenDisplayHandler.EnqueueMessage($"Loaded shader '{shaderName}'."); +// } + +// public void SetTextureSize(int width, int height) +// { +// textureSize = (width, height); + +// CreateTextures(); + +// refreshRendererAndShader = true; +// } + +// private void CreateTextures() +// { +// if (textureSize.Width == 0 || textureSize.Height == 0) +// return; + +// for (int i = 0; i < textures.Length; i++) +// textures[i] = new Texture(textureSize.Width, textureSize.Height, PixelFormat.Rgba8888, shaderBundle.Manifest.Filter, shaderBundle.Manifest.Wrap); + +// lastTextureUpdate = 0; +// } + +// public void SetTextureData(byte[] data) +// { +// textures[lastTextureUpdate].SetData(data); +// lastTextureUpdate++; +// if (lastTextureUpdate >= shaderBundle.Manifest.Samplers) lastTextureUpdate = 0; +// } + +// public void SetScreenViewport((int, int, int, int) viewport) +// { +// inputViewport = viewport; + +// lastTextureUpdate = 0; + +// refreshRendererAndShader = true; +// } + +// public void FlushTextures() +// { +// for (int i = 0; i < shaderBundle.Manifest.Samplers; i++) +// textures[i]?.ClearData(); + +// lastTextureUpdate = 0; + +// refreshRendererAndShader = true; +// } + +// public void Resize(Rectangle clientRectangle, Size screenSize) +// { +// GL.Viewport(0, 0, clientRectangle.Width, clientRectangle.Height); + +// projectionMatrix = Matrix4.CreateOrthographicOffCenter(0.0f, clientRectangle.Width, clientRectangle.Height, 0.0f, -10.0f, 10.0f); + +// switch (Program.Configuration.ScreenSizeMode) +// { +// case ScreenSizeMode.Stretch: +// { +// modelviewMatrix = Matrix4.CreateScale(clientRectangle.Width, clientRectangle.Height, 1.0f); + +// outputViewport = (clientRectangle.X, clientRectangle.Y, clientRectangle.Width, clientRectangle.Height); +// } +// break; + +// case ScreenSizeMode.Scale: +// { +// var multiplier = (float)Math.Min(clientRectangle.Width / (double)screenSize.Width, clientRectangle.Height / (double)screenSize.Height); + +// var adjustedWidth = screenSize.Width * multiplier; +// var adjustedHeight = screenSize.Height * multiplier; +// var adjustedX = (float)Math.Floor((clientRectangle.Width - adjustedWidth) / 2.0f); +// var adjustedY = (float)Math.Floor((clientRectangle.Height - adjustedHeight) / 2.0f); + +// modelviewMatrix = Matrix4.CreateScale(adjustedWidth, adjustedHeight, 1.0f) * Matrix4.CreateTranslation(adjustedX, adjustedY, 1.0f); + +// outputViewport = (clientRectangle.X, clientRectangle.Y, clientRectangle.Width, clientRectangle.Height); +// } +// break; + +// case ScreenSizeMode.Integer: +// { +// var multiplier = (float)Math.Min(Math.Floor(clientRectangle.Width / (double)inputViewport.Width), Math.Floor(clientRectangle.Height / (double)inputViewport.Height)); + +// var adjustedWidth = inputViewport.Width * multiplier; +// var adjustedHeight = inputViewport.Height * multiplier; +// var adjustedX = (float)Math.Floor((clientRectangle.Width - adjustedWidth) / 2.0f); +// var adjustedY = (float)Math.Floor((clientRectangle.Height - adjustedHeight) / 2.0f); + +// modelviewMatrix = Matrix4.CreateScale(adjustedWidth, adjustedHeight, 1.0f) * Matrix4.CreateTranslation(adjustedX, adjustedY, 1.0f); + +// outputViewport = ((int)adjustedX, (int)adjustedY, (int)adjustedWidth, (int)adjustedHeight); +// } +// break; +// } + +// onScreenDisplayHandler.SetViewport((clientRectangle.X, clientRectangle.Y, clientRectangle.Width, clientRectangle.Height)); +// onScreenDisplayHandler.SetProjectionMatrix(projectionMatrix); +// onScreenDisplayHandler.SetModelviewMatrix(modelviewMatrix); + +// refreshRendererAndShader = true; +// } + +// public void Render() +// { +// if (refreshRendererAndShader) +// { +// shaderBundle.SetTextureSize(new Vector2(textureSize.Width, textureSize.Height)); +// shaderBundle.SetInputViewport(new Vector4(inputViewport.X, inputViewport.Y, inputViewport.Width, inputViewport.Height)); +// shaderBundle.SetOutputViewport(new Vector4(outputViewport.X, outputViewport.Y, outputViewport.Width, outputViewport.Height)); +// shaderBundle.SetProjectionMatrix(projectionMatrix); +// shaderBundle.SetModelviewMatrix(modelviewMatrix); + +// GC.Collect(); + +// refreshRendererAndShader = false; +// } + +// for (int i = 0; i < shaderBundle.Manifest.Samplers; i++) +// textures[i]?.Activate(TextureUnit.Texture0 + ((lastTextureUpdate + i) % shaderBundle.Manifest.Samplers)); + +// shaderBundle.Activate(); +// vertexBuffer.Render(); + +// onScreenDisplayHandler.Render(deltaTime); +// } +// } +//} diff --git a/Assets/Plugins/Essgee/Graphics/GraphicsHandler.cs.meta b/Assets/Plugins/Essgee/Graphics/GraphicsHandler.cs.meta new file mode 100644 index 0000000..aee3b89 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/GraphicsHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 20fd3917138ed8943be76a9d49cd4327 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics/OnScreenDisplayHandler.cs b/Assets/Plugins/Essgee/Graphics/OnScreenDisplayHandler.cs new file mode 100644 index 0000000..120b79f --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/OnScreenDisplayHandler.cs @@ -0,0 +1,374 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using System.Drawing; +//using System.Runtime.InteropServices; +//using System.Reflection; + +//using OpenTK; +//using OpenTK.Graphics; + +//using Essgee.Graphics.Shaders; + +//namespace Essgee.Graphics +//{ +// public class OnScreenDisplayHandler : IDisposable +// { +// readonly static int messageDefaultSeconds = 5; +// readonly static int maxStringListLength = 128; +// readonly static int stringListPurgeSize = 16; + +// readonly static string glslUniformProjection = "projection"; +// readonly static string glslUniformModelview = "modelview"; +// readonly static string glslUniformSourceSampler = "source"; +// readonly static string glslUniformTextColor = "textColor"; +// readonly static string glslUniformFontSize = "fontSize"; +// readonly static string glslUniformCharacterOffset = "characterOffset"; + +// readonly static string glslVersion = "#version 300 es\n"; +// readonly static string glslESPrecision = "precision mediump float; precision mediump int;\n"; +// readonly static string glslMainStart = "void main(void){"; +// readonly static string glslMainEnd = "}\n"; + +// readonly static string vertexUniforms = $"uniform mat4 {glslUniformProjection}; uniform mat4 {glslUniformModelview};\n"; +// readonly static string vertexOuts = "out vec2 vertTexCoord;\n"; +// readonly static string vertexMain = $"vertTexCoord = inTextureCoords; gl_Position = {glslUniformProjection} * {glslUniformModelview} * vec4(inPosition.x, inPosition.y, 0.0, 1.0);\n"; + +// readonly static string fragmentUniforms = $"uniform sampler2D {glslUniformSourceSampler}; uniform vec4 {glslUniformTextColor}; uniform vec2 {glslUniformFontSize}; uniform vec2 {glslUniformCharacterOffset};\n"; +// readonly static string fragmentIns = "in vec2 vertTexCoord;\n"; +// readonly static string fragmentOuts = "out vec4 fragColor;\n"; + +// readonly static string fragmentMain = +// $"vec2 localTexCoord = vec2(({glslUniformCharacterOffset}.x + vertTexCoord.x) / {glslUniformFontSize}.x, ({glslUniformCharacterOffset}.y + vertTexCoord.y) / {glslUniformFontSize}.y);\n" + +// $"vec4 outColor = textColor * texture(source, localTexCoord);\n" + +// "if(outColor.a == 0.0) discard;\n" + +// "fragColor = outColor;\n"; + +// readonly static ushort[] characterIndices = new ushort[] { 0, 1, 2, 2, 3, 0 }; + +// readonly static Color4 colorSuccess = new Color4(192, 255, 192, 255); +// readonly static Color4 colorWarning = new Color4(255, 192, 160, 255); +// readonly static Color4 colorError = new Color4(255, 128, 128, 255); +// readonly static Color4 colorCore = new Color4(192, 192, 255, 255); +// readonly static Color4 colorDebug = new Color4(192, 128, 255, 255); + +// readonly OnScreenDisplayVertex[] characterVertices; + +// readonly List<VertexElement> vertexElements; +// readonly int vertexStructSize; +// readonly VertexBuffer characterVertexBuffer; + +// readonly Texture fontTexture; +// readonly float characterSourceSize; +// readonly float characterDefaultWidth; +// readonly GLSLShader shader; + +// readonly Dictionary<char, Vector2> characterOffsetDict; +// readonly Dictionary<char, (float start, float width)> characterWidthDict; + +// readonly List<OnScreenDisplayMessage> stringList; + +// (int X, int Y, int Width, int Height) viewport; + +// bool disposed = false; + +// public OnScreenDisplayHandler(Bitmap osdFontBitmap) +// { +// characterSourceSize = (osdFontBitmap.Width / 16.0f); +// characterDefaultWidth = characterSourceSize - (2.0f * (characterSourceSize / 8)); + +// characterOffsetDict = new Dictionary<char, Vector2>(); +// characterWidthDict = new Dictionary<char, (float, float)>(); +// for (var ch = '\0'; ch < (char)((osdFontBitmap.Width / characterSourceSize) * (osdFontBitmap.Height / characterSourceSize)); ch++) +// { +// float x = (ch % (osdFontBitmap.Width / (int)characterSourceSize)) * characterSourceSize; +// float y = (ch / (osdFontBitmap.Width / (int)characterSourceSize)) * characterSourceSize; + +// float width = characterSourceSize; +// float start = 0.0f; +// for (float xc = x + (characterSourceSize - 1); xc >= x; xc--) +// { +// var pixel = osdFontBitmap.GetPixel((int)xc, (int)y); +// if (pixel.R == 0xFF && pixel.G == 0x00 && pixel.B == 0x00 && pixel.A == 0xFF) +// { +// width = (xc - x); +// osdFontBitmap.SetPixel((int)xc, (int)y, Color.Transparent); +// } + +// if (pixel.R == 0xFF && pixel.G == 0xFF && pixel.B == 0x00 && pixel.A == 0xFF) +// { +// start = (xc - x); +// osdFontBitmap.SetPixel((int)xc, (int)y, Color.Transparent); +// } +// } + +// characterOffsetDict.Add(ch, new Vector2(x, y)); +// characterWidthDict.Add(ch, (start, width)); +// } + +// fontTexture = new Texture(osdFontBitmap, filter: FilterMode.Nearest); + +// (vertexElements, vertexStructSize) = VertexBuffer.DeconstructVertexLayout<OnScreenDisplayVertex>(); + +// characterVertices = new OnScreenDisplayVertex[] +// { +// new OnScreenDisplayVertex() { Position = new Vector2(0.0f, characterSourceSize), TextureCoords = new Vector2(0.0f, characterSourceSize) }, +// new OnScreenDisplayVertex() { Position = new Vector2(0.0f, 0.0f), TextureCoords = new Vector2(0.0f, 0.0f) }, +// new OnScreenDisplayVertex() { Position = new Vector2(characterSourceSize, 0.0f), TextureCoords = new Vector2(characterSourceSize, 0.0f) }, +// new OnScreenDisplayVertex() { Position = new Vector2(characterSourceSize, characterSourceSize), TextureCoords = new Vector2(characterSourceSize, characterSourceSize) }, +// }; + +// characterVertexBuffer = new VertexBuffer(); +// characterVertexBuffer.SetVertexData(characterVertices); +// characterVertexBuffer.SetIndices(characterIndices); + +// shader = new GLSLShader(); +// shader.SetVertexShaderCode(glslVersion, glslESPrecision, vertexUniforms, VertexBuffer.GetShaderPreamble(vertexElements), vertexOuts, glslMainStart, vertexMain, glslMainEnd); +// shader.SetFragmentShaderCode(glslVersion, glslESPrecision, fragmentUniforms, fragmentIns, fragmentOuts, glslMainStart, fragmentMain, glslMainEnd); +// shader.LinkProgram(); + +// shader.SetUniformMatrix(glslUniformProjection, false, Matrix4.Identity); +// shader.SetUniformMatrix(glslUniformModelview, false, Matrix4.Identity); +// shader.SetUniform(glslUniformSourceSampler, 0); +// shader.SetUniform(glslUniformTextColor, Color4.White); +// shader.SetUniform(glslUniformFontSize, new Vector2(fontTexture.Width, fontTexture.Height)); +// shader.SetUniform(glslUniformCharacterOffset, Vector2.Zero); + +// stringList = new List<OnScreenDisplayMessage>(); +// } + +// ~OnScreenDisplayHandler() +// { +// Dispose(false); +// } + +// public void Dispose() +// { +// Dispose(true); +// GC.SuppressFinalize(this); +// } + +// protected virtual void Dispose(bool disposing) +// { +// if (disposed) return; + +// if (disposing) +// { +// if (fontTexture != null) fontTexture.Dispose(); +// if (characterVertexBuffer != null) characterVertexBuffer.Dispose(); +// if (shader != null) shader.Dispose(); +// } + +// disposed = true; +// } + +// public void SetViewport(ValueTuple<int, int, int, int> view) +// { +// viewport = view; +// } + +// public void SetProjectionMatrix(Matrix4 mat4) +// { +// shader.SetUniformMatrix(glslUniformProjection, false, mat4); +// } + +// public void SetModelviewMatrix(Matrix4 mat4) +// { +// shader.SetUniformMatrix(glslUniformModelview, false, mat4); +// } + +// public void SendString(string str, int x, int y) +// { +// SendString(str, x, y, Color4.White); +// } + +// public void SendString(string str, int x, int y, Color4 color) +// { +// stringList.Add(new OnScreenDisplayMessage() +// { +// X = x, +// Y = y, +// Color = color, +// Text = str, +// ShowUntil = DateTime.Now, +// IsLogEntry = false +// }); +// } + +// public void EnqueueMessage(string str) +// { +// EnqueueMessage(str, Color4.White); +// } + +// public void EnqueueMessage(string str, Color4 color) +// { +// var split = str.Split(new string[] { Environment.NewLine, "\n" }, StringSplitOptions.RemoveEmptyEntries); +// for (var i = 0; i < split.Length; i++) +// { +// stringList.Add(new OnScreenDisplayMessage() +// { +// X = 0, +// Y = 0, +// Color = color, +// Text = split[i], +// ShowUntil = DateTime.Now + TimeSpan.FromTicks(i) + TimeSpan.FromSeconds(messageDefaultSeconds), +// IsLogEntry = true +// }); +// } +// } + +// public void EnqueueMessageSuccess(string str) +// { +// EnqueueMessage(str, colorSuccess); +// } + +// public void EnqueueMessageWarning(string str) +// { +// EnqueueMessage(str, colorWarning); +// } + +// public void EnqueueMessageError(string str) +// { +// EnqueueMessage(str, colorError); +// } + +// public void EnqueueMessageCore(string str) +// { +// EnqueueMessage(str, colorCore); +// } + +// public void EnqueueMessageDebug(string str) +// { +// if (Program.AppEnvironment.DebugMode) +// EnqueueMessage(str, colorDebug); +// } + +// public void Render(float deltaTime) +// { +// shader.Activate(); +// fontTexture.Activate(); + +// RenderStrings(deltaTime); +// RenderLogMessages(deltaTime); + +// stringList.RemoveAll(x => !x.IsLogEntry && x.ShowUntil < DateTime.Now); +// stringList.RemoveAll(x => x.IsLogEntry && x.Color.A <= 0.0f); + +// if (stringList.Count > maxStringListLength) +// stringList.RemoveRange(0, stringListPurgeSize); + +// fontTexture.Deactivate(); +// } + +// private int MeasureString(string @string) +// { +// float width = 0.0f; +// foreach (var ch in @string) +// { +// if (characterWidthDict.ContainsKey(ch)) +// { +// width -= characterWidthDict[ch].start; +// width += characterWidthDict[ch].width; +// } +// else +// width += characterDefaultWidth; +// } +// return (int)width; +// } + +// private void RenderStrings(float deltaTime) +// { +// foreach (var @string in stringList.ToList().Where(x => !x.IsLogEntry)) +// { +// float x = @string.X, y = @string.Y; +// if (x < 0.0f) x = (viewport.Width + x) - MeasureString(@string.Text); +// if (y < 0.0f) y = (viewport.Height + y) - characterSourceSize; +// shader.SetUniform(glslUniformTextColor, @string.Color); +// ParseAndRenderString(@string, ref x, ref y); +// } +// } + +// private void RenderLogMessages(float deltaTime) +// { +// var logY = (viewport.Height - (characterSourceSize + (characterSourceSize / 2))); +// foreach (var @string in stringList.ToList().Where(x => x.IsLogEntry).OrderByDescending(x => x.ShowUntil)) +// { +// float x = characterSourceSize / 2.0f, y = logY; +// logY -= characterSourceSize; +// shader.SetUniform(glslUniformTextColor, @string.Color); +// ParseAndRenderString(@string, ref x, ref y); + +// if ((logY + characterSourceSize) < 0) break; +// } + +// var timeNow = DateTime.Now; +// foreach (var @string in stringList.ToList().Where(x => x.IsLogEntry)) +// { +// if ((@string.ShowUntil.Ticks - timeNow.Ticks) < TimeSpan.TicksPerSecond) +// @string.Color = new Color4(@string.Color.R, @string.Color.G, @string.Color.B, Math.Max(0.0f, @string.Color.A - (deltaTime / 25.0f))); +// } +// } + +// private void ParseAndRenderString(OnScreenDisplayMessage @string, ref float x, ref float y) +// { +// foreach (var c in @string.Text) +// { +// var ch = c; + +// if (!characterWidthDict.ContainsKey(ch)) +// ch = '\0'; + +// x -= characterWidthDict[ch].start; + +// if (ch == '\n' || ch == '\r') +// { +// x = @string.X; +// y += characterSourceSize; +// continue; +// } +// else if (ch != ' ') +// { +// var osdModelview = Matrix4.Identity; +// osdModelview *= Matrix4.CreateTranslation(x, y, 0.0f); + +// shader.SetUniformMatrix(glslUniformModelview, false, osdModelview); +// shader.SetUniform(glslUniformCharacterOffset, characterOffsetDict[ch]); + +// characterVertexBuffer.Render(); +// } + +// x += characterWidthDict[ch].width; +// } +// } + +// [StructLayout(LayoutKind.Sequential, Pack = 1)] +// public struct OnScreenDisplayVertex : IVertexStruct +// { +// [VertexElement(AttributeIndex = 0)] +// public Vector2 Position; +// [VertexElement(AttributeIndex = 1)] +// public Vector2 TextureCoords; +// } + +// public class OnScreenDisplayMessage +// { +// public int X { get; set; } +// public int Y { get; set; } +// public Color4 Color { get; set; } +// public string Text { get; set; } +// public DateTime ShowUntil { get; set; } +// public bool IsLogEntry { get; set; } + +// public OnScreenDisplayMessage() +// { +// X = Y = 0; +// Color = Color4.White; +// Text = string.Empty; +// ShowUntil = DateTime.Now; +// IsLogEntry = false; +// } +// } +// } +//} diff --git a/Assets/Plugins/Essgee/Graphics/OnScreenDisplayHandler.cs.meta b/Assets/Plugins/Essgee/Graphics/OnScreenDisplayHandler.cs.meta new file mode 100644 index 0000000..5b4e979 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/OnScreenDisplayHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d339431b097f84640a5625605af96f59 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics/RenderControl.cs b/Assets/Plugins/Essgee/Graphics/RenderControl.cs new file mode 100644 index 0000000..289e66e --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/RenderControl.cs @@ -0,0 +1,108 @@ +//using System; +//using System.ComponentModel; +//using System.Diagnostics; +//using System.Diagnostics.CodeAnalysis; +//using System.Drawing; +//using System.Runtime.InteropServices; + +//namespace Essgee.Graphics +//{ +// [DesignTimeVisible(true), ToolboxItem(true)] +// [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", +// Justification = "False positive. IDisposable is inherited via IFunctionality. See http://stackoverflow.com/questions/8925925/code-analysis-ca1063-fires-when-deriving-from-idisposable-and-providing-implemen for details.")] +// public class RenderControl : GLControl, IComponent +// { +// public event EventHandler<EventArgs> Render; + +// bool isRuntime => (LicenseManager.UsageMode != LicenseUsageMode.Designtime); +// bool isReady => (isRuntime && GraphicsContext.CurrentContext != null); + +// DebugProc debugCallback; + +// bool wasShown = false; + +// public RenderControl() : base(GraphicsMode.Default, 3, 0, GraphicsContextFlags.Default) +// { +// if (!isRuntime) return; + +// Application.Idle += ((s, e) => { if (isReady) Invalidate(); }); +// } + +// protected override bool IsInputKey(Keys keyData) +// { +// switch (keyData) +// { +// case Keys.Right: +// case Keys.Left: +// case Keys.Up: +// case Keys.Down: +// case (Keys.Shift | Keys.Right): +// case (Keys.Shift | Keys.Left): +// case (Keys.Shift | Keys.Up): +// case (Keys.Shift | Keys.Down): +// return true; + +// default: +// return base.IsInputKey(keyData); +// } +// } + +// protected override void OnPaint(PaintEventArgs e) +// { +// if (!isReady) +// { +// e.Graphics.Clear(BackColor); +// using (Pen pen = new Pen(Color.Red, 3.0f)) +// { +// e.Graphics.InterpolationMode = InterpolationMode.HighQualityBilinear; +// e.Graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; +// e.Graphics.SmoothingMode = SmoothingMode.HighQuality; +// e.Graphics.DrawLine(pen, Point.Empty, new Point(ClientRectangle.Right, ClientRectangle.Bottom)); +// e.Graphics.DrawLine(pen, new Point(0, ClientRectangle.Bottom), new Point(ClientRectangle.Right, 0)); +// } +// return; +// } + +// if (!wasShown) +// { +// OnResize(EventArgs.Empty); +// wasShown = true; +// } + +// OnRender(EventArgs.Empty); + +// SwapBuffers(); +// } + +// protected virtual void OnRender(EventArgs e) +// { +// if (!isReady) return; +// Render?.Invoke(this, e); +// } + +// protected override void OnLoad(EventArgs e) +// { +// if (!isReady) return; + +// if (Program.AppEnvironment.EnableOpenGLDebug) +// { +// debugCallback = new DebugProc(GLDebugCallback); +// GL.DebugMessageCallback(debugCallback, IntPtr.Zero); +// } + +// GL.ClearColor(BackColor); +// base.OnLoad(e); +// } + +// protected override void OnResize(EventArgs e) +// { +// if (!isReady) return; +// base.OnResize(e); +// } + +// private void GLDebugCallback(DebugSource source, DebugType type, int id, DebugSeverity severity, int length, IntPtr message, IntPtr userParam) +// { +// Debug.Print($"{(type == DebugType.DebugTypeError ? "GL ERROR!" : "GL callback")} - source={source}, type={type}, severity={severity}, message={Marshal.PtrToStringAnsi(message)}"); +// } +// } +//} diff --git a/Assets/Plugins/Essgee/Graphics/RenderControl.cs.meta b/Assets/Plugins/Essgee/Graphics/RenderControl.cs.meta new file mode 100644 index 0000000..4192dcc --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/RenderControl.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bd60d4cba4b596b4caca2011e09f52f2 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics/Shaders.meta b/Assets/Plugins/Essgee/Graphics/Shaders.meta new file mode 100644 index 0000000..b653ad8 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Shaders.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9883a8b3cddfccc44b67d176b7f1ac47 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Graphics/Shaders/BundleManifest.cs b/Assets/Plugins/Essgee/Graphics/Shaders/BundleManifest.cs new file mode 100644 index 0000000..c73bbe6 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Shaders/BundleManifest.cs @@ -0,0 +1,27 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; + +//using Newtonsoft.Json; +//using Newtonsoft.Json.Converters; + +//namespace Essgee.Graphics.Shaders +//{ +// public class BundleManifest +// { +// [JsonConverter(typeof(StringEnumConverter))] +// public FilterMode Filter { get; set; } +// [JsonConverter(typeof(StringEnumConverter))] +// public WrapMode Wrap { get; set; } +// public int Samplers { get; set; } + +// public BundleManifest() +// { +// Filter = FilterMode.Linear; +// Wrap = WrapMode.Repeat; +// Samplers = 3; +// } +// } +//} diff --git a/Assets/Plugins/Essgee/Graphics/Shaders/BundleManifest.cs.meta b/Assets/Plugins/Essgee/Graphics/Shaders/BundleManifest.cs.meta new file mode 100644 index 0000000..dd9f7f6 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Shaders/BundleManifest.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eb0a360511a90b64291ef7d4b8bf80c0 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics/Shaders/GLSLShader.cs b/Assets/Plugins/Essgee/Graphics/Shaders/GLSLShader.cs new file mode 100644 index 0000000..6483991 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Shaders/GLSLShader.cs @@ -0,0 +1,214 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using System.Reflection; + +//using OpenTK.Graphics.OpenGL; + +//using Essgee.Exceptions; + +//namespace Essgee.Graphics.Shaders +//{ +// public class GLSLShader : IDisposable +// { +// int vertexShaderObject, fragmentShaderObject, geometryShaderObject; +// List<string> vertexShaderCode, fragmentShaderCode, geometryShaderCode; + +// readonly int programObject; + +// static readonly string[] uniformSetMethods = +// { +// "Uniform1", "Uniform2", "Uniform3", "Uniform4" +// }; + +// static readonly string[] uniformSetMethodsMatrix = +// { +// "UniformMatrix2", "UniformMatrix2x3", "UniformMatrix2x4", +// "UniformMatrix3", "UniformMatrix3x2", "UniformMatrix3x4", +// "UniformMatrix4", "UniformMatrix4x2", "UniformMatrix4x3" +// }; + +// Dictionary<string, int> uniformLocations; +// Dictionary<string, dynamic> uniformData; +// Dictionary<Type, FastInvokeHandler> uniformMethods; + +// bool disposed = false; + +// public GLSLShader() +// { +// vertexShaderObject = fragmentShaderObject = geometryShaderObject = -1; +// programObject = GL.CreateProgram(); + +// uniformLocations = new Dictionary<string, int>(); +// uniformData = new Dictionary<string, dynamic>(); +// uniformMethods = new Dictionary<Type, FastInvokeHandler>(); +// } + +// ~GLSLShader() +// { +// Dispose(false); +// } + +// public void Dispose() +// { +// Dispose(true); +// GC.SuppressFinalize(this); +// } + +// protected virtual void Dispose(bool disposing) +// { +// if (disposed) +// return; + +// if (disposing) +// { +// DetachDeleteShader(programObject, vertexShaderObject); +// DetachDeleteShader(programObject, fragmentShaderObject); +// DetachDeleteShader(programObject, geometryShaderObject); + +// GL.DeleteProgram(programObject); +// } + +// disposed = true; +// } + +// public void SetVertexShaderCode(params string[] shaderCode) +// { +// DetachDeleteShader(programObject, vertexShaderObject); +// vertexShaderObject = GenerateShader(ShaderType.VertexShader, vertexShaderCode = shaderCode.ToList()); +// } + +// public void SetFragmentShaderCode(params string[] shaderCode) +// { +// DetachDeleteShader(programObject, fragmentShaderObject); +// fragmentShaderObject = GenerateShader(ShaderType.FragmentShader, fragmentShaderCode = shaderCode.ToList()); +// } + +// public void SetGeometryShaderCode(params string[] shaderCode) +// { +// DetachDeleteShader(programObject, geometryShaderObject); +// geometryShaderObject = GenerateShader(ShaderType.GeometryShader, geometryShaderCode = shaderCode.ToList()); +// } + +// private int GenerateShader(ShaderType shaderType, List<string> shaderCode) +// { +// var handle = GL.CreateShader(shaderType); +// GL.ShaderSource(handle, shaderCode.Count, shaderCode.ToArray(), shaderCode.Select(x => x.Length).ToArray()); +// GL.CompileShader(handle); + +// GL.GetShaderInfoLog(handle, out string infoLog); +// GL.GetShader(handle, ShaderParameter.CompileStatus, out int statusCode); +// if (statusCode != 1) +// throw new GraphicsException($"Shader compile for {shaderType} failed: {infoLog}"); + +// return handle; +// } + +// private void DetachDeleteShader(int programObject, int shaderObject) +// { +// if (shaderObject != -1 && GL.IsShader(shaderObject)) +// { +// GL.DetachShader(programObject, shaderObject); +// GL.DeleteShader(shaderObject); +// } +// } + +// public void LinkProgram() +// { +// LinkProgram(programObject, new int[] { vertexShaderObject, fragmentShaderObject, geometryShaderObject }); +// } + +// private void LinkProgram(int programObject, int[] shaderObjects) +// { +// foreach (var shaderObject in shaderObjects.Where(x => x != -1)) +// GL.AttachShader(programObject, shaderObject); + +// GL.LinkProgram(programObject); +// GL.GetProgramInfoLog(programObject, out string infoLog); +// GL.GetProgram(programObject, GetProgramParameterName.LinkStatus, out int statusCode); +// if (statusCode != 1) +// throw new GraphicsException($"Shader program link failed: {infoLog}"); +// } + +// public void Activate() +// { +// if (programObject == -1) throw new GraphicsException("Invalid shader program handle"); +// GL.UseProgram(programObject); +// } + +// public void SetUniform(string name, dynamic data) +// { +// Activate(); + +// Type type = data.GetType(); + +// if (!uniformLocations.ContainsKey(name)) +// uniformLocations.Add(name, GL.GetUniformLocation(programObject, name)); + +// uniformData[name] = data; + +// if (uniformMethods.ContainsKey(type)) +// { +// uniformMethods[type](null, new object[] { uniformLocations[name], data }); +// } +// else +// { +// foreach (string methodName in uniformSetMethods) +// { +// Type[] argTypes = new Type[] { typeof(int), type }; +// MethodInfo methodInfo = typeof(GL).GetMethod(methodName, argTypes); + +// if (methodInfo != null) +// { +// uniformMethods[type] = FastMethodInvoker.GetMethodInvoker(methodInfo); +// uniformMethods[type](null, new object[] { uniformLocations[name], data }); +// return; +// } +// } + +// throw new GraphicsException("No Uniform method found"); +// } +// } + +// public void SetUniformMatrix(string name, bool transpose, dynamic data) +// { +// Activate(); + +// Type type = data.GetType(); +// if (!uniformLocations.ContainsKey(name)) +// uniformLocations.Add(name, GL.GetUniformLocation(programObject, name)); + +// uniformData[name] = data; + +// if (uniformMethods.ContainsKey(type)) +// { +// uniformMethods[type](null, new object[] { uniformLocations[name], transpose, data }); +// } +// else +// { +// foreach (string methodName in uniformSetMethodsMatrix) +// { +// Type[] argTypes = new Type[] { typeof(int), typeof(bool), data.GetType().MakeByRefType() }; +// MethodInfo methodInfo = typeof(GL).GetMethod(methodName, argTypes); + +// if (methodInfo != null) +// { +// uniformMethods[type] = FastMethodInvoker.GetMethodInvoker(methodInfo); +// uniformMethods[type](null, new object[] { uniformLocations[name], transpose, data }); +// return; +// } +// } + +// throw new GraphicsException("No UniformMatrix method found"); +// } +// } + +// public dynamic GetUniform(string name) +// { +// if (!uniformData.ContainsKey(name)) throw new ArgumentException(); +// return uniformData[name]; +// } +// } +//} diff --git a/Assets/Plugins/Essgee/Graphics/Shaders/GLSLShader.cs.meta b/Assets/Plugins/Essgee/Graphics/Shaders/GLSLShader.cs.meta new file mode 100644 index 0000000..732b934 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Shaders/GLSLShader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a68ffee335bae1a4a916390d6cf1c0aa \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics/Shaders/ShaderBundle.cs b/Assets/Plugins/Essgee/Graphics/Shaders/ShaderBundle.cs new file mode 100644 index 0000000..818e1a0 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Shaders/ShaderBundle.cs @@ -0,0 +1,201 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using System.IO; +//using System.Reflection; +//using System.Windows.Forms; + +//using OpenTK; + +//using Essgee.Extensions; + +//namespace Essgee.Graphics.Shaders +//{ +// public class ShaderBundle : IDisposable +// { +// public const int MaxNumSourceSamplers = 8; + +// readonly static string manifestFilename = "Manifest.json"; +// readonly static string fragmentFilename = "Fragment.glsl"; + +// readonly static string glslUniformSourceSamplers = "source"; + +// readonly static string glslUniformProjection = "projection"; +// readonly static string glslUniformModelview = "modelview"; +// readonly static string glslUniformTextureSize = "textureSize"; +// readonly static string glslUniformInputViewport = "inputViewport"; +// readonly static string glslUniformOutputViewport = "outputViewport"; + +// readonly static string glslVersion = "#version 300 es\n"; +// readonly static string glslESPrecision = "precision mediump float; precision mediump int;\n"; +// readonly static string glslMainStart = "void main(void){"; +// readonly static string glslMainEnd = "}\n"; + +// readonly static string defaultVertexUniforms = $"uniform mat4 {glslUniformProjection}; uniform mat4 {glslUniformModelview}; uniform vec2 {glslUniformTextureSize}; uniform vec4 {glslUniformInputViewport};\n"; +// readonly static string defaultVertexOuts = "out vec4 vertColor; out vec2 vertTexCoord;\n"; +// readonly static string defaultVertexMain = $"vertColor = inColor; gl_Position = {glslUniformProjection} * {glslUniformModelview} * vec4(inPosition.x, inPosition.y, inPosition.z, 1.0);\n"; + +// readonly static string defaultVertexTexCoord = "vertTexCoord = inTexCoord;"; + +// readonly static string defaultFragmentUniforms = $"uniform sampler2D {glslUniformSourceSamplers}[{MaxNumSourceSamplers}]; uniform vec2 {glslUniformTextureSize}; uniform vec4 {glslUniformInputViewport}; uniform vec4 {glslUniformOutputViewport};"; +// readonly static string defaultFragmentIns = "in vec4 vertColor; in vec2 vertTexCoord; layout(origin_upper_left, pixel_center_integer) in vec4 gl_FragCoord;\n"; +// readonly static string defaultFragmentOuts = "out vec4 fragColor;\n"; + +// readonly List<VertexElement> vertexElements; +// readonly int vertexStructSize; + +// readonly string vertexPreamble, vertexMain, fragmentPreamble; +// string manifestJson, fragmentMain; + +// public BundleManifest Manifest { get; private set; } + +// GLSLShader internalShader; + +// bool disposed = false; + +// public ShaderBundle(Type vertexType) +// { +// (vertexElements, vertexStructSize) = VertexBuffer.DeconstructVertexLayout(vertexType); + +// var vertexPreambleBuilder = new StringBuilder(); +// vertexPreambleBuilder.Append(glslVersion); +// vertexPreambleBuilder.Append(glslESPrecision); +// vertexPreambleBuilder.Append(defaultVertexUniforms); +// vertexPreambleBuilder.Append(VertexBuffer.GetShaderPreamble(vertexElements)); +// vertexPreambleBuilder.Append(defaultVertexOuts); +// vertexPreamble = vertexPreambleBuilder.ToString(); + +// var vertexMainBuilder = new StringBuilder(); +// vertexMainBuilder.Append(glslMainStart); +// vertexMainBuilder.Append(defaultVertexMain); +// vertexMainBuilder.Append(defaultVertexTexCoord); +// vertexMainBuilder.Append(glslMainEnd); +// vertexMain = vertexMainBuilder.ToString(); + +// var fragmentPreambleBuilder = new StringBuilder(); +// fragmentPreambleBuilder.Append(glslVersion); +// fragmentPreambleBuilder.Append(glslESPrecision); +// fragmentPreambleBuilder.Append(defaultFragmentUniforms); +// fragmentPreambleBuilder.Append(defaultFragmentIns); +// fragmentPreambleBuilder.Append(defaultFragmentOuts); +// fragmentPreamble = fragmentPreambleBuilder.ToString(); +// } + +// public ShaderBundle(string shaderName, Type vertexType) : this(vertexType) +// { +// LoadBundle(shaderName); +// } + +// ~ShaderBundle() +// { +// Dispose(false); +// } + +// public void Dispose() +// { +// Dispose(true); +// GC.SuppressFinalize(this); +// } + +// protected virtual void Dispose(bool disposing) +// { +// if (disposed) +// return; + +// if (disposing) +// internalShader.Dispose(); + +// disposed = true; +// } + +// public void LoadBundle(string shaderName) +// { +// /* Try loading embedded shader first... */ +// var shaderEmbeddedPath = $"{Application.ProductName}.Assets.Shaders.{shaderName}"; +// var shaderEmbeddedManifestFile = $"{shaderEmbeddedPath}.{manifestFilename}"; +// if (Assembly.GetExecutingAssembly().IsEmbeddedResourceAvailable(shaderEmbeddedManifestFile)) +// { +// manifestJson = Assembly.GetExecutingAssembly().ReadEmbeddedTextFile(shaderEmbeddedManifestFile); +// fragmentMain = Assembly.GetExecutingAssembly().ReadEmbeddedTextFile($"{shaderEmbeddedPath}.{fragmentFilename}"); +// } +// /* If embedded shader wasn't found, try loading from assets directory... */ +// else +// { +// var bundlePath = Path.Combine(Program.ShaderPath, shaderName); + +// if (!Directory.Exists(bundlePath)) throw new DirectoryNotFoundException($"Shader {shaderName} not found"); + +// var manifestPath = Path.Combine(bundlePath, manifestFilename); +// if (!File.Exists(manifestPath)) throw new FileNotFoundException($"Manifest {manifestFilename} not found in {bundlePath}"); + +// manifestJson = File.ReadAllText(manifestPath); + +// var fragmentPath = Path.Combine(bundlePath, fragmentFilename); +// if (!File.Exists(fragmentPath)) throw new FileNotFoundException($"Fragment shader {fragmentFilename} not found in {bundlePath}"); + +// using (var reader = new StreamReader(new FileStream(fragmentPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) +// { +// fragmentMain = reader.ReadToEnd(); +// } +// } + +// /* Now initialize GLSL shader using manifest and fragment code */ +// InitializeBundle(manifestJson, fragmentMain); +// } + +// private void InitializeBundle(string manifestJson, string fragmentMain) +// { +// Manifest = manifestJson.DeserializeObject<BundleManifest>(); +// if (Manifest.Samplers > MaxNumSourceSamplers) +// { +// // TODO: give user a warning or something? +// Manifest.Samplers = MaxNumSourceSamplers; +// } + +// internalShader = new GLSLShader(); +// internalShader.SetVertexShaderCode(vertexPreamble, vertexMain); +// internalShader.SetFragmentShaderCode(fragmentPreamble, fragmentMain); +// internalShader.LinkProgram(); + +// internalShader.SetUniformMatrix(glslUniformModelview, false, Matrix4.Identity); +// internalShader.SetUniform(glslUniformTextureSize, Vector2.One); +// internalShader.SetUniform(glslUniformInputViewport, Vector4.One); +// internalShader.SetUniform(glslUniformOutputViewport, Vector4.One); + +// for (int i = 0; i < MaxNumSourceSamplers; i++) +// internalShader.SetUniform($"{glslUniformSourceSamplers}[{i}]", i); +// } + +// public void SetProjectionMatrix(Matrix4 mat4) +// { +// internalShader.SetUniformMatrix(glslUniformProjection, false, mat4); +// } + +// public void SetModelviewMatrix(Matrix4 mat4) +// { +// internalShader.SetUniformMatrix(glslUniformModelview, false, mat4); +// } + +// public void SetTextureSize(Vector2 vec2) +// { +// internalShader.SetUniform(glslUniformTextureSize, vec2); +// } + +// public void SetInputViewport(Vector4 vec4) +// { +// internalShader.SetUniform(glslUniformInputViewport, vec4); +// } + +// public void SetOutputViewport(Vector4 vec4) +// { +// internalShader.SetUniform(glslUniformOutputViewport, vec4); +// } + +// public void Activate() +// { +// internalShader.Activate(); +// } +// } +//} diff --git a/Assets/Plugins/Essgee/Graphics/Shaders/ShaderBundle.cs.meta b/Assets/Plugins/Essgee/Graphics/Shaders/ShaderBundle.cs.meta new file mode 100644 index 0000000..8a71e13 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Shaders/ShaderBundle.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9505180589ab61c4488cd36d3aed6245 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics/Texture.cs b/Assets/Plugins/Essgee/Graphics/Texture.cs new file mode 100644 index 0000000..7072a21 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Texture.cs @@ -0,0 +1,200 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using System.Drawing; +//using System.Drawing.Imaging; +//using System.Runtime.InteropServices; + +//using OpenTK.Graphics.OpenGL; + +//using GlPixelFormat = OpenTK.Graphics.OpenGL.PixelFormat; +//using GdiPixelFormat = System.Drawing.Imaging.PixelFormat; + +//namespace Essgee.Graphics +//{ +// public class Texture : IDisposable +// { +// static readonly Dictionary<GdiPixelFormat, PixelFormat> gdiPixelFormatMap = new Dictionary<GdiPixelFormat, PixelFormat>() +// { +// { GdiPixelFormat.Format32bppArgb, PixelFormat.Rgba8888 }, +// { GdiPixelFormat.Format24bppRgb, PixelFormat.Rgb888 } +// }; + +// static readonly Dictionary<PixelFormat, (PixelInternalFormat, GlPixelFormat, int)> glPixelFormatMap = new Dictionary<PixelFormat, (PixelInternalFormat, GlPixelFormat, int)>() +// { +// { PixelFormat.Rgba8888, (PixelInternalFormat.Rgba8, GlPixelFormat.Bgra, 4) }, +// { PixelFormat.Rgb888, (PixelInternalFormat.Rgb8, GlPixelFormat.Bgr, 3) } +// }; + +// readonly static int maxTextureSize; + +// int textureHandle; + +// public int Width { get; private set; } +// public int Height { get; private set; } + +// PixelInternalFormat pixelInternalFormat; +// GlPixelFormat glPixelFormat; +// int bytesPerPixel, dataSize; +// byte[] currentData; + +// TextureMinFilter minFilter; +// TextureMagFilter magFilter; +// TextureWrapMode wrapMode; + +// bool disposed = false; + +// static Texture() +// { +// maxTextureSize = GL.GetInteger(GetPName.MaxTextureSize); +// } + +// public Texture(int width, int height, PixelFormat pixelFormat, FilterMode filter = FilterMode.Linear, WrapMode wrap = WrapMode.Repeat) +// { +// InitializeRaw(width, height, pixelFormat, filter, wrap); +// } + +// public Texture(Bitmap image, FilterMode filter = FilterMode.Linear, WrapMode wrap = WrapMode.Repeat) +// { +// if (!gdiPixelFormatMap.ContainsKey(image.PixelFormat)) +// throw new ArgumentException($"Unsupported pixel format {image.PixelFormat}", nameof(image)); + +// InitializeRaw(image.Width, image.Height, gdiPixelFormatMap[image.PixelFormat], filter, wrap); + +// var bitmapData = image.LockBits(new Rectangle(0, 0, image.Width, image.Height), ImageLockMode.ReadOnly, image.PixelFormat); +// var imageData = new byte[bitmapData.Height * bitmapData.Stride]; +// Marshal.Copy(bitmapData.Scan0, imageData, 0, imageData.Length); +// SetData(imageData); +// image.UnlockBits(bitmapData); +// } + +// ~Texture() +// { +// Dispose(false); +// } + +// public void Dispose() +// { +// Dispose(true); +// GC.SuppressFinalize(this); +// } + +// protected virtual void Dispose(bool disposing) +// { +// if (disposed) +// return; + +// if (disposing) +// { +// if (GL.IsTexture(textureHandle)) +// GL.DeleteTexture(textureHandle); +// } + +// disposed = true; +// } + +// private void InitializeRaw(int width, int height, PixelFormat pixelFormat, FilterMode filter, WrapMode wrap) +// { +// if (width <= 0 || width > maxTextureSize) throw new ArgumentOutOfRangeException(nameof(width), $"Invalid width {width}"); +// Width = width; + +// if (height <= 0 || height > maxTextureSize) throw new ArgumentOutOfRangeException(nameof(height), $"Invalid height {height}"); +// Height = height; + +// if (!glPixelFormatMap.ContainsKey(pixelFormat)) throw new ArgumentException($"Unsupported pixel format {pixelFormat}", nameof(pixelFormat)); +// (pixelInternalFormat, glPixelFormat, bytesPerPixel) = glPixelFormatMap[pixelFormat]; + +// dataSize = (width * height * bytesPerPixel); + +// switch (filter) +// { +// case FilterMode.Linear: +// minFilter = TextureMinFilter.Linear; +// magFilter = TextureMagFilter.Linear; +// break; + +// case FilterMode.Nearest: +// minFilter = TextureMinFilter.Nearest; +// magFilter = TextureMagFilter.Nearest; +// break; + +// default: +// throw new ArgumentException("Invalid filter mode", nameof(filter)); +// } + +// switch (wrap) +// { +// case WrapMode.Repeat: wrapMode = TextureWrapMode.Repeat; break; +// case WrapMode.Border: wrapMode = TextureWrapMode.ClampToBorder; break; +// case WrapMode.Edge: wrapMode = TextureWrapMode.ClampToEdge; break; +// case WrapMode.Mirror: wrapMode = TextureWrapMode.MirroredRepeat; break; +// default: throw new ArgumentException("Invalid wrap mode", nameof(wrap)); +// } + +// GenerateHandles(); +// InitializeTexture(); +// } + +// private void GenerateHandles() +// { +// textureHandle = GL.GenTexture(); +// } + +// private void InitializeTexture() +// { +// GL.BindTexture(TextureTarget.Texture2D, textureHandle); +// GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)minFilter); +// GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)magFilter); +// GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)wrapMode); +// GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)wrapMode); +// if (bytesPerPixel != 4) GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1); +// GL.TexImage2D(TextureTarget.Texture2D, 0, pixelInternalFormat, Width, Height, 0, glPixelFormat, PixelType.UnsignedByte, IntPtr.Zero); +// GL.BindTexture(TextureTarget.Texture2D, 0); +// } + +// public void SetData(byte[] data) +// { +// if (data == null) throw new ArgumentNullException(nameof(data), "Image data is null"); +// if (data.Length != dataSize) throw new ArgumentException($"Image data size mismatch; excepted {dataSize} bytes, got {data.Length} bytes", nameof(data)); + +// GL.BindTexture(TextureTarget.Texture2D, textureHandle); +// GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, Width, Height, glPixelFormat, PixelType.UnsignedByte, (currentData = data)); +// } + +// public byte[] GetData() +// { +// return currentData; +// } + +// public void ClearData() +// { +// var emptyData = new byte[dataSize]; +// SetData(emptyData); +// } + +// public void Activate() +// { +// Activate(TextureUnit.Texture0); +// } + +// public void Activate(TextureUnit textureUnit) +// { +// if (textureHandle == -1) throw new InvalidOperationException("Invalid texture handle"); +// GL.ActiveTexture(textureUnit); +// GL.BindTexture(TextureTarget.Texture2D, textureHandle); +// } + +// public void Deactivate() +// { +// Deactivate(TextureUnit.Texture0); +// } + +// public void Deactivate(TextureUnit textureUnit) +// { +// GL.ActiveTexture(textureUnit); +// GL.BindTexture(TextureTarget.Texture2D, 0); +// } +// } +//} diff --git a/Assets/Plugins/Essgee/Graphics/Texture.cs.meta b/Assets/Plugins/Essgee/Graphics/Texture.cs.meta new file mode 100644 index 0000000..c5778e8 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/Texture.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 27df8906b58443d4ab2ca4181c41616e \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics/VertexBuffer.cs b/Assets/Plugins/Essgee/Graphics/VertexBuffer.cs new file mode 100644 index 0000000..b6c4f6e --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/VertexBuffer.cs @@ -0,0 +1,276 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using System.Runtime.InteropServices; +//using System.Reflection; + +//using OpenTK; +//using OpenTK.Graphics; +//using OpenTK.Graphics.OpenGL; + +//using Essgee.Exceptions; + +//namespace Essgee.Graphics +//{ +// public class VertexBuffer : IDisposable +// { +// static readonly Dictionary<Type, VertexAttribPointerType> pointerTypeTranslator = new Dictionary<Type, VertexAttribPointerType>() +// { +// { typeof(byte), VertexAttribPointerType.UnsignedByte }, +// { typeof(sbyte), VertexAttribPointerType.Byte }, +// { typeof(ushort), VertexAttribPointerType.UnsignedShort }, +// { typeof(short), VertexAttribPointerType.Short }, +// { typeof(uint), VertexAttribPointerType.UnsignedInt }, +// { typeof(int), VertexAttribPointerType.Int }, +// { typeof(float), VertexAttribPointerType.Float }, +// { typeof(double), VertexAttribPointerType.Double }, +// { typeof(Vector2), VertexAttribPointerType.Float }, +// { typeof(Vector3), VertexAttribPointerType.Float }, +// { typeof(Vector4), VertexAttribPointerType.Float }, +// { typeof(Color4), VertexAttribPointerType.Float } +// }; + +// static readonly Dictionary<Type, string> glslTypeTranslator = new Dictionary<Type, string>() +// { +// { typeof(uint), "uint" }, +// { typeof(int), "int" }, +// { typeof(float), "float" }, +// { typeof(double), "double" }, +// { typeof(Vector2), "vec2" }, +// { typeof(Vector3), "vec3" }, +// { typeof(Vector4), "vec4" }, +// { typeof(Color4), "vec4" } +// }; + +// static readonly Dictionary<Type, DrawElementsType> drawElementsTypeTranslator = new Dictionary<Type, DrawElementsType>() +// { +// { typeof(byte), DrawElementsType.UnsignedByte }, +// { typeof(ushort), DrawElementsType.UnsignedShort }, +// { typeof(uint), DrawElementsType.UnsignedInt } +// }; + +// readonly int vaoHandle, vboHandle; +// int numElementsToDraw; + +// PrimitiveType primitiveType; + +// int elementBufferHandle; +// DrawElementsType drawElementsType; + +// List<VertexElement> vertexElements; +// int vertexStructSize; + +// bool disposed = false; + +// public VertexBuffer() +// { +// vaoHandle = GL.GenVertexArray(); +// vboHandle = GL.GenBuffer(); +// numElementsToDraw = -1; + +// primitiveType = PrimitiveType.Triangles; + +// elementBufferHandle = -1; +// drawElementsType = DrawElementsType.UnsignedByte; + +// vertexElements = null; +// vertexStructSize = -1; +// } + +// ~VertexBuffer() +// { +// Dispose(false); +// } + +// public void Dispose() +// { +// Dispose(true); +// GC.SuppressFinalize(this); +// } + +// protected virtual void Dispose(bool disposing) +// { +// if (disposed) +// return; + +// if (disposing) +// { +// if (GL.IsVertexArray(vaoHandle)) +// GL.DeleteVertexArray(vaoHandle); + +// if (GL.IsBuffer(vboHandle)) +// GL.DeleteBuffer(vboHandle); + +// if (GL.IsBuffer(elementBufferHandle)) +// GL.DeleteBuffer(elementBufferHandle); +// } + +// disposed = true; +// } + +// public static (List<VertexElement>, int) DeconstructVertexLayout<T>() where T : struct, IVertexStruct +// { +// return DeconstructVertexLayout(typeof(T)); +// } + +// public static (List<VertexElement>, int) DeconstructVertexLayout(Type vertexType) +// { +// if (!typeof(IVertexStruct).IsAssignableFrom(vertexType)) throw new Exceptions.GraphicsException("Cannot deconstruct layout of non-vertex type"); + +// var elements = new List<VertexElement>(); +// var structSize = Marshal.SizeOf(vertexType); + +// foreach (var field in vertexType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) +// { +// var attribs = field.GetCustomAttributes(typeof(VertexElementAttribute), false); +// if (attribs == null || attribs.Length != 1) continue; + +// var elementAttribute = (attribs[0] as VertexElementAttribute); + +// var numComponents = Marshal.SizeOf(field.FieldType); + +// if (field.FieldType.IsValueType && !field.FieldType.IsEnum) +// { +// var structFields = field.FieldType.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); +// if (structFields == null || structFields.Length < 1 || structFields.Length > 4) throw new Exceptions.GraphicsException("Invalid number of fields in struct"); +// numComponents = structFields.Length; +// } + +// elements.Add(new VertexElement() +// { +// AttributeIndex = elementAttribute.AttributeIndex, +// DataType = field.FieldType, +// NumComponents = numComponents, +// OffsetInVertex = Marshal.OffsetOf(vertexType, field.Name).ToInt32(), +// Name = field.Name +// }); +// } + +// return (elements, structSize); +// } + +// public void SetPrimitiveType(PrimitiveType primType) +// { +// primitiveType = primType; +// } + +// public PrimitiveType GetPrimitiveType() +// { +// return primitiveType; +// } + +// public void SetVertexData<TVertex>(TVertex[] vertices) where TVertex : struct, IVertexStruct +// { +// (vertexElements, vertexStructSize) = DeconstructVertexLayout<TVertex>(); + +// GL.BindVertexArray(vaoHandle); + +// GL.BindBuffer(BufferTarget.ArrayBuffer, vboHandle); +// GL.BufferData(BufferTarget.ArrayBuffer, new IntPtr(vertexStructSize * vertices.Length), vertices, BufferUsageHint.StaticDraw); + +// foreach (var element in vertexElements) +// { +// GL.EnableVertexAttribArray(element.AttributeIndex); +// GL.VertexAttribPointer(element.AttributeIndex, element.NumComponents, GetVertexAttribPointerType(element.DataType), false, vertexStructSize, element.OffsetInVertex); +// } + +// numElementsToDraw = vertices.Length; + +// GL.BindVertexArray(0); +// } + +// public void SetIndices<TIndex>(TIndex[] indices) where TIndex : struct, IConvertible +// { +// drawElementsType = GetDrawElementsType(typeof(TIndex)); + +// if (elementBufferHandle == -1) +// elementBufferHandle = GL.GenBuffer(); + +// GL.BindBuffer(BufferTarget.ElementArrayBuffer, elementBufferHandle); +// GL.BufferData(BufferTarget.ElementArrayBuffer, new IntPtr(Marshal.SizeOf(typeof(TIndex)) * indices.Length), indices, BufferUsageHint.StaticDraw); + +// numElementsToDraw = indices.Length; +// } + +// private VertexAttribPointerType GetVertexAttribPointerType(Type type) +// { +// if (pointerTypeTranslator.ContainsKey(type)) +// return pointerTypeTranslator[type]; +// else +// throw new ArgumentException("Unimplemented or unsupported vertex attribute pointer type"); +// } + +// private DrawElementsType GetDrawElementsType(Type type) +// { +// if (drawElementsTypeTranslator.ContainsKey(type)) +// return drawElementsTypeTranslator[type]; +// else +// throw new ArgumentException("Unsupported draw elements type"); +// } + +// private static string GetGlslDataType(Type type) +// { +// if (glslTypeTranslator.ContainsKey(type)) +// return glslTypeTranslator[type]; +// else +// throw new ArgumentException("Unimplemented or unsupported GLSL data type"); +// } + +// public string GetShaderPreamble(string prefix = "in") +// { +// return GetShaderPreamble(vertexElements, prefix); +// } + +// public static string GetShaderPreamble(List<VertexElement> vertexElements, string prefix = "in") +// { +// var stringBuilder = new StringBuilder(); +// for (int i = 0; i < vertexElements.Count; i++) +// { +// var element = vertexElements[i]; +// stringBuilder.AppendLine($"layout(location = {element.AttributeIndex}) in {GetGlslDataType(element.DataType)} {prefix}{element.Name};"); +// } +// return stringBuilder.ToString(); +// } + +// public void Render() +// { +// GL.BindVertexArray(vaoHandle); + +// if (elementBufferHandle != -1) +// { +// GL.BindBuffer(BufferTarget.ElementArrayBuffer, elementBufferHandle); +// GL.DrawElements(primitiveType, numElementsToDraw, drawElementsType, 0); +// } +// else +// GL.DrawArrays(primitiveType, 0, numElementsToDraw); +// } +// } + +// public interface IVertexStruct { } + +// [AttributeUsage(AttributeTargets.Field, AllowMultiple = false, Inherited = false)] +// public class VertexElementAttribute : Attribute +// { +// public int AttributeIndex { get; set; } + +// public VertexElementAttribute() +// { +// AttributeIndex = -1; +// } +// } + +// [StructLayout(LayoutKind.Sequential, Pack = 1)] +// public struct CommonVertex : IVertexStruct +// { +// [VertexElement(AttributeIndex = 0)] +// public Vector3 Position; +// [VertexElement(AttributeIndex = 1)] +// public Vector3 Normal; +// [VertexElement(AttributeIndex = 2)] +// public Color4 Color; +// [VertexElement(AttributeIndex = 3)] +// public Vector2 TexCoord; +// } +//} diff --git a/Assets/Plugins/Essgee/Graphics/VertexBuffer.cs.meta b/Assets/Plugins/Essgee/Graphics/VertexBuffer.cs.meta new file mode 100644 index 0000000..bffd985 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/VertexBuffer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f6abd7f4ed8a2dc4a8950cb920e39bda \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Graphics/VertexElement.cs b/Assets/Plugins/Essgee/Graphics/VertexElement.cs new file mode 100644 index 0000000..3d7a1d6 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/VertexElement.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Graphics +{ + public sealed class VertexElement + { + public int AttributeIndex { get; internal set; } + public Type DataType { get; internal set; } + public int NumComponents { get; internal set; } + public int OffsetInVertex { get; internal set; } + public string Name { get; internal set; } + + public VertexElement() + { + AttributeIndex = -1; + DataType = null; + NumComponents = -1; + OffsetInVertex = -1; + Name = string.Empty; + } + } +} diff --git a/Assets/Plugins/Essgee/Graphics/VertexElement.cs.meta b/Assets/Plugins/Essgee/Graphics/VertexElement.cs.meta new file mode 100644 index 0000000..04469e5 --- /dev/null +++ b/Assets/Plugins/Essgee/Graphics/VertexElement.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2e3f41f8d2cd2534a8906786dbe34d25 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Keys.cs b/Assets/Plugins/Essgee/Keys.cs new file mode 100644 index 0000000..8e10ef0 --- /dev/null +++ b/Assets/Plugins/Essgee/Keys.cs @@ -0,0 +1,216 @@ +using System.Runtime.InteropServices; +using System; +using UnityEngine; +using System.ComponentModel; + +[Flags] +[ComVisible(true)] +public enum Keys +{ + KeyCode = 0xFFFF, + Modifiers = -65536, + None = 0, + LButton = 1, + RButton = 2, + Cancel = 3, + MButton = 4, + XButton1 = 5, + XButton2 = 6, + Back = 8, + Tab = 9, + LineFeed = 0xA, + Clear = 0xC, + Return = 0xD, + Enter = 0xD, + ShiftKey = 0x10, + ControlKey = 0x11, + Menu = 0x12, + Pause = 0x13, + Capital = 0x14, + CapsLock = 0x14, + KanaMode = 0x15, + HanguelMode = 0x15, + HangulMode = 0x15, + JunjaMode = 0x17, + FinalMode = 0x18, + HanjaMode = 0x19, + KanjiMode = 0x19, + Escape = 0x1B, + IMEConvert = 0x1C, + IMENonconvert = 0x1D, + IMEAccept = 0x1E, + IMEAceept = 0x1E, + IMEModeChange = 0x1F, + Space = 0x20, + Prior = 0x21, + PageUp = 0x21, + Next = 0x22, + PageDown = 0x22, + End = 0x23, + Home = 0x24, + Left = 0x25, + Up = 0x26, + Right = 0x27, + Down = 0x28, + Select = 0x29, + Print = 0x2A, + Execute = 0x2B, + Snapshot = 0x2C, + PrintScreen = 0x2C, + Insert = 0x2D, + Delete = 0x2E, + Help = 0x2F, + D0 = 0x30, + D1 = 0x31, + D2 = 0x32, + D3 = 0x33, + D4 = 0x34, + D5 = 0x35, + D6 = 0x36, + D7 = 0x37, + D8 = 0x38, + D9 = 0x39, + A = 0x41, + B = 0x42, + C = 0x43, + D = 0x44, + E = 0x45, + F = 0x46, + G = 0x47, + H = 0x48, + I = 0x49, + J = 0x4A, + K = 0x4B, + L = 0x4C, + M = 0x4D, + N = 0x4E, + O = 0x4F, + P = 0x50, + Q = 0x51, + R = 0x52, + S = 0x53, + T = 0x54, + U = 0x55, + V = 0x56, + W = 0x57, + X = 0x58, + Y = 0x59, + Z = 0x5A, + LWin = 0x5B, + RWin = 0x5C, + Apps = 0x5D, + Sleep = 0x5F, + NumPad0 = 0x60, + NumPad1 = 0x61, + NumPad2 = 0x62, + NumPad3 = 0x63, + NumPad4 = 0x64, + NumPad5 = 0x65, + NumPad6 = 0x66, + NumPad7 = 0x67, + NumPad8 = 0x68, + NumPad9 = 0x69, + Multiply = 0x6A, + Add = 0x6B, + Separator = 0x6C, + Subtract = 0x6D, + Decimal = 0x6E, + Divide = 0x6F, + F1 = 0x70, + F2 = 0x71, + F3 = 0x72, + F4 = 0x73, + F5 = 0x74, + F6 = 0x75, + F7 = 0x76, + F8 = 0x77, + F9 = 0x78, + F10 = 0x79, + F11 = 0x7A, + F12 = 0x7B, + F13 = 0x7C, + F14 = 0x7D, + F15 = 0x7E, + F16 = 0x7F, + F17 = 0x80, + F18 = 0x81, + F19 = 0x82, + F20 = 0x83, + F21 = 0x84, + F22 = 0x85, + F23 = 0x86, + F24 = 0x87, + NumLock = 0x90, + Scroll = 0x91, + LShiftKey = 0xA0, + RShiftKey = 0xA1, + LControlKey = 0xA2, + RControlKey = 0xA3, + LMenu = 0xA4, + RMenu = 0xA5, + BrowserBack = 0xA6, + BrowserForward = 0xA7, + BrowserRefresh = 0xA8, + BrowserStop = 0xA9, + BrowserSearch = 0xAA, + BrowserFavorites = 0xAB, + BrowserHome = 0xAC, + VolumeMute = 0xAD, + VolumeDown = 0xAE, + VolumeUp = 0xAF, + MediaNextTrack = 0xB0, + MediaPreviousTrack = 0xB1, + MediaStop = 0xB2, + MediaPlayPause = 0xB3, + LaunchMail = 0xB4, + SelectMedia = 0xB5, + LaunchApplication1 = 0xB6, + LaunchApplication2 = 0xB7, + OemSemicolon = 0xBA, + Oem1 = 0xBA, + Oemplus = 0xBB, + Oemcomma = 0xBC, + OemMinus = 0xBD, + OemPeriod = 0xBE, + OemQuestion = 0xBF, + Oem2 = 0xBF, + Oemtilde = 0xC0, + Oem3 = 0xC0, + OemOpenBrackets = 0xDB, + Oem4 = 0xDB, + OemPipe = 0xDC, + Oem5 = 0xDC, + OemCloseBrackets = 0xDD, + Oem6 = 0xDD, + OemQuotes = 0xDE, + Oem7 = 0xDE, + Oem8 = 0xDF, + OemBackslash = 0xE2, + Oem102 = 0xE2, + ProcessKey = 0xE5, + Packet = 0xE7, + Attn = 0xF6, + Crsel = 0xF7, + Exsel = 0xF8, + EraseEof = 0xF9, + Play = 0xFA, + Zoom = 0xFB, + NoName = 0xFC, + Pa1 = 0xFD, + OemClear = 0xFE, + Shift = 0x10000, + Control = 0x20000, + Alt = 0x40000 +} + +[Flags] +[ComVisible(true)] +public enum MouseButtons +{ + Left = 0x100000, + None = 0, + Right = 0x200000, + Middle = 0x400000, + XButton1 = 0x800000, + XButton2 = 0x1000000 +} \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Keys.cs.meta b/Assets/Plugins/Essgee/Keys.cs.meta new file mode 100644 index 0000000..e6bc2dc --- /dev/null +++ b/Assets/Plugins/Essgee/Keys.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c8194d726152edf4aa339a393f801f58 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Metadata.meta b/Assets/Plugins/Essgee/Metadata.meta new file mode 100644 index 0000000..3dedfe3 --- /dev/null +++ b/Assets/Plugins/Essgee/Metadata.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5b91d039db189d445a206d1852ae838a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Metadata/GameMetadata.cs b/Assets/Plugins/Essgee/Metadata/GameMetadata.cs new file mode 100644 index 0000000..e44a316 --- /dev/null +++ b/Assets/Plugins/Essgee/Metadata/GameMetadata.cs @@ -0,0 +1,21 @@ +using System; + +using Essgee.Emulation; + +namespace Essgee.Metadata +{ + public class GameMetadata + { + public string FileName { get; set; } = string.Empty; + public string KnownName { get; set; } = string.Empty; + public string Notes { get; set; } = string.Empty; + public uint RomCrc32 { get; set; } = 0xFFFFFFFF; + public int RomSize { get; set; } = 0; + public int RamSize { get; set; } = 0; + public Type MapperType { get; set; } = null; + public bool HasNonVolatileRam { get; set; } = false; + public TVStandard PreferredTVStandard { get; set; } = TVStandard.Auto; + public Region PreferredRegion { get; set; } = Region.Auto; + public bool AllowMemoryControl { get; set; } = true; + } +} diff --git a/Assets/Plugins/Essgee/Metadata/GameMetadata.cs.meta b/Assets/Plugins/Essgee/Metadata/GameMetadata.cs.meta new file mode 100644 index 0000000..2a11bd7 --- /dev/null +++ b/Assets/Plugins/Essgee/Metadata/GameMetadata.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b2847d634a727684b8739d5ea9e6ee73 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Metadata/GameMetadataHandler.cs b/Assets/Plugins/Essgee/Metadata/GameMetadataHandler.cs new file mode 100644 index 0000000..ce16cb2 --- /dev/null +++ b/Assets/Plugins/Essgee/Metadata/GameMetadataHandler.cs @@ -0,0 +1,280 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.IO; +using System.Xml.Serialization; +using System.ComponentModel; + +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +using Essgee.Emulation; +using Essgee.Exceptions; +using Essgee.Extensions; +using Essgee.Graphics; +using Essgee.Utilities; + +namespace Essgee.Metadata +{ + public class GameMetadataHandler + { + readonly static string datDirectoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "No-Intro"); + readonly static string metadataDatabaseFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "MetadataDatabase.json"); + + readonly Dictionary<string, DatFile> datFiles; + readonly List<CartridgeJSON> cartMetadataDatabase; + + + public int NumKnownSystems { get { return datFiles.Count; } } + public int NumKnownGames { get { return datFiles.Sum(x => x.Value.Game.Count()); } } + + public GameMetadataHandler() + { + + XmlRootAttribute root; + XmlSerializer serializer; + + /* Read No-Intro .dat files */ + datFiles = new Dictionary<string, DatFile>(); + foreach (var file in Directory.EnumerateFiles(datDirectoryPath, "*.dat")) + { + root = new XmlRootAttribute("datafile") { IsNullable = true }; + serializer = new XmlSerializer(typeof(DatFile), root); + using (FileStream stream = new FileStream(Path.Combine(datDirectoryPath, file), FileMode.Open)) + { + datFiles.Add(Path.GetFileName(file), (DatFile)serializer.Deserialize(stream)); + } + } + + /* Read cartridge metadata database */ + cartMetadataDatabase = metadataDatabaseFilePath.DeserializeFromFile<List<CartridgeJSON>>(); + + EssgeeLogger.EnqueueMessageSuccess($"Metadata initialized; {NumKnownGames} game(s) known across {NumKnownSystems} system(s)."); + } + + public GameMetadata GetGameMetadata(string datFilename, string romFilename, uint romCrc32, int romSize) + { + /* Sanity checks */ + if (!datFiles.ContainsKey(datFilename)) throw new HandlerException("Requested .dat file not found"); + + /* Get information from No-Intro .dat */ + var datFile = datFiles[datFilename]; + var crcString = string.Format("{0:X8}", romCrc32); + var sizeString = string.Format("{0:D}", romSize); + var gameInfo = datFile.Game.FirstOrDefault(x => x.Rom.Any(y => y.Crc == crcString && y.Size == sizeString)); + + /* Get information from cartridge metadata database */ + var cartridgeInfo = cartMetadataDatabase.FirstOrDefault(x => x.Crc32 == romCrc32 && x.RomSize == romSize); + + /* Create game metadata */ + var gameMetadata = new GameMetadata() + { + FileName = Path.GetFileName(romFilename), + KnownName = gameInfo?.Name, + RomCrc32 = romCrc32, + RomSize = romSize + }; + + if (cartridgeInfo != null) + { + if (gameMetadata.KnownName == null) + gameMetadata.KnownName = cartridgeInfo.Name; + + gameMetadata.Notes = cartridgeInfo.Notes; + gameMetadata.RamSize = cartridgeInfo.RamSize; + gameMetadata.MapperType = cartridgeInfo.Mapper; + gameMetadata.HasNonVolatileRam = cartridgeInfo.HasNonVolatileRam; + gameMetadata.PreferredTVStandard = cartridgeInfo.PreferredTVStandard; + gameMetadata.PreferredRegion = cartridgeInfo.PreferredRegion; + gameMetadata.AllowMemoryControl = cartridgeInfo.AllowMemoryControl; + } + + if (gameMetadata.KnownName == null) + gameMetadata.KnownName = "unrecognized game"; + + return gameMetadata; + } + + public class CartridgeJSON + { + [JsonProperty(Required = Required.Always)] + public string Name { get; set; } = string.Empty; + + [JsonProperty(Required = Required.Always)] + public string Notes { get; set; } = string.Empty; + + [JsonProperty(Required = Required.Always), JsonConverter(typeof(HexadecimalJsonConverter))] + public uint Crc32 { get; set; } = 0xFFFFFFFF; + + [JsonProperty(Required = Required.Always)] + public int RomSize { get; set; } = 0; + + [JsonProperty(Required = Required.Default), DefaultValue(0)] + public int RamSize { get; set; } = 0; + + [JsonProperty(Required = Required.Default), JsonConverter(typeof(TypeNameJsonConverter), "Essgee.Emulation.Cartridges"), DefaultValue(null)] + public Type Mapper { get; set; } = null; + + [JsonProperty(Required = Required.Default), DefaultValue(false)] + public bool HasNonVolatileRam { get; set; } = false; + + [JsonProperty(Required = Required.Default), JsonConverter(typeof(StringEnumConverter)), DefaultValue(TVStandard.Auto)] + public TVStandard PreferredTVStandard { get; set; } = TVStandard.Auto; + + [JsonProperty(Required = Required.Default), JsonConverter(typeof(StringEnumConverter)), DefaultValue(Region.Auto)] + public Region PreferredRegion { get; set; } = Region.Auto; + + [JsonProperty(Required = Required.Default), DefaultValue(true)] + public bool AllowMemoryControl { get; set; } = true; + } + + public class DatHeader + { + [XmlElement("name")] + public string Name { get; set; } + [XmlElement("description")] + public string Description { get; set; } + [XmlElement("category")] + public string Category { get; set; } + [XmlElement("version")] + public string Version { get; set; } + [XmlElement("date")] + public string Date { get; set; } + [XmlElement("author")] + public string Author { get; set; } + [XmlElement("email")] + public string Email { get; set; } + [XmlElement("homepage")] + public string Homepage { get; set; } + [XmlElement("url")] + public string Url { get; set; } + [XmlElement("comment")] + public string Comment { get; set; } + } + + public class DatRelease + { + [XmlAttribute("name")] + public string Name { get; set; } + [XmlAttribute("region")] + public string Region { get; set; } + [XmlAttribute("language")] + public string Language { get; set; } + [XmlAttribute("date")] + public string Date { get; set; } + [XmlAttribute("default")] + public string Default { get; set; } + } + + public class DatBiosSet + { + [XmlAttribute("name")] + public string Name { get; set; } + [XmlAttribute("description")] + public string Description { get; set; } + [XmlAttribute("default")] + public string Default { get; set; } + } + + public class DatRom + { + [XmlAttribute("name")] + public string Name { get; set; } + [XmlAttribute("size")] + public string Size { get; set; } + [XmlAttribute("crc")] + public string Crc { get; set; } + [XmlAttribute("sha1")] + public string Sha1 { get; set; } + [XmlAttribute("md5")] + public string Md5 { get; set; } + [XmlAttribute("merge")] + public string Merge { get; set; } + [XmlAttribute("status")] + public string Status { get; set; } + [XmlAttribute("date")] + public string Date { get; set; } + } + + public class DatDisk + { + [XmlAttribute("name")] + public string Name { get; set; } + [XmlAttribute("sha1")] + public string Sha1 { get; set; } + [XmlAttribute("md5")] + public string Md5 { get; set; } + [XmlAttribute("merge")] + public string Merge { get; set; } + [XmlAttribute("status")] + public string Status { get; set; } + } + + public class DatSample + { + [XmlAttribute("name")] + public string Name { get; set; } + } + + public class DatArchive + { + [XmlAttribute("name")] + public string Name { get; set; } + } + + public class DatGame + { + [XmlAttribute("name")] + public string Name { get; set; } + [XmlAttribute("sourcefile")] + public string SourceFile { get; set; } + [XmlAttribute("isbios")] + public string IsBios { get; set; } + [XmlAttribute("cloneof")] + public string CloneOf { get; set; } + [XmlAttribute("romof")] + public string RomOf { get; set; } + [XmlAttribute("sampleof")] + public string SampleOf { get; set; } + [XmlAttribute("board")] + public string Board { get; set; } + [XmlAttribute("rebuildto")] + public string RebuildTo { get; set; } + + [XmlElement("year")] + public string Year { get; set; } + [XmlElement("manufacturer")] + public string Manufacturer { get; set; } + + [XmlElement("release")] + public DatRelease[] Release { get; set; } + + [XmlElement("biosset")] + public DatBiosSet[] BiosSet { get; set; } + + [XmlElement("rom")] + public DatRom[] Rom { get; set; } + + [XmlElement("disk")] + public DatDisk[] Disk { get; set; } + + [XmlElement("sample")] + public DatSample[] Sample { get; set; } + + [XmlElement("archive")] + public DatArchive[] Archive { get; set; } + } + + [Serializable()] + public class DatFile + { + [XmlElement("header")] + public DatHeader Header { get; set; } + + [XmlElement("game")] + public DatGame[] Game { get; set; } + } + } +} diff --git a/Assets/Plugins/Essgee/Metadata/GameMetadataHandler.cs.meta b/Assets/Plugins/Essgee/Metadata/GameMetadataHandler.cs.meta new file mode 100644 index 0000000..ae67271 --- /dev/null +++ b/Assets/Plugins/Essgee/Metadata/GameMetadataHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5a21d7f6de7eac540bfe506b411d4ef8 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/StandInfo.cs b/Assets/Plugins/Essgee/StandInfo.cs new file mode 100644 index 0000000..767b9ac --- /dev/null +++ b/Assets/Plugins/Essgee/StandInfo.cs @@ -0,0 +1,40 @@ + +using Essgee; +using System; +using System.IO; + +public static class StandInfo +{ + const string jsonConfigFileName = "Config.json"; + const string saveDataDirectoryName = "Saves"; + const string screenshotDirectoryName = "Screenshots"; + const string saveStateDirectoryName = "Savestates"; + const string extraDataDirectoryName = "Extras"; + static string ProductName = ""; + + readonly static string programDataDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), ProductName); + readonly static string programConfigPath = Path.Combine(programDataDirectory, jsonConfigFileName); + + public static Configuration Configuration { get; set; } + + public static string ShaderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "Shaders"); + public static string SaveDataPath = Path.Combine(programDataDirectory, saveDataDirectoryName); + public static string ScreenshotPath = Path.Combine(programDataDirectory, screenshotDirectoryName); + public static string SaveStatePath = Path.Combine(programDataDirectory, saveStateDirectoryName); + public static string ExtraDataPath = Path.Combine(programDataDirectory, extraDataDirectoryName); + + static Random mRandom; + public static Random Random + { + get + { + if (mRandom == null) + { + mRandom = new Random(); + } + return mRandom; + } + } + + public static string ProductVersion { get; internal set; } +} \ No newline at end of file diff --git a/Assets/Plugins/Essgee/StandInfo.cs.meta b/Assets/Plugins/Essgee/StandInfo.cs.meta new file mode 100644 index 0000000..c4e673b --- /dev/null +++ b/Assets/Plugins/Essgee/StandInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 96771057db04a714b94d5bc96a516946 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities.meta b/Assets/Plugins/Essgee/Utilities.meta new file mode 100644 index 0000000..5748152 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: efbb7663b0390d544a2f62d060d8441d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Utilities/AltKeyFilter.cs b/Assets/Plugins/Essgee/Utilities/AltKeyFilter.cs new file mode 100644 index 0000000..2429c1b --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/AltKeyFilter.cs @@ -0,0 +1,10 @@ +//namespace Essgee.Utilities +//{ +// class AltKeyFilter : IMessageFilter +// { +// public bool PreFilterMessage(ref Message m) +// { +// return (m.Msg == 0x0104 && ((int)m.LParam & 0x20000000) != 0); +// } +// } +//} diff --git a/Assets/Plugins/Essgee/Utilities/AltKeyFilter.cs.meta b/Assets/Plugins/Essgee/Utilities/AltKeyFilter.cs.meta new file mode 100644 index 0000000..4f2957a --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/AltKeyFilter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0857ccfa13cf054469f9f76a26a93de9 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/BindableToolStripMenuItem.cs b/Assets/Plugins/Essgee/Utilities/BindableToolStripMenuItem.cs new file mode 100644 index 0000000..cdac512 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/BindableToolStripMenuItem.cs @@ -0,0 +1,48 @@ +//using System; +//using System.Windows.Forms; +//using System.Drawing; +//using System.Diagnostics.CodeAnalysis; + +//namespace Essgee.Utilities +//{ +// [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", +// Justification = "False positive. IDisposable is inherited via IFunctionality. See http://stackoverflow.com/questions/8925925/code-analysis-ca1063-fires-when-deriving-from-idisposable-and-providing-implemen for details.")] +// public class BindableToolStripMenuItem : ToolStripMenuItem, IBindableComponent +// { +// public BindableToolStripMenuItem() : base() { } +// public BindableToolStripMenuItem(string text) : base(text) { } +// public BindableToolStripMenuItem(Image image) : base(image) { } +// public BindableToolStripMenuItem(string text, Image image) : base(text, image) { } +// public BindableToolStripMenuItem(string text, Image image, EventHandler onClick) : base(text, image, onClick) { } +// public BindableToolStripMenuItem(string text, Image image, params ToolStripMenuItem[] dropDownItems) : base(text, image, dropDownItems) { } +// public BindableToolStripMenuItem(string text, Image image, EventHandler onClick, Keys shortcutKeys) : base(text, image, onClick, shortcutKeys) { } +// public BindableToolStripMenuItem(string text, Image image, EventHandler onClick, string name) : base(text, image, onClick, name) { } + +// BindingContext bindingContext; +// ControlBindingsCollection dataBindings; + +// public BindingContext BindingContext +// { +// get +// { +// if (bindingContext == null) +// bindingContext = new BindingContext(); +// return bindingContext; +// } +// set +// { +// bindingContext = value; +// } +// } + +// public ControlBindingsCollection DataBindings +// { +// get +// { +// if (dataBindings == null) +// dataBindings = new ControlBindingsCollection(this); +// return dataBindings; +// } +// } +// } +//} diff --git a/Assets/Plugins/Essgee/Utilities/BindableToolStripMenuItem.cs.meta b/Assets/Plugins/Essgee/Utilities/BindableToolStripMenuItem.cs.meta new file mode 100644 index 0000000..ce5bf9a --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/BindableToolStripMenuItem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bd5e5aa99d4a1b642b4f6c18a4a29988 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/Crc32.cs b/Assets/Plugins/Essgee/Utilities/Crc32.cs new file mode 100644 index 0000000..ae1110a --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/Crc32.cs @@ -0,0 +1,98 @@ +using System; +using System.IO; + +namespace Essgee.Utilities +{ + public static class Crc32 + { + static readonly uint[] crcTable; + static readonly uint crcPolynomial = 0xEDB88320; + static readonly uint crcSeed = 0xFFFFFFFF; + + static Crc32() + { + crcTable = new uint[256]; + + for (int i = 0; i < 256; i++) + { + uint entry = (uint)i; + for (int j = 0; j < 8; j++) + { + if ((entry & 0x00000001) == 0x00000001) + entry = (entry >> 1) ^ crcPolynomial; + else + entry = (entry >> 1); + } + crcTable[i] = entry; + } + } + + private static void VerifyStartAndLength(int dataLength, int segmentStart, int segmentLength) + { + if (segmentStart >= dataLength) throw new Crc32Exception("Segment start offset is greater than total length"); + if (segmentLength > dataLength) throw new Crc32Exception("Segment length is greater than total length"); + if ((segmentStart + segmentLength) > dataLength) throw new Crc32Exception("Segment end offset is greater than total length"); + } + + public static uint Calculate(FileInfo fileInfo) + { + return Calculate(fileInfo, 0, (int)fileInfo.Length); + } + + public static uint Calculate(FileInfo fileInfo, int start, int length) + { + VerifyStartAndLength((int)fileInfo.Length, start, length); + + using (FileStream file = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + return Calculate(file, start, length); + } + } + + public static uint Calculate(Stream stream) + { + return Calculate(stream, 0, (int)stream.Length); + } + + public static uint Calculate(Stream stream, int start, int length) + { + VerifyStartAndLength((int)stream.Length, start, length); + + uint crc = 0; + + var lastStreamPosition = stream.Position; + + byte[] data = new byte[length]; + stream.Position = start; + stream.Read(data, 0, length); + crc = Calculate(data, 0, data.Length); + stream.Position = lastStreamPosition; + + return crc; + } + + public static uint Calculate(byte[] data) + { + return Calculate(data, 0, data.Length); + } + + public static uint Calculate(byte[] data, int start, int length) + { + VerifyStartAndLength(data.Length, start, length); + + uint crc = crcSeed; + for (int i = start; i < (start + length); i++) + crc = ((crc >> 8) ^ crcTable[data[i] ^ (crc & 0x000000FF)]); + return ~crc; + } + } + + [Serializable] + public class Crc32Exception : Exception + { + public Crc32Exception() : base() { } + public Crc32Exception(string message) : base(message) { } + public Crc32Exception(string message, Exception innerException) : base(message, innerException) { } + public Crc32Exception(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) : base(info, context) { } + } +} diff --git a/Assets/Plugins/Essgee/Utilities/Crc32.cs.meta b/Assets/Plugins/Essgee/Utilities/Crc32.cs.meta new file mode 100644 index 0000000..2c4e673 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/Crc32.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 21bde0d19c7628445ac123f8bb7c8f75 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/HexadecimalJsonConverter.cs b/Assets/Plugins/Essgee/Utilities/HexadecimalJsonConverter.cs new file mode 100644 index 0000000..dcb7624 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/HexadecimalJsonConverter.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Newtonsoft.Json; + +namespace Essgee.Utilities +{ + public class HexadecimalJsonConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + // TODO: maybe actually check things? + return true; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + switch (Type.GetTypeCode(value.GetType())) + { + case TypeCode.Byte: + case TypeCode.SByte: + writer.WriteValue($"0x{value:X2}"); + break; + case TypeCode.UInt16: + case TypeCode.Int16: + writer.WriteValue($"0x{value:X4}"); + break; + case TypeCode.UInt32: + case TypeCode.Int32: + writer.WriteValue($"0x{value:X8}"); + break; + case TypeCode.UInt64: + case TypeCode.Int64: + writer.WriteValue($"0x{value:X16}"); + break; + default: + throw new JsonSerializationException(); + } + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if ((reader.Value is string value) && value.StartsWith("0x")) + return Convert.ChangeType(Convert.ToUInt64(value, 16), objectType); + else + throw new JsonSerializationException(); + } + } +} diff --git a/Assets/Plugins/Essgee/Utilities/HexadecimalJsonConverter.cs.meta b/Assets/Plugins/Essgee/Utilities/HexadecimalJsonConverter.cs.meta new file mode 100644 index 0000000..f4e9327 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/HexadecimalJsonConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 33727d827e6ef604baf39a57c3b9211d \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/InterfaceDictionaryConverter.cs b/Assets/Plugins/Essgee/Utilities/InterfaceDictionaryConverter.cs new file mode 100644 index 0000000..d6dd61b --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/InterfaceDictionaryConverter.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Reflection; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace Essgee.Utilities +{ + public class InterfaceDictionaryConverter<TInterface> : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return (objectType == typeof(TInterface)); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + if (!objectType.IsGenericType || objectType.GetGenericTypeDefinition() != typeof(Dictionary<,>)) throw new InvalidOperationException("Can only deserialize dictionaries"); + + var dictionary = (System.Collections.IDictionary)Activator.CreateInstance(objectType); + + var jObject = JObject.Load(reader); + foreach (var child in jObject.Children()) + { + Type type = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(y => typeof(TInterface).IsAssignableFrom(y) && !y.IsInterface && !y.IsAbstract && y.Name == child.Path); + if (type != null) + dictionary.Add(child.Path, JsonConvert.DeserializeObject(child.First.ToString(), type)); + } + + return dictionary; + } + + public override bool CanWrite + { + get { return false; } + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} diff --git a/Assets/Plugins/Essgee/Utilities/InterfaceDictionaryConverter.cs.meta b/Assets/Plugins/Essgee/Utilities/InterfaceDictionaryConverter.cs.meta new file mode 100644 index 0000000..c094ee4 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/InterfaceDictionaryConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 38387730a3743044698acbb9afe6aa67 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/IsBootstrapRomPathAttribute.cs b/Assets/Plugins/Essgee/Utilities/IsBootstrapRomPathAttribute.cs new file mode 100644 index 0000000..0ea7cb5 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/IsBootstrapRomPathAttribute.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Utilities +{ + [AttributeUsage(AttributeTargets.Property)] + public class IsBootstrapRomPathAttribute : Attribute { } +} diff --git a/Assets/Plugins/Essgee/Utilities/IsBootstrapRomPathAttribute.cs.meta b/Assets/Plugins/Essgee/Utilities/IsBootstrapRomPathAttribute.cs.meta new file mode 100644 index 0000000..705a2f4 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/IsBootstrapRomPathAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 12ab3971c05a8034ea04df685e2c6f2d \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/MachineIndexAttribute.cs b/Assets/Plugins/Essgee/Utilities/MachineIndexAttribute.cs new file mode 100644 index 0000000..c950e67 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/MachineIndexAttribute.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Utilities +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class MachineIndexAttribute : Attribute + { + public int Index { get; private set; } + + public MachineIndexAttribute(int index) + { + Index = index; + } + } +} diff --git a/Assets/Plugins/Essgee/Utilities/MachineIndexAttribute.cs.meta b/Assets/Plugins/Essgee/Utilities/MachineIndexAttribute.cs.meta new file mode 100644 index 0000000..9b7b2a8 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/MachineIndexAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 63aa5f9e1fb94184c96245b9b13e4325 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/StateRequiredAttribute.cs b/Assets/Plugins/Essgee/Utilities/StateRequiredAttribute.cs new file mode 100644 index 0000000..9153286 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/StateRequiredAttribute.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Utilities +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class StateRequiredAttribute : Attribute { } +} diff --git a/Assets/Plugins/Essgee/Utilities/StateRequiredAttribute.cs.meta b/Assets/Plugins/Essgee/Utilities/StateRequiredAttribute.cs.meta new file mode 100644 index 0000000..6b79703 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/StateRequiredAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7cc6e75e78730c445a27a5f59f93c4e5 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/TypeNameJsonConverter.cs b/Assets/Plugins/Essgee/Utilities/TypeNameJsonConverter.cs new file mode 100644 index 0000000..dd77492 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/TypeNameJsonConverter.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +using Newtonsoft.Json; + +namespace Essgee.Utilities +{ + public class TypeNameJsonConverter : JsonConverter + { + readonly string searchNamespace; + + public TypeNameJsonConverter(string searchNamespace) + { + this.searchNamespace = searchNamespace; + } + + public override bool CanConvert(Type objectType) + { + // TODO: maybe actually check things? + return true; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + if (value is Type) + { + var type = (value as Type); + if (type.Namespace != searchNamespace) throw new JsonSerializationException(); + writer.WriteValue(type.Name); + } + else + throw new JsonSerializationException(); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var type = Type.GetType($"{searchNamespace}.{reader.Value}"); + if (type != null) return type; + else throw new JsonSerializationException(); + } + } +} diff --git a/Assets/Plugins/Essgee/Utilities/TypeNameJsonConverter.cs.meta b/Assets/Plugins/Essgee/Utilities/TypeNameJsonConverter.cs.meta new file mode 100644 index 0000000..6614f15 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/TypeNameJsonConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7a7c859a28b7ac348b1fc4904077955d \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/XInput.meta b/Assets/Plugins/Essgee/Utilities/XInput.meta new file mode 100644 index 0000000..0e74294 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 37b3e1d7093e0f545807ab54c1209c8b +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Plugins/Essgee/Utilities/XInput/Controller.cs b/Assets/Plugins/Essgee/Utilities/XInput/Controller.cs new file mode 100644 index 0000000..f959b27 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/Controller.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Utilities.XInput +{ + public class Controller + { + XInputState inputStatesCurrent, inputStatesPrev; + bool timedVibrationEnabled; + DateTime vibrationStopTime; + + public bool IsConnected { get; private set; } + public int UserIndex { get; private set; } + + public Controller(int index) + { + inputStatesCurrent = inputStatesPrev = new XInputState(); + timedVibrationEnabled = false; + vibrationStopTime = DateTime.Now; + + IsConnected = false; + UserIndex = index; + } + + public void Update() + { + XInputState newInputState = new XInputState(); + Errors result = (Errors)NativeMethods.GetState(UserIndex, ref newInputState); + if (result == Errors.Success) + { + IsConnected = true; + inputStatesPrev = inputStatesCurrent; + inputStatesCurrent = newInputState; + + if ((inputStatesCurrent.Gamepad.sThumbLX < XInputGamepad.LeftThumbDeadzone && inputStatesCurrent.Gamepad.sThumbLX > -XInputGamepad.LeftThumbDeadzone) && + (inputStatesCurrent.Gamepad.sThumbLY < XInputGamepad.LeftThumbDeadzone && inputStatesCurrent.Gamepad.sThumbLY > -XInputGamepad.LeftThumbDeadzone)) + { + inputStatesCurrent.Gamepad.sThumbLX = inputStatesCurrent.Gamepad.sThumbLY = 0; + } + + if ((inputStatesCurrent.Gamepad.sThumbRX < XInputGamepad.RightThumbDeadzone && inputStatesCurrent.Gamepad.sThumbRX > -XInputGamepad.RightThumbDeadzone) && + (inputStatesCurrent.Gamepad.sThumbRY < XInputGamepad.RightThumbDeadzone && inputStatesCurrent.Gamepad.sThumbRY > -XInputGamepad.RightThumbDeadzone)) + { + inputStatesCurrent.Gamepad.sThumbRX = inputStatesCurrent.Gamepad.sThumbRY = 0; + } + + if (inputStatesCurrent.Gamepad.bLeftTrigger < XInputGamepad.TriggerThreshold) inputStatesCurrent.Gamepad.bLeftTrigger = 0; + if (inputStatesCurrent.Gamepad.bRightTrigger < XInputGamepad.TriggerThreshold) inputStatesCurrent.Gamepad.bRightTrigger = 0; + + if (timedVibrationEnabled && DateTime.Now >= vibrationStopTime) + { + timedVibrationEnabled = false; + Vibrate(0.0f, 0.0f); + } + } + else if (result == Errors.DeviceNotConnected) + { + IsConnected = false; + } + else + throw new Exception(string.Format("Error code {0}", (int)result)); + } + + public ControllerState GetControllerState() + { + return new ControllerState + { + Buttons = inputStatesCurrent.Gamepad.Buttons, + LeftThumbstick = new ThumbstickPosition(inputStatesCurrent.Gamepad.sThumbLX / 32768.0f, inputStatesCurrent.Gamepad.sThumbLY / 32768.0f), + RightThumbstick = new ThumbstickPosition(inputStatesCurrent.Gamepad.sThumbRX / 32768.0f, inputStatesCurrent.Gamepad.sThumbRY / 32768.0f), + LeftTrigger = (inputStatesCurrent.Gamepad.bLeftTrigger / 255.0f), + RightTrigger = (inputStatesCurrent.Gamepad.bRightTrigger / 255.0f) + }; + } + + public bool IsDPadUpPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.DPadUp); + } + + public bool IsDPadDownPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.DPadDown); + } + + public bool IsDPadLeftPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.DPadLeft); + } + + public bool IsDPadRightPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.DPadRight); + } + + public bool IsStartPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.Start); + } + + public bool IsBackPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.Back); + } + + public bool IsLeftThumbPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.LeftThumb); + } + + public bool IsRightThumbPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.RightThumb); + } + + public bool IsLeftShoulderPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.LeftShoulder); + } + + public bool IsRightShoulderPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.RightShoulder); + } + + public bool IsAPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.A); + } + + public bool IsBPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.B); + } + + public bool IsXPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.X); + } + + public bool IsYPressed() + { + return inputStatesCurrent.Gamepad.Buttons.HasFlag(Buttons.Y); + } + + public void Vibrate(float leftMotor, float rightMotor) + { + XInputVibration vibrationState = new XInputVibration(); + vibrationState.wLeftMotorSpeed = (ushort)(leftMotor * 65535.0f); + vibrationState.wRightMotorSpeed = (ushort)(rightMotor * 65535.0f); + NativeMethods.SetState(UserIndex, ref vibrationState); + } + + public void Vibrate(float leftMotor, float rightMotor, TimeSpan duration) + { + Vibrate(leftMotor, rightMotor); + + vibrationStopTime = DateTime.Now.Add(duration); + timedVibrationEnabled = true; + } + } + + public class ThumbstickPosition + { + public float X { get; private set; } + public float Y { get; private set; } + + public ThumbstickPosition(float x, float y) + { + X = x; + Y = y; + } + + public override string ToString() + { + return string.Format(System.Globalization.CultureInfo.InvariantCulture, "({0}, {1})", X, Y); + } + } +} diff --git a/Assets/Plugins/Essgee/Utilities/XInput/Controller.cs.meta b/Assets/Plugins/Essgee/Utilities/XInput/Controller.cs.meta new file mode 100644 index 0000000..146f2f1 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/Controller.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 208c78465e149914e92390762f768a04 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/XInput/ControllerManager.cs b/Assets/Plugins/Essgee/Utilities/XInput/ControllerManager.cs new file mode 100644 index 0000000..71d3333 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/ControllerManager.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Utilities.XInput +{ + public static class ControllerManager + { + const int maxControllers = 4; + + static Controller[] controllers; + + static ControllerManager() + { + controllers = new Controller[maxControllers]; + for (int i = 0; i < controllers.Length; i++) + controllers[i] = new Controller(i); + } + + public static Controller GetController(int index) + { + if (index < 0 || index >= maxControllers) throw new Exception("Controller index out of range"); + return controllers[index]; + } + + public static void Update() + { + for (int i = 0; i < controllers.Length; i++) + controllers[i].Update(); + } + } +} diff --git a/Assets/Plugins/Essgee/Utilities/XInput/ControllerManager.cs.meta b/Assets/Plugins/Essgee/Utilities/XInput/ControllerManager.cs.meta new file mode 100644 index 0000000..e0cc01e --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/ControllerManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d3b109e035af2d0488fee85624c9a90b \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/XInput/ControllerState.cs b/Assets/Plugins/Essgee/Utilities/XInput/ControllerState.cs new file mode 100644 index 0000000..0883f6d --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/ControllerState.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Essgee.Utilities.XInput +{ + public class ControllerState + { + public Buttons Buttons { get; set; } + public ThumbstickPosition LeftThumbstick { get; set; } + public ThumbstickPosition RightThumbstick { get; set; } + public float LeftTrigger { get; set; } + public float RightTrigger { get; set; } + + public bool IsConnected { get; set; } + public int UserIndex { get; set; } + + public ControllerState() + { + Buttons = Buttons.None; + LeftThumbstick = new ThumbstickPosition(0.0f, 0.0f); + RightThumbstick = new ThumbstickPosition(0.0f, 0.0f); + LeftTrigger = 0.0f; + RightTrigger = 0.0f; + + IsConnected = false; + UserIndex = -1; + } + + public bool IsAnyUpDirectionPressed() + { + return IsDPadUpPressed() || LeftThumbstick.Y > 0.5f; + } + + public bool IsAnyDownDirectionPressed() + { + return IsDPadDownPressed() || LeftThumbstick.Y < -0.5f; + } + + public bool IsAnyLeftDirectionPressed() + { + return IsDPadLeftPressed() || LeftThumbstick.X < -0.5f; + } + + public bool IsAnyRightDirectionPressed() + { + return IsDPadRightPressed() || LeftThumbstick.X > 0.5f; + } + + public bool IsDPadUpPressed() + { + return Buttons.HasFlag(Buttons.DPadUp); + } + + public bool IsDPadDownPressed() + { + return Buttons.HasFlag(Buttons.DPadDown); + } + + public bool IsDPadLeftPressed() + { + return Buttons.HasFlag(Buttons.DPadLeft); + } + + public bool IsDPadRightPressed() + { + return Buttons.HasFlag(Buttons.DPadRight); + } + + public bool IsStartPressed() + { + return Buttons.HasFlag(Buttons.Start); + } + + public bool IsBackPressed() + { + return Buttons.HasFlag(Buttons.Back); + } + + public bool IsLeftThumbPressed() + { + return Buttons.HasFlag(Buttons.LeftThumb); + } + + public bool IsRightThumbPressed() + { + return Buttons.HasFlag(Buttons.RightThumb); + } + + public bool IsLeftShoulderPressed() + { + return Buttons.HasFlag(Buttons.LeftShoulder); + } + + public bool IsRightShoulderPressed() + { + return Buttons.HasFlag(Buttons.RightShoulder); + } + + public bool IsAPressed() + { + return Buttons.HasFlag(Buttons.A); + } + + public bool IsBPressed() + { + return Buttons.HasFlag(Buttons.B); + } + + public bool IsXPressed() + { + return Buttons.HasFlag(Buttons.X); + } + + public bool IsYPressed() + { + return Buttons.HasFlag(Buttons.Y); + } + } +} diff --git a/Assets/Plugins/Essgee/Utilities/XInput/ControllerState.cs.meta b/Assets/Plugins/Essgee/Utilities/XInput/ControllerState.cs.meta new file mode 100644 index 0000000..da9a3ae --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/ControllerState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 62cf2ece5ee5c44458ac5646bb2f8af8 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/XInput/NativeMethods.cs b/Assets/Plugins/Essgee/Utilities/XInput/NativeMethods.cs new file mode 100644 index 0000000..fe2aae7 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/NativeMethods.cs @@ -0,0 +1,25 @@ +using System.Runtime.InteropServices; + +namespace Essgee.Utilities.XInput +{ + static class NativeMethods + { + const string dllName = "xinput9_1_0.dll"; + + public const int FlagGamepad = 0x00000001; + + [DllImport(dllName, EntryPoint = "XInputGetState")] + public static extern int GetState(int dwUserIndex, ref XInputState pState); + [DllImport(dllName, EntryPoint = "XInputSetState")] + public static extern int SetState(int dwUserIndex, ref XInputVibration pVibration); + [DllImport(dllName, EntryPoint = "XInputGetCapabilities")] + public static extern int GetCapabilities(int dwUserIndex, int dwFlags, ref XInputCapabilities pCapabilities); + } + + public enum Errors + { + Success = 0x00000000, + BadArguments = 0x000000A0, + DeviceNotConnected = 0x0000048F + } +} diff --git a/Assets/Plugins/Essgee/Utilities/XInput/NativeMethods.cs.meta b/Assets/Plugins/Essgee/Utilities/XInput/NativeMethods.cs.meta new file mode 100644 index 0000000..3f0a5cf --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/NativeMethods.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2f4639905082c50448ca8a1477b55057 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/XInput/XInputCapabilities.cs b/Assets/Plugins/Essgee/Utilities/XInput/XInputCapabilities.cs new file mode 100644 index 0000000..2c6ffd3 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/XInputCapabilities.cs @@ -0,0 +1,47 @@ +using System; +using System.Runtime.InteropServices; + +namespace Essgee.Utilities.XInput +{ + /* https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.xinput_capabilities%28v=vs.85%29.aspx */ + [StructLayout(LayoutKind.Explicit)] + public struct XInputCapabilities + { + [FieldOffset(0)] + byte type; + [FieldOffset(1)] + byte subType; + [FieldOffset(2)] + ushort flags; + [FieldOffset(4)] + public XInputGamepad Gamepad; + [FieldOffset(16)] + public XInputVibration Vibration; + + public DeviceType Type { get { return (DeviceType)type; } } + public DeviceSubType SubType { get { return (DeviceSubType)subType; } } + public DeviceFlags Flags { get { return (DeviceFlags)flags; } } + } + + public enum DeviceType + { + Gamepad = 0x01 + } + + public enum DeviceSubType + { + Gamepad = 0x01, + Wheel = 0x02, + ArcadeStick = 0x03, + FlightStick = 0x04, + DancePad = 0x05, + Guitar = 0x06, + DrumKit = 0x08 + } + + [Flags] + public enum DeviceFlags + { + VoiceSupported = 0x0004 + } +} diff --git a/Assets/Plugins/Essgee/Utilities/XInput/XInputCapabilities.cs.meta b/Assets/Plugins/Essgee/Utilities/XInput/XInputCapabilities.cs.meta new file mode 100644 index 0000000..c916d61 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/XInputCapabilities.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 400677b412126194caf14f5d627b34a2 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/XInput/XInputGamepad.cs b/Assets/Plugins/Essgee/Utilities/XInput/XInputGamepad.cs new file mode 100644 index 0000000..6793fdd --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/XInputGamepad.cs @@ -0,0 +1,51 @@ +using System; +using System.Runtime.InteropServices; + +namespace Essgee.Utilities.XInput +{ + /* https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.xinput_gamepad%28v=vs.85%29.aspx */ + [StructLayout(LayoutKind.Explicit)] + public struct XInputGamepad + { + [FieldOffset(0)] + ushort wButtons; + [FieldOffset(2)] + public byte bLeftTrigger; + [FieldOffset(3)] + public byte bRightTrigger; + [FieldOffset(4)] + public short sThumbLX; + [FieldOffset(6)] + public short sThumbLY; + [FieldOffset(8)] + public short sThumbRX; + [FieldOffset(10)] + public short sThumbRY; + + public const int LeftThumbDeadzone = 7849; + public const int RightThumbDeadzone = 8689; + public const int TriggerThreshold = 30; + + public Buttons Buttons { get { return (Buttons)wButtons; } } + } + + [Flags] + public enum Buttons + { + None = 0x0000, + DPadUp = 0x0001, + DPadDown = 0x0002, + DPadLeft = 0x0004, + DPadRight = 0x0008, + Start = 0x0010, + Back = 0x0020, + LeftThumb = 0x0040, + RightThumb = 0x0080, + LeftShoulder = 0x0100, + RightShoulder = 0x0200, + A = 0x1000, + B = 0x2000, + X = 0x4000, + Y = 0x8000 + } +} diff --git a/Assets/Plugins/Essgee/Utilities/XInput/XInputGamepad.cs.meta b/Assets/Plugins/Essgee/Utilities/XInput/XInputGamepad.cs.meta new file mode 100644 index 0000000..86f6d2b --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/XInputGamepad.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dee5c4630dd189049abf4d41bd4348d8 \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/XInput/XInputState.cs b/Assets/Plugins/Essgee/Utilities/XInput/XInputState.cs new file mode 100644 index 0000000..8ecf014 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/XInputState.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Essgee.Utilities.XInput +{ + /* https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.xinput_state%28v=vs.85%29.aspx */ + [StructLayout(LayoutKind.Explicit)] + public struct XInputState + { + [FieldOffset(0)] + public uint dwPacketNumber; + [FieldOffset(4)] + public XInputGamepad Gamepad; + } +} diff --git a/Assets/Plugins/Essgee/Utilities/XInput/XInputState.cs.meta b/Assets/Plugins/Essgee/Utilities/XInput/XInputState.cs.meta new file mode 100644 index 0000000..64bb742 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/XInputState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b18170cd37fc33d4ba897ffbe7198deb \ No newline at end of file diff --git a/Assets/Plugins/Essgee/Utilities/XInput/XInputVibration.cs b/Assets/Plugins/Essgee/Utilities/XInput/XInputVibration.cs new file mode 100644 index 0000000..dc3730a --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/XInputVibration.cs @@ -0,0 +1,14 @@ +using System.Runtime.InteropServices; + +namespace Essgee.Utilities.XInput +{ + /* https://msdn.microsoft.com/en-us/library/windows/desktop/microsoft.directx_sdk.reference.xinput_vibration%28v=vs.85%29.aspx */ + [StructLayout(LayoutKind.Explicit)] + public struct XInputVibration + { + [FieldOffset(0)] + public ushort wLeftMotorSpeed; + [FieldOffset(2)] + public ushort wRightMotorSpeed; + } +} diff --git a/Assets/Plugins/Essgee/Utilities/XInput/XInputVibration.cs.meta b/Assets/Plugins/Essgee/Utilities/XInput/XInputVibration.cs.meta new file mode 100644 index 0000000..c25fa98 --- /dev/null +++ b/Assets/Plugins/Essgee/Utilities/XInput/XInputVibration.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 66e6171736477a944b9299a66469f407 \ No newline at end of file diff --git a/Assets/Plugins/Newtonsoft.Json.dll b/Assets/Plugins/Newtonsoft.Json.dll new file mode 100644 index 0000000..341d08f Binary files /dev/null and b/Assets/Plugins/Newtonsoft.Json.dll differ diff --git a/Assets/Plugins/Newtonsoft.Json.dll.meta b/Assets/Plugins/Newtonsoft.Json.dll.meta new file mode 100644 index 0000000..7124f76 --- /dev/null +++ b/Assets/Plugins/Newtonsoft.Json.dll.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1a58e3ab1bbec0a4998d14ae26c5651d \ No newline at end of file