diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity.meta new file mode 100644 index 00000000..5dfc2134 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b136ca247e0175d48a98c06d18889c44 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AppEnvironment.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AppEnvironment.cs new file mode 100644 index 00000000..bd085153 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AppEnvironment.cs @@ -0,0 +1,15 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AppEnvironment.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AppEnvironment.cs.meta new file mode 100644 index 00000000..197f722b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AppEnvironment.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ce38d9b5f910b4c4185e3fab7eb45ecf \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AxiMemory.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AxiMemory.cs new file mode 100644 index 00000000..25255e28 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AxiMemory.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +namespace Essgee.Utilities +{ + internal unsafe static class AxiMemoryEx + { + static HashSet GCHandles = new HashSet(); + + public static void Init() + { + FreeAllGCHandle(); + set_TempBuffer = new byte[0x100000]; + } + + public static void GetObjectPtr(this object srcObj, ref GCHandle handle, ref uint* ptr) + { + GetObjectPtr(srcObj, ref handle, out IntPtr intptr); + ptr = (uint*)intptr; + } + + public static void GetObjectPtr(this object srcObj, ref GCHandle handle, ref short* ptr) + { + GetObjectPtr(srcObj, ref handle, out IntPtr intptr); + ptr = (short*)intptr; + } + public static void GetObjectPtr(this object srcObj, ref GCHandle handle, ref ushort* ptr) + { + GetObjectPtr(srcObj, ref handle, out IntPtr intptr); + ptr = (ushort*)intptr; + } + public static void GetObjectPtr(this object srcObj, ref GCHandle handle, ref int* ptr) + { + GetObjectPtr(srcObj, ref handle, out IntPtr intptr); + ptr = (int*)intptr; + } + public static void GetObjectPtr(this object srcObj, ref GCHandle handle, ref byte* ptr) + { + GetObjectPtr(srcObj, ref handle, out IntPtr intptr); + ptr = (byte*)intptr; + } + + static void GetObjectPtr(this object srcObj, ref GCHandle handle, out IntPtr intptr) + { + ReleaseGCHandle(ref handle); + handle = GCHandle.Alloc(srcObj, GCHandleType.Pinned); + GCHandles.Add(handle); + intptr = handle.AddrOfPinnedObject(); + } + + public static void ReleaseGCHandle(this ref GCHandle handle) + { + if (handle.IsAllocated) + handle.Free(); + GCHandles.Remove(handle); + } + + public static void FreeAllGCHandle() + { + foreach (var handle in GCHandles) + { + if (handle.IsAllocated) + handle.Free(); + } + GCHandles.Clear(); + } + + #region 指针化 TempBuffer + static byte[] TempBuffer_src; + static GCHandle TempBuffer_handle; + public static byte* TempBuffer; + public static byte[] set_TempBuffer + { + set + { + TempBuffer_handle.ReleaseGCHandle(); + if (value == null) + return; + TempBuffer_src = value; + TempBuffer_src.GetObjectPtr(ref TempBuffer_handle, ref TempBuffer); + } + } + #endregion + + public static void Write(this BinaryWriter bw, byte* bufferPtr, int offset, int count) + { + // 使用指针复制数据到临时数组 + Buffer.MemoryCopy(bufferPtr + offset, TempBuffer, 0, count); + // 使用BinaryWriter写入临时数组 + bw.Write(TempBuffer_src, 0, count); + } + public static void Write(this FileStream fs, byte* bufferPtr, int offset, int count) + { + // 使用指针复制数据到临时数组 + Buffer.MemoryCopy(bufferPtr + offset, TempBuffer, 0, count); + // 使用BinaryWriter写入临时数组 + fs.Write(TempBuffer_src, 0, count); + } + public static int Read(this FileStream fs, byte* bufferPtr, int offset, int count) + { + // 使用BinaryWriter写入临时数组 + count = fs.Read(TempBuffer_src, offset, count); + // 使用指针复制数据到临时数组 + Buffer.MemoryCopy(TempBuffer, bufferPtr + offset, 0, count); + return count; + } + } + + internal unsafe static class AxiArray + { + + public static void Copy(byte* src, int srcindex, byte* target, int targetindex, int count) + { + int singlesize = sizeof(byte); + long totalBytesToCopy = count * singlesize; + Buffer.MemoryCopy(&src[srcindex], &target[targetindex], totalBytesToCopy, totalBytesToCopy); + } + public static void Copy(short* src, int srcindex, short* target, int targetindex, int count) + { + int singlesize = sizeof(short); + long totalBytesToCopy = count * singlesize; + Buffer.MemoryCopy(&src[srcindex], &target[targetindex], totalBytesToCopy, totalBytesToCopy); + } + public static void Copy(ushort* src, int srcindex, ushort* target, int targetindex, int count) + { + int singlesize = sizeof(ushort); + long totalBytesToCopy = count * singlesize; + Buffer.MemoryCopy(&src[srcindex], &target[targetindex], totalBytesToCopy, totalBytesToCopy); + } + + public static void Copy(byte* src, byte* target, int index, int count) + { + int singlesize = sizeof(byte); + long totalBytesToCopy = count * singlesize; + Buffer.MemoryCopy(&src[index], &target[index], totalBytesToCopy, totalBytesToCopy); + } + + public static void Copy(ushort* src, ushort* target, int index, int count) + { + int singlesize = sizeof(ushort); + long totalBytesToCopy = count * singlesize; + Buffer.MemoryCopy(&src[index], &target[index], totalBytesToCopy, totalBytesToCopy); + } + public static void Copy(ushort* src, ushort* target, int count) + { + int singlesize = sizeof(ushort); + long totalBytesToCopy = count * singlesize; + Buffer.MemoryCopy(src, target, totalBytesToCopy, totalBytesToCopy); + } + public static void Copy(byte* src, byte* target, int count) + { + int singlesize = sizeof(byte); + long totalBytesToCopy = count * singlesize; + Buffer.MemoryCopy(src, target, totalBytesToCopy, totalBytesToCopy); + } + public static void Clear(byte* data, int index, int lenght) + { + for (int i = index; i < lenght; i++, index++) + data[index] = 0; + } + public static void Clear(ushort* data, int index, int lenght) + { + for (int i = index; i < lenght; i++, index++) + data[index] = 0; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AxiMemory.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AxiMemory.cs.meta new file mode 100644 index 00000000..98cca219 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/AxiMemory.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 922bbb45b85e7de4f8bce1c6f407009f \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Configuration.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Configuration.cs new file mode 100644 index 00000000..959f804d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Configuration.cs @@ -0,0 +1,58 @@ +using Essgee.Emulation.Configuration; +using Essgee.Utilities; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System.Collections.Generic; +using System.Drawing; + +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 RecentFiles { get; set; } + + //[JsonConverter(typeof(InterfaceDictionaryConverter))] + public Dictionary Machines { get; set; } + + public Dictionary 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(RecentFilesCapacity); + + Machines = new Dictionary(); + + DebugWindows = new Dictionary(); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Configuration.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Configuration.cs.meta new file mode 100644 index 00000000..575b88ca --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Configuration.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1cb6affe10cc31441b6301f115fbc2ab \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EmuStandInfo.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EmuStandInfo.cs new file mode 100644 index 00000000..df3cfa98 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EmuStandInfo.cs @@ -0,0 +1,44 @@ + +using Essgee; +using System; + +public static class EmuStandInfo +{ + //metaData + //public static string datDirectoryPath; + //public static string metadataDatabaseFilePath; + + + public static string jsonConfigFileName;//= "Config.json"; + public static string saveDataDirectoryName;//= "Saves"; + public static string screenshotDirectoryName;//= "Screenshots"; + public static string saveStateDirectoryName;//= "Savestates"; + public static string extraDataDirectoryName;//= "Extras"; + public static string ProductName;//= ""; + + public static string programDataDirectory;// = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), ProductName); + public 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; set; } +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EmuStandInfo.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EmuStandInfo.cs.meta new file mode 100644 index 00000000..702705be --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EmuStandInfo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c668ecd37f7dd4e4092fcac10c385c9b \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation.meta new file mode 100644 index 00000000..ca594039 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 267425c59f2efba48a44a3b55a5eb01d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio.meta new file mode 100644 index 00000000..c3f058b2 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ab3f0235ad62cb046ac1518f29d21e81 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.Wave.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.Wave.cs new file mode 100644 index 00000000..da9eb68e --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.Wave.cs @@ -0,0 +1,19 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.Wave.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.Wave.cs.meta new file mode 100644 index 00000000..23d2fffd --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.Wave.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 595436ed6c508904399695fee24279ff \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.cs new file mode 100644 index 00000000..4d9144d5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Essgee.Emulation.Audio +{ + public partial class CGBAudio : DMGAudio, IAudio + { + public CGBAudio() + { + //channelSampleBuffer = new List[numChannels]; + //for (int i = 0; i < numChannels; i++) channelSampleBuffer[i] = new List(); + //mixedSampleBuffer = new List(); + + //改为二维数组 + channelSampleBuffer_Init(numChannels, 1470); + mixedSampleBuffer_set = new short[1470]; + + channel1 = new Square(true); + channel2 = new Square(false); + channel3 = new CGBWave(); + channel4 = new Noise(); + + samplesPerFrame = cyclesPerFrame = cyclesPerSample = -1; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.cs.meta new file mode 100644 index 00000000..58d22615 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/CGBAudio.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 40f2c16e17787974881d5614068c46b7 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Noise.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Noise.cs new file mode 100644 index 00000000..db82aa59 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Noise.cs @@ -0,0 +1,214 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Noise.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Noise.cs.meta new file mode 100644 index 00000000..8d27ee98 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Noise.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0992ef0e8260e124c9667aea4433025c \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Square.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Square.cs new file mode 100644 index 00000000..b20478ea --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Square.cs @@ -0,0 +1,269 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Square.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Square.cs.meta new file mode 100644 index 00000000..639fff9b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Square.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: eab50abebc5d5f341889e031ded6a571 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Wave.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Wave.cs new file mode 100644 index 00000000..6448d3e7 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Wave.cs @@ -0,0 +1,186 @@ +using System; + +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)EmuStandInfo.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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Wave.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Wave.cs.meta new file mode 100644 index 00000000..acfa2fae --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.Wave.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a9f4769005789914785d05d63578c931 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.cs new file mode 100644 index 00000000..228d6922 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.cs @@ -0,0 +1,494 @@ +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; + +namespace Essgee.Emulation.Audio +{ + public unsafe 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[] channelSampleBuffer; + #region //指针化 channelSampleBuffer + static short[][] channelSampleBuffer_src; + static GCHandle[] channelSampleBuffer_handle; + public static short*[] channelSampleBuffer; + public static int[] channelSampleBufferLength; + public static int channelSampleBuffer_writePos; + public static bool channelSampleBuffer_IsNull => channelSampleBuffer == null; + public static void channelSampleBuffer_Init(int length1, int Lenght2) + { + if (channelSampleBuffer_src != null) + { + for (int i = 0; i < channelSampleBuffer_src.Length; i++) + channelSampleBuffer_handle[i].ReleaseGCHandle(); + } + + channelSampleBuffer_src = new short[length1][]; + channelSampleBuffer_handle = new GCHandle[length1]; + channelSampleBuffer = new short*[length1]; + channelSampleBuffer_writePos = 0; + for (int i = 0; i < channelSampleBuffer_src.Length; i++) + { + channelSampleBuffer_src[i] = new short[Lenght2]; + channelSampleBuffer_src[i].GetObjectPtr(ref channelSampleBuffer_handle[i], ref channelSampleBuffer[i]); + } + } + #endregion + //protected List mixedSampleBuffer; + + #region //指针化 mixedSampleBuffer + short[] mixedSampleBuffer_src; + GCHandle mixedSampleBuffer_handle; + public short* mixedSampleBuffer; + public int mixedSampleBufferLength; + public int mixedSampleBuffer_writePos; + public bool mixedSampleBuffer_IsNull => mixedSampleBuffer == null; + public short[] mixedSampleBuffer_set + { + set + { + mixedSampleBuffer_handle.ReleaseGCHandle(); + mixedSampleBuffer_src = value; + mixedSampleBufferLength = value.Length; + mixedSampleBuffer_writePos = 0; + mixedSampleBuffer_src.GetObjectPtr(ref mixedSampleBuffer_handle, ref mixedSampleBuffer); + } + } + #endregion + + public virtual event EventHandler 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[numChannels]; + //for (int i = 0; i < numChannels; i++) channelSampleBuffer[i] = new List(); + //mixedSampleBuffer = new List(); + + //改为二维数组 + channelSampleBuffer_Init(numChannels, 1470); + mixedSampleBuffer_set = new short[1470]; + + + 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)) + if (mixedSampleBuffer_writePos >= (samplesPerFrame * numOutputChannels)) + { + //EnqueueSamplesEventArgs eventArgs = EnqueueSamplesEventArgs.Create( + // numChannels, + // channelSampleBuffer.Select(x => x.ToArray()).ToArray(), + // new bool[] { !channel1ForceEnable, !channel2ForceEnable, !channel3ForceEnable, !channel4ForceEnable }, + // mixedSampleBuffer.ToArray()); + + EnqueueSamplesEventArgs eventArgs = EnqueueSamplesEventArgs.Create( + numChannels, + channelSampleBuffer, + new bool[] { !channel1ForceEnable, !channel2ForceEnable, !channel3ForceEnable, !channel4ForceEnable }, + mixedSampleBuffer, + mixedSampleBuffer_writePos); + + OnEnqueueSamples(eventArgs); + + FlushSamples(); + + eventArgs.Release(); + + } + + 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); + + //二维指针下标 + channelSampleBuffer_writePos++; + channelSampleBuffer[0][channelSampleBuffer_writePos] = ch1; + channelSampleBuffer[1][channelSampleBuffer_writePos] = ch2; + channelSampleBuffer[2][channelSampleBuffer_writePos] = ch3; + channelSampleBuffer[3][channelSampleBuffer_writePos] = 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); + //指针下标 + mixedSampleBuffer_writePos++; + mixedSampleBuffer[mixedSampleBuffer_writePos] = (short)mixed; + } + } + + public void FlushSamples() + { + //for (int i = 0; i < numChannels; i++) + // channelSampleBuffer[i].Clear(); + channelSampleBuffer_writePos = 0; + + //mixedSampleBuffer.Clear(); + mixedSampleBuffer_writePos = 0; + } + + 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.cs.meta new file mode 100644 index 00000000..d9dc4d65 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/DMGAudio.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b1912726afeeb8a48bd9e35bf55fe8e3 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IAudio.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IAudio.cs new file mode 100644 index 00000000..c3415d3d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IAudio.cs @@ -0,0 +1,26 @@ +using Essgee.EventArguments; +using System; + +namespace Essgee.Emulation.Audio +{ + interface IAudio + { + event EventHandler 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IAudio.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IAudio.cs.meta new file mode 100644 index 00000000..ced8ebc9 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IAudio.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c7d71d4c287c7024f88c3e76da01431f \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IDMGAudioChannel.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IDMGAudioChannel.cs new file mode 100644 index 00000000..5de0da69 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IDMGAudioChannel.cs @@ -0,0 +1,19 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IDMGAudioChannel.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IDMGAudioChannel.cs.meta new file mode 100644 index 00000000..beebd854 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/IDMGAudioChannel.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b23834762d9307449a8e4b923fb2688b \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SN76489.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SN76489.cs new file mode 100644 index 00000000..35967514 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SN76489.cs @@ -0,0 +1,468 @@ +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Audio +{ + public unsafe 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[] channelSampleBuffer; + + + #region //指针化 channelSampleBuffer + static short[][] channelSampleBuffer_src; + static GCHandle[] channelSampleBuffer_handle; + public static short*[] channelSampleBuffer; + public static int[] channelSampleBufferLength; + public static int channelSampleBuffer_writePos; + public static bool channelSampleBuffer_IsNull => channelSampleBuffer == null; + public static void channelSampleBuffer_Init(int length1, int Lenght2) + { + if (channelSampleBuffer_src != null) + { + for (int i = 0; i < channelSampleBuffer_src.Length; i++) + channelSampleBuffer_handle[i].ReleaseGCHandle(); + } + + channelSampleBuffer_src = new short[length1][]; + channelSampleBuffer_handle = new GCHandle[length1]; + channelSampleBuffer = new short*[length1]; + channelSampleBuffer_writePos = 0; + for (int i = 0; i < channelSampleBuffer_src.Length; i++) + { + channelSampleBuffer_src[i] = new short[Lenght2]; + channelSampleBuffer_src[i].GetObjectPtr(ref channelSampleBuffer_handle[i], ref channelSampleBuffer[i]); + } + } + #endregion + + + //protected List mixedSampleBuffer; + + #region //指针化 mixedSampleBuffer + short[] mixedSampleBuffer_src; + GCHandle mixedSampleBuffer_handle; + public short* mixedSampleBuffer; + public int mixedSampleBufferLength; + public int mixedSampleBuffer_writePos; + public bool mixedSampleBuffer_IsNull => mixedSampleBuffer == null; + public short[] mixedSampleBuffer_set + { + set + { + mixedSampleBuffer_handle.ReleaseGCHandle(); + mixedSampleBuffer_src = value; + mixedSampleBufferLength = value.Length; + mixedSampleBuffer_writePos = 0; + mixedSampleBuffer_src.GetObjectPtr(ref mixedSampleBuffer_handle, ref mixedSampleBuffer); + } + } + #endregion + + + public virtual event EventHandler 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[numChannels]; + //for (int i = 0; i < numChannels; i++) channelSampleBuffer[i] = new List(); + //mixedSampleBuffer = new List(); + + //改为二维数组 + channelSampleBuffer_Init(numChannels, 1470); + mixedSampleBuffer_set = new short[1470]; + + + 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)) + if (mixedSampleBuffer_writePos >= (samplesPerFrame * numOutputChannels)) + { + //EnqueueSamplesEventArgs eventArgs = EnqueueSamplesEventArgs.Create( + // numChannels, + // channelSampleBuffer.Select(x => x.ToArray()).ToArray(), + // new bool[] { !channel1ForceEnable, !channel2ForceEnable, !channel3ForceEnable, !channel4ForceEnable }, + // mixedSampleBuffer.ToArray()); + + EnqueueSamplesEventArgs eventArgs = EnqueueSamplesEventArgs.Create( + numChannels, + channelSampleBuffer, + new bool[] { !channel1ForceEnable, !channel2ForceEnable, !channel3ForceEnable, !channel4ForceEnable }, + mixedSampleBuffer, + mixedSampleBufferLength); + + OnEnqueueSamples(eventArgs); + + FlushSamples(); + + eventArgs.Release(); + + } + + 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); + + //二维指针下标 + channelSampleBuffer_writePos++; + channelSampleBuffer[0][channelSampleBuffer_writePos] = ch1; + channelSampleBuffer[1][channelSampleBuffer_writePos] = ch2; + channelSampleBuffer[2][channelSampleBuffer_writePos] = ch3; + channelSampleBuffer[3][channelSampleBuffer_writePos] = 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); + //指针下标 + mixedSampleBuffer_writePos++; + mixedSampleBuffer[mixedSampleBuffer_writePos] = (short)mixed; + } + } + + public void FlushSamples() + { + //for (int i = 0; i < numChannels; i++) + // channelSampleBuffer[i].Clear(); + channelSampleBuffer_writePos = 0; + + //mixedSampleBuffer.Clear(); + mixedSampleBuffer_writePos = 0; + } + + 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SN76489.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SN76489.cs.meta new file mode 100644 index 00000000..2369837b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SN76489.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5b081ff484b016a4faafebb32b5d6bd1 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaGGPSG.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaGGPSG.cs new file mode 100644 index 00000000..f1ce6f55 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaGGPSG.cs @@ -0,0 +1,88 @@ +using Essgee.Utilities; + +namespace Essgee.Emulation.Audio +{ + public unsafe 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); + + //二维指针下标 + channelSampleBuffer_writePos++; + channelSampleBuffer[0][channelSampleBuffer_writePos] = ch1; + channelSampleBuffer[1][channelSampleBuffer_writePos] = ch2; + channelSampleBuffer[2][channelSampleBuffer_writePos] = ch3; + channelSampleBuffer[3][channelSampleBuffer_writePos] = 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); + //指针下标 + mixedSampleBuffer_writePos++; + mixedSampleBuffer[mixedSampleBuffer_writePos] = (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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaGGPSG.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaGGPSG.cs.meta new file mode 100644 index 00000000..0be21bfd --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaGGPSG.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 63b4d9dd3dcd81942aab4e783f7ca197 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaSMSPSG.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaSMSPSG.cs new file mode 100644 index 00000000..8480b473 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaSMSPSG.cs @@ -0,0 +1,19 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaSMSPSG.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaSMSPSG.cs.meta new file mode 100644 index 00000000..69fd5652 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Audio/SegaSMSPSG.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: bdf808e342a96d74fbbe4a6ea16509eb \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU.meta new file mode 100644 index 00000000..09cd98cc --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 11616634c9d44b541b8505293e437778 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/ICPU.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/ICPU.cs new file mode 100644 index 00000000..2193de41 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/ICPU.cs @@ -0,0 +1,13 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/ICPU.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/ICPU.cs.meta new file mode 100644 index 00000000..74619681 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/ICPU.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d1eb29143cb5d8e49bb3ebe09e89a215 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.CycleCounts.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.CycleCounts.cs new file mode 100644 index 00000000..d22affd9 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.CycleCounts.cs @@ -0,0 +1,53 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.CycleCounts.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.CycleCounts.cs.meta new file mode 100644 index 00000000..cd0268bd --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.CycleCounts.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e1d2dab87d28f044ca71114b277aee3a \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Disassembly.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Disassembly.cs new file mode 100644 index 00000000..a3823d90 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Disassembly.cs @@ -0,0 +1,201 @@ +using System.Linq; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Disassembly.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Disassembly.cs.meta new file mode 100644 index 00000000..d48ca216 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Disassembly.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7de4aca2c5f71b94b9509b8ad7b6ef8f \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesNoPrefix.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesNoPrefix.cs new file mode 100644 index 00000000..7d37dcb7 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesNoPrefix.cs @@ -0,0 +1,281 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesNoPrefix.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesNoPrefix.cs.meta new file mode 100644 index 00000000..4c94393a --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesNoPrefix.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e4fe2d8ddc2d10849b6c7c6d64db0d4f \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesPrefixCB.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesPrefixCB.cs new file mode 100644 index 00000000..f88d7e3d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesPrefixCB.cs @@ -0,0 +1,281 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesPrefixCB.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesPrefixCB.cs.meta new file mode 100644 index 00000000..6e4d3dfa --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.OpcodesPrefixCB.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 36c6024a407a43f43bf41f1e38274ad9 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Register.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Register.cs new file mode 100644 index 00000000..873c2d33 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Register.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Register.cs.meta new file mode 100644 index 00000000..024f0ea0 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.Register.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e29886b06189649459149576fcb83444 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.cs new file mode 100644 index 00000000..5fb6e4b7 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.cs @@ -0,0 +1,972 @@ +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +using System.Linq; +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.cs.meta new file mode 100644 index 00000000..00c9c494 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 18c6dc23d0278e847b2cc8483998c6fc \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83CGB.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83CGB.cs new file mode 100644 index 00000000..d07c091d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83CGB.cs @@ -0,0 +1,57 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83CGB.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83CGB.cs.meta new file mode 100644 index 00000000..76928941 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/SM83CGB.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cf45a7372b4385e429895c45fe9f4db0 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.CycleCounts.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.CycleCounts.cs new file mode 100644 index 00000000..ed9f3405 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.CycleCounts.cs @@ -0,0 +1,95 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.CycleCounts.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.CycleCounts.cs.meta new file mode 100644 index 00000000..a72f1faf --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.CycleCounts.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3c40dfcb38d98fe45a313cee0df21449 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Disassembly.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Disassembly.cs new file mode 100644 index 00000000..a6008fde --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Disassembly.cs @@ -0,0 +1,485 @@ +using System.Linq; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Disassembly.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Disassembly.cs.meta new file mode 100644 index 00000000..b8e260b8 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Disassembly.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9fa152db63418e74ab7f359175172290 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesNoPrefix.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesNoPrefix.cs new file mode 100644 index 00000000..ab2fa186 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesNoPrefix.cs @@ -0,0 +1,283 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesNoPrefix.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesNoPrefix.cs.meta new file mode 100644 index 00000000..5088845a --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesNoPrefix.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a8c690c6cf2d024418c1412af53e453f \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixCB.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixCB.cs new file mode 100644 index 00000000..e3ac5370 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixCB.cs @@ -0,0 +1,281 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixCB.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixCB.cs.meta new file mode 100644 index 00000000..8da79c87 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixCB.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1eaf728772256b548a5dd0f76aac95a8 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs new file mode 100644 index 00000000..bb0dd646 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs @@ -0,0 +1,281 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs.meta new file mode 100644 index 00000000..6aa7faf6 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFD.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 59243a6d2ecd14d41816ff23fd5204cb \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs new file mode 100644 index 00000000..8723f467 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs @@ -0,0 +1,281 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs.meta new file mode 100644 index 00000000..7e9f34d0 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixDDFDCB.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9f1c07a0773a3a94486e1d50ed7552cb \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixED.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixED.cs new file mode 100644 index 00000000..3032c206 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixED.cs @@ -0,0 +1,281 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixED.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixED.cs.meta new file mode 100644 index 00000000..d58a23f4 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.OpcodesPrefixED.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5a8dae171d8e67546b93ab06aafbcb53 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Register.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Register.cs new file mode 100644 index 00000000..26af5c11 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Register.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Register.cs.meta new file mode 100644 index 00000000..37eb9956 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.Register.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 09764bf46e9451741a9fa2016022c5ea \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.cs new file mode 100644 index 00000000..c6066d04 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.cs @@ -0,0 +1,1498 @@ +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.cs.meta new file mode 100644 index 00000000..caf0cd9a --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CPU/Z80A.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 02ea1ee0691d94f4ba12b23ecea75623 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CartridgeLoader.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CartridgeLoader.cs new file mode 100644 index 00000000..596b948b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CartridgeLoader.cs @@ -0,0 +1,78 @@ +using Essgee.Emulation.Machines; +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +using System.Collections.Generic; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Reflection; + +namespace Essgee.Emulation +{ + public static class CartridgeLoader + { + static Dictionary fileExtensionSystemDictionary; + + static CartridgeLoader() + { + fileExtensionSystemDictionary = new Dictionary(); + foreach (var machineType in Assembly.GetExecutingAssembly().GetTypes().Where(x => typeof(IMachine).IsAssignableFrom(x) && !x.IsInterface).OrderBy(x => x.GetCustomAttribute()?.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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CartridgeLoader.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CartridgeLoader.cs.meta new file mode 100644 index 00000000..7f7ea3c9 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/CartridgeLoader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f1b72196f7ea1bf41b9b580316878200 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges.meta new file mode 100644 index 00000000..603fa142 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fd37849c4f2dfbb4fa2e82cdc6fc36e3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Coleco.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Coleco.meta new file mode 100644 index 00000000..f6a383c7 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Coleco.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3f9cb94bc81a41e45a844926b14f89eb +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Coleco/ColecoCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Coleco/ColecoCartridge.cs new file mode 100644 index 00000000..5278bcfb --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Coleco/ColecoCartridge.cs @@ -0,0 +1,78 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Coleco/ColecoCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Coleco/ColecoCartridge.cs.meta new file mode 100644 index 00000000..d0a208b8 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Coleco/ColecoCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9d70811b8aea410499364dc0485491e3 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/ICartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/ICartridge.cs new file mode 100644 index 00000000..ef381702 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/ICartridge.cs @@ -0,0 +1,20 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/ICartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/ICartridge.cs.meta new file mode 100644 index 00000000..99393f89 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/ICartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 69d4af746d98c1b4ab29d5be6b2d9f2d \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo.meta new file mode 100644 index 00000000..196bc877 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3eb99a6b5fa5ea348b79aa6dfc080f12 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs new file mode 100644 index 00000000..62fe3967 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs @@ -0,0 +1,421 @@ +using System; +using System.ComponentModel; +using System.Linq; + +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 = EmuStandInfo.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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs.meta new file mode 100644 index 00000000..e6b4a2c7 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/GBCameraCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3887c3ff437835449bdf694c3e812afa \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs new file mode 100644 index 00000000..9f039c0b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs @@ -0,0 +1,7 @@ +namespace Essgee.Emulation.Cartridges.Nintendo +{ + public interface IGameBoyCartridge : ICartridge + { + void SetCartridgeConfig(bool battery, bool rtc, bool rumble); + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs.meta new file mode 100644 index 00000000..9fee5771 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/IGameBoyCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 8f215e1ba8582ab42b21a0dae809cb6e \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs new file mode 100644 index 00000000..93afc77d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs @@ -0,0 +1,132 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs.meta new file mode 100644 index 00000000..975cb253 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC1Cartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9e0eda098544f8e4aa894031f92b2b57 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs new file mode 100644 index 00000000..c84e692b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs @@ -0,0 +1,123 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs.meta new file mode 100644 index 00000000..1b7cd176 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC2Cartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b5795dc3ee1b33746a64186a778e2bd6 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs new file mode 100644 index 00000000..60d7886d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs @@ -0,0 +1,261 @@ +using System; +using System.Linq; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs.meta new file mode 100644 index 00000000..123814f7 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC3Cartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 739832bbd2b7ce54e8658790d0d1b4ae \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs new file mode 100644 index 00000000..63889c94 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs @@ -0,0 +1,137 @@ +using System; + +namespace Essgee.Emulation.Cartridges.Nintendo +{ + // TODO: rumble? + + public class MBC5Cartridge : IGameBoyCartridge + { + public event EventHandler 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs.meta new file mode 100644 index 00000000..5427293d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/MBC5Cartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f083c0cfa9c1cf54ba90f19320c53c16 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs new file mode 100644 index 00000000..68d1a792 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs @@ -0,0 +1,77 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs.meta new file mode 100644 index 00000000..e426369d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/NoMapperCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 90768449a6b38a440b08bb61118b37b7 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/SpecializedLoader.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/SpecializedLoader.cs new file mode 100644 index 00000000..c6028f2e --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/SpecializedLoader.cs @@ -0,0 +1,101 @@ +using Essgee.Exceptions; +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/SpecializedLoader.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/SpecializedLoader.cs.meta new file mode 100644 index 00000000..93599c17 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Nintendo/SpecializedLoader.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 40e53d5096e06264a829b803422f047e \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega.meta new file mode 100644 index 00000000..d73ad575 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ce378fe5a7c0ff14e8ffd0ad6c86ac65 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/CodemastersCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/CodemastersCartridge.cs new file mode 100644 index 00000000..d61c7fd0 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/CodemastersCartridge.cs @@ -0,0 +1,120 @@ +using Essgee.Exceptions; +using Essgee.Utilities; +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/CodemastersCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/CodemastersCartridge.cs.meta new file mode 100644 index 00000000..465846d0 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/CodemastersCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f7cca9db8813d3c45a1ce0cfb7792b71 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs new file mode 100644 index 00000000..44477a32 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs @@ -0,0 +1,91 @@ +using Essgee.Exceptions; +using Essgee.Utilities; +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs.meta new file mode 100644 index 00000000..fa5e9fc1 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMSX8kMapperCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ec1ba102183679945bca01f524bb1868 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs new file mode 100644 index 00000000..37a5d00f --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs @@ -0,0 +1,94 @@ +using Essgee.Exceptions; +using Essgee.Utilities; +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs.meta new file mode 100644 index 00000000..221eb0d2 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanMapperCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 510ae156eb8cf4e4fb7c5fa573062f07 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs new file mode 100644 index 00000000..299e19b1 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs @@ -0,0 +1,175 @@ +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs.meta new file mode 100644 index 00000000..d27b63ef --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/KoreanSpriteMapperCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9ebf387a119ca924bac38d2244e917ec \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs new file mode 100644 index 00000000..33935e00 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs @@ -0,0 +1,99 @@ +using Essgee.Exceptions; +using Essgee.Utilities; +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs.meta new file mode 100644 index 00000000..4a3a9e81 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/Multicart4PakAllActionCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 92b6b8db8e7eac6469ed11bfe02b5428 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaMapperCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaMapperCartridge.cs new file mode 100644 index 00000000..0d987e57 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaMapperCartridge.cs @@ -0,0 +1,147 @@ +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaMapperCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaMapperCartridge.cs.meta new file mode 100644 index 00000000..51d6ef23 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaMapperCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d86b15c129c010f4f911b9aba4e5670a \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaSGCartridge.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaSGCartridge.cs new file mode 100644 index 00000000..8849c9fb --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaSGCartridge.cs @@ -0,0 +1,89 @@ +using Essgee.Utilities; +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaSGCartridge.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaSGCartridge.cs.meta new file mode 100644 index 00000000..cbd2155c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Cartridges/Sega/SegaSGCartridge.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a9ece9b21b502a2468df0428e956bbba \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration.meta new file mode 100644 index 00000000..20d4d891 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f8270d4162b9c6a42afbed3387a40a37 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/ColecoVision.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/ColecoVision.cs new file mode 100644 index 00000000..761ae479 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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 EssgeeMotionKey ControlsUp { get; set; } + //todo Unity [DropDownControl("Controls", "Down", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsDown { get; set; } + //todo Unity [DropDownControl("Controls", "Left", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsLeft { get; set; } + //todo Unity [DropDownControl("Controls", "Right", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsRight { get; set; } + //todo Unity [DropDownControl("Controls", "Left Button", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsButtonLeft { get; set; } + //todo Unity [DropDownControl("Controls", "Right Button", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsButtonRight { get; set; } + + //todo Unity [DropDownControl("Controls", "Keypad 1", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypad1 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 2", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypad2 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 3", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypad3 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 4", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypad4 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 5", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypad5 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 6", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypad6 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 7", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypad7 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 8", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypad8 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 9", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypad9 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad 0", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypad0 { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad *", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypadStar { get; set; } + //todo Unity [DropDownControl("Controls", "Keypad #", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsKeypadNumberSign { get; set; } + + public ColecoVision() + { + BiosRom = string.Empty; + + ControlsUp = EssgeeMotionKey.Up; + ControlsDown = EssgeeMotionKey.Down; + ControlsLeft = EssgeeMotionKey.Left; + ControlsRight = EssgeeMotionKey.Right; + ControlsButtonLeft = EssgeeMotionKey.A; + ControlsButtonRight = EssgeeMotionKey.S; + + ControlsKeypad1 = EssgeeMotionKey.NumPad1; + ControlsKeypad2 = EssgeeMotionKey.NumPad2; + ControlsKeypad3 = EssgeeMotionKey.NumPad3; + ControlsKeypad4 = EssgeeMotionKey.NumPad4; + ControlsKeypad5 = EssgeeMotionKey.NumPad5; + ControlsKeypad6 = EssgeeMotionKey.NumPad6; + ControlsKeypad7 = EssgeeMotionKey.NumPad7; + ControlsKeypad8 = EssgeeMotionKey.NumPad8; + ControlsKeypad9 = EssgeeMotionKey.NumPad9; + ControlsKeypad0 = EssgeeMotionKey.NumPad0; + ControlsKeypadStar = EssgeeMotionKey.Multiply; + ControlsKeypadNumberSign = EssgeeMotionKey.Divide; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/ColecoVision.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/ColecoVision.cs.meta new file mode 100644 index 00000000..ba580cf6 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/ColecoVision.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4ae1eb77c5770f141917f1d7cb917ca1 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoy.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoy.cs new file mode 100644 index 00000000..a0a66913 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoy.cs @@ -0,0 +1,83 @@ +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 +{ + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] + public class ElementPriorityAttribute : Attribute + { + public int Priority { get; set; } + + public ElementPriorityAttribute(int priority) + { + Priority = priority; + } + } + + [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 EssgeeMotionKey ControlsUp { get; set; } + //todo Unity [DropDownControl("Controls", "Down", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsDown { get; set; } + //todo Unity [DropDownControl("Controls", "Left", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsLeft { get; set; } + //todo Unity [DropDownControl("Controls", "Right", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsRight { get; set; } + //todo Unity [DropDownControl("Controls", "A", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsA { get; set; } + //todo Unity [DropDownControl("Controls", "B", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsB { get; set; } + //todo Unity [DropDownControl("Controls", "Select", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsSelect { get; set; } + //todo Unity [DropDownControl("Controls", "Start", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsStart { get; set; } + + public GameBoy() + { + UseBootstrap = false; + BootstrapRom = string.Empty; + + SerialDevice = typeof(DummyDevice); + CameraSource = GBCameraCartridge.ImageSources.Noise; + CameraImageFile = string.Empty; + + ControlsUp = EssgeeMotionKey.Up; + ControlsDown = EssgeeMotionKey.Down; + ControlsLeft = EssgeeMotionKey.Left; + ControlsRight = EssgeeMotionKey.Right; + ControlsA = EssgeeMotionKey.S; + ControlsB = EssgeeMotionKey.A; + ControlsSelect = EssgeeMotionKey.Space; + ControlsStart = EssgeeMotionKey.Enter; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoy.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoy.cs.meta new file mode 100644 index 00000000..cfee7f82 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0f330bdde80f4154a97f9d8e34402812 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoyColor.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoyColor.cs new file mode 100644 index 00000000..83e929f8 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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 EssgeeMotionKey ControlsUp { get; set; } + //todo Unity [DropDownControl("Controls", "Down", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsDown { get; set; } + //todo Unity [DropDownControl("Controls", "Left", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsLeft { get; set; } + //todo Unity [DropDownControl("Controls", "Right", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsRight { get; set; } + //todo Unity [DropDownControl("Controls", "A", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsA { get; set; } + //todo Unity [DropDownControl("Controls", "B", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsB { get; set; } + //todo Unity [DropDownControl("Controls", "Select", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsSelect { get; set; } + //todo Unity [DropDownControl("Controls", "Start", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsStart { get; set; } + //todo Unity [DropDownControl("Controls", "Send IR Signal", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey 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 = EssgeeMotionKey.Up; + ControlsDown = EssgeeMotionKey.Down; + ControlsLeft = EssgeeMotionKey.Left; + ControlsRight = EssgeeMotionKey.Right; + ControlsA = EssgeeMotionKey.S; + ControlsB = EssgeeMotionKey.A; + ControlsSelect = EssgeeMotionKey.Space; + ControlsStart = EssgeeMotionKey.Enter; + ControlsSendIR = EssgeeMotionKey.Back; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoyColor.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoyColor.cs.meta new file mode 100644 index 00000000..0af47d0c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameBoyColor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4e72130cc49f0324fa58c96d9aaa4771 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameGear.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameGear.cs new file mode 100644 index 00000000..9297077f --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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 EssgeeMotionKey ControlsUp { get; set; } + //todo Unity [DropDownControl("Controls", "D-Pad Down", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsDown { get; set; } + //todo Unity [DropDownControl("Controls", "D-Pad Left", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsLeft { get; set; } + //todo Unity [DropDownControl("Controls", "D-Pad Right", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsRight { get; set; } + //todo Unity [DropDownControl("Controls", "Button 1", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsButton1 { get; set; } + //todo Unity [DropDownControl("Controls", "Button 2", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsButton2 { get; set; } + //todo Unity [DropDownControl("Controls", "Start", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey ControlsStart { get; set; } + + public bool AllowMemoryControl { get; set; } + + public GameGear() + { + BootstrapRom = string.Empty; + + Region = Region.Export; + + ControlsUp = EssgeeMotionKey.Up; + ControlsDown = EssgeeMotionKey.Down; + ControlsLeft = EssgeeMotionKey.Left; + ControlsRight = EssgeeMotionKey.Right; + ControlsButton1 = EssgeeMotionKey.A; + ControlsButton2 = EssgeeMotionKey.S; + ControlsStart = EssgeeMotionKey.Return; + + AllowMemoryControl = true; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameGear.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameGear.cs.meta new file mode 100644 index 00000000..c6c0a6a3 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/GameGear.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f8c8deb0a7eac484e8b82758c20c92cd \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/IConfiguration.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/IConfiguration.cs new file mode 100644 index 00000000..6723941f --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/IConfiguration.cs @@ -0,0 +1,4 @@ +namespace Essgee.Emulation.Configuration +{ + public interface IConfiguration { } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/IConfiguration.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/IConfiguration.cs.meta new file mode 100644 index 00000000..700b26aa --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/IConfiguration.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 12f32f85f1566d740bd12985ea4a9834 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/MasterSystem.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/MasterSystem.cs new file mode 100644 index 00000000..b6fc5697 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/MasterSystem.cs @@ -0,0 +1,113 @@ +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 EssgeeMotionKey InputPause { get; set; } + //todo Unity [DropDownControl("General", "Reset Button", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey 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 EssgeeMotionKey Joypad1Up { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Down", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Down { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Left", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Left { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Right", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Right + { + get; + set; + } + //todo Unity [DropDownControl("Controller Port 1", "Button 1", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 2", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey 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 EssgeeMotionKey Joypad2Up { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Down", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Down { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Left", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Left { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Right", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Right { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 1", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 2", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Button2 { get; set; } + + public bool AllowMemoryControl { get; set; } + + public MasterSystem() + { + BootstrapRom = string.Empty; + + TVStandard = TVStandard.NTSC; + Region = Region.Export; + VDPType = VDPTypes.SMS2GG; + + InputPause = EssgeeMotionKey.Space; + InputReset = EssgeeMotionKey.Back; + + Joypad1DeviceType = InputDevice.Controller; + Joypad1Up = EssgeeMotionKey.Up; + Joypad1Down = EssgeeMotionKey.Down; + Joypad1Left = EssgeeMotionKey.Left; + Joypad1Right = EssgeeMotionKey.Right; + Joypad1Button1 = EssgeeMotionKey.A; + Joypad1Button2 = EssgeeMotionKey.S; + + Joypad2DeviceType = InputDevice.Controller; + Joypad2Up = EssgeeMotionKey.NumPad8; + Joypad2Down = EssgeeMotionKey.NumPad2; + Joypad2Left = EssgeeMotionKey.NumPad4; + Joypad2Right = EssgeeMotionKey.NumPad6; + Joypad2Button1 = EssgeeMotionKey.NumPad1; + Joypad2Button2 = EssgeeMotionKey.NumPad3; + + AllowMemoryControl = true; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/MasterSystem.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/MasterSystem.cs.meta new file mode 100644 index 00000000..5262134d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/MasterSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 28c56adc13b701a4580ddd74b71da8f6 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/SC3000.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/SC3000.cs new file mode 100644 index 00000000..6c82e4ad --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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 EssgeeMotionKey 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 EssgeeMotionKey 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 EssgeeMotionKey InputPlayTape { get; set; } + + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Up", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Up { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Down", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Down { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Left", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Left { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Right", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Right { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 1", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 2", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Button2 { get; set; } + + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Up", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Up { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Down", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Down { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Left", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Left { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Right", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Right { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 1", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 2", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Button2 { get; set; } + + public SC3000() + { + TVStandard = TVStandard.NTSC; + + InputReset = EssgeeMotionKey.F12; + InputChangeMode = EssgeeMotionKey.F1; + InputPlayTape = EssgeeMotionKey.F2; + + Joypad1Up = EssgeeMotionKey.Up; + Joypad1Down = EssgeeMotionKey.Down; + Joypad1Left = EssgeeMotionKey.Left; + Joypad1Right = EssgeeMotionKey.Right; + Joypad1Button1 = EssgeeMotionKey.A; + Joypad1Button2 = EssgeeMotionKey.S; + + Joypad2Up = EssgeeMotionKey.NumPad8; + Joypad2Down = EssgeeMotionKey.NumPad2; + Joypad2Left = EssgeeMotionKey.NumPad4; + Joypad2Right = EssgeeMotionKey.NumPad6; + Joypad2Button1 = EssgeeMotionKey.NumPad1; + Joypad2Button2 = EssgeeMotionKey.NumPad3; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/SC3000.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/SC3000.cs.meta new file mode 100644 index 00000000..d6603af2 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/SC3000.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 727a7522f4818ff44a2728f98cda2b66 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/SG1000.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/SG1000.cs new file mode 100644 index 00000000..3a8eca49 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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 EssgeeMotionKey InputPause { get; set; } + + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Up", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Up { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Down", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Down { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Left", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Left { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "D-Pad Right", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Right { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 1", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 1", "Button 2", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad1Button2 { get; set; } + + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Up", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Up { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Down", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Down { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Left", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Left { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "D-Pad Right", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Right { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 1", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Button1 { get; set; } + //todo Unity [DropDownControl("Controller Port 2", "Button 2", typeof(Keys), Keys.F11)] + //[JsonConverter(typeof(StringEnumConverter))] + public EssgeeMotionKey Joypad2Button2 { get; set; } + + public SG1000() + { + TVStandard = TVStandard.NTSC; + + InputPause = EssgeeMotionKey.Space; + + Joypad1Up = EssgeeMotionKey.Up; + Joypad1Down = EssgeeMotionKey.Down; + Joypad1Left = EssgeeMotionKey.Left; + Joypad1Right = EssgeeMotionKey.Right; + Joypad1Button1 = EssgeeMotionKey.A; + Joypad1Button2 = EssgeeMotionKey.S; + + Joypad2Up = EssgeeMotionKey.NumPad8; + Joypad2Down = EssgeeMotionKey.NumPad2; + Joypad2Left = EssgeeMotionKey.NumPad4; + Joypad2Right = EssgeeMotionKey.NumPad6; + Joypad2Button1 = EssgeeMotionKey.NumPad1; + Joypad2Button2 = EssgeeMotionKey.NumPad3; + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/SG1000.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/SG1000.cs.meta new file mode 100644 index 00000000..75083d09 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Configuration/SG1000.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ff8803776ef43a6489bbe3909015e4af \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/EmulatorHandler.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/EmulatorHandler.cs new file mode 100644 index 00000000..33f0d192 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/EmulatorHandler.cs @@ -0,0 +1,369 @@ +using Essgee.Emulation.Configuration; +using Essgee.Emulation.Machines; +using Essgee.EventArguments; +using Essgee.Metadata; +using Essgee.Utilities; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Threading; + +namespace Essgee.Emulation +{ + public class EmulatorHandler + { + readonly Action exceptionHandler; + + public IMachine emulator { get; private set; } + + 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 pauseStateChangesRequested = new Queue(); + + public event EventHandler SendLogMessage + { + add { emulator.SendLogMessage += value; } + remove { emulator.SendLogMessage -= value; } + } + + public event EventHandler EmulationReset + { + add { emulator.EmulationReset += value; } + remove { emulator.EmulationReset -= value; } + } + + public event EventHandler RenderScreen + { + add { emulator.RenderScreen += value; } + remove { emulator.RenderScreen -= value; } + } + + public event EventHandler SizeScreen + { + add { emulator.SizeScreen += value; } + remove { emulator.SizeScreen -= value; } + } + + public event EventHandler ChangeViewport + { + add { emulator.ChangeViewport += value; } + remove { emulator.ChangeViewport -= value; } + } + + public event EventHandler PollInput + { + add { emulator.PollInput += value; } + remove { emulator.PollInput -= value; } + } + + public event EventHandler EnqueueSamples + { + add { emulator.EnqueueSamples += value; } + remove { emulator.EnqueueSamples -= value; } + } + + public event EventHandler SaveExtraData + { + add { emulator.SaveExtraData += value; } + remove { emulator.SaveExtraData -= value; } + } + + public event EventHandler EnableRumble + { + add { emulator.EnableRumble += value; } + remove { emulator.EnableRumble -= value; } + } + + public event EventHandler 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 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(EmuStandInfo.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) + { + //初始化AxiMem + AxiMemoryEx.Init(); + + currentGameMetadata = gameMetadata; + + byte[] ramData = new byte[currentGameMetadata.RamSize]; + + var savePath = Path.Combine(EmuStandInfo.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(EmuStandInfo.SaveDataPath, Path.ChangeExtension(currentGameMetadata.FileName, "sav")); + File.WriteAllBytes(savePath, ramData); + } + } + + public Dictionary 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; } + + public void Update_Frame() + { + if (!emulationThreadRunning) + return; + + while (pauseStateChangesRequested.Count > 0) + { + var newPauseState = pauseStateChangesRequested.Dequeue(); + emulationThreadPaused = newPauseState; + + PauseChanged?.Invoke(this, EventArgs.Empty); + } + + emulator.RunFrame(); + + + if (configChangeRequested) + { + emulator.SetConfiguration(newConfiguration); + configChangeRequested = false; + } + + } + + 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/EmulatorHandler.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/EmulatorHandler.cs.meta new file mode 100644 index 00000000..21be8978 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/EmulatorHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2a3dbee1ff1b11c4f82f85a480959bee \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Enumerations.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Enumerations.cs new file mode 100644 index 00000000..ce95808e --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Enumerations.cs @@ -0,0 +1,55 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Enumerations.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Enumerations.cs.meta new file mode 100644 index 00000000..b057d8be --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Enumerations.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3dcbfdb676ca9d943a2c4e7049a71487 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices.meta new file mode 100644 index 00000000..8528b60f --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 380af0157665736438ef805bfda57a98 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo.meta new file mode 100644 index 00000000..a12b50b0 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b00fddc211d9d854580cc2b110c5e524 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/DummyDevice.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/DummyDevice.cs new file mode 100644 index 00000000..2fcc2e33 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/DummyDevice.cs @@ -0,0 +1,18 @@ +using Essgee.EventArguments; +using System; +using System.ComponentModel; + +namespace Essgee.Emulation.ExtDevices.Nintendo +{ + [Description("None")] + //todo Unity [ElementPriority(0)] + public class DummyDevice : ISerialDevice + { + public event EventHandler 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/DummyDevice.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/DummyDevice.cs.meta new file mode 100644 index 00000000..1c152dcb --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/DummyDevice.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0ac549cbc5bf99c48933ba2369c6838e \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GBPrinter.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GBPrinter.cs new file mode 100644 index 00000000..08a68c3a --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GBPrinter.cs @@ -0,0 +1,432 @@ +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 imageData; + byte marginBefore, marginAfter, palette, exposure; + + int imageHeight; + int printDelay; + + byte serialData; + + public event EventHandler SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public GBPrinter() + { + imageData = new List(); + } + + 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 decomp = new List(); + List decomp = ObjectPoolAuto.AcquireList(); + + 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; + + ObjectPoolAuto.Release(decomp); + } + + 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GBPrinter.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GBPrinter.cs.meta new file mode 100644 index 00000000..67701229 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GBPrinter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 46eed2246cd4ff74792eeedfbe26b3e6 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs new file mode 100644 index 00000000..40029073 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs @@ -0,0 +1,87 @@ +using Essgee.EventArguments; +using System; +using System.ComponentModel; +using System.IO; +using System.IO.MemoryMappedFiles; + +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 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs.meta new file mode 100644 index 00000000..66382cf5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/GameBoyIPC.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ffe92e23b6dc04e4a9827a21262ccfe3 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/ISerialDevice.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/ISerialDevice.cs new file mode 100644 index 00000000..97a3cc2c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/ISerialDevice.cs @@ -0,0 +1,15 @@ +using Essgee.EventArguments; +using System; + +namespace Essgee.Emulation.ExtDevices.Nintendo +{ + public interface ISerialDevice + { + event EventHandler SaveExtraData; + + void Initialize(); + void Shutdown(); + + byte ExchangeBit(int left, byte data); + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/ISerialDevice.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/ISerialDevice.cs.meta new file mode 100644 index 00000000..6f7fce5a --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/ExtDevices/Nintendo/ISerialDevice.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b2db0a79b5ac7344990fd1e3711a8a4a \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines.meta new file mode 100644 index 00000000..5ba35678 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5c3c1bb41ecdcb043ac9083b76f8dee4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/ColecoVision.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/ColecoVision.cs new file mode 100644 index 00000000..7df654ed --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/ColecoVision.cs @@ -0,0 +1,444 @@ +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges; +using Essgee.Emulation.Cartridges.Coleco; +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.Video; +using Essgee.EventArguments; +using Essgee.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +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 SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler RenderScreen + { + add { vdp.RenderScreen += value; } + remove { vdp.RenderScreen -= value; } + } + + public event EventHandler SizeScreen + { + add { vdp.SizeScreen += value; } + remove { vdp.SizeScreen -= value; } + } + + public event EventHandler ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler EnqueueSamples + { + add { psg.EnqueueSamples += value; } + remove { psg.EnqueueSamples -= value; } + } + + public event EventHandler SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler 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 = PollInputEventArgs.Create(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + pollInputEventArgs.Release(); + + }; + } + + 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(EmuStandInfo.Configuration.SampleRate); + psg?.SetOutputChannels(2); + psg?.SetClockRate(psgClock); + psg?.SetRefreshRate(refreshRate); + + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / refreshRate); + + var eventArgs = ChangeViewportEventArgs.Create(vdp.Viewport); + OnChangeViewport(eventArgs); + eventArgs.Release(); + } + + 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 state) + public void SetState(Dictionary state) + { + SaveStateHandler.PerformSetState(cartridge, (Dictionary)state[nameof(cartridge)]); + wram = (byte[])state[nameof(wram)]; + SaveStateHandler.PerformSetState(cpu, (Dictionary)state[nameof(cpu)]); + SaveStateHandler.PerformSetState(vdp, (Dictionary)state[nameof(vdp)]); + SaveStateHandler.PerformSetState(psg, (Dictionary)state[nameof(psg)]); + + portControls1 = (ushort)state[nameof(portControls1)]; + portControls2 = (ushort)state[nameof(portControls2)]; + controlsReadMode = (byte)state[nameof(controlsReadMode)]; + isNmi = (bool)state[nameof(isNmi)]; + isNmiPending = (bool)state[nameof(isNmiPending)]; + + ReconfigureSystem(); + } + + public Dictionary GetState() + { + return new Dictionary + { + [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 GetDebugInformation() + { + var dict = new Dictionary + { + { "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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/ColecoVision.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/ColecoVision.cs.meta new file mode 100644 index 00000000..f3f14c50 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/ColecoVision.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6861fcd381d9ac84c9ec16b4d8eb2bce \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoy.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoy.cs new file mode 100644 index 00000000..723811a8 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoy.cs @@ -0,0 +1,719 @@ +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges.Nintendo; +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.ExtDevices.Nintendo; +using Essgee.Emulation.Video.Nintendo; +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +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 SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler RenderScreen + { + add { video.RenderScreen += value; } + remove { video.RenderScreen -= value; } + } + + public event EventHandler SizeScreen + { + add { video.SizeScreen += value; } + remove { video.SizeScreen -= value; } + } + + public event EventHandler ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler EnqueueSamples + { + add { audio.EnqueueSamples += value; } + remove { audio.EnqueueSamples -= value; } + } + + public event EventHandler SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler 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; + + public Configuration.GameBoy configuration { get; private set; } + + 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 = PollInputEventArgs.Create(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + pollInputEventArgs.Release(); + }; + } + + 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(EmuStandInfo.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 */ + var eventArgs = ChangeViewportEventArgs.Create(video.Viewport); + OnChangeViewport(eventArgs); + eventArgs.Release(); + } + + 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 state) + { + throw new NotImplementedException(); + } + + public Dictionary GetState() + { + throw new NotImplementedException(); + } + + public Dictionary GetDebugInformation() + { + var dict = new Dictionary + { + { "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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoy.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoy.cs.meta new file mode 100644 index 00000000..93997691 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoy.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d24bc8f5b7f44b2439d6860b8d024aee \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoyColor.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoyColor.cs new file mode 100644 index 00000000..56c4f1cf --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoyColor.cs @@ -0,0 +1,877 @@ +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges.Nintendo; +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.ExtDevices.Nintendo; +using Essgee.Emulation.Video.Nintendo; +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; + +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 SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler RenderScreen + { + add { video.RenderScreen += value; } + remove { video.RenderScreen -= value; } + } + + public event EventHandler SizeScreen + { + add { video.SizeScreen += value; } + remove { video.SizeScreen -= value; } + } + + public event EventHandler ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler EnqueueSamples + { + add { audio.EnqueueSamples += value; } + remove { audio.EnqueueSamples -= value; } + } + + public event EventHandler SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler 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; + + public 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 = PollInputEventArgs.Create(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + pollInputEventArgs.Release(); + }; + } + + 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(EmuStandInfo.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 */ + var eventArgs = ChangeViewportEventArgs.Create(video.Viewport); + OnChangeViewport(eventArgs); + eventArgs.Release(); + } + + 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 state) + { + throw new NotImplementedException(); + } + + public Dictionary GetState() + { + throw new NotImplementedException(); + } + + public Dictionary GetDebugInformation() + { + var dict = new Dictionary + { + { "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 = (EmuStandInfo.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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoyColor.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoyColor.cs.meta new file mode 100644 index 00000000..39e580cc --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameBoyColor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4840cc971763acf498a4729ba103aef2 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameGear.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameGear.cs new file mode 100644 index 00000000..e396fca7 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameGear.cs @@ -0,0 +1,513 @@ +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 System; +using System.Collections.Generic; +using System.Linq; +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 SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler RenderScreen + { + add { vdp.RenderScreen += value; } + remove { vdp.RenderScreen -= value; } + } + + public event EventHandler SizeScreen + { + add { vdp.SizeScreen += value; } + remove { vdp.SizeScreen -= value; } + } + + public event EventHandler ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler EnqueueSamples + { + add { psg.EnqueueSamples += value; } + remove { psg.EnqueueSamples -= value; } + } + + public event EventHandler SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler 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; + + public 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 = PollInputEventArgs.Create(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + pollInputEventArgs.Release(); + }; + } + + 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(EmuStandInfo.Configuration.SampleRate); + psg?.SetOutputChannels(2); + psg?.SetClockRate(psgClock); + psg?.SetRefreshRate(refreshRate); + + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / refreshRate); + + var eventArgs = ChangeViewportEventArgs.Create(vdp.Viewport); + OnChangeViewport(eventArgs); + eventArgs.Release(); + } + + 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 state) + public void SetState(Dictionary state) + { + configuration.Region = (Region)state[nameof(configuration.Region)]; + + SaveStateHandler.PerformSetState(bootstrap, (Dictionary)state[nameof(bootstrap)]); + SaveStateHandler.PerformSetState(cartridge, (Dictionary)state[nameof(cartridge)]); + wram = (byte[])state[nameof(wram)]; + SaveStateHandler.PerformSetState(cpu, (Dictionary)state[nameof(cpu)]); + SaveStateHandler.PerformSetState(vdp, (Dictionary)state[nameof(vdp)]); + SaveStateHandler.PerformSetState(psg, (Dictionary)state[nameof(psg)]); + + portMemoryControl = (byte)state[nameof(portMemoryControl)]; + portIoControl = (byte)state[nameof(portIoControl)]; + hCounterLatched = (byte)state[nameof(hCounterLatched)]; + portIoAB = (byte)state[nameof(portIoAB)]; + portIoBMisc = (byte)state[nameof(portIoBMisc)]; + + portIoC = (byte)state[nameof(portIoC)]; + portParallelData = (byte)state[nameof(portParallelData)]; + portDataDirNMI = (byte)state[nameof(portDataDirNMI)]; + portTxBuffer = (byte)state[nameof(portTxBuffer)]; + portRxBuffer = (byte)state[nameof(portRxBuffer)]; + portSerialControl = (byte)state[nameof(portSerialControl)]; + + ReconfigureSystem(); + } + + public Dictionary GetState() + { + return new Dictionary + { + [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 GetDebugInformation() + { + var dict = new Dictionary + { + { "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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameGear.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameGear.cs.meta new file mode 100644 index 00000000..9925a158 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/GameGear.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: de9201bc81725c14e8ce6987146f89d8 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/IMachine.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/IMachine.cs new file mode 100644 index 00000000..3740adbc --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/IMachine.cs @@ -0,0 +1,50 @@ +using Essgee.Emulation.Configuration; +using Essgee.EventArguments; +using System; +using System.Collections.Generic; + +namespace Essgee.Emulation.Machines +{ + public interface IMachine + { + event EventHandler SendLogMessage; + event EventHandler EmulationReset; + event EventHandler RenderScreen; + event EventHandler SizeScreen; + event EventHandler ChangeViewport; + event EventHandler PollInput; + event EventHandler EnqueueSamples; + event EventHandler SaveExtraData; + event EventHandler 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 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 state); + Dictionary GetState(); + + void Load(byte[] romData, byte[] ramData, Type mapperType); + byte[] GetCartridgeRam(); + bool IsCartridgeRamSaveNeeded(); + + void RunFrame(); + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/IMachine.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/IMachine.cs.meta new file mode 100644 index 00000000..29f53d9c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/IMachine.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: e3358260543f04a48b308b485fdc8380 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/MasterSystem.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/MasterSystem.cs new file mode 100644 index 00000000..45699c2c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/MasterSystem.cs @@ -0,0 +1,650 @@ +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 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 SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler RenderScreen + { + add { vdp.RenderScreen += value; } + remove { vdp.RenderScreen -= value; } + } + + public event EventHandler SizeScreen + { + add { vdp.SizeScreen += value; } + remove { vdp.SizeScreen -= value; } + } + + public event EventHandler ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler EnqueueSamples + { + add { psg.EnqueueSamples += value; } + remove { psg.EnqueueSamples -= value; } + } + + public event EventHandler SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler 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; + + public Configuration.MasterSystem configuration { get; private set; } + + List 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(); + //lastControllerState = new ControllerState(); + + vdp.EndOfScanline += (s, e) => + { + PollInputEventArgs pollInputEventArgs = PollInputEventArgs.Create(); + OnPollInput(pollInputEventArgs); + + lastKeysDown.Clear(); + lastKeysDown.AddRange(pollInputEventArgs.Keyboard); + //lastControllerState = pollInputEventArgs.ControllerState; + lastMouseButtons = pollInputEventArgs.MouseButtons; + lastMousePosition = pollInputEventArgs.MousePosition; + + HandlePauseButton(); + pollInputEventArgs.Release(); + + }; + } + + 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(EmuStandInfo.Configuration.SampleRate); + psg?.SetOutputChannels(2); + psg?.SetClockRate(psgClock); + psg?.SetRefreshRate(RefreshRate); + + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / RefreshRate); + + var eventArgs = ChangeViewportEventArgs.Create(vdp.Viewport); + OnChangeViewport(eventArgs); + eventArgs.Release(); + + 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 state) + public void SetState(Dictionary state) + { + configuration.TVStandard = (TVStandard)state[nameof(configuration.TVStandard)]; + configuration.Region = (Region)state[nameof(configuration.Region)]; + + SaveStateHandler.PerformSetState(bootstrap, (Dictionary)state[nameof(bootstrap)]); + SaveStateHandler.PerformSetState(cartridge, (Dictionary)state[nameof(cartridge)]); + wram = (byte[])state[nameof(wram)]; + SaveStateHandler.PerformSetState(cpu, (Dictionary)state[nameof(cpu)]); + SaveStateHandler.PerformSetState(vdp, (Dictionary)state[nameof(vdp)]); + SaveStateHandler.PerformSetState(psg, (Dictionary)state[nameof(psg)]); + + inputDevices = (InputDevice[])state[nameof(inputDevices)]; + lightgunLatched = (bool)state[nameof(lightgunLatched)]; + + portMemoryControl = (byte)state[nameof(portMemoryControl)]; + portIoControl = (byte)state[nameof(portIoControl)]; + hCounterLatched = (byte)state[nameof(hCounterLatched)]; + + ReconfigureSystem(); + } + + public Dictionary GetState() + { + return new Dictionary + { + [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 GetDebugInformation() + { + var dict = new Dictionary + { + { "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)) + state &= (byte)~ControllerInputs.Up; + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Down : configuration.Joypad2Down)) + state &= (byte)~ControllerInputs.Down; + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Left : configuration.Joypad2Left)) + state &= (byte)~ControllerInputs.Left; + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Right : configuration.Joypad2Right)) + state &= (byte)~ControllerInputs.Right; + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Button1 : configuration.Joypad2Button1)) + state &= (byte)~ControllerInputs.TL; + if (lastKeysDown.Contains(port == 0 ? configuration.Joypad1Button2 : configuration.Joypad2Button2)) + state &= (byte)~ControllerInputs.TR; + + //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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/MasterSystem.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/MasterSystem.cs.meta new file mode 100644 index 00000000..f4de8b9a --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/MasterSystem.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3c4298b2c8d87b948bca1a380584a60f \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SC3000.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SC3000.cs new file mode 100644 index 00000000..b9c6f70a --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SC3000.cs @@ -0,0 +1,617 @@ +using Essgee.Emulation.Audio; +using Essgee.Emulation.Cartridges; +using Essgee.Emulation.Cartridges.Sega; +using Essgee.Emulation.Configuration; +using Essgee.Emulation.CPU; +using Essgee.Emulation.Peripherals; +using Essgee.Emulation.Video; +using Essgee.EventArguments; +using Essgee.Utilities; +using System; +using System.Collections.Generic; +using System.Linq; + +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 SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler RenderScreen + { + add { vdp.RenderScreen += value; } + remove { vdp.RenderScreen -= value; } + } + + public event EventHandler SizeScreen + { + add { vdp.SizeScreen += value; } + remove { vdp.SizeScreen -= value; } + } + + public event EventHandler ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler EnqueueSamples + { + add { psg.EnqueueSamples += value; } + remove { psg.EnqueueSamples -= value; } + } + + public event EventHandler SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler 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; + + public 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 = PollInputEventArgs.Create(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + pollInputEventArgs.Release(); + }; + } + + 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(EmuStandInfo.Configuration.SampleRate); + psg?.SetOutputChannels(2); + psg?.SetClockRate(psgClock); + psg?.SetRefreshRate(RefreshRate); + + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / RefreshRate); + + var eventArgs = ChangeViewportEventArgs.Create(vdp.Viewport); + OnChangeViewport(eventArgs); + eventArgs.Release(); + } + + 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 state) + public void SetState(Dictionary state) + { + configuration.TVStandard = (TVStandard)state[nameof(configuration.TVStandard)]; + + SaveStateHandler.PerformSetState(cartridge, (Dictionary)state[nameof(cartridge)]); + wram = (byte[])state[nameof(wram)]; + SaveStateHandler.PerformSetState(cpu, (Dictionary)state[nameof(cpu)]); + SaveStateHandler.PerformSetState(vdp, (Dictionary)state[nameof(vdp)]); + SaveStateHandler.PerformSetState(psg, (Dictionary)state[nameof(psg)]); + SaveStateHandler.PerformSetState(ppi, (Dictionary)state[nameof(ppi)]); + keyboard = (bool[,])(state[nameof(keyboard)]); + ReconfigureSystem(); + } + + public Dictionary GetState() + { + return new Dictionary + { + [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 GetDebugInformation() + { + var dict = new Dictionary + { + { "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"); + var logeventArgs = SendLogMessageEventArgs.Create($"Selected {modeString} mode."); + SendLogMessage(this, logeventArgs); + logeventArgs.Release(); + + } + changeInputButtonPressed = keysDown.Contains(configuration.InputChangeMode); + + /* Toggle tape playback */ + if (keysDown.Contains(configuration.InputPlayTape) && !tapePlayButtonPressed) + { + isTapePlaying = !isTapePlaying; + var playString = (isTapePlaying ? "playing" : "stopped"); + var logeventArgs = SendLogMessageEventArgs.Create($"Tape is {playString}."); + SendLogMessage(this, logeventArgs); + logeventArgs.Release(); + } + tapePlayButtonPressed = keysDown.Contains(configuration.InputPlayTape); + + if (keyboardMode) + { + /* Handle keyboard */ + + // TODO: Replace hardcoded English layout w/ user-configurable settings + keyboard[0, 0] = keysDown.Contains(EssgeeMotionKey.D1); + keyboard[0, 1] = keysDown.Contains(EssgeeMotionKey.D2); + keyboard[0, 2] = keysDown.Contains(EssgeeMotionKey.D3); + keyboard[0, 3] = keysDown.Contains(EssgeeMotionKey.D4); + keyboard[0, 4] = keysDown.Contains(EssgeeMotionKey.D5); + keyboard[0, 5] = keysDown.Contains(EssgeeMotionKey.D6); + keyboard[0, 6] = keysDown.Contains(EssgeeMotionKey.D7); + + keyboard[1, 0] = keysDown.Contains(EssgeeMotionKey.Q); + keyboard[1, 1] = keysDown.Contains(EssgeeMotionKey.W); + keyboard[1, 2] = keysDown.Contains(EssgeeMotionKey.E); + keyboard[1, 3] = keysDown.Contains(EssgeeMotionKey.R); + keyboard[1, 4] = keysDown.Contains(EssgeeMotionKey.T); + keyboard[1, 5] = keysDown.Contains(EssgeeMotionKey.Y); + keyboard[1, 6] = keysDown.Contains(EssgeeMotionKey.U); + + keyboard[2, 0] = keysDown.Contains(EssgeeMotionKey.A); + keyboard[2, 1] = keysDown.Contains(EssgeeMotionKey.S); + keyboard[2, 2] = keysDown.Contains(EssgeeMotionKey.D); + keyboard[2, 3] = keysDown.Contains(EssgeeMotionKey.F); + keyboard[2, 4] = keysDown.Contains(EssgeeMotionKey.G); + keyboard[2, 5] = keysDown.Contains(EssgeeMotionKey.H); + keyboard[2, 6] = keysDown.Contains(EssgeeMotionKey.J); + + keyboard[3, 0] = keysDown.Contains(EssgeeMotionKey.Z); + keyboard[3, 1] = keysDown.Contains(EssgeeMotionKey.X); + keyboard[3, 2] = keysDown.Contains(EssgeeMotionKey.C); + keyboard[3, 3] = keysDown.Contains(EssgeeMotionKey.V); + keyboard[3, 4] = keysDown.Contains(EssgeeMotionKey.B); + keyboard[3, 5] = keysDown.Contains(EssgeeMotionKey.N); + keyboard[3, 6] = keysDown.Contains(EssgeeMotionKey.M); + + keyboard[4, 0] = keysDown.Contains(EssgeeMotionKey.None); // Alphanumerics, Eng Dier's + keyboard[4, 1] = keysDown.Contains(EssgeeMotionKey.Space); + keyboard[4, 2] = keysDown.Contains(EssgeeMotionKey.Home); // Clr, Home + keyboard[4, 3] = keysDown.Contains(EssgeeMotionKey.Back); // Del, Ins + keyboard[4, 4] = keysDown.Contains(EssgeeMotionKey.None); // Not on English keyboard? + keyboard[4, 5] = keysDown.Contains(EssgeeMotionKey.None); // "" + keyboard[4, 6] = keysDown.Contains(EssgeeMotionKey.None); // "" + + keyboard[5, 0] = keysDown.Contains(EssgeeMotionKey.Oemcomma); + keyboard[5, 1] = keysDown.Contains(EssgeeMotionKey.OemPeriod); + keyboard[5, 2] = keysDown.Contains(EssgeeMotionKey.OemQuestion); // Forward slash + keyboard[5, 3] = keysDown.Contains(EssgeeMotionKey.None); // Pi + keyboard[5, 4] = keysDown.Contains(EssgeeMotionKey.Down); + keyboard[5, 5] = keysDown.Contains(EssgeeMotionKey.Left); + keyboard[5, 6] = keysDown.Contains(EssgeeMotionKey.Right); + + keyboard[6, 0] = keysDown.Contains(EssgeeMotionKey.K); + keyboard[6, 1] = keysDown.Contains(EssgeeMotionKey.L); + keyboard[6, 2] = keysDown.Contains(EssgeeMotionKey.Oemplus); // Semicolon + keyboard[6, 3] = keysDown.Contains(EssgeeMotionKey.OemSemicolon); // Colon + keyboard[6, 4] = keysDown.Contains(EssgeeMotionKey.OemCloseBrackets); + keyboard[6, 5] = keysDown.Contains(EssgeeMotionKey.Enter); + keyboard[6, 6] = keysDown.Contains(EssgeeMotionKey.Up); + + keyboard[7, 0] = keysDown.Contains(EssgeeMotionKey.I); + keyboard[7, 1] = keysDown.Contains(EssgeeMotionKey.O); + keyboard[7, 2] = keysDown.Contains(EssgeeMotionKey.P); + keyboard[7, 3] = keysDown.Contains(EssgeeMotionKey.PageUp); // @ + keyboard[7, 4] = keysDown.Contains(EssgeeMotionKey.OemOpenBrackets); + keyboard[7, 5] = keysDown.Contains(EssgeeMotionKey.None); // Not on English keyboard? + keyboard[7, 6] = keysDown.Contains(EssgeeMotionKey.None); // "" + + keyboard[8, 0] = keysDown.Contains(EssgeeMotionKey.D8); + keyboard[8, 1] = keysDown.Contains(EssgeeMotionKey.D9); + keyboard[8, 2] = keysDown.Contains(EssgeeMotionKey.D0); + keyboard[8, 3] = keysDown.Contains(EssgeeMotionKey.OemMinus); + keyboard[8, 4] = keysDown.Contains(EssgeeMotionKey.Oemtilde); // ^, ~ + keyboard[8, 5] = keysDown.Contains(EssgeeMotionKey.OemPipe); // Yen, Pipe, Pound? + keyboard[8, 6] = keysDown.Contains(EssgeeMotionKey.PageDown); // Break + + keyboard[9, 6] = keysDown.Contains(EssgeeMotionKey.RControlKey); // Graph + + keyboard[10, 6] = keysDown.Contains(EssgeeMotionKey.LControlKey); // Ctrl + + keyboard[11, 5] = keysDown.Contains(EssgeeMotionKey.Tab); // Func + keyboard[11, 6] = keysDown.Contains(EssgeeMotionKey.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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SC3000.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SC3000.cs.meta new file mode 100644 index 00000000..22081594 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SC3000.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2df7595ac4e8eff45a21dab6630ec56c \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SG1000.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SG1000.cs new file mode 100644 index 00000000..bb581106 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SG1000.cs @@ -0,0 +1,415 @@ +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 System; +using System.Collections.Generic; +using System.Linq; + +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 SendLogMessage; + protected virtual void OnSendLogMessage(SendLogMessageEventArgs e) { SendLogMessage?.Invoke(this, e); } + + public event EventHandler EmulationReset; + protected virtual void OnEmulationReset(EventArgs e) { EmulationReset?.Invoke(this, e); } + + public event EventHandler RenderScreen + { + add { vdp.RenderScreen += value; } + remove { vdp.RenderScreen -= value; } + } + + public event EventHandler SizeScreen + { + add { vdp.SizeScreen += value; } + remove { vdp.SizeScreen -= value; } + } + + public event EventHandler ChangeViewport; + protected virtual void OnChangeViewport(ChangeViewportEventArgs e) { ChangeViewport?.Invoke(this, e); } + + public event EventHandler PollInput; + protected virtual void OnPollInput(PollInputEventArgs e) { PollInput?.Invoke(this, e); } + + public event EventHandler EnqueueSamples + { + add { psg.EnqueueSamples += value; } + remove { psg.EnqueueSamples -= value; } + } + + public event EventHandler SaveExtraData; + protected virtual void OnSaveExtraData(SaveExtraDataEventArgs e) { SaveExtraData?.Invoke(this, e); } + + public event EventHandler 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; + + public 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 = PollInputEventArgs.Create(); + OnPollInput(pollInputEventArgs); + ParseInput(pollInputEventArgs); + pollInputEventArgs.Release(); + }; + } + + 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(EmuStandInfo.Configuration.SampleRate); + psg?.SetOutputChannels(2); + psg?.SetClockRate(psgClock); + psg?.SetRefreshRate(RefreshRate); + + currentMasterClockCyclesInFrame = 0; + totalMasterClockCyclesInFrame = (int)Math.Round(masterClock / RefreshRate); + + var eventArgs = ChangeViewportEventArgs.Create(vdp.Viewport); + OnChangeViewport(eventArgs); + eventArgs.Release(); + + } + + 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 state) + public void SetState(Dictionary state) + { + configuration.TVStandard = (TVStandard)state[nameof(configuration.TVStandard)]; + + SaveStateHandler.PerformSetState(cartridge, (Dictionary)state[nameof(cartridge)]); + wram = (byte[])state[nameof(wram)]; + SaveStateHandler.PerformSetState(cpu, (Dictionary)state[nameof(cpu)]); + SaveStateHandler.PerformSetState(vdp, (Dictionary)state[nameof(vdp)]); + SaveStateHandler.PerformSetState(psg, (Dictionary)state[nameof(psg)]); + + ReconfigureSystem(); + } + + public Dictionary GetState() + { + return new Dictionary + { + [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 GetDebugInformation() + { + var dict = new Dictionary + { + { "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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SG1000.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SG1000.cs.meta new file mode 100644 index 00000000..2014a6e5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Machines/SG1000.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cfe23eacb3173be4a94a9775b3289053 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals.meta new file mode 100644 index 00000000..2ef6bf42 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 565e2e3b2a9ae8a4d843b313678a1165 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/IPeripheral.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/IPeripheral.cs new file mode 100644 index 00000000..b4431e24 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/IPeripheral.cs @@ -0,0 +1,12 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/IPeripheral.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/IPeripheral.cs.meta new file mode 100644 index 00000000..064436b6 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/IPeripheral.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 399a15f4c8bd5824fac256665037afad \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/Intel8255.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/Intel8255.cs new file mode 100644 index 00000000..b3d30ebe --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/Intel8255.cs @@ -0,0 +1,95 @@ +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/Intel8255.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/Intel8255.cs.meta new file mode 100644 index 00000000..a3b9b2d5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Peripherals/Intel8255.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5d39dd8d77215c64e981c2bebaac1901 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/SaveStateHandler.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/SaveStateHandler.cs new file mode 100644 index 00000000..e45ea458 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/SaveStateHandler.cs @@ -0,0 +1,149 @@ +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(EmuStandInfo.ProductVersion).Major:D3}"; + + public static Dictionary 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); + } + } + } + + public static void Save(Stream stream, string machineName, Dictionary 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 state) + + public static void PerformSetState(object obj, Dictionary 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 PerformGetState(object obj) + public static Dictionary PerformGetState(object obj) + { + //var state = new Dictionary(); + var state = new Dictionary(); + + 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/SaveStateHandler.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/SaveStateHandler.cs.meta new file mode 100644 index 00000000..36a44137 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/SaveStateHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 174dc0116b075184494eb514aea3ad36 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Utilities.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Utilities.cs new file mode 100644 index 00000000..b07a169f --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Utilities.cs @@ -0,0 +1,57 @@ +using System; + +namespace Essgee.Emulation +{ + public unsafe 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 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 unsafe 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Utilities.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Utilities.cs.meta new file mode 100644 index 00000000..96ee3cfa --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Utilities.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 09a14347df3fa794380f049632d22821 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video.meta new file mode 100644 index 00000000..47b777fb --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a26b509fa7ea461489c1010e81e82d7f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/IVideo.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/IVideo.cs new file mode 100644 index 00000000..47305aa9 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/IVideo.cs @@ -0,0 +1,32 @@ +using Essgee.EventArguments; +using System; + +namespace Essgee.Emulation.Video +{ + interface IVideo + { + (int X, int Y, int Width, int Height) Viewport { get; } + + event EventHandler RenderScreen; + void OnRenderScreen(RenderScreenEventArgs e); + + event EventHandler EndOfScanline; + void OnEndOfScanline(EventArgs e); + + event EventHandler 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/IVideo.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/IVideo.cs.meta new file mode 100644 index 00000000..a3fdf618 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/IVideo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4992318d978fee542b3b9cc4011f00d7 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo.meta new file mode 100644 index 00000000..0aa68f6b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3eda1878d76837140be1b0e89f93f37a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/CGBVideo.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/CGBVideo.cs new file mode 100644 index 00000000..4c9fc788 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/CGBVideo.cs @@ -0,0 +1,529 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/CGBVideo.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/CGBVideo.cs.meta new file mode 100644 index 00000000..874fe8de --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/CGBVideo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4025ca74baf3418409354b5129430a4f \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/DMGVideo.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/DMGVideo.cs new file mode 100644 index 00000000..a8f738c7 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/DMGVideo.cs @@ -0,0 +1,883 @@ +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +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 SizeScreen; + public virtual void OnSizeScreen(SizeScreenEventArgs e) { SizeScreen?.Invoke(this, e); } + + public virtual event EventHandler RenderScreen; + public virtual void OnRenderScreen(RenderScreenEventArgs e) { RenderScreen?.Invoke(this, e); } + + public virtual event EventHandler 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(); + } + + + GCHandle? lasyRenderHandle; + 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 */ + + // 固定数组,防止垃圾回收器移动它 + var bitmapcolorRect_handle = GCHandle.Alloc(outputFramebuffer.Clone() as byte[], GCHandleType.Pinned); + // 获取数组的指针 + IntPtr mFrameDataPtr = bitmapcolorRect_handle.AddrOfPinnedObject(); + var eventArgs = RenderScreenEventArgs.Create(displayActiveWidth, displayActiveHeight, mFrameDataPtr); + OnRenderScreen(eventArgs); + eventArgs.Release(); + if (lasyRenderHandle != null) + lasyRenderHandle.Value.Free(); + lasyRenderHandle = bitmapcolorRect_handle; + + //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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/DMGVideo.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/DMGVideo.cs.meta new file mode 100644 index 00000000..93163d64 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/Nintendo/DMGVideo.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 42b7849ed9e0ba741a3d7cdfb174512d \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaGGVDP.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaGGVDP.cs new file mode 100644 index 00000000..bea49851 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaGGVDP.cs @@ -0,0 +1,130 @@ +using Essgee.EventArguments; +using Essgee.Utilities; +using System; +using System.Runtime.InteropServices; +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Video +{ + /* Sega 315-5378, Game Gear */ + public unsafe 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]; + cram_set = 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]; + outputFramebuffer_set = new byte[Viewport.Width * Viewport.Height * 4]; + + /* Update resolution/display timing */ + UpdateResolution(); + } + + //GCHandle? lasyRenderHandle; + protected override void PrepareRenderScreen() + { + //// 固定数组,防止垃圾回收器移动它 + //var bitmapcolorRect_handle = GCHandle.Alloc(outputFramebuffer.Clone() as byte[], GCHandleType.Pinned); + //// 获取数组的指针 + //IntPtr mFrameDataPtr = bitmapcolorRect_handle.AddrOfPinnedObject(); + + var eventArgs = RenderScreenEventArgs.Create(numVisiblePixels, numVisibleScanlines, outputFramebuffer_Ptr); + OnRenderScreen(eventArgs); + eventArgs.Release(); + //if (lasyRenderHandle != null) + // lasyRenderHandle.Value.Free(); + //lasyRenderHandle = bitmapcolorRect_handle; + + //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 unsafe 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaGGVDP.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaGGVDP.cs.meta new file mode 100644 index 00000000..0754e4f6 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaGGVDP.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: f75093c71287a3b45b7fe57be15d49b8 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaSMSVDP.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaSMSVDP.cs new file mode 100644 index 00000000..59af3b61 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaSMSVDP.cs @@ -0,0 +1,960 @@ +using Essgee.EventArguments; +using Essgee.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +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 unsafe 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; + + #region //指针化 cram + static byte[] cram_src; + static GCHandle cram_handle; + public static byte* cram; + public static int cramLength; + public static bool cram_IsNull => cram == null; + public static byte[] cram_set + { + set + { + cram_handle.ReleaseGCHandle(); + cram_src = value; + cramLength = value.Length; + cram_src.GetObjectPtr(ref cram_handle, ref cram); + } + } + #endregion + + + [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]; + registers_set = new byte[0x0B]; + cram_set = 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; + for (int i = 0; i < cramLength; 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]; + outputFramebuffer_set = 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; + } + } + + //GCHandle? lasyRenderHandle; + protected override void PrepareRenderScreen() + { + // 固定数组,防止垃圾回收器移动它 + //var bitmapcolorRect_handle = GCHandle.Alloc(outputFramebuffer.Clone() as byte[], GCHandleType.Pinned); + //var bitmapcolorRect_handle = GCHandle.Alloc(outputFramebuffer, GCHandleType.Pinned); + //// 获取数组的指针 + //IntPtr mFrameDataPtr = bitmapcolorRect_handle.AddrOfPinnedObject(); + + var eventArgs = RenderScreenEventArgs.Create(numVisiblePixels, numVisibleScanlines, outputFramebuffer_Ptr); + OnRenderScreen(eventArgs); + eventArgs.Release(); + //if (lasyRenderHandle != null) + // lasyRenderHandle.Value.Free(); + //lasyRenderHandle = bitmapcolorRect_handle; + + //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 unsafe 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) + if (register < registersLength) + registers[register] = value; + + if (register == 0x00 || register == 0x01) + UpdateResolution(); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaSMSVDP.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaSMSVDP.cs.meta new file mode 100644 index 00000000..0d3a6b4c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/SegaSMSVDP.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b2208cc9d471f2340aa4019b18607dda \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/TMS99xxA.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/TMS99xxA.cs new file mode 100644 index 00000000..372c4217 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/TMS99xxA.cs @@ -0,0 +1,956 @@ +using Essgee.EventArguments; +using Essgee.Utilities; +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; +using static Essgee.Emulation.Utilities; + +namespace Essgee.Emulation.Video +{ + /* Texas Instruments TMS99xxA family */ + public unsafe 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 SizeScreen; + public virtual void OnSizeScreen(SizeScreenEventArgs e) { SizeScreen?.Invoke(this, e); } + + public virtual event EventHandler RenderScreen; + public virtual void OnRenderScreen(RenderScreenEventArgs e) { RenderScreen?.Invoke(this, e); } + + public virtual event EventHandler 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; + + #region //指针化 registers + static byte[] registers_src; + static GCHandle registers_handle; + public static byte* registers; + public static int registersLength; + public static bool registers_IsNull => registers == null; + public static byte[] registers_set + { + set + { + registers_handle.ReleaseGCHandle(); + registers_src = value; + registersLength = value.Length; + registers_src.GetObjectPtr(ref registers_handle, ref registers); + } + } + #endregion + + #region //指针化 vram + static byte[] vram_src; + static GCHandle vram_handle; + public static byte* vram; + public static int vramLength; + public static bool vram_IsNull => vram == null; + public static byte[] vram_set + { + set + { + vram_handle.ReleaseGCHandle(); + vram_src = value; + vramLength = value.Length; + vram_src.GetObjectPtr(ref vram_handle, ref vram); + } + } + #endregion + + [StateRequired] + protected (int Number, int Y, int X, int Pattern, int Attribute)[][] spriteBuffer; + + //protected ushort vramMask16k => 0x3FFF; + //protected ushort vramMask4k => 0x0FFF; + protected const ushort vramMask16k = 0x3FFF; + protected const 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; + + #region //指针化 outputFramebuffer + static byte[] outputFramebuffer_src; + static GCHandle outputFramebuffer_handle; + public static IntPtr outputFramebuffer_Ptr; + public static byte* outputFramebuffer; + public static int outputFramebufferLength; + public static bool outputFramebuffer_IsNull => outputFramebuffer == null; + public static byte[] outputFramebuffer_set + { + set + { + + outputFramebuffer_handle.ReleaseGCHandle(); + outputFramebuffer_src = value; + outputFramebufferLength = value.Length; + outputFramebuffer_src.GetObjectPtr(ref outputFramebuffer_handle, ref outputFramebuffer); + outputFramebuffer_Ptr = outputFramebuffer_handle.AddrOfPinnedObject(); + } + } + #endregion + + + 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]; + registers_set = new byte[0x08]; + vram_set = 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 < registersLength; i++) registers[i] = 0; + for (int i = 0; i < vramLength; 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]; + outputFramebuffer_set = 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); + var eventArgs = SizeScreenEventArgs.Create(numVisiblePixels, numVisibleScanlines); + OnSizeScreen(eventArgs); + eventArgs.Release(); + + } + + 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; + } + } + + //GCHandle? lasyRenderHandle; + protected virtual void PrepareRenderScreen() + { + //// 固定数组,防止垃圾回收器移动它 + //var bitmapcolorRect_handle = GCHandle.Alloc(outputFramebuffer.Clone() as byte[], GCHandleType.Pinned); + //// 获取数组的指针 + //IntPtr mFrameDataPtr = bitmapcolorRect_handle.AddrOfPinnedObject(); + var eventArgs = RenderScreenEventArgs.Create(numVisiblePixels, numVisibleScanlines, outputFramebuffer_Ptr); + OnRenderScreen(eventArgs); + eventArgs.Release(); + //if (lasyRenderHandle != null) + // lasyRenderHandle.Value.Free(); + //lasyRenderHandle = bitmapcolorRect_handle; + //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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/TMS99xxA.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/TMS99xxA.cs.meta new file mode 100644 index 00000000..7baf48f5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Emulation/Video/TMS99xxA.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a235296639e8898418d408c99ebe70fc \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Enumerations.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Enumerations.cs new file mode 100644 index 00000000..812852d5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Enumerations.cs @@ -0,0 +1,17 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Enumerations.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Enumerations.cs.meta new file mode 100644 index 00000000..cfecbcd8 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Enumerations.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2f9f52ebdd4aee949bf1ee9b137c9ec7 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Essgee.Unity.asmdef b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Essgee.Unity.asmdef new file mode 100644 index 00000000..2bfd508c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Essgee.Unity.asmdef @@ -0,0 +1,14 @@ +{ + "name": "Essgee.Unity", + "rootNamespace": "", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": true, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Essgee.Unity.asmdef.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Essgee.Unity.asmdef.meta new file mode 100644 index 00000000..b755be51 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Essgee.Unity.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a9b5dbf2cb3b0c94e917bebc6af53b30 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeLogger.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeLogger.cs new file mode 100644 index 00000000..ca6cf095 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeLogger.cs @@ -0,0 +1,42 @@ +public interface IEssgeeLogger +{ + void Debug(string message); + void Warning(string message); + void Err(string message); +} +public static class EssgeeLogger +{ + static IEssgeeLogger essgeeLogger; + public static void Init(IEssgeeLogger logger) + { + essgeeLogger = logger; + } + public static void WriteLine(string message = null) + { + essgeeLogger.Debug(message); + } + public static void Err(string message = null) + { + essgeeLogger.Err(message); + } + public static void EnqueueMessage(string message = null) + { + essgeeLogger.Debug(message); + } + public static void EnqueueMessageSuccess(string message = null) + { + essgeeLogger.Debug(message); + } + public static void EnqueueMessageWarning(string message = null) + { + essgeeLogger.Warning(message); + } + + internal static void Assert(bool condition, string message) + { + if (!condition) + { + essgeeLogger.Debug(message); + } + } +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeLogger.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeLogger.cs.meta new file mode 100644 index 00000000..35abea26 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeLogger.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cdcc61f43394b604d9ff021fce4eeace \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeMotionKey.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeMotionKey.cs new file mode 100644 index 00000000..a5439faa --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeMotionKey.cs @@ -0,0 +1,214 @@ +using System; +using System.Runtime.InteropServices; + +[Flags] +[ComVisible(true)] +public enum EssgeeMotionKey +{ + 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeMotionKey.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeMotionKey.cs.meta new file mode 100644 index 00000000..e6bc2dcb --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EssgeeMotionKey.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: c8194d726152edf4aa339a393f801f58 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments.meta new file mode 100644 index 00000000..773ed789 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 9ebca929cde32f948ae9c370636aa6d2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/ChangeViewportEventArgs.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/ChangeViewportEventArgs.cs new file mode 100644 index 00000000..c281476e --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/ChangeViewportEventArgs.cs @@ -0,0 +1,28 @@ +using System; + +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; + //} + + public static ChangeViewportEventArgs Create((int, int, int, int) viewport) + { + var eventArgs = ObjectPoolAuto.Acquire(); + eventArgs.Viewport = viewport; + return eventArgs; + } + } + public static class ChangeViewportEventArgsEx + { + public static void Release(this ChangeViewportEventArgs eventArgs) + { + ObjectPoolAuto.Release(eventArgs); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/ChangeViewportEventArgs.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/ChangeViewportEventArgs.cs.meta new file mode 100644 index 00000000..70940925 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/ChangeViewportEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a5fdb88b89c3ee84b861d3156f877eec \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/EnqueueSamplesEventArgs.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/EnqueueSamplesEventArgs.cs new file mode 100644 index 00000000..5f4e1932 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/EnqueueSamplesEventArgs.cs @@ -0,0 +1,45 @@ +using System; + +namespace Essgee.EventArguments +{ + public unsafe class EnqueueSamplesEventArgs : EventArgs + { + public int NumChannels { get; set; } + //public short[][] ChannelSamples { get; set; } + public short*[] ChannelSamples { get; set; } + public bool[] IsChannelMuted { get; set; } + public short* MixedSamples { get; set; } + public int MixedSamplesLength { get; set; } + + //public EnqueueSamplesEventArgs(int numChannels, short[][] channelSamples, bool[] isMuted, short[] mixedSamples) + //{ + // NumChannels = numChannels; + // ChannelSamples = channelSamples; + // IsChannelMuted = isMuted; + // MixedSamples = mixedSamples; + //} + + //public static EnqueueSamplesEventArgs Create(int numChannels, short[][] channelSamples, bool[] isMuted, short[] mixedSamples) + public static EnqueueSamplesEventArgs Create(int numChannels, short*[] channelSamples, bool[] isMuted, short* mixedSamples,int mixedSamplesLength) + { + var eventArgs = ObjectPoolAuto.Acquire(); + eventArgs.NumChannels = numChannels; + eventArgs.ChannelSamples = channelSamples; + eventArgs.IsChannelMuted = isMuted; + eventArgs.MixedSamples = mixedSamples; + eventArgs.MixedSamplesLength = mixedSamplesLength; + return eventArgs; + } + } + public unsafe static class EnqueueSamplesEventArgsEx + { + public static void Release(this EnqueueSamplesEventArgs eventArgs) + { + eventArgs.NumChannels = 1; + eventArgs.ChannelSamples = null; + eventArgs.IsChannelMuted = null; + eventArgs.MixedSamples = null; + ObjectPoolAuto.Release(eventArgs); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/EnqueueSamplesEventArgs.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/EnqueueSamplesEventArgs.cs.meta new file mode 100644 index 00000000..83facde9 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/EnqueueSamplesEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 43ad2fa85569796468e3bd2923aaf5db \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/PollInputEventArgs.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/PollInputEventArgs.cs new file mode 100644 index 00000000..ce97ad5c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/PollInputEventArgs.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; + +namespace Essgee.EventArguments +{ + public class PollInputEventArgs : EventArgs + { + public List 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(); + + // MouseButtons = MouseButtons.None; + // MousePosition = (0, 0); + + // ControllerState = new ControllerState(); + //} + + public static PollInputEventArgs Create() + { + var eventArgs = ObjectPoolAuto.Acquire(); + //eventArgs.Keyboard = new List(); + eventArgs.Keyboard = ObjectPoolAuto.AcquireList(); + eventArgs.MouseButtons = MouseButtons.None; + eventArgs.MousePosition = (0, 0); + + //eventArgs.ControllerState = new ControllerState(); + //eventArgs.ControllerState = ObjectPoolAuto.Acquire(); + return eventArgs; + } + } + public static class PollInputEventArgsEx + { + public static void Release(this PollInputEventArgs eventArgs) + { + ObjectPoolAuto.Release(eventArgs.Keyboard); + eventArgs.Keyboard = null; + eventArgs.MouseButtons = MouseButtons.None; + eventArgs.MousePosition = (0, 0); + //ObjectPoolAuto.Release(eventArgs.ControllerState); + //eventArgs.ControllerState = null; + + ObjectPoolAuto.Release(eventArgs); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/PollInputEventArgs.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/PollInputEventArgs.cs.meta new file mode 100644 index 00000000..62f987ef --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/PollInputEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ada5c874b28f2c24f8622fd7dce81592 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/RenderScreenEventArgs.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/RenderScreenEventArgs.cs new file mode 100644 index 00000000..a525b95e --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/RenderScreenEventArgs.cs @@ -0,0 +1,37 @@ +using System; + +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 IntPtr FrameDataPtr { get; private set; } + + // public RenderScreenEventArgs(int width, int height, IntPtr ptr) + //{ + // Width = width; + // Height = height; + // //FrameData = data; + // FrameDataPtr = ptr; + //} + + public static RenderScreenEventArgs Create(int width, int height, IntPtr ptr) + { + var eventArgs = ObjectPoolAuto.Acquire(); + eventArgs.Width = width; + eventArgs.Height = height; + //FrameData = data; + eventArgs.FrameDataPtr = ptr; + return eventArgs; + } + } + public static class RenderScreenEventArgsEx + { + public static void Release(this RenderScreenEventArgs eventArgs) + { + ObjectPoolAuto.Release(eventArgs); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/RenderScreenEventArgs.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/RenderScreenEventArgs.cs.meta new file mode 100644 index 00000000..bbe7faf9 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/RenderScreenEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: effb4e50cecefc94194ff7943b6d9bf9 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SaveExtraDataEventArgs.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SaveExtraDataEventArgs.cs new file mode 100644 index 00000000..215366ef --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SaveExtraDataEventArgs.cs @@ -0,0 +1,29 @@ +using System; + +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 static SaveExtraDataEventArgs Create(ExtraDataTypes type, ExtraDataOptions option, string desc, object data) + { + var eventArgs = ObjectPoolAuto.Acquire(); + eventArgs.DataType = type; + eventArgs.Options = option; + eventArgs.Description = desc; + eventArgs.Data = data; + return eventArgs; + } + } + public static class SaveExtraDataEventArgsEx + { + public static void Release(this SaveExtraDataEventArgs eventArgs) + { + ObjectPoolAuto.Release(eventArgs); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SaveExtraDataEventArgs.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SaveExtraDataEventArgs.cs.meta new file mode 100644 index 00000000..66b734e7 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SaveExtraDataEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 239da568cc1e77a4c8cf9692726fbd37 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SendLogMessageEventArgs.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SendLogMessageEventArgs.cs new file mode 100644 index 00000000..5e9db7b3 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SendLogMessageEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace Essgee.EventArguments +{ + public class SendLogMessageEventArgs : EventArgs + { + public string Message { get; private set; } + + + public static SendLogMessageEventArgs Create(string message) + { + var eventArgs = ObjectPoolAuto.Acquire(); + eventArgs.Message = message; + return eventArgs; + } + } + public static class SendLogMessageEventArgsEx + { + public static void Release(this SendLogMessageEventArgs eventArgs) + { + ObjectPoolAuto.Release(eventArgs); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SendLogMessageEventArgs.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SendLogMessageEventArgs.cs.meta new file mode 100644 index 00000000..50355e88 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SendLogMessageEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 73ff88c017bde9147946020156991f15 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SizeScreenEventArgs.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SizeScreenEventArgs.cs new file mode 100644 index 00000000..796bd1b6 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SizeScreenEventArgs.cs @@ -0,0 +1,25 @@ +using System; + +namespace Essgee.EventArguments +{ + public class SizeScreenEventArgs : EventArgs + { + public int Width { get; private set; } + public int Height { get; private set; } + + public static SizeScreenEventArgs Create(int width, int height) + { + var eventArgs = ObjectPoolAuto.Acquire(); + eventArgs.Width = width; + eventArgs.Height = height; + return eventArgs; + } + } + public static class SizeScreenEventArgsEx + { + public static void Release(this SizeScreenEventArgs eventArgs) + { + ObjectPoolAuto.Release(eventArgs); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SizeScreenEventArgs.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SizeScreenEventArgs.cs.meta new file mode 100644 index 00000000..3a61f866 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/EventArguments/SizeScreenEventArgs.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 9ab5875b41ee4fa4790e97a80748fcbd \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions.meta new file mode 100644 index 00000000..444088aa --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d1c46752ccd1bc9458481cc6745a9d00 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/CartridgeLoaderException.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/CartridgeLoaderException.cs new file mode 100644 index 00000000..06790268 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/CartridgeLoaderException.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/CartridgeLoaderException.cs.meta new file mode 100644 index 00000000..29074389 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/CartridgeLoaderException.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d5e871a4c155d6343910ef62bb192f5a \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/EmulationException.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/EmulationException.cs new file mode 100644 index 00000000..7fd0a73e --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/EmulationException.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/EmulationException.cs.meta new file mode 100644 index 00000000..3292cff5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/EmulationException.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d29afc05220518e41b9c5ebb04cec0a6 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/GraphicsException.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/GraphicsException.cs new file mode 100644 index 00000000..d4e3ce51 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/GraphicsException.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/GraphicsException.cs.meta new file mode 100644 index 00000000..2d7a3d13 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/GraphicsException.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 292f826ddc00e6f41b4f4ba46a9a6ece \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/HandlerException.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/HandlerException.cs new file mode 100644 index 00000000..b4aa5923 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/HandlerException.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/HandlerException.cs.meta new file mode 100644 index 00000000..4b03e09b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Exceptions/HandlerException.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6c6b7a048bc880a449b2b81ae303f70d \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions.meta new file mode 100644 index 00000000..cb376e74 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 2558194e2eb5320458689c46b71d51d5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/AssemblyExtensionMethods.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/AssemblyExtensionMethods.cs new file mode 100644 index 00000000..6120fb51 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/AssemblyExtensionMethods.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; +using System.Linq; +using System.Reflection; + +namespace Essgee.Extensions +{ + public static class AssemblyExtensionMethods + { + public static T GetAttribute(this ICustomAttributeProvider assembly, bool inherit = false) where T : Attribute + { + return assembly.GetCustomAttributes(typeof(T), inherit).OfType().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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/AssemblyExtensionMethods.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/AssemblyExtensionMethods.cs.meta new file mode 100644 index 00000000..d971fb3b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/AssemblyExtensionMethods.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2a919f96fbccdab4f98631971a5ad641 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/GeneralExtensionMethods.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/GeneralExtensionMethods.cs new file mode 100644 index 00000000..8c877291 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/GeneralExtensionMethods.cs @@ -0,0 +1,33 @@ +using System; +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(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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/GeneralExtensionMethods.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/GeneralExtensionMethods.cs.meta new file mode 100644 index 00000000..29f74bdc --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/GeneralExtensionMethods.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b82a62a18635b33479a83fdcf68428e5 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/SerializationExtensionMethods.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/SerializationExtensionMethods.cs new file mode 100644 index 00000000..385154c8 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/SerializationExtensionMethods.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; +using System.IO; + +namespace Essgee.Extensions +{ + public static class SerializationExtensionMethods + { + public static void SerializeToFile(this object obj, string jsonFileName) + { + SerializeToFile(obj, jsonFileName, new JsonSerializerSettings()); + } + + 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(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(this string jsonString) + { + return (T)JsonConvert.DeserializeObject(jsonString, typeof(T), new JsonSerializerSettings() { Formatting = Formatting.Indented }); + } + } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/SerializationExtensionMethods.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/SerializationExtensionMethods.cs.meta new file mode 100644 index 00000000..f489bc1c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Extensions/SerializationExtensionMethods.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 016c6214586d0ed44a4181b198587385 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata.meta new file mode 100644 index 00000000..19817c95 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ae0b7e73691b4fd448b102af0875a373 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadata.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadata.cs new file mode 100644 index 00000000..513359f9 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadata.cs @@ -0,0 +1,20 @@ +using Essgee.Emulation; +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadata.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadata.cs.meta new file mode 100644 index 00000000..2a11bd7a --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadata.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b2847d634a727684b8739d5ea9e6ee73 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadataHandler.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadataHandler.cs new file mode 100644 index 00000000..7fce5473 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadataHandler.cs @@ -0,0 +1,307 @@ +using Essgee.Emulation; +using Essgee.Exceptions; +using Essgee.Utilities; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.IO; +using System.Linq; +using System.Xml.Serialization; + +namespace Essgee.Metadata +{ + /// + /// 单独定义meta加载接口 + /// + public interface IGameMetaReources + { + public bool GetCartMetadataDatabase(out string loadedData); + public bool GetDatBytes(string DatName,out byte[] loadedData); + } + + public class GameMetadataHandler + { + //static string datDirectoryPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "No-Intro"); + //static string metadataDatabaseFilePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Assets", "MetadataDatabase.json"); + + IGameMetaReources gameMetaReources; + //readonly Dictionary datFiles; + readonly List cartMetadataDatabase; + + //public int NumKnownSystems { get { return datFiles.Count; } } + //public int NumKnownGames { get { return datFiles.Sum(x => x.Value.Game.Count()); } } + + public GameMetadataHandler(IGameMetaReources metaresources) + { + gameMetaReources = metaresources; + + if(!gameMetaReources.GetCartMetadataDatabase(out string loadedData)) + throw new HandlerException("CartMetadataDatabase file not found"); + + cartMetadataDatabase = JsonConvert.DeserializeObject>(loadedData); + + + //改为接口直接读取 + //XmlRootAttribute root; + //XmlSerializer serializer; + + ///* Read No-Intro .dat files */ + + //datFiles = new Dictionary(); + //foreach (var file in Directory.EnumerateFiles(EmuStandInfo.datDirectoryPath, "*.dat")) + //{ + // root = new XmlRootAttribute("datafile") { IsNullable = true }; + // serializer = new XmlSerializer(typeof(DatFile), root); + // using (FileStream stream = new FileStream(Path.Combine(EmuStandInfo.datDirectoryPath, file), FileMode.Open)) + // { + // datFiles.Add(Path.GetFileName(file), (DatFile)serializer.Deserialize(stream)); + // } + //} + + ///* Read cartridge metadata database */ + //cartMetadataDatabase = EmuStandInfo.metadataDatabaseFilePath.DeserializeFromFile>(); + + ////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"); + + //接口直接读取 + if (!gameMetaReources.GetDatBytes(datFilename, out byte[] loadedData)) + throw new HandlerException("Requested .dat file not found"); + + DatFile datFile; + + XmlRootAttribute root; + XmlSerializer serializer; + root = new XmlRootAttribute("datafile") { IsNullable = true }; + serializer = new XmlSerializer(typeof(DatFile), root); + using (MemoryStream stream = new MemoryStream(loadedData)) + { + datFile = (DatFile)serializer.Deserialize(stream); + } + + /* 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadataHandler.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadataHandler.cs.meta new file mode 100644 index 00000000..ae67271b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Metadata/GameMetadataHandler.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5a21d7f6de7eac540bfe506b411d4ef8 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/ObjectPoolAuto.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/ObjectPoolAuto.cs new file mode 100644 index 00000000..c55fd88c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/ObjectPoolAuto.cs @@ -0,0 +1,399 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +internal static class ObjectPoolAuto +{ + /************************************************************************************************************************/ + + /// + /// 获取或者创建一个新的 + /// + /// Remember to 需要回收参见这个 + public static T Acquire() + where T : class, new() + => ObjectPool.Acquire(); + + /// + /// 获取或者创建一个新的 + /// + /// Remember to 需要回收参见这个 + public static void Acquire(out T item) + where T : class, new() + => item = ObjectPool.Acquire(); + /************************************************************************************************************************/ + + /// + /// 回收对象 + /// + public static void Release(T item) + where T : class, new() + => ObjectPool.Release(item); + + /// + /// 回收对象 + /// + public static void Release(ref T item) where T : class, new() + { + if (item != null) + { + ObjectPool.Release(item); + item = null; + } + } + + /************************************************************************************************************************/ + public const string + NotClearError = " They must be cleared before being released to the pool and not modified after that."; + + /************************************************************************************************************************/ + + /// + /// 获取或创建List + /// + /// Remember to 回收参见此方法 + public static List AcquireList() + { + var list = ObjectPool>.Acquire(); + EssgeeLogger.Assert(list.Count == 0, "A pooled list is not empty." + NotClearError); + return list; + } + + /// + /// 回收List + /// + public static void Release(List list) + { + list.Clear(); + ObjectPool>.Release(list); + } + + /************************************************************************************************************************/ + + /// + /// 获取或创建Queue + /// + /// Remember to 回收参见此方法 + public static Queue AcquireQueue() + { + var queue = ObjectPool>.Acquire(); + EssgeeLogger.Assert(queue.Count == 0, "A pooled list is not empty." + NotClearError); + return queue; + } + + /// + /// 回收Queue + /// + public static void Release(Queue list) + { + list.Clear(); + ObjectPool>.Release(list); + } + + + /************************************************************************************************************************/ + + /// + /// 获取或创建HashSet + /// + public static HashSet AcquireSet() + { + var set = ObjectPool>.Acquire(); + EssgeeLogger.Assert(set.Count == 0, "A pooled set is not empty." + NotClearError); + return set; + } + + /// + /// 释放HashSet + /// + public static void Release(HashSet set) + { + set.Clear(); + ObjectPool>.Release(set); + } + + /************************************************************************************************************************/ + + /// + /// 获取一个字符串StringBuilder + /// + /// Remember to 回收参见这个 + public static StringBuilder AcquireStringBuilder() + { + var builder = ObjectPool.Acquire(); + EssgeeLogger.Assert(builder.Length == 0, $"A pooled {nameof(StringBuilder)} is not empty." + NotClearError); + return builder; + } + + /// + /// 回收 StringBuilder + /// + public static void Release(StringBuilder builder) + { + builder.Length = 0; + ObjectPool.Release(builder); + } + + /// + /// 回收 StringBuilder + /// + public static string ReleaseToString(this StringBuilder builder) + { + var result = builder.ToString(); + Release(builder); + return result; + } + + /************************************************************************************************************************/ + + private static class Cache + { + public static readonly Dictionary, T>> + Results = new Dictionary, T>>(); + } + + /// + /// 此方法主要用于频繁绘制缓存,比如说GUI绘制 + /// + public static T GetCachedResult(Func function) + { + var method = function.Method; + if (!Cache.Results.TryGetValue(method, out var result)) + { + + result = new KeyValuePair, T>(function, function()); + Cache.Results.Add(method, result); + } + else if (result.Key != function) + { + EssgeeLogger.WriteLine( + $"{nameof(GetCachedResult)}<{typeof(T).Name}>" + + $" was previously called on {method.Name} with a different target." + + " This likely means that a new delegate is being passed into every call" + + " so it can't actually return the same cached object."); + } + + return result.Value; + } + + /************************************************************************************************************************/ + + public static class Disposable + { + /************************************************************************************************************************/ + + /// + /// Calls to get a spare if + /// + public static IDisposable Acquire(out T item) + where T : class, new() + => ObjectPool.Disposable.Acquire(out item); + + /************************************************************************************************************************/ + + /// + /// Calls to get a spare if + /// + public static IDisposable AcquireList(out List list) + { + var disposable = ObjectPool>.Disposable.Acquire(out list, onRelease: (l) => l.Clear()); + EssgeeLogger.Assert(list.Count == 0, "A pooled list is not empty." + NotClearError); + return disposable; + } + + /************************************************************************************************************************/ + + /// + /// Calls to get a spare if + /// + public static IDisposable AcquireSet(out HashSet set) + { + var disposable = ObjectPool>.Disposable.Acquire(out set, onRelease: (s) => s.Clear()); + EssgeeLogger.Assert(set.Count == 0, "A pooled set is not empty." + NotClearError); + return disposable; + } + + /************************************************************************************************************************/ + } + /************************************************************************************************************************/ +} + +public static class ObjectPool where T : class, new() +{ + /************************************************************************************************************************/ + + private static readonly List + Items = new List(); + + /************************************************************************************************************************/ + + /// The number of spare items currently in the pool. + public static int Count + { + get => Items.Count; + set + { + var count = Items.Count; + if (count < value) + { + if (Items.Capacity < value) + Items.Capacity = NextPowerOfTwo(value); + + do + { + Items.Add(new T()); + count++; + } + while (count < value); + + } + else if (count > value) + { + Items.RemoveRange(value, count - value); + } + } + } + + public static int NextPowerOfTwo(int value) + { + if (value <= 0) + { + throw new ArgumentException("Value must be greater than zero."); + } + + int powerOfTwo = 1; + while (powerOfTwo < value) + { + powerOfTwo <<= 1; // equivalent to multiplying by 2 + } + + return powerOfTwo; + } + + /************************************************************************************************************************/ + + /// + /// If the is less than the specified value, this method increases it to that value by + /// creating new objects. + /// + public static void SetMinCount(int count) + { + if (Count < count) + Count = count; + } + + /************************************************************************************************************************/ + + /// The of the internal list of spare items. + public static int Capacity + { + get => Items.Capacity; + set + { + if (Items.Count > value) + Items.RemoveRange(value, Items.Count - value); + Items.Capacity = value; + } + } + + /************************************************************************************************************************/ + + /// Returns a spare item if there are any, or creates a new one. + /// Remember to it when you are done. + public static T Acquire() + { + var count = Items.Count; + if (count == 0) + { + return new T(); + } + else + { + count--; + var item = Items[count]; + Items.RemoveAt(count); + + return item; + } + } + + /************************************************************************************************************************/ + + /// Adds the `item` to the list of spares so it can be reused. + public static void Release(T item) + { + Items.Add(item); + + } + + /************************************************************************************************************************/ + + /// Returns a description of the state of this pool. + public static string GetDetails() + { + return + $"{typeof(T).Name}" + + $" ({nameof(Count)} = {Items.Count}" + + $", {nameof(Capacity)} = {Items.Capacity}" + + ")"; + } + + /************************************************************************************************************************/ + + /// + /// An system to allow pooled objects to be acquired and released within using + /// statements instead of needing to manually release everything. + /// + public sealed class Disposable : IDisposable + { + /************************************************************************************************************************/ + + private static readonly List LazyStack = new List(); + + private static int _ActiveDisposables; + + private T _Item; + private Action _OnRelease; + + /************************************************************************************************************************/ + + private Disposable() { } + + /// + /// Calls to set the `item` and returns an + /// that will call on the `item` when disposed. + /// + public static IDisposable Acquire(out T item, Action onRelease = null) + { + Disposable disposable; + + if (LazyStack.Count <= _ActiveDisposables) + { + LazyStack.Add(disposable = new Disposable()); + } + else + { + disposable = LazyStack[_ActiveDisposables]; + } + + _ActiveDisposables++; + + disposable._Item = item = ObjectPool.Acquire(); + disposable._OnRelease = onRelease; + return disposable; + } + + /************************************************************************************************************************/ + + void IDisposable.Dispose() + { + _OnRelease?.Invoke(_Item); + Release(_Item); + _ActiveDisposables--; + } + + /************************************************************************************************************************/ + } +} + diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/ObjectPoolAuto.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/ObjectPoolAuto.cs.meta new file mode 100644 index 00000000..92543ea2 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/ObjectPoolAuto.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 401c483d18aba7c45b7ae47fc4a5b19a \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities.meta new file mode 100644 index 00000000..3d6ea178 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: fd20bf051feed2649a0b54c1c23246b0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/Crc32.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/Crc32.cs new file mode 100644 index 00000000..78b8691c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/Crc32.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/Crc32.cs.meta new file mode 100644 index 00000000..2c4e673c --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/Crc32.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 21bde0d19c7628445ac123f8bb7c8f75 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/HexadecimalJsonConverter.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/HexadecimalJsonConverter.cs new file mode 100644 index 00000000..deff333f --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/HexadecimalJsonConverter.cs @@ -0,0 +1,47 @@ +using Newtonsoft.Json; +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/HexadecimalJsonConverter.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/HexadecimalJsonConverter.cs.meta new file mode 100644 index 00000000..f4e9327b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/HexadecimalJsonConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 33727d827e6ef604baf39a57c3b9211d \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/InterfaceDictionaryConverter.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/InterfaceDictionaryConverter.cs new file mode 100644 index 00000000..39dba133 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/InterfaceDictionaryConverter.cs @@ -0,0 +1,44 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Essgee.Utilities +{ + public class InterfaceDictionaryConverter : 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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/InterfaceDictionaryConverter.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/InterfaceDictionaryConverter.cs.meta new file mode 100644 index 00000000..c094ee46 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/InterfaceDictionaryConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 38387730a3743044698acbb9afe6aa67 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/IsBootstrapRomPathAttribute.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/IsBootstrapRomPathAttribute.cs new file mode 100644 index 00000000..295c4a1b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/IsBootstrapRomPathAttribute.cs @@ -0,0 +1,7 @@ +using System; + +namespace Essgee.Utilities +{ + [AttributeUsage(AttributeTargets.Property)] + public class IsBootstrapRomPathAttribute : Attribute { } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/IsBootstrapRomPathAttribute.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/IsBootstrapRomPathAttribute.cs.meta new file mode 100644 index 00000000..705a2f43 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/IsBootstrapRomPathAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 12ab3971c05a8034ea04df685e2c6f2d \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/MachineIndexAttribute.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/MachineIndexAttribute.cs new file mode 100644 index 00000000..908d4e5b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/MachineIndexAttribute.cs @@ -0,0 +1,15 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/MachineIndexAttribute.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/MachineIndexAttribute.cs.meta new file mode 100644 index 00000000..9b7b2a88 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/MachineIndexAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 63aa5f9e1fb94184c96245b9b13e4325 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/StateRequiredAttribute.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/StateRequiredAttribute.cs new file mode 100644 index 00000000..9393c1e4 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/StateRequiredAttribute.cs @@ -0,0 +1,7 @@ +using System; + +namespace Essgee.Utilities +{ + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class StateRequiredAttribute : Attribute { } +} diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/StateRequiredAttribute.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/StateRequiredAttribute.cs.meta new file mode 100644 index 00000000..6b797037 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/StateRequiredAttribute.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7cc6e75e78730c445a27a5f59f93c4e5 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/TypeNameJsonConverter.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/TypeNameJsonConverter.cs new file mode 100644 index 00000000..c1c100ea --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/TypeNameJsonConverter.cs @@ -0,0 +1,40 @@ +using Newtonsoft.Json; +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/TypeNameJsonConverter.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/TypeNameJsonConverter.cs.meta new file mode 100644 index 00000000..6614f15e --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/TypeNameJsonConverter.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 7a7c859a28b7ac348b1fc4904077955d \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput.meta new file mode 100644 index 00000000..9c55cf14 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e7cd06ff5a47d6b49b280e1f99c667e4 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/Controller.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/Controller.cs new file mode 100644 index 00000000..be0ade96 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/Controller.cs @@ -0,0 +1,178 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/Controller.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/Controller.cs.meta new file mode 100644 index 00000000..146f2f10 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/Controller.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 208c78465e149914e92390762f768a04 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerManager.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerManager.cs new file mode 100644 index 00000000..ea562e9f --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerManager.cs @@ -0,0 +1,30 @@ +using System; + +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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerManager.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerManager.cs.meta new file mode 100644 index 00000000..e0cc01eb --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerManager.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d3b109e035af2d0488fee85624c9a90b \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerState.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerState.cs new file mode 100644 index 00000000..57c2cba9 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerState.cs @@ -0,0 +1,116 @@ +//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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerState.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerState.cs.meta new file mode 100644 index 00000000..da9a3ae8 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/ControllerState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 62cf2ece5ee5c44458ac5646bb2f8af8 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/NativeMethods.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/NativeMethods.cs new file mode 100644 index 00000000..c7dc8076 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/NativeMethods.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/NativeMethods.cs.meta new file mode 100644 index 00000000..3f0a5cf3 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/NativeMethods.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 2f4639905082c50448ca8a1477b55057 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputCapabilities.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputCapabilities.cs new file mode 100644 index 00000000..08da7a84 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputCapabilities.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputCapabilities.cs.meta new file mode 100644 index 00000000..c916d614 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputCapabilities.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 400677b412126194caf14f5d627b34a2 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputGamepad.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputGamepad.cs new file mode 100644 index 00000000..4ebf55b5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputGamepad.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputGamepad.cs.meta new file mode 100644 index 00000000..86f6d2b5 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputGamepad.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: dee5c4630dd189049abf4d41bd4348d8 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputState.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputState.cs new file mode 100644 index 00000000..fb5dd6b2 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputState.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputState.cs.meta new file mode 100644 index 00000000..64bb742a --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputState.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b18170cd37fc33d4ba897ffbe7198deb \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputVibration.cs b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputVibration.cs new file mode 100644 index 00000000..f48be3df --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/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/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputVibration.cs.meta b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputVibration.cs.meta new file mode 100644 index 00000000..c25fa984 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Essgee.Unity/Utilities/XInput/XInputVibration.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 66e6171736477a944b9299a66469f407 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Newtonsoft.Json.dll b/AxibugEmuOnline.Client/Assets/Plugins/Newtonsoft.Json.dll new file mode 100644 index 00000000..341d08fc Binary files /dev/null and b/AxibugEmuOnline.Client/Assets/Plugins/Newtonsoft.Json.dll differ diff --git a/AxibugEmuOnline.Client/Assets/Plugins/Newtonsoft.Json.dll.meta b/AxibugEmuOnline.Client/Assets/Plugins/Newtonsoft.Json.dll.meta new file mode 100644 index 00000000..7124f76e --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Plugins/Newtonsoft.Json.dll.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 1a58e3ab1bbec0a4998d14ae26c5651d \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Resources/EssgeeUnity.meta b/AxibugEmuOnline.Client/Assets/Resources/EssgeeUnity.meta new file mode 100644 index 00000000..42fba224 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Resources/EssgeeUnity.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a28b26e2dc66db145b9e59d48c63cda8 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Resources/EssgeeUnity/EssgeeUnity.prefab b/AxibugEmuOnline.Client/Assets/Resources/EssgeeUnity/EssgeeUnity.prefab new file mode 100644 index 00000000..4e39a45d --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Resources/EssgeeUnity/EssgeeUnity.prefab @@ -0,0 +1,352 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &70883439141109485 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1697793132499616605} + - component: {fileID: 736821097941426334} + - component: {fileID: 1479243231233559559} + - component: {fileID: 7015183808095004452} + - component: {fileID: 6011412303868462633} + m_Layer: 0 + m_Name: EssgeeUnity + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1697793132499616605 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 70883439141109485} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 4829774629647575852} + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &736821097941426334 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 70883439141109485} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ee8551c24327cd04a81ffb5de0146f9c, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!114 &1479243231233559559 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 70883439141109485} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: f675565b1f4ed7744a8ea9788c92429d, type: 3} + m_Name: + m_EditorClassIdentifier: + mWidth: 160 + mHeight: 144 + mDataLenght: 0 + m_rawBufferWarper: {fileID: 0} + m_drawCanvas: {fileID: 1415903496979242101} + m_drawCanvasrect: {fileID: 4829774629647575852} +--- !u!114 &7015183808095004452 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 70883439141109485} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 89abab1d3b1020340a147d74e60751ce, type: 3} + m_Name: + m_EditorClassIdentifier: + m_as: {fileID: 6011412303868462633} +--- !u!82 &6011412303868462633 +AudioSource: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 70883439141109485} + m_Enabled: 1 + serializedVersion: 4 + OutputAudioMixerGroup: {fileID: 0} + m_audioClip: {fileID: 0} + m_Resource: {fileID: 0} + m_PlayOnAwake: 0 + m_Volume: 1 + m_Pitch: 1 + Loop: 1 + Mute: 0 + Spatialize: 0 + SpatializePostEffects: 0 + Priority: 128 + DopplerLevel: 1 + MinDistance: 1 + MaxDistance: 500 + Pan2D: 0 + rolloffMode: 0 + BypassEffects: 0 + BypassListenerEffects: 0 + BypassReverbZones: 0 + rolloffCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + - serializedVersion: 3 + time: 1 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + panLevelCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + spreadCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 0 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 + reverbZoneMixCustomCurve: + serializedVersion: 2 + m_Curve: + - serializedVersion: 3 + time: 0 + value: 1 + inSlope: 0 + outSlope: 0 + tangentMode: 0 + weightedMode: 0 + inWeight: 0.33333334 + outWeight: 0.33333334 + m_PreInfinity: 2 + m_PostInfinity: 2 + m_RotationOrder: 4 +--- !u!1 &2203418964758308711 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 153069469667130431} + - component: {fileID: 1996193783355217829} + - component: {fileID: 1415903496979242101} + m_Layer: 5 + m_Name: GameRawImage + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &153069469667130431 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2203418964758308711} + m_LocalRotation: {x: 1, y: 0, z: 0, w: 0} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 4829774629647575852} + m_LocalEulerAnglesHint: {x: 180, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!222 &1996193783355217829 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2203418964758308711} + m_CullTransparentMesh: 1 +--- !u!114 &1415903496979242101 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2203418964758308711} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 1344c3c82d62a2a41a3576d8abb8e3ea, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Texture: {fileID: 0} + m_UVRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 +--- !u!1 &5558964681998005947 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4829774629647575852} + - component: {fileID: 6904606342347745421} + - component: {fileID: 2921482546112766419} + - component: {fileID: 7864505849399022799} + m_Layer: 5 + m_Name: Canvas + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &4829774629647575852 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5558964681998005947} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0, y: 0, z: 0} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 153069469667130431} + m_Father: {fileID: 1697793132499616605} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0, y: 0} +--- !u!223 &6904606342347745421 +Canvas: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5558964681998005947} + m_Enabled: 1 + serializedVersion: 3 + m_RenderMode: 0 + m_Camera: {fileID: 0} + m_PlaneDistance: 100 + m_PixelPerfect: 0 + m_ReceivesEvents: 1 + m_OverrideSorting: 0 + m_OverridePixelPerfect: 0 + m_SortingBucketNormalizedSize: 0 + m_VertexColorAlwaysGammaSpace: 0 + m_AdditionalShaderChannelsFlag: 0 + m_UpdateRectTransformForStandalone: 0 + m_SortingLayerID: 0 + m_SortingOrder: 0 + m_TargetDisplay: 0 +--- !u!114 &2921482546112766419 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5558964681998005947} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 0cd44c1031e13a943bb63640046fad76, type: 3} + m_Name: + m_EditorClassIdentifier: + m_UiScaleMode: 0 + m_ReferencePixelsPerUnit: 100 + m_ScaleFactor: 1 + m_ReferenceResolution: {x: 800, y: 600} + m_ScreenMatchMode: 0 + m_MatchWidthOrHeight: 0 + m_PhysicalUnit: 3 + m_FallbackScreenDPI: 96 + m_DefaultSpriteDPI: 96 + m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 +--- !u!114 &7864505849399022799 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5558964681998005947} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: dc42784cf147c0c48a680349fa168899, type: 3} + m_Name: + m_EditorClassIdentifier: + m_IgnoreReversedGraphics: 1 + m_BlockingObjects: 0 + m_BlockingMask: + serializedVersion: 2 + m_Bits: 4294967295 diff --git a/AxibugEmuOnline.Client/Assets/Resources/EssgeeUnity/EssgeeUnity.prefab.meta b/AxibugEmuOnline.Client/Assets/Resources/EssgeeUnity/EssgeeUnity.prefab.meta new file mode 100644 index 00000000..2a3ab2ae --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Resources/EssgeeUnity/EssgeeUnity.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 20e1ff69d8c691b4299757181385f497 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/AxibugEmuOnline.Client.asmdef b/AxibugEmuOnline.Client/Assets/Script/AppMain/AxibugEmuOnline.Client.asmdef index 05af0da8..b7f7eaf3 100644 --- a/AxibugEmuOnline.Client/Assets/Script/AppMain/AxibugEmuOnline.Client.asmdef +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/AxibugEmuOnline.Client.asmdef @@ -5,7 +5,8 @@ "AxiReplay", "VirtualNes.Core", "UIEffect2018", - "Mame.Core" + "Mame.Core", + "Essgee.Unity" ], "includePlatforms": [], "excludePlatforms": [], diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator.meta new file mode 100644 index 00000000..d07c1c33 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7896c3085a4ad5d4583d7cc52601ccc0 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgee.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgee.cs new file mode 100644 index 00000000..36ebbbfd --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgee.cs @@ -0,0 +1,726 @@ +using Essgee; +using Essgee.Emulation; +using Essgee.Emulation.Configuration; +using Essgee.EventArguments; +using Essgee.Exceptions; +using Essgee.Extensions; +using Essgee.Metadata; +using Essgee.Utilities; +using Essgee.Utilities.XInput; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEngine; + +public class Essgeeinit : MonoBehaviour +{ + static Essgeeinit instance; + public static System.Diagnostics.Stopwatch sw = System.Diagnostics.Stopwatch.StartNew(); + public static bool bInGame => instance?.emulatorHandler?.IsRunning == true ? true : false; + #region + UEGVideoPlayer graphicsHandler; + UEGSoundPlayer soundHandler; + GameMetadataHandler gameMetadataHandler; + GameMetadata lastGameMetadata; + EmulatorHandler emulatorHandler; + UEGResources uegResources; + UEGLog uegLog; + + bool lastUserPauseState; + (int x, int y, int width, int height) currentViewport; + double currentPixelAspectRatio; + byte[] lastFramebufferData; + (int width, int height) lastFramebufferSize; + private UEGKeyboard mUniKeyboard; + + #endregion + + void Awake() + { + instance = this; + + uegResources = new UEGResources(); + uegLog = new UEGLog(); + InitAll(uegResources, Application.persistentDataPath); + LoadAndRunCartridge("G:/psjapa.sms"); + //LoadAndRunCartridge("G:/Ninja_Gaiden_(UE)_type_A_[!].sms"); + //LoadAndRunCartridge("G:/SML2.gb"); + } + + void OnDisable() + { + SaveConfiguration(); + Dispose(false); + } + + private void Update() + { + if (!emulatorHandler.IsRunning) + return; + mUniKeyboard.UpdateInputKey(); + + emulatorHandler.Update_Frame(); + } + + void InitAll(IGameMetaReources metaresources,string CustonDataDir) + { + //ʼ + InitAppEnvironment(CustonDataDir); + InitEmu(); + //ϸڳʼ + InitializeHandlers(metaresources); + } + + private void InitAppEnvironment(string CustonDataDir) + { + EssgeeLogger.Init(uegLog); + + //EmuStandInfo.datDirectoryPath = Path.Combine(BaseDataDir, "EssgeeAssets", "No-Intro"); + //EmuStandInfo.metadataDatabaseFilePath = Path.Combine(BaseDataDir, "EssgeeAssets", "MetadataDatabase.json"); + + EmuStandInfo.jsonConfigFileName = "Config.json"; + EmuStandInfo.saveDataDirectoryName = "Saves"; + EmuStandInfo.screenshotDirectoryName = "Screenshots"; + EmuStandInfo.saveStateDirectoryName = "Savestates"; + EmuStandInfo.extraDataDirectoryName = "Extras"; + EmuStandInfo.ProductName = "AxibugEmu"; + EmuStandInfo.ProductVersion = ""; + + EmuStandInfo.programDataDirectory = Path.Combine(CustonDataDir, EmuStandInfo.ProductName); + EmuStandInfo.programConfigPath = Path.Combine(EmuStandInfo.programDataDirectory, EmuStandInfo.jsonConfigFileName); + + EmuStandInfo.ShaderPath = Path.Combine(CustonDataDir, "Assets", "Shaders"); + EmuStandInfo.SaveDataPath = Path.Combine(EmuStandInfo.programDataDirectory, EmuStandInfo.saveDataDirectoryName); + EmuStandInfo.ScreenshotPath = Path.Combine(EmuStandInfo.programDataDirectory, EmuStandInfo.screenshotDirectoryName); + EmuStandInfo.SaveStatePath = Path.Combine(EmuStandInfo.programDataDirectory, EmuStandInfo.saveStateDirectoryName); + EmuStandInfo.ExtraDataPath = Path.Combine(EmuStandInfo.programDataDirectory, EmuStandInfo.extraDataDirectoryName); + + LoadConfiguration(); + + + if (!Directory.Exists(EmuStandInfo.SaveDataPath)) + Directory.CreateDirectory(EmuStandInfo.SaveDataPath); + + if (!Directory.Exists(EmuStandInfo.ScreenshotPath)) + Directory.CreateDirectory(EmuStandInfo.ScreenshotPath); + + if (!Directory.Exists(EmuStandInfo.SaveStatePath)) + Directory.CreateDirectory(EmuStandInfo.SaveStatePath); + + if (!Directory.Exists(EmuStandInfo.ExtraDataPath)) + Directory.CreateDirectory(EmuStandInfo.ExtraDataPath); + + if (AppEnvironment.EnableLogger) + { + //TODO رDebug + //Logger.Flush(); + //Logger.Close(); + } + } + + void InitEmu() + { + //keysDown = new List(); + } + + #region ϸڳʼ + + private void InitializeHandlers(IGameMetaReources metaresources) + { + InitializeOSDHandler(); + InitializeGraphicsHandler(); + InitializeSoundHandler(); + InitializeMetadataHandler(metaresources); + + mUniKeyboard = this.gameObject.AddComponent(); + } + + private void InitializeOSDHandler() + { + + //var osdFontText = Assembly.GetExecutingAssembly().ReadEmbeddedImageFile($"{Application.ProductName}.Assets.OsdFont.png"); + //onScreenDisplayHandler = new OnScreenDisplayHandler(osdFontText); + + //onScreenDisplayHandler?.EnqueueMessageDebug($"Hello from {GetProductNameAndVersionString(true)}, this is a debug build!\nOSD handler initialized; font bitmap is {osdFontText.Width}x{osdFontText.Height}."); + + //if (onScreenDisplayHandler == null) throw new HandlerException("Failed to initialize OSD handler"); + } + + private void InitializeGraphicsHandler() + { + graphicsHandler = this.gameObject.GetComponent(); + //graphicsHandler = new GraphicsHandler(onScreenDisplayHandler); + //graphicsHandler?.LoadShaderBundle(Program.Configuration.LastShader); + } + + private void InitializeSoundHandler() + { + soundHandler = this.gameObject.GetComponent(); + //soundHandler = new SoundHandler(onScreenDisplayHandler, Program.Configuration.SampleRate, 2, ExceptionHandler); + //soundHandler.SetVolume(Program.Configuration.Volume); + //soundHandler.SetMute(Program.Configuration.Mute); + //soundHandler.SetLowPassFilter(Program.Configuration.LowPassFilter); + //soundHandler.Startup(); + } + + private void InitializeMetadataHandler(IGameMetaReources metaresources) + { + //gameMetadataHandler = new GameMetadataHandler(onScreenDisplayHandler); + gameMetadataHandler = new GameMetadataHandler(metaresources); + } + #endregion + void Dispose(bool disposing) + { + //TODO ͷʱ + //if (disposing) + //{ + // if (components != null) components.Dispose(); + + // if (onScreenDisplayHandler != null) onScreenDisplayHandler.Dispose(); + // if (graphicsHandler != null) graphicsHandler.Dispose(); + // if (soundHandler != null) soundHandler.Dispose(); + //} + + //base.Dispose(disposing); + } + #region + private static void LoadConfiguration() + { + //TODO ʱü + //Directory.CreateDirectory(EmuStandInfo.programDataDirectory); + //if (!File.Exists(EmuStandInfo.programConfigPath) || (EmuStandInfo.Configuration = EmuStandInfo.programConfigPath.DeserializeFromFile()) == null) + //{ + // EmuStandInfo.Configuration = new Configuration(); + // EmuStandInfo.Configuration.SerializeToFile(EmuStandInfo.programConfigPath); + //} + + EmuStandInfo.Configuration = new Configuration(); + + List machineType = new List(); + machineType.Add(typeof(GameBoy)); + machineType.Add(typeof(GameBoyColor)); + machineType.Add(typeof(ColecoVision)); + machineType.Add(typeof(GameGear)); + machineType.Add(typeof(MasterSystem)); + machineType.Add(typeof(SC3000)); + machineType.Add(typeof(SG1000)); + + //foreach (var machineConfigType in Assembly.GetExecutingAssembly().GetTypes().Where(x => typeof(IConfiguration).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract)) + foreach (var machineConfigType in machineType) + { + if (!EmuStandInfo.Configuration.Machines.ContainsKey(machineConfigType.Name)) + EmuStandInfo.Configuration.Machines.Add(machineConfigType.Name, (IConfiguration)Activator.CreateInstance(machineConfigType)); + } + + //foreach (var debuggerFormType in Assembly.GetExecutingAssembly().GetTypes().Where(x => typeof(IDebuggerForm).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract)) + //{ + // if (!StandInfo.Configuration.DebugWindows.ContainsKey(debuggerFormType.Name)) + // StandInfo.Configuration.DebugWindows.Add(debuggerFormType.Name, Point.Empty); + //} + } + + private void ApplyConfigOverrides(Type machineType) + { + var forcePowerOnWithoutCart = false; + var hasTVStandardOverride = false; + var hasRegionOverride = false; + var hasDisallowMemoryControlOverride = false; + + var overrideConfig = EmuStandInfo.Configuration.Machines[machineType.Name].CloneObject(); + + if (lastGameMetadata == null) + { + var property = overrideConfig.GetType().GetProperty("UseBootstrap"); + if (property != null && (bool)property.GetValue(overrideConfig) != true) + { + property.SetValue(overrideConfig, true); + forcePowerOnWithoutCart = true; + } + } + + if (lastGameMetadata != null && lastGameMetadata.PreferredTVStandard != TVStandard.Auto) + { + var property = overrideConfig.GetType().GetProperty("TVStandard"); + if (property != null) + { + property.SetValue(overrideConfig, lastGameMetadata.PreferredTVStandard); + hasTVStandardOverride = true; + } + } + + if (lastGameMetadata != null && lastGameMetadata.PreferredRegion != Essgee.Emulation.Region.Auto) + { + var property = overrideConfig.GetType().GetProperty("Region"); + if (property != null) + { + property.SetValue(overrideConfig, lastGameMetadata.PreferredRegion); + hasRegionOverride = true; + } + } + + if (lastGameMetadata != null && lastGameMetadata.AllowMemoryControl != true) + { + var propertyMem = overrideConfig.GetType().GetProperty("AllowMemoryControl"); + if (propertyMem != null) + { + propertyMem.SetValue(overrideConfig, lastGameMetadata.AllowMemoryControl); + hasDisallowMemoryControlOverride = true; + + var propertyBoot = overrideConfig.GetType().GetProperty("UseBootstrap"); + if (propertyBoot != null) + { + propertyBoot.SetValue(overrideConfig, false); + } + } + } + + if (forcePowerOnWithoutCart) + EssgeeLogger.EnqueueMessageWarning("Bootstrap ROM is disabled in settings; enabling it for this startup."); + + if (hasTVStandardOverride) + EssgeeLogger.EnqueueMessageWarning($"Overriding TV standard setting; running game as {lastGameMetadata?.PreferredTVStandard}."); + + if (hasRegionOverride) + EssgeeLogger.EnqueueMessageWarning($"Overriding region setting; running game as {lastGameMetadata?.PreferredRegion}."); + + if (hasDisallowMemoryControlOverride) + EssgeeLogger.EnqueueMessageWarning("Game-specific hack: Preventing software from reconfiguring memory control.\nBootstrap ROM has been disabled for this startup due to memory control hack."); + + if (forcePowerOnWithoutCart || hasTVStandardOverride || hasRegionOverride || hasDisallowMemoryControlOverride) + emulatorHandler.SetConfiguration(overrideConfig); + } + public static void SaveConfiguration() + { + EmuStandInfo.Configuration.SerializeToFile(EmuStandInfo.programConfigPath); + } + #endregion + + #region ģ + + public void SetEmuFpsLimit(bool bOpen) + { + emulatorHandler?.SetFpsLimiting(bOpen); + } + public void SetSoundMute(bool bOpen) + { + //soundHandler?.SetMute(Program.Configuration.Mute); + } + public void SetSoundLowPassFilter(bool bOpen) + { + //soundHandler?.SetLowPassFilter(Program.Configuration.LowPassFilter);; + } + public void SetTemporaryPause(bool newTemporaryPauseState) + { + if (emulatorHandler == null || !emulatorHandler.IsRunning || !EmuStandInfo.Configuration.AutoPause) return; + + if (newTemporaryPauseState) + emulatorHandler.Pause(true); + else if (!lastUserPauseState) + emulatorHandler.Pause(false); + } + #endregion + + #region ģ + + + private void PowerOnWithoutCartridge(Type machineType) + { + //TODO IsRecording?? Ҫʵ + //if (soundHandler.IsRecording) + // soundHandler.CancelRecording(); + + InitializeEmulation(machineType); + + lastGameMetadata = null; + + ApplyConfigOverrides(machineType); + + + emulatorHandler.Startup(); + + EssgeeLogger.EnqueueMessageSuccess("Power on without cartridge."); + } + + + private void LoadAndRunCartridge(string fileName) + { + Application.targetFrameRate = 60; + try + { + var (machineType, romData) = CartridgeLoader.Load(fileName, "ROM image"); + + //TODO IsRecording?? Ҫʵ + //if (soundHandler.IsRecording) + // soundHandler.CancelRecording(); + + + InitializeEmulation(machineType); + + lastGameMetadata = gameMetadataHandler.GetGameMetadata(emulatorHandler.Information.DatFileName, fileName, Crc32.Calculate(romData), romData.Length); + + ApplyConfigOverrides(machineType); + + emulatorHandler.LoadCartridge(romData, lastGameMetadata); + + //AddToRecentFiles(fileName); + //CreateRecentFilesMenu(); + //CreateLoadSaveStateMenus(); + //CreateToggleGraphicsLayersMenu(); + //CreateToggleSoundChannelsMenu(); + + //takeScreenshotToolStripMenuItem.Enabled = pauseToolStripMenuItem.Enabled = resetToolStripMenuItem.Enabled = stopToolStripMenuItem.Enabled = true; + //loadStateToolStripMenuItem.Enabled = saveStateToolStripMenuItem.Enabled = true; + //startRecordingToolStripMenuItem.Enabled = true; + //toggleLayersToolStripMenuItem.Enabled = enableChannelsToolStripMenuItem.Enabled = true; + + + //ʼͬƽ̨İť + mUniKeyboard.Init(emulatorHandler.emulator); + + emulatorHandler.Startup(); + + + //ʼƵ + soundHandler.Initialize(); + + //SizeAndPositionWindow(); + //SetWindowTitleAndStatus(); + + EssgeeLogger.EnqueueMessage($"Loaded '{lastGameMetadata?.KnownName ?? "unrecognized game"}'."); + } + catch (Exception ex) when (!AppEnvironment.DebugMode) + { + ExceptionHandler(ex); + } + } + private void InitializeEmulation(Type machineType) + { + if (emulatorHandler != null) + ShutdownEmulation(); + + emulatorHandler = new EmulatorHandler(machineType, ExceptionHandler); + emulatorHandler.Initialize(); + + emulatorHandler.SendLogMessage += EmulatorHandler_SendLogMessage; + emulatorHandler.EmulationReset += EmulatorHandler_EmulationReset; + emulatorHandler.RenderScreen += EmulatorHandler_RenderScreen; + emulatorHandler.SizeScreen += EmulatorHandler_SizeScreen; + emulatorHandler.ChangeViewport += EmulatorHandler_ChangeViewport; + emulatorHandler.PollInput += EmulatorHandler_PollInput; + emulatorHandler.EnqueueSamples += EnqueueSoundSamples; + emulatorHandler.SaveExtraData += EmulatorHandler_SaveExtraData; + emulatorHandler.EnableRumble += EmulatorHandler_EnableRumble; + emulatorHandler.PauseChanged += EmulatorHandler_PauseChanged; + + //emulatorHandler.EnqueueSamples += soundDebuggerForm.EnqueueSamples; + + emulatorHandler.SetFpsLimiting(EmuStandInfo.Configuration.LimitFps); + + emulatorHandler.SetConfiguration(EmuStandInfo.Configuration.Machines[machineType.Name]); + + currentPixelAspectRatio = emulatorHandler.Information.PixelAspectRatio; + + //pauseToolStripMenuItem.DataBindings.Clear(); + //pauseToolStripMenuItem.CheckedChanged += (s, e) => + //{ + // var pauseState = (s as ToolStripMenuItem).Checked; + + // emulatorHandler.Pause(pauseState); + // lastUserPauseState = pauseState; + //}; + + EssgeeLogger.EnqueueMessageSuccess($"{emulatorHandler.Information.Manufacturer} {emulatorHandler.Information.Model} emulation initialized."); + } + + + private void ExceptionHandler(Exception ex) + { + //this.CheckInvokeMethod(() => + //{ + if (!AppEnvironment.TemporaryDisableCustomExceptionForm) + { + //TODO debugڣ + //(_, ExceptionResult result, string prefix, string postfix) = ExceptionForm.GetExceptionInfo(ex); + + //if (result == ExceptionResult.Continue) + //{ + // //MessageBox.Show($"{prefix}{ex.InnerException?.Message ?? ex.Message}\n\n{postfix}.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); + // EssgeeLogger.Err($"{prefix}{ex.InnerException?.Message ?? ex.Message}\n\n{postfix}."); + //} + //else + //{ + // //var exceptionForm = new ExceptionForm(ex) { Owner = this }; + // //exceptionForm.ShowDialog(); + + // switch (result) + // { + // case ExceptionResult.StopEmulation: + // SignalStopEmulation(); + // break; + + // case ExceptionResult.ExitApplication: + // Environment.Exit(-1); + // break; + // } + //} + } + else + { + var exceptionInfoBuilder = new StringBuilder(); + exceptionInfoBuilder.AppendLine($"Thread: {ex.Data["Thread"] ?? ""}"); + exceptionInfoBuilder.AppendLine($"Function: {ex.TargetSite.ReflectedType.FullName}.{ex.TargetSite.Name}"); + exceptionInfoBuilder.AppendLine($"Exception: {ex.GetType().Name}"); + exceptionInfoBuilder.Append($"Message: {ex.Message}"); + + var isUnhandled = Convert.ToBoolean(ex.Data["IsUnhandled"]); + + if (!isUnhandled && ex is CartridgeLoaderException) + { + //MessageBox.Show($"{ex.InnerException?.Message ?? ex.Message}\n\nFailed to load cartridge.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning); + EssgeeLogger.Err($"{ex.InnerException?.Message ?? ex.Message}\n\nFailed to load cartridge."); + } + else if (!isUnhandled && ex is EmulationException) + { + //MessageBox.Show($"An emulation exception has occured!\n\n{exceptionInfoBuilder.ToString()}\n\nEmulation cannot continue and will be terminated.", "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error); + EssgeeLogger.Err($"An emulation exception has occured!\n\n{exceptionInfoBuilder.ToString()}\n\nEmulation cannot continue and will be terminated."); + SignalStopEmulation(); + } + else + { + var errorBuilder = new StringBuilder(); + errorBuilder.AppendLine("An unhandled exception has occured!"); + errorBuilder.AppendLine(); + errorBuilder.AppendLine(exceptionInfoBuilder.ToString()); + errorBuilder.AppendLine(); + errorBuilder.AppendLine("Exception occured:"); + errorBuilder.AppendLine($"{ex.StackTrace}"); + errorBuilder.AppendLine(); + errorBuilder.AppendLine("Execution cannot continue and the application will be terminated."); + + //EssgeeLogger.Err(errorBuilder.ToString(), "Exception", MessageBoxButtons.OK, MessageBoxIcon.Error); + EssgeeLogger.Err(errorBuilder.ToString()); + + Environment.Exit(-1); + } + } + //}); + } + + + + private void SignalStopEmulation() + { + ShutdownEmulation(); + + lastGameMetadata = null; + + //takeScreenshotToolStripMenuItem.Enabled = pauseToolStripMenuItem.Enabled = resetToolStripMenuItem.Enabled = stopToolStripMenuItem.Enabled = false; + //loadStateToolStripMenuItem.Enabled = saveStateToolStripMenuItem.Enabled = false; + //startRecordingToolStripMenuItem.Enabled = false; + //toggleLayersToolStripMenuItem.Enabled = enableChannelsToolStripMenuItem.Enabled = false; + + //SetWindowTitleAndStatus(); + } + + private void ShutdownEmulation() + { + if (emulatorHandler == null) return; + + emulatorHandler.SaveCartridge(); + + emulatorHandler.SendLogMessage -= EmulatorHandler_SendLogMessage; + emulatorHandler.EmulationReset -= EmulatorHandler_EmulationReset; + emulatorHandler.RenderScreen -= EmulatorHandler_RenderScreen; + emulatorHandler.SizeScreen -= EmulatorHandler_SizeScreen; + emulatorHandler.ChangeViewport -= EmulatorHandler_ChangeViewport; + emulatorHandler.PollInput -= EmulatorHandler_PollInput; + emulatorHandler.EnqueueSamples -= EnqueueSoundSamples; + emulatorHandler.SaveExtraData -= EmulatorHandler_SaveExtraData; + emulatorHandler.EnableRumble -= EmulatorHandler_EnableRumble; + emulatorHandler.PauseChanged -= EmulatorHandler_PauseChanged; + + //emulatorHandler.EnqueueSamples -= soundDebuggerForm.EnqueueSamples; + + emulatorHandler.Shutdown(); + while (emulatorHandler.IsRunning) { } + + emulatorHandler = null; + GC.Collect(); + + //graphicsHandler?.FlushTextures(); + + EssgeeLogger.WriteLine("Emulation stopped."); + } + #endregion + + #region ģڲ¼ + + private void EmulatorHandler_SendLogMessage(object sender, SendLogMessageEventArgs e) + { + //this.CheckInvokeMethod(delegate () { onScreenDisplayHandler.EnqueueMessageCore($"{emulatorHandler.Information.Model}: {e.Message}"); }); + //TODO log + EssgeeLogger.EnqueueMessageSuccess($"{emulatorHandler.Information.Model}: {e.Message}"); + } + + private void EmulatorHandler_EmulationReset(object sender, EventArgs e) + { + //this.CheckInvokeMethod(delegate () { onScreenDisplayHandler.EnqueueMessage("Emulation reset."); }); + EssgeeLogger.EnqueueMessageSuccess("Emulation reset."); + } + + private void EmulatorHandler_RenderScreen(object sender, RenderScreenEventArgs e) + { + //this.CheckInvokeMethod(delegate () + //{ + + //if (e.Width != lastFramebufferSize.width || e.Height != lastFramebufferSize.height) + //{ + // lastFramebufferSize = (e.Width, e.Height); + // graphicsHandler?.SetTextureSize(e.Width, e.Height); + //} + //lastFramebufferData = e.FrameData; + //graphicsHandler?.SetTextureData(e.FrameData); + + //graphicsHandler.SubmitVideo(e.Width, e.Height, e.FrameData, 0); + graphicsHandler.SubmitVideo(e.Width, e.Height, e.FrameDataPtr, 0); + + // TODO: create emulation "EndOfFrame" event for this? + ControllerManager.Update(); + //}); + } + + private void EmulatorHandler_SizeScreen(object sender, SizeScreenEventArgs e) + { + //TODO ʵ ĻС + + //this.CheckInvokeMethod(delegate () + //{ + // lastFramebufferSize = (e.Width, e.Height); + // graphicsHandler?.SetTextureSize(e.Width, e.Height); + //}); + } + + private void EmulatorHandler_ChangeViewport(object sender, ChangeViewportEventArgs e) + { + //TODO ʵ + + //this.CheckInvokeMethod(delegate () + //{ + // graphicsHandler?.SetScreenViewport(currentViewport = e.Viewport); + // SizeAndPositionWindow(); + //}); + } + + private void EmulatorHandler_PollInput(object sender, PollInputEventArgs e) + { + //TODO Inputʵ + + //e.Keyboard = mUniKeyboard.mKeyCodeCore.GetPressedKeys(); + e.Keyboard.AddRange(mUniKeyboard.mKeyCodeCore.GetPressedKeys()); + e.MouseButtons = default; + e.MousePosition = default; + + // TODO: rare, random, weird argument exceptions on e.Keyboard assignment; does this lock help?? + //lock (uiLock) + //{ + // e.Keyboard = new List(keysDown); + // e.MouseButtons = mouseButtonsDown; + + // var vx = (currentViewport.x - 50); + // var dvx = renderControl.ClientSize.Width / (currentViewport.width - (double)vx); + // var dvy = renderControl.ClientSize.Height / (currentViewport.height - (double)currentViewport.y); + // e.MousePosition = ((int)(mousePosition.x / dvx) - vx, (int)(mousePosition.y / dvy) - currentViewport.y); + + // if (EmuStandInfo.Configuration.EnableXInput) + // e.ControllerState = ControllerManager.GetController(0).GetControllerState(); + //} + } + + private void EmulatorHandler_SaveExtraData(object sender, SaveExtraDataEventArgs e) + { + /* Extract options etc. */ + var includeDateTime = e.Options.HasFlag(ExtraDataOptions.IncludeDateTime); + var allowOverwrite = e.Options.HasFlag(ExtraDataOptions.AllowOverwrite); + + var extension = string.Empty; + switch (e.DataType) + { + case ExtraDataTypes.Image: extension = "png"; break; + case ExtraDataTypes.Raw: extension = "bin"; break; + default: throw new EmulationException($"Unknown extra data type {e.DataType}"); + } + + /* Generate filename/path */ + var filePrefix = $"{Path.GetFileNameWithoutExtension(lastGameMetadata.FileName)} ({e.Description}{(includeDateTime ? $" {DateTime.Now:yyyy-MM-dd HH-mm-ss})" : ")")}"; + var filePath = Path.Combine(EmuStandInfo.ExtraDataPath, $"{filePrefix}.{extension}"); + if (!allowOverwrite) + { + var existingFiles = Directory.EnumerateFiles(EmuStandInfo.ExtraDataPath, $"{filePrefix}*{extension}"); + if (existingFiles.Contains(filePath)) + for (int i = 2; existingFiles.Contains(filePath = Path.Combine(EmuStandInfo.ExtraDataPath, $"{filePrefix} ({i}).{extension}")); i++) { } + } + + /* Handle data */ + //if (e.Data is Bitmap image) + if (e.DataType == ExtraDataTypes.Image) + { + /* Images, ex. GB Printer printouts */ + //image.Save(filePath); + + //TODO ͼ洢 + } + else if (e.Data is byte[] raw) + { + /* Raw bytes */ + using (var file = new FileStream(filePath, FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) + { + file.Write(raw, 0, raw.Length); + } + } + } + + private void EmulatorHandler_EnableRumble(object sender, EventArgs e) + { + if (EmuStandInfo.Configuration.EnableXInput && EmuStandInfo.Configuration.EnableRumble) + ControllerManager.GetController(0).Vibrate(0.0f, 0.5f, TimeSpan.FromSeconds(0.1f)); + } + + private void EmulatorHandler_PauseChanged(object sender, EventArgs e) + { + //SetWindowTitleAndStatus(); + + if (emulatorHandler.IsPaused) + { + //TODO Ƶͣ + //soundHandler?.ClearSampleBuffer(); + } + } + + public unsafe void EnqueueSoundSamples(object sender, EnqueueSamplesEventArgs e) + { + //if (sampleQueue.Count > MaxQueueLength) + //{ + // var samplesToDrop = (sampleQueue.Count - MaxQueueLength); + // onScreenDisplayHandler.EnqueueMessageDebug($"({GetType().Name}/{DateTime.Now.Second:D2}s) Sample queue overflow; dropping {samplesToDrop} of {sampleQueue.Count} samples."); + // for (int i = 0; i < samplesToDrop; i++) + // if (sampleQueue.Count != 0) + // sampleQueue.Dequeue(); + //} + + //sampleQueue.Enqueue(e.MixedSamples.ToArray()); + + //if (IsRecording) + //{ + // dataChunk.AddSampleData(e.MixedSamples); + // waveHeader.FileLength += (uint)e.MixedSamples.Length; + //} + + //TODO Ƶ + //soundHandler.SubmitSamples(e.MixedSamples, e.ChannelSamples, e.MixedSamples.Length); + soundHandler.SubmitSamples(e.MixedSamples, e.ChannelSamples, e.MixedSamplesLength); + } + #endregion +} + diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgee.cs.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgee.cs.meta new file mode 100644 index 00000000..9c5fb7ab --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgee.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ee8551c24327cd04a81ffb5de0146f9c \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface.meta new file mode 100644 index 00000000..a2174bf6 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7b4ae45e80fe88044a5f7c7f35621f54 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/KeyCodeCore.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/KeyCodeCore.cs new file mode 100644 index 00000000..3ea94e80 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/KeyCodeCore.cs @@ -0,0 +1,418 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; + +public class KeyCodeCore +{ + //public Dictionary dictKeyCfgs = new Dictionary(); + public Dictionary dictKey2Motion = new Dictionary(); + public Dictionary dictMotion2RealKey = new Dictionary() + { +{ EssgeeUnityKey.P1_UP,KeyCode.W}, +{ EssgeeUnityKey.P1_DOWN,KeyCode.S}, +{ EssgeeUnityKey.P1_LEFT,KeyCode.A}, +{ EssgeeUnityKey.P1_RIGHT,KeyCode.D}, +{ EssgeeUnityKey.P1_BTN_1,KeyCode.J}, +{ EssgeeUnityKey.P1_BTN_2,KeyCode.K}, +{ EssgeeUnityKey.P1_BTN_3,KeyCode.U}, +{ EssgeeUnityKey.P1_BTN_4,KeyCode.I}, +{ EssgeeUnityKey.P1_POTION_1,KeyCode.Return}, +{ EssgeeUnityKey.P1_POTION_2,KeyCode.RightShift}, +{ EssgeeUnityKey.P2_UP,KeyCode.UpArrow}, +{ EssgeeUnityKey.P2_DOWN,KeyCode.DownArrow}, +{ EssgeeUnityKey.P2_LEFT,KeyCode.LeftArrow}, +{ EssgeeUnityKey.P2_RIGHT,KeyCode.RightArrow}, +{ EssgeeUnityKey.P2_BTN_1,KeyCode.Keypad1}, +{ EssgeeUnityKey.P2_BTN_2,KeyCode.Keypad2}, +{ EssgeeUnityKey.P2_BTN_3,KeyCode.Keypad4}, +{ EssgeeUnityKey.P2_BTN_4,KeyCode.Keypad5}, +{ EssgeeUnityKey.P2_POTION_1,KeyCode.Keypad0}, +{ EssgeeUnityKey.P2_POTION_2,KeyCode.KeypadPeriod}, +{ EssgeeUnityKey.P3_UP,KeyCode.F12}, +{ EssgeeUnityKey.P3_DOWN,KeyCode.F12}, +{ EssgeeUnityKey.P3_LEFT,KeyCode.F12}, +{ EssgeeUnityKey.P3_RIGHT,KeyCode.F12}, +{ EssgeeUnityKey.P3_BTN_1,KeyCode.F12}, +{ EssgeeUnityKey.P3_BTN_2,KeyCode.F12}, +{ EssgeeUnityKey.P3_BTN_3,KeyCode.F12}, +{ EssgeeUnityKey.P3_BTN_4,KeyCode.F12}, +{ EssgeeUnityKey.P3_POTION_1,KeyCode.F12}, +{ EssgeeUnityKey.P3_POTION_2,KeyCode.F12}, +{ EssgeeUnityKey.P4_UP,KeyCode.F12}, +{ EssgeeUnityKey.P4_DOWN,KeyCode.F12}, +{ EssgeeUnityKey.P4_LEFT,KeyCode.F12}, +{ EssgeeUnityKey.P4_RIGHT,KeyCode.F12}, +{ EssgeeUnityKey.P4_BTN_1,KeyCode.F12}, +{ EssgeeUnityKey.P4_BTN_2,KeyCode.F12}, +{ EssgeeUnityKey.P4_BTN_3,KeyCode.F12}, +{ EssgeeUnityKey.P4_BTN_4,KeyCode.F12}, +{ EssgeeUnityKey.P4_POTION_1,KeyCode.F12}, +{ EssgeeUnityKey.P4_POTION_2,KeyCode.F12}, + }; + public ulong[] CheckList; + public EssgeeMotionKey[] mCurrKey = new EssgeeMotionKey[0]; + List temp = new List(); + ulong tempInputAllData = 0; + UEGKeyboard mUniKeyboard; + ulong last_CurryInpuAllData_test = 0; + public static class EssgeeUnityKey + { + public const ulong NONE = 0; + public const ulong P1_UP = 1; + public const ulong P1_DOWN = 1 << 1; + public const ulong P1_LEFT = 1 << 2; + public const ulong P1_RIGHT = 1 << 3; + public const ulong P1_BTN_1 = 1 << 4; + public const ulong P1_BTN_2 = 1 << 5; + public const ulong P1_BTN_3 = 1 << 6; + public const ulong P1_BTN_4 = 1 << 7; + public const ulong P1_POTION_1 = 1 << 8; + public const ulong P1_POTION_2 = 1 << 9; + public const ulong P2_UP = 65536; + public const ulong P2_DOWN = 65536 << 1; + public const ulong P2_LEFT = 65536 << 2; + public const ulong P2_RIGHT = 65536 << 3; + public const ulong P2_BTN_1 = 65536 << 4; + public const ulong P2_BTN_2 = 65536 << 5; + public const ulong P2_BTN_3 = 65536 << 6; + public const ulong P2_BTN_4 = 65536 << 7; + public const ulong P2_POTION_1 = 65536 << 8; + public const ulong P2_POTION_2 = 65536 << 9; + public const ulong P3_UP = 4294967296; + public const ulong P3_DOWN = 4294967296 << 1; + public const ulong P3_LEFT = 4294967296 << 2; + public const ulong P3_RIGHT = 4294967296 << 3; + public const ulong P3_BTN_1 = 4294967296 << 4; + public const ulong P3_BTN_2 = 4294967296 << 5; + public const ulong P3_BTN_3 = 4294967296 << 6; + public const ulong P3_BTN_4 = 654294967296536 << 7; + public const ulong P3_POTION_1 = 4294967296 << 8; + public const ulong P3_POTION_2 = 4294967296 << 9; + public const ulong P4_UP = 281474976710656; + public const ulong P4_DOWN = 281474976710656 << 1; + public const ulong P4_LEFT = 281474976710656 << 2; + public const ulong P4_RIGHT = 281474976710656 << 3; + public const ulong P4_BTN_1 = 281474976710656 << 4; + public const ulong P4_BTN_2 = 281474976710656 << 5; + public const ulong P4_BTN_3 = 281474976710656 << 6; + public const ulong P4_BTN_4 = 281474976710656 << 7; + public const ulong P4_POTION_1 = 281474976710656 << 8; + public const ulong P4_POTION_2 = 281474976710656 << 9; + } + + public EssgeeMotionKey[] GetPressedKeys() + { + return mCurrKey; + } + + public void SetRePlay(bool IsReplay) + { + //bReplayMode = IsReplay; + } + public void Init(Essgee.Emulation.Machines.IMachine Machine, UEGKeyboard uniKeyboard, bool IsReplay) + { + mUniKeyboard = uniKeyboard; + //dictKeyCfgs.Clear(); + dictKey2Motion.Clear(); + if (Machine is Essgee.Emulation.Machines.MasterSystem) + { + var machine = (Essgee.Emulation.Machines.MasterSystem)Machine; + //dictKeyCfgs.Add(KeyCode.W, machine.configuration.Joypad1Up); + //dictKeyCfgs.Add(KeyCode.S, machine.configuration.Joypad1Down); + //dictKeyCfgs.Add(KeyCode.A, machine.configuration.Joypad1Left); + //dictKeyCfgs.Add(KeyCode.D, machine.configuration.Joypad1Right); + //dictKeyCfgs.Add(KeyCode.J, machine.configuration.Joypad1Button1); + //dictKeyCfgs.Add(KeyCode.K, machine.configuration.Joypad1Button2); + + //dictKeyCfgs.Add(KeyCode.UpArrow, machine.configuration.Joypad2Up); + //dictKeyCfgs.Add(KeyCode.DownArrow, machine.configuration.Joypad2Down); + //dictKeyCfgs.Add(KeyCode.LeftArrow, machine.configuration.Joypad2Left); + //dictKeyCfgs.Add(KeyCode.RightAlt, machine.configuration.Joypad2Right); + //dictKeyCfgs.Add(KeyCode.Alpha1, machine.configuration.Joypad2Button1); + //dictKeyCfgs.Add(KeyCode.Alpha2, machine.configuration.Joypad2Button2); + + + dictKey2Motion.Add(EssgeeUnityKey.P1_UP, machine.configuration.Joypad1Up); + dictKey2Motion.Add(EssgeeUnityKey.P1_DOWN, machine.configuration.Joypad1Down); + dictKey2Motion.Add(EssgeeUnityKey.P1_LEFT, machine.configuration.Joypad1Left); + dictKey2Motion.Add(EssgeeUnityKey.P1_RIGHT, machine.configuration.Joypad1Right); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_1, machine.configuration.Joypad1Button1); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_2, machine.configuration.Joypad1Button2); + + dictKey2Motion.Add(EssgeeUnityKey.P2_UP, machine.configuration.Joypad2Up); + dictKey2Motion.Add(EssgeeUnityKey.P2_DOWN, machine.configuration.Joypad2Down); + dictKey2Motion.Add(EssgeeUnityKey.P2_LEFT, machine.configuration.Joypad2Left); + dictKey2Motion.Add(EssgeeUnityKey.P2_RIGHT, machine.configuration.Joypad2Right); + dictKey2Motion.Add(EssgeeUnityKey.P2_BTN_1, machine.configuration.Joypad2Button1); + dictKey2Motion.Add(EssgeeUnityKey.P2_BTN_2, machine.configuration.Joypad2Button2); + } + else if (Machine is Essgee.Emulation.Machines.GameBoy) + { + var machine = (Essgee.Emulation.Machines.GameBoy)Machine; + + //dictKeyCfgs.Add(KeyCode.W, machine.configuration.ControlsUp); + //dictKeyCfgs.Add(KeyCode.S, machine.configuration.ControlsDown); + //dictKeyCfgs.Add(KeyCode.A, machine.configuration.ControlsLeft); + //dictKeyCfgs.Add(KeyCode.D, machine.configuration.ControlsRight); + //dictKeyCfgs.Add(KeyCode.J, machine.configuration.ControlsB); + //dictKeyCfgs.Add(KeyCode.K, machine.configuration.ControlsA); + //dictKeyCfgs.Add(KeyCode.Return, machine.configuration.ControlsStart); + //dictKeyCfgs.Add(KeyCode.RightShift, machine.configuration.ControlsSelect); + + dictKey2Motion.Add(EssgeeUnityKey.P1_UP, machine.configuration.ControlsUp); + dictKey2Motion.Add(EssgeeUnityKey.P1_DOWN, machine.configuration.ControlsDown); + dictKey2Motion.Add(EssgeeUnityKey.P1_LEFT, machine.configuration.ControlsLeft); + dictKey2Motion.Add(EssgeeUnityKey.P1_RIGHT, machine.configuration.ControlsRight); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_1, machine.configuration.ControlsB); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_2, machine.configuration.ControlsA); + dictKey2Motion.Add(EssgeeUnityKey.P1_POTION_1, machine.configuration.ControlsStart); + dictKey2Motion.Add(EssgeeUnityKey.P1_POTION_2, machine.configuration.ControlsSelect); + } + else if (Machine is Essgee.Emulation.Machines.GameBoyColor) + { + var machine = (Essgee.Emulation.Machines.GameBoyColor)Machine; + + //dictKeyCfgs.Add(KeyCode.W, machine.configuration.ControlsUp); + //dictKeyCfgs.Add(KeyCode.S, machine.configuration.ControlsDown); + //dictKeyCfgs.Add(KeyCode.A, machine.configuration.ControlsLeft); + //dictKeyCfgs.Add(KeyCode.D, machine.configuration.ControlsRight); + //dictKeyCfgs.Add(KeyCode.J, machine.configuration.ControlsB); + //dictKeyCfgs.Add(KeyCode.K, machine.configuration.ControlsA); + + //dictKeyCfgs.Add(KeyCode.Return, machine.configuration.ControlsStart); + //dictKeyCfgs.Add(KeyCode.RightShift, machine.configuration.ControlsSelect); + //dictKeyCfgs.Add(KeyCode.Space, machine.configuration.ControlsSendIR); + + dictKey2Motion.Add(EssgeeUnityKey.P1_UP, machine.configuration.ControlsUp); + dictKey2Motion.Add(EssgeeUnityKey.P1_DOWN, machine.configuration.ControlsDown); + dictKey2Motion.Add(EssgeeUnityKey.P1_LEFT, machine.configuration.ControlsLeft); + dictKey2Motion.Add(EssgeeUnityKey.P1_RIGHT, machine.configuration.ControlsRight); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_1, machine.configuration.ControlsA); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_2, machine.configuration.ControlsB); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_3, machine.configuration.ControlsSendIR); + dictKey2Motion.Add(EssgeeUnityKey.P1_POTION_1, machine.configuration.ControlsStart); + dictKey2Motion.Add(EssgeeUnityKey.P1_POTION_2, machine.configuration.ControlsSelect); + + } + else if (Machine is Essgee.Emulation.Machines.GameGear) + { + var machine = (Essgee.Emulation.Machines.GameGear)Machine; + //dictKeyCfgs.Add(KeyCode.W, machine.configuration.ControlsUp); + //dictKeyCfgs.Add(KeyCode.S, machine.configuration.ControlsDown); + //dictKeyCfgs.Add(KeyCode.A, machine.configuration.ControlsLeft); + //dictKeyCfgs.Add(KeyCode.D, machine.configuration.ControlsRight); + //dictKeyCfgs.Add(KeyCode.J, machine.configuration.ControlsButton2); + //dictKeyCfgs.Add(KeyCode.K, machine.configuration.ControlsButton1); + //dictKeyCfgs.Add(KeyCode.Return, machine.configuration.ControlsStart); + + + dictKey2Motion.Add(EssgeeUnityKey.P1_UP, machine.configuration.ControlsUp); + dictKey2Motion.Add(EssgeeUnityKey.P1_DOWN, machine.configuration.ControlsDown); + dictKey2Motion.Add(EssgeeUnityKey.P1_LEFT, machine.configuration.ControlsLeft); + dictKey2Motion.Add(EssgeeUnityKey.P1_RIGHT, machine.configuration.ControlsRight); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_1, machine.configuration.ControlsButton2); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_2, machine.configuration.ControlsButton1); + dictKey2Motion.Add(EssgeeUnityKey.P1_POTION_1, machine.configuration.ControlsStart); + } + else if (Machine is Essgee.Emulation.Machines.SC3000) + { + var machine = (Essgee.Emulation.Machines.SC3000)Machine; + + /* + * InputReset = MotionKey.F12; + InputChangeMode = MotionKey.F1; + InputPlayTape = MotionKey.F2; + + Joypad1Up = MotionKey.Up; + Joypad1Down = MotionKey.Down; + Joypad1Left = MotionKey.Left; + Joypad1Right = MotionKey.Right; + Joypad1Button1 = MotionKey.A; + Joypad1Button2 = MotionKey.S; + + Joypad2Up = MotionKey.NumPad8; + Joypad2Down = MotionKey.NumPad2; + Joypad2Left = MotionKey.NumPad4; + Joypad2Right = MotionKey.NumPad6; + Joypad2Button1 = MotionKey.NumPad1; + Joypad2Button2 = MotionKey.NumPad3; + */ + + //dictKeyCfgs.Add(KeyCode.F12, machine.configuration.InputReset); + + //dictKeyCfgs.Add(KeyCode.F1, machine.configuration.InputChangeMode); + //dictKeyCfgs.Add(KeyCode.F2, machine.configuration.InputPlayTape); + + //dictKeyCfgs.Add(KeyCode.W, machine.configuration.Joypad1Up); + //dictKeyCfgs.Add(KeyCode.S, machine.configuration.Joypad1Down); + //dictKeyCfgs.Add(KeyCode.A, machine.configuration.Joypad1Left); + //dictKeyCfgs.Add(KeyCode.D, machine.configuration.Joypad1Right); + //dictKeyCfgs.Add(KeyCode.J, machine.configuration.Joypad1Button2); + //dictKeyCfgs.Add(KeyCode.K, machine.configuration.Joypad1Button1); + + //dictKeyCfgs.Add(KeyCode.UpArrow, machine.configuration.Joypad2Up); + //dictKeyCfgs.Add(KeyCode.DownArrow, machine.configuration.Joypad2Down); + //dictKeyCfgs.Add(KeyCode.LeftArrow, machine.configuration.Joypad2Left); + //dictKeyCfgs.Add(KeyCode.RightAlt, machine.configuration.Joypad2Right); + //dictKeyCfgs.Add(KeyCode.Alpha1, machine.configuration.Joypad2Button1); + //dictKeyCfgs.Add(KeyCode.Alpha2, machine.configuration.Joypad2Button2); + + + dictKey2Motion.Add(EssgeeUnityKey.P1_POTION_1, machine.configuration.InputChangeMode); + dictKey2Motion.Add(EssgeeUnityKey.P1_POTION_2, machine.configuration.InputPlayTape); + + dictKey2Motion.Add(EssgeeUnityKey.P1_UP, machine.configuration.Joypad1Up); + dictKey2Motion.Add(EssgeeUnityKey.P1_DOWN, machine.configuration.Joypad1Down); + dictKey2Motion.Add(EssgeeUnityKey.P1_LEFT, machine.configuration.Joypad1Left); + dictKey2Motion.Add(EssgeeUnityKey.P1_RIGHT, machine.configuration.Joypad1Right); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_1, machine.configuration.Joypad1Button2); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_2, machine.configuration.Joypad1Button1); + + dictKey2Motion.Add(EssgeeUnityKey.P2_UP, machine.configuration.Joypad1Up); + dictKey2Motion.Add(EssgeeUnityKey.P2_DOWN, machine.configuration.Joypad1Down); + dictKey2Motion.Add(EssgeeUnityKey.P2_LEFT, machine.configuration.Joypad1Left); + dictKey2Motion.Add(EssgeeUnityKey.P2_RIGHT, machine.configuration.Joypad1Right); + dictKey2Motion.Add(EssgeeUnityKey.P2_BTN_1, machine.configuration.Joypad1Button2); + dictKey2Motion.Add(EssgeeUnityKey.P2_BTN_2, machine.configuration.Joypad1Button1); + + } + else if (Machine is Essgee.Emulation.Machines.SG1000) + { + var machine = (Essgee.Emulation.Machines.SG1000)Machine; + + /* + TVStandard = TVStandard.NTSC; + + InputPause = MotionKey.Space; + + Joypad1Up = MotionKey.Up; + Joypad1Down = MotionKey.Down; + Joypad1Left = MotionKey.Left; + Joypad1Right = MotionKey.Right; + Joypad1Button1 = MotionKey.A; + Joypad1Button2 = MotionKey.S; + + Joypad2Up = MotionKey.NumPad8; + Joypad2Down = MotionKey.NumPad2; + Joypad2Left = MotionKey.NumPad4; + Joypad2Right = MotionKey.NumPad6; + Joypad2Button1 = MotionKey.NumPad1; + Joypad2Button2 = MotionKey.NumPad3; + */ + + //dictKeyCfgs.Add(KeyCode.W, machine.configuration.Joypad1Up); + //dictKeyCfgs.Add(KeyCode.S, machine.configuration.Joypad1Down); + //dictKeyCfgs.Add(KeyCode.A, machine.configuration.Joypad1Left); + //dictKeyCfgs.Add(KeyCode.D, machine.configuration.Joypad1Right); + //dictKeyCfgs.Add(KeyCode.J, machine.configuration.Joypad1Button2); + //dictKeyCfgs.Add(KeyCode.K, machine.configuration.Joypad1Button1); + + //dictKeyCfgs.Add(KeyCode.UpArrow, machine.configuration.Joypad2Up); + //dictKeyCfgs.Add(KeyCode.DownArrow, machine.configuration.Joypad2Down); + //dictKeyCfgs.Add(KeyCode.LeftArrow, machine.configuration.Joypad2Left); + //dictKeyCfgs.Add(KeyCode.RightAlt, machine.configuration.Joypad2Right); + //dictKeyCfgs.Add(KeyCode.Alpha1, machine.configuration.Joypad2Button2); + //dictKeyCfgs.Add(KeyCode.Alpha2, machine.configuration.Joypad2Button1); + + + + dictKey2Motion.Add(EssgeeUnityKey.P1_UP, machine.configuration.Joypad1Up); + dictKey2Motion.Add(EssgeeUnityKey.P1_DOWN, machine.configuration.Joypad1Down); + dictKey2Motion.Add(EssgeeUnityKey.P1_LEFT, machine.configuration.Joypad1Left); + dictKey2Motion.Add(EssgeeUnityKey.P1_RIGHT, machine.configuration.Joypad1Right); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_1, machine.configuration.Joypad1Button2); + dictKey2Motion.Add(EssgeeUnityKey.P1_BTN_2, machine.configuration.Joypad1Button1); + + dictKey2Motion.Add(EssgeeUnityKey.P2_UP, machine.configuration.Joypad1Up); + dictKey2Motion.Add(EssgeeUnityKey.P2_DOWN, machine.configuration.Joypad1Down); + dictKey2Motion.Add(EssgeeUnityKey.P2_LEFT, machine.configuration.Joypad1Left); + dictKey2Motion.Add(EssgeeUnityKey.P2_RIGHT, machine.configuration.Joypad1Right); + dictKey2Motion.Add(EssgeeUnityKey.P2_BTN_1, machine.configuration.Joypad1Button2); + dictKey2Motion.Add(EssgeeUnityKey.P2_BTN_2, machine.configuration.Joypad1Button1); + } + CheckList = dictKey2Motion.Keys.ToArray(); + + //mUniKeyboard.btnP1.Key = new long[] { (long)MotionKey.P1_GAMESTART }; + //mUniKeyboard.btnCoin1.Key = new long[] { (long)MotionKey.P1_INSERT_COIN }; + //mUniKeyboard.btnA.Key = new long[] { (long)MotionKey.P1_BTN_1 }; + //mUniKeyboard.btnB.Key = new long[] { (long)MotionKey.P1_BTN_2 }; + //mUniKeyboard.btnC.Key = new long[] { (long)MotionKey.P1_BTN_3 }; + //mUniKeyboard.btnD.Key = new long[] { (long)MotionKey.P1_BTN_4 }; + ////mUniKeyboard.btnE.Key = new long[] { (long)MotionKey.P1_BTN_5 }; + ////mUniKeyboard.btnF.Key = new long[] { (long)MotionKey.P1_BTN_6 }; + //mUniKeyboard.btnAB.Key = new long[] { (long)MotionKey.P1_BTN_1, (long)MotionKey.P1_BTN_2 }; + //mUniKeyboard.btnCD.Key = new long[] { (long)MotionKey.P1_BTN_3, (long)MotionKey.P1_BTN_4 }; + //mUniKeyboard.btnABC.Key = new long[] { (long)MotionKey.P1_BTN_1, (long)MotionKey.P1_BTN_2, (long)MotionKey.P1_BTN_3 }; + } + + public void UpdateLogic() + { + tempInputAllData = 0; + temp.Clear(); + for (int i = 0; i < CheckList.Length; i++) + { + ulong key = CheckList[i]; + if (Input.GetKey(dictMotion2RealKey[key])) + { + EssgeeMotionKey mk = dictKey2Motion[key]; + temp.Add(mk); + tempInputAllData |= (ulong)mk; + } + } + mCurrKey = temp.ToArray(); + + + //if (bReplayMode) return; + //tempInputAllData = 0; + //temp.Clear(); + //for (int i = 0; i < CheckList.Length; i++) + //{ + // if (Input.GetKey(CheckList[i])) + // { + // EssgeeMotionKey mk = dictKeyCfgs[CheckList[i]]; + // temp.Add(mk); + // tempInputAllData |= (ulong)mk; + // } + //} + //mCurrKey = temp.ToArray(); + + //for (int i = 0; i < mUniKeyboard.mUIBtns.Count; i++) + //{ + // if (mUniKeyboard.mUIBtns[i].bHotKey) + // { + // for (int j = 0; j < mUniKeyboard.mUIBtns[i].Key.Length; j++) + // { + // MotionKey mk = (MotionKey)mUniKeyboard.mUIBtns[i].Key[j]; + // temp.Add(mk); + // tempInputAllData |= (ulong)mk; + // } + // } + //} + + //Vector2Int inputV2 = mUniKeyboard.mJoystick.RawInputV2; + ////Debug.Log($"{inputV2.x},{inputV2.y}"); + //if (inputV2.x > 0) + //{ + // temp.Add(MotionKey.P1_RIGHT); + // tempInputAllData |= (ulong)MotionKey.P1_RIGHT; + //} + //else if (inputV2.x < 0) + //{ + // temp.Add(MotionKey.P1_LEFT); + // tempInputAllData |= (ulong)MotionKey.P1_LEFT; + //} + //if (inputV2.y > 0) + //{ + // temp.Add(MotionKey.P1_UP); + // tempInputAllData |= (ulong)MotionKey.P1_UP; + //} + //else if (inputV2.y < 0) + //{ + // temp.Add(MotionKey.P1_DOWN); + // tempInputAllData |= (ulong)MotionKey.P1_DOWN; + //} + //CurryInpuAllData = tempInputAllData; + //mCurrKey = temp.ToArray(); + + } +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/KeyCodeCore.cs.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/KeyCodeCore.cs.meta new file mode 100644 index 00000000..28d1460f --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/KeyCodeCore.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 3054336185b15f84d9543bdafb49907f \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGKeyboard.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGKeyboard.cs new file mode 100644 index 00000000..59348577 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGKeyboard.cs @@ -0,0 +1,77 @@ +using UnityEngine; + +public class UEGKeyboard : MonoBehaviour +{ + public KeyCodeCore mKeyCodeCore = new KeyCodeCore(); + #region + //public UILongClickButton btnP1; + //public UILongClickButton btnCoin1; + //public UILongClickButton btnA; + //public UILongClickButton btnB; + //public UILongClickButton btnC; + //public UILongClickButton btnD; + ////public UILongClickButton btnE; + ////public UILongClickButton btnF; + //public UILongClickButton btnAB; + //public UILongClickButton btnCD; + //public UILongClickButton btnABC; + //public Transform tfKeyPad; + //public FloatingJoystick mJoystick; + #endregion + + //public List mUIBtns = new List(); + + void Awake() + { + //mJoystick = GameObject.Find("tfJoystick").GetComponent(); + //tfKeyPad = GameObject.Find("tfKeyPad").transform; + //btnP1 = GameObject.Find("btnP1").GetComponent(); + //btnCoin1 = GameObject.Find("btnCoin1").GetComponent(); + //btnA = GameObject.Find("btnA").GetComponent(); + //btnB = GameObject.Find("btnB").GetComponent(); + //btnC = GameObject.Find("btnC").GetComponent(); + //btnD = GameObject.Find("btnD").GetComponent(); + ////btnE = GameObject.Find("btnE")?.GetComponent(); + ////btnF = GameObject.Find("btnF")?.GetComponent(); + //btnAB = GameObject.Find("btnAB").GetComponent(); + //btnCD = GameObject.Find("btnCD").GetComponent(); + //btnABC = GameObject.Find("btnABC").GetComponent(); + + //mUIBtns.Add(btnP1); + //mUIBtns.Add(btnCoin1); + //mUIBtns.Add(btnA); + //mUIBtns.Add(btnB); + //mUIBtns.Add(btnC); + //mUIBtns.Add(btnD); + //mUIBtns.Add(btnAB); + //mUIBtns.Add(btnCD); + //mUIBtns.Add(btnABC); + + //if (btnE != null) + //{ + // mUIBtns.Add(btnE); + // btnE.gameObject.SetActive(false); + //} + //else + //{ + // mUIBtns.Add(btnF); + // btnF.gameObject.SetActive(false); + //} + + +#if UNITY_STANDALONE_WIN || UNITY_EDITOR + //tfKeyPad.gameObject.SetActive(false); +#endif + } + + public void Init(Essgee.Emulation.Machines.IMachine Machine) + { + mKeyCodeCore.Init(Machine, this, false); + } + + + public void UpdateInputKey() + { + mKeyCodeCore.UpdateLogic(); + } +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGKeyboard.cs.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGKeyboard.cs.meta new file mode 100644 index 00000000..99f5db93 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGKeyboard.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0a0d59a993f52994d9ed72d3e9be7c16 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGLog.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGLog.cs new file mode 100644 index 00000000..e7da1618 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGLog.cs @@ -0,0 +1,17 @@ +public class UEGLog : IEssgeeLogger +{ + public void Debug(string message) + { + UnityEngine.Debug.Log(message); + } + + + public void Warning(string message) + { + UnityEngine.Debug.LogWarning(message); + } + public void Err(string message) + { + UnityEngine.Debug.LogError(message); + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGLog.cs.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGLog.cs.meta new file mode 100644 index 00000000..6c8a5ca8 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGLog.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 0e0aaeb14aacc0e4ca88a94680bd4b90 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGMouse.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGMouse.cs new file mode 100644 index 00000000..1c64b41f --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGMouse.cs @@ -0,0 +1,23 @@ +//using MAME.Core; +//using UnityEngine; + +//public class UEGMouse : MonoBehaviour, IMouse +//{ +// static int mX, mY; +// public byte[] buttons = new byte[2]; +// void Update() +// { +// mX = (int)Input.mousePosition.x; +// mY = (int)Input.mousePosition.y; +// buttons[0] = Input.GetMouseButton(0) ? (byte)1 : (byte)0; +// buttons[1] = Input.GetMouseButton(1) ? (byte)1 : (byte)0; +// } + +// public void MouseXY(out int X, out int Y, out byte[] MouseButtons) +// { +// X = mX; +// Y = mY * -1; +// MouseButtons = buttons; +// } + +//} diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGMouse.cs.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGMouse.cs.meta new file mode 100644 index 00000000..cb734ce3 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGMouse.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 5000dd7c855d83549b136afe42b2bb47 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGResources.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGResources.cs new file mode 100644 index 00000000..46094e41 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGResources.cs @@ -0,0 +1,35 @@ +using Essgee.Metadata; +using UnityEngine; + +public class UEGResources : IGameMetaReources +{ + const string ResourceRoot = "Essgee.Unity/"; + + public bool GetCartMetadataDatabase(out string loadedData) + { + try + { + loadedData = Resources.Load(ResourceRoot + "MetadataDatabase.json").text; + return true; + } + catch + { + loadedData = null; + return false; + } + } + + public bool GetDatBytes(string DatName, out byte[] loadedData) + { + try + { + loadedData = Resources.Load(ResourceRoot + "Dat/" + DatName).bytes; + return true; + } + catch + { + loadedData = null; + return false; + } + } +} \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGResources.cs.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGResources.cs.meta new file mode 100644 index 00000000..6203dc09 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGResources.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b6985010eefa09440bc8a294aabf5622 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGSoundPlayer.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGSoundPlayer.cs new file mode 100644 index 00000000..434b3c28 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGSoundPlayer.cs @@ -0,0 +1,99 @@ +using System; +using UnityEngine; + +public class UEGSoundPlayer : MonoBehaviour//, ISoundPlayer +{ + [SerializeField] + private AudioSource m_as; + private RingBuffer _buffer = new RingBuffer(4096); + private TimeSpan lastElapsed; + public double audioFPS { get; private set; } + float lastData = 0; + + + void Awake() + { + AudioClip dummy = AudioClip.Create("dummy", 1, 2, AudioSettings.outputSampleRate, false); + //AudioClip dummy = AudioClip.Create("dummy", 1, 2, 44100, false); + dummy.SetData(new float[] { 1, 1 }, 0); + m_as.clip = dummy; + m_as.loop = true; + m_as.spatialBlend = 1; + } + + public void Initialize() + { + if (!m_as.isPlaying) + { + m_as.Play(); + } + } + + public void StopPlay() + { + if (m_as.isPlaying) + { + m_as.Stop(); + } + } + + void OnAudioFilterRead(float[] data, int channels) + { + if (!Essgeeinit.bInGame) return; + int step = channels; + for (int i = 0; i < data.Length; i += step) + { + float rawFloat = lastData; + if (_buffer.TryRead(out float rawData)) + { + rawFloat = rawData; + } + + data[i] = rawFloat; + for (int fill = 1; fill < step; fill++) + data[i + fill] = rawFloat; + lastData = rawFloat; + } + } + + public unsafe void SubmitSamples(short* buffer, short*[] ChannelSamples, int samples_a) + { + var current = Essgeeinit.sw.Elapsed; + var delta = current - lastElapsed; + lastElapsed = current; + audioFPS = 1d / delta.TotalSeconds; + + + //for (int i = 0; i < samples_a; i++) + //{ + // short left = BitConverter.ToInt16(buffer, i * 2 * 2); + // //short right = BitConverter.ToInt16(buffer, i * 2 * 2 + 2); + // _buffer.Write(left / 32767.0f); + // //_buffer.Write(right / 32767.0f); + //} + + for (int i = 0; i < samples_a; i += 2) + { + _buffer.Write(buffer[i] / 32767.0f); + //_buffer.Write(buffer[i] / 32767.0f); + } + } + + public void BufferWirte(int Off, byte[] Data) + { + } + + public void GetCurrentPosition(out int play_position, out int write_position) + { + play_position = 0; + write_position = 0; + } + + public void SetVolume(int Vol) + { + //TODO + if (m_as) + return; + m_as.volume = Vol; + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGSoundPlayer.cs.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGSoundPlayer.cs.meta new file mode 100644 index 00000000..54a0048b --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGSoundPlayer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a6b878f13700e454ebfa9b7eb26ad804 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGVideoPlayer.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGVideoPlayer.cs new file mode 100644 index 00000000..b9399459 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGVideoPlayer.cs @@ -0,0 +1,109 @@ +using System; +using UnityEngine; +using UnityEngine.UI; + +public class UEGVideoPlayer : MonoBehaviour +{ + [SerializeField] + private int mWidth; + [SerializeField] + private int mHeight; + [SerializeField] + private int mDataLenght; + [SerializeField] + private Texture2D m_rawBufferWarper; + [SerializeField] + private RawImage m_drawCanvas; + [SerializeField] + private RectTransform m_drawCanvasrect; + //byte[] mFrameData; + IntPtr mFrameDataPtr; + + private TimeSpan lastElapsed; + public double videoFPS { get; private set; } + public ulong mFrame { get; private set; } + bool bInit = false; + bool bHadData = false; + + private void Awake() + { + mFrame = 0; + m_drawCanvas = GameObject.Find("GameRawImage").GetComponent(); + m_drawCanvasrect = m_drawCanvas.GetComponent(); + } + + public void Initialize() + { + m_drawCanvas.color = Color.white; + + if (m_rawBufferWarper == null) + { + mDataLenght = mWidth * mHeight * 4; + //mFrameData = new byte[mDataLenght]; + + //// ̶飬ֹƶ + //var bitmapcolorRect_handle = GCHandle.Alloc(mFrameData, GCHandleType.Pinned); + //// ȡָ + //mFrameDataPtr = bitmapcolorRect_handle.AddrOfPinnedObject(); + + + //MAMEBGRA32úú + m_rawBufferWarper = new Texture2D(mWidth, mHeight, TextureFormat.BGRA32, false); + //m_rawBufferWarper = new Texture2D(mWidth, mHeight, TextureFormat.ARGB32, false); + m_rawBufferWarper.filterMode = FilterMode.Point; + } + + //mFrameDataPtr = framePtr; + m_drawCanvas.texture = m_rawBufferWarper; + bInit = true; + + float targetWidth = ((float)mWidth / mHeight) * m_drawCanvasrect.rect.height; + m_drawCanvasrect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, targetWidth); + } + + public void StopVideo() + { + bInit = false; + m_drawCanvas.color = new Color(0, 0, 0, 0); + } + + void Update() + { + if (!bHadData) + return; + + if (!bInit) + { + Initialize(); + return; + } + m_rawBufferWarper.LoadRawTextureData(mFrameDataPtr, mDataLenght); + m_rawBufferWarper.Apply(); + } + + //public void SubmitVideo(int width, int height, byte[] data, long frame_number) + public void SubmitVideo(int width, int height, IntPtr ptr, long frame_number) + { + + mFrame = (ulong)frame_number; + var current = Essgeeinit.sw.Elapsed; + var delta = current - lastElapsed; + lastElapsed = current; + videoFPS = 1d / delta.TotalSeconds; + //mFrameData = data; + mFrameDataPtr = ptr; + + if (!bHadData) + { + mWidth = width; + mHeight = height; + bHadData = true; + } + //Debug.Log($"frame_number -> {frame_number}"); + } + + public byte[] GetScreenImg() + { + return (m_drawCanvas.texture as Texture2D).EncodeToJPG(); + } +} diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGVideoPlayer.cs.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGVideoPlayer.cs.meta new file mode 100644 index 00000000..4643091a --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UEGVideoPlayer.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ac6bf3b2881889b418e5926193ca59f4 \ No newline at end of file diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UniMouse.cs b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UniMouse.cs new file mode 100644 index 00000000..34e25a48 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UniMouse.cs @@ -0,0 +1,23 @@ +//using MAME.Core; +//using UnityEngine; + +//public class UniMouse : MonoBehaviour, IMouse +//{ +// static int mX, mY; +// public byte[] buttons = new byte[2]; +// void Update() +// { +// mX = (int)Input.mousePosition.x; +// mY = (int)Input.mousePosition.y; +// buttons[0] = Input.GetMouseButton(0) ? (byte)1 : (byte)0; +// buttons[1] = Input.GetMouseButton(1) ? (byte)1 : (byte)0; +// } + +// public void MouseXY(out int X, out int Y, out byte[] MouseButtons) +// { +// X = mX; +// Y = mY * -1; +// MouseButtons = buttons; +// } + +//} diff --git a/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UniMouse.cs.meta b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UniMouse.cs.meta new file mode 100644 index 00000000..b9168f81 --- /dev/null +++ b/AxibugEmuOnline.Client/Assets/Script/AppMain/Emulator/EssgeeEmulator/UEssgeeInterface/UniMouse.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: d5855fd9c3285e144b7db2b76cf55d77 \ No newline at end of file