├── .gitignore ├── AUTHORS ├── Assets ├── StreamingAssets.meta ├── StreamingAssets │ ├── bpong.gb │ ├── bpong.gb.meta │ ├── bpong.txt │ └── bpong.txt.meta ├── UnityGB.meta └── UnityGB │ ├── DemoScene.unity │ ├── DemoScene.unity.meta │ ├── Materials.meta │ ├── Materials │ ├── ScreenMaterial.mat │ └── ScreenMaterial.mat.meta │ ├── Scripts.meta │ └── Scripts │ ├── DefaultAudioOutput.cs │ ├── DefaultAudioOutput.cs.meta │ ├── DefaultEmulatorManager.cs │ ├── DefaultEmulatorManager.cs.meta │ ├── DefaultSaveMemory.cs │ ├── DefaultSaveMemory.cs.meta │ ├── DefaultVideoOutput.cs │ ├── DefaultVideoOutput.cs.meta │ ├── UnityGB.meta │ └── UnityGB │ ├── Audio.meta │ ├── Audio │ ├── NoiseGenerator.cs │ ├── NoiseGenerator.cs.meta │ ├── SoundChip.cs │ ├── SoundChip.cs.meta │ ├── SquareWaveGenerator.cs │ ├── SquareWaveGenerator.cs.meta │ ├── VoluntaryWaveGenerator.cs │ └── VoluntaryWaveGenerator.cs.meta │ ├── CPU.cs │ ├── CPU.cs.meta │ ├── Emulator.cs │ ├── Emulator.cs.meta │ ├── EmulatorBase.cs │ ├── EmulatorBase.cs.meta │ ├── IAudioOutput.cs │ ├── IAudioOutput.cs.meta │ ├── ISaveMemory.cs │ ├── ISaveMemory.cs.meta │ ├── IVideoOutput.cs │ ├── IVideoOutput.cs.meta │ ├── ROM.cs │ └── ROM.cs.meta ├── LICENSE ├── ProjectSettings ├── AudioManager.asset ├── DynamicsManager.asset ├── EditorBuildSettings.asset ├── EditorSettings.asset ├── GraphicsSettings.asset ├── InputManager.asset ├── NavMeshAreas.asset ├── NavMeshLayers.asset ├── NetworkManager.asset ├── Physics2DSettings.asset ├── ProjectSettings.asset ├── ProjectVersion.txt ├── QualitySettings.asset ├── TagManager.asset └── TimeManager.asset └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ### Unity ### 2 | [Ll]ibrary/ 3 | [Tt]emp/ 4 | [Oo]bj/ 5 | [Bb]uild/ 6 | 7 | # Autogenerated VS/MD solution and project files 8 | *.csproj 9 | *.unityproj 10 | *.sln 11 | *.suo 12 | *.tmp 13 | *.user 14 | *.userprefs 15 | *.pidb 16 | *.booproj 17 | 18 | # Unity3D generated meta files 19 | *.pidb.meta 20 | 21 | # Unity3D Generated File On Crash Reports 22 | sysinfo.txt 23 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Primary authors: 2 | 3 | * Jonathan Odul 4 | 5 | Contributors: 6 | 7 | * Alban Fichet 8 | * Michael Birken 9 | * Shane O'Brien 10 | -------------------------------------------------------------------------------- /Assets/StreamingAssets.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cba8bda1fa2439e46971b1cb9578bcff 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/StreamingAssets/bpong.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/Assets/StreamingAssets/bpong.gb -------------------------------------------------------------------------------- /Assets/StreamingAssets/bpong.gb.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: be4b8e206fe3b67419dbfa17e3c1465f 3 | DefaultImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /Assets/StreamingAssets/bpong.txt: -------------------------------------------------------------------------------- 1 | bpong.gb downloaded at http://furrtek.free.fr/?a=gbasm. 2 | This game is open-source and you can find the source code on the website. -------------------------------------------------------------------------------- /Assets/StreamingAssets/bpong.txt.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: c96e01bc62370154cba21d92762f39e1 3 | TextScriptImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /Assets/UnityGB.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 261d54b63b2bf3046b4923fdf9f558e8 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/UnityGB/DemoScene.unity: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/Assets/UnityGB/DemoScene.unity -------------------------------------------------------------------------------- /Assets/UnityGB/DemoScene.unity.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 087ffcbbcb4e8af408303de8687b72a2 3 | DefaultImporter: 4 | userData: 5 | -------------------------------------------------------------------------------- /Assets/UnityGB/Materials.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e38d77290a2ea406c95a8e56ba8bfbe9 3 | folderAsset: yes 4 | timeCreated: 1438685676 5 | licenseType: Free 6 | DefaultImporter: 7 | userData: 8 | assetBundleName: 9 | assetBundleVariant: 10 | -------------------------------------------------------------------------------- /Assets/UnityGB/Materials/ScreenMaterial.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/Assets/UnityGB/Materials/ScreenMaterial.mat -------------------------------------------------------------------------------- /Assets/UnityGB/Materials/ScreenMaterial.mat.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bacd1475b441948ca8591b0b988cae4d 3 | timeCreated: 1438685684 4 | licenseType: Free 5 | NativeFormatImporter: 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 6b23ed26a89d1364ab2ae30824a48295 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/DefaultAudioOutput.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Threading; 6 | 7 | using UnityGB; 8 | 9 | [RequireComponent(typeof(AudioSource))] 10 | public class DefaultAudioOutput : MonoBehaviour, IAudioOutput 11 | { 12 | public float Gain = 0.05f; 13 | 14 | private int _samplesAvailable; 15 | private PipeStream _pipeStream; 16 | private byte[] _buffer; 17 | 18 | void Awake() 19 | { 20 | // Get Unity Buffer size 21 | int bufferLength = 0, numBuffers = 0; 22 | AudioSettings.GetDSPBufferSize(out bufferLength, out numBuffers); 23 | _samplesAvailable = bufferLength; 24 | 25 | // Prepare our buffer 26 | _pipeStream = new PipeStream(); 27 | _pipeStream.MaxBufferLength = _samplesAvailable * 2 * 2; 28 | _buffer = new byte[_samplesAvailable * 2]; 29 | } 30 | 31 | void OnAudioFilterRead(float[] data, int channels) 32 | { 33 | // This method is not called if you don't own Unity PRO. 34 | 35 | if (_buffer.Length != data.Length) 36 | { 37 | Debug.Log("Does DSPBufferSize or speakerMode changed? Audio disabled."); 38 | return; 39 | } 40 | 41 | int r = _pipeStream.Read(_buffer, 0, data.Length); 42 | for (int i=0; i _buffer = new Queue(); 66 | private long _maxBufferLength = 8192; 67 | 68 | public long MaxBufferLength 69 | { 70 | get { return _maxBufferLength; } 71 | set { _maxBufferLength = value; } 72 | } 73 | 74 | public new void Dispose() 75 | { 76 | _buffer.Clear(); 77 | } 78 | 79 | public override void Flush() 80 | { 81 | } 82 | 83 | public override long Seek(long offset, SeekOrigin origin) 84 | { 85 | throw new NotImplementedException(); 86 | } 87 | 88 | public override void SetLength(long value) 89 | { 90 | throw new NotImplementedException(); 91 | } 92 | 93 | public override int Read(byte[] buffer, int offset, int count) 94 | { 95 | if (offset != 0) 96 | throw new NotImplementedException("Offsets with value of non-zero are not supported"); 97 | if (buffer == null) 98 | throw new ArgumentException("Buffer is null"); 99 | if (offset + count > buffer.Length) 100 | throw new ArgumentException("The sum of offset and count is greater than the buffer length. "); 101 | if (offset < 0 || count < 0) 102 | throw new ArgumentOutOfRangeException("offset", "offset or count is negative."); 103 | 104 | if (count == 0) 105 | return 0; 106 | 107 | int readLength = 0; 108 | 109 | lock (_buffer) 110 | { 111 | // fill the read buffer 112 | for (; readLength < count && Length > 0; readLength++) 113 | { 114 | buffer [readLength] = _buffer.Dequeue(); 115 | } 116 | } 117 | 118 | return readLength; 119 | } 120 | 121 | private bool ReadAvailable(int count) 122 | { 123 | return (Length >= count); 124 | } 125 | 126 | public override void Write(byte[] buffer, int offset, int count) 127 | { 128 | if (buffer == null) 129 | throw new ArgumentException("Buffer is null"); 130 | if (offset + count > buffer.Length) 131 | throw new ArgumentException("The sum of offset and count is greater than the buffer length. "); 132 | if (offset < 0 || count < 0) 133 | throw new ArgumentOutOfRangeException("offset", "offset or count is negative."); 134 | if (count == 0) 135 | return; 136 | 137 | lock (_buffer) 138 | { 139 | while (Length >= _maxBufferLength) 140 | return; 141 | 142 | // queue up the buffer data 143 | foreach (byte b in buffer) 144 | { 145 | _buffer.Enqueue(b); 146 | } 147 | } 148 | } 149 | 150 | public override bool CanRead 151 | { 152 | get { return true; } 153 | } 154 | 155 | public override bool CanSeek 156 | { 157 | get { return false; } 158 | } 159 | 160 | public override bool CanWrite 161 | { 162 | get { return true; } 163 | } 164 | 165 | public override long Length 166 | { 167 | get { return _buffer.Count; } 168 | } 169 | 170 | public override long Position 171 | { 172 | get { return 0; } 173 | set { throw new NotImplementedException(); } 174 | } 175 | } 176 | 177 | } -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/DefaultAudioOutput.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5d1429fce58af0c48858cf75545f3aa0 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/DefaultEmulatorManager.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections; 4 | using System.Collections.Generic; 5 | using System.IO; 6 | 7 | using UnityGB; 8 | 9 | public class DefaultEmulatorManager : MonoBehaviour 10 | { 11 | public string Filename; 12 | public Renderer ScreenRenderer; 13 | 14 | public EmulatorBase Emulator 15 | { 16 | get; 17 | private set; 18 | } 19 | 20 | private Dictionary _keyMapping; 21 | 22 | // Use this for initialization 23 | void Start() 24 | { 25 | // Init Keyboard mapping 26 | _keyMapping = new Dictionary(); 27 | _keyMapping.Add(KeyCode.UpArrow, EmulatorBase.Button.Up); 28 | _keyMapping.Add(KeyCode.DownArrow, EmulatorBase.Button.Down); 29 | _keyMapping.Add(KeyCode.LeftArrow, EmulatorBase.Button.Left); 30 | _keyMapping.Add(KeyCode.RightArrow, EmulatorBase.Button.Right); 31 | _keyMapping.Add(KeyCode.Z, EmulatorBase.Button.A); 32 | _keyMapping.Add(KeyCode.X, EmulatorBase.Button.B); 33 | _keyMapping.Add(KeyCode.Space, EmulatorBase.Button.Start); 34 | _keyMapping.Add(KeyCode.LeftShift, EmulatorBase.Button.Select); 35 | 36 | 37 | // Load emulator 38 | IVideoOutput drawable = new DefaultVideoOutput(); 39 | IAudioOutput audio = GetComponent(); 40 | ISaveMemory saveMemory = new DefaultSaveMemory(); 41 | Emulator = new Emulator(drawable, audio, saveMemory); 42 | ScreenRenderer.material.mainTexture = ((DefaultVideoOutput) Emulator.Video).Texture; 43 | 44 | gameObject.GetComponent().enabled = false; 45 | StartCoroutine(LoadRom(Filename)); 46 | } 47 | 48 | void Update() 49 | { 50 | // Input 51 | foreach (KeyValuePair entry in _keyMapping) 52 | { 53 | if (Input.GetKeyDown(entry.Key)) 54 | Emulator.SetInput(entry.Value, true); 55 | else if (Input.GetKeyUp(entry.Key)) 56 | Emulator.SetInput(entry.Value, false); 57 | } 58 | 59 | if (Input.GetKeyDown(KeyCode.T)) 60 | { 61 | byte[] screenshot = ((DefaultVideoOutput) Emulator.Video).Texture.EncodeToPNG(); 62 | File.WriteAllBytes("./screenshot.png", screenshot); 63 | Debug.Log("Screenshot saved."); 64 | } 65 | } 66 | 67 | 68 | 69 | private IEnumerator LoadRom(string filename) 70 | { 71 | string path = System.IO.Path.Combine(Application.streamingAssetsPath, filename); 72 | Debug.Log("Loading ROM from " + path + "."); 73 | 74 | if (!File.Exists (path)) { 75 | Debug.LogError("File couldn't be found."); 76 | yield break; 77 | } 78 | 79 | WWW www = new WWW("file://" + path); 80 | yield return www; 81 | 82 | if (string.IsNullOrEmpty(www.error)) 83 | { 84 | Emulator.LoadRom(www.bytes); 85 | StartCoroutine(Run()); 86 | } else 87 | Debug.LogError("Error during loading the ROM.\n" + www.error); 88 | } 89 | 90 | private IEnumerator Run() 91 | { 92 | gameObject.GetComponent().enabled = true; 93 | while (true) 94 | { 95 | // Run 96 | Emulator.RunNextStep(); 97 | 98 | yield return null; 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/DefaultEmulatorManager.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f5ed4c0fccfc424478952dd4c2e2bfe9 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/DefaultSaveMemory.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.IO; 4 | 5 | using UnityGB; 6 | 7 | public class DefaultSaveMemory : ISaveMemory 8 | { 9 | public void Save(string name, byte[] data) 10 | { 11 | if (data == null) 12 | return; 13 | 14 | string path = System.IO.Path.Combine(Application.streamingAssetsPath, name + ".sav"); 15 | try 16 | { 17 | File.WriteAllBytes(path, data); 18 | } catch (System.Exception e) 19 | { 20 | Debug.LogError("Couldn't save save file."); 21 | Debug.Log(e.Message); 22 | } 23 | } 24 | 25 | public byte[] Load(string name) 26 | { 27 | string path = System.IO.Path.Combine(Application.streamingAssetsPath, name + ".sav"); 28 | 29 | if (!File.Exists (path)) { 30 | Debug.Log("No save file could be found for " + name + "."); 31 | return null; 32 | } 33 | 34 | byte[] data = null; 35 | try 36 | { 37 | data = File.ReadAllBytes(path); 38 | } catch (System.Exception e) 39 | { 40 | Debug.LogError("Couldn't load save file."); 41 | Debug.Log(e.Message); 42 | } 43 | 44 | return data; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/DefaultSaveMemory.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 88a79dfcb25e7214c8b13e5a978a742f 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/DefaultVideoOutput.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | using UnityGB; 5 | 6 | public class DefaultVideoOutput : IVideoOutput 7 | { 8 | public int Width 9 | { 10 | get; 11 | private set; 12 | } 13 | 14 | public int Height 15 | { 16 | get; 17 | private set; 18 | } 19 | 20 | public Texture2D Texture 21 | { 22 | get; 23 | private set; 24 | } 25 | 26 | public void SetSize(int w, int h) 27 | { 28 | Width = w; 29 | Height = h; 30 | Texture = new Texture2D(w, h, TextureFormat.RGB24, false); 31 | Texture.filterMode = FilterMode.Point; 32 | } 33 | 34 | public void SetPixels(uint[] colors) 35 | { 36 | int x, y; 37 | for (int i = 0; i < colors.Length; ++i) 38 | { 39 | x = i % Width; 40 | y = i / Width; 41 | Texture.SetPixel(x, Height - 1 - y, UIntToColor(colors [i])); 42 | } 43 | Texture.Apply(); 44 | } 45 | 46 | private Color UIntToColor(uint color) 47 | { 48 | byte r = (byte)(color >> 16); 49 | byte g = (byte)(color >> 8); 50 | byte b = (byte)(color >> 0); 51 | return new Color(r / 255f, g / 255f, b / 255f, 255f); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/DefaultVideoOutput.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 29e162d882ddfda49aaed2d5bca5b027 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d2cf3cec68c3cf04f888cebca083115c 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Audio.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 486a160c904637a4fa82da8e63b8d046 3 | folderAsset: yes 4 | DefaultImporter: 5 | userData: 6 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Audio/NoiseGenerator.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0414 2 | using System; 3 | 4 | namespace UnityGB 5 | { 6 | public class NoiseGenerator 7 | { 8 | // Indicates sound is to be played on the left channel of a stereo sound 9 | public const int CHAN_LEFT = 1; 10 | 11 | // Indictaes sound is to be played on the right channel of a stereo sound 12 | public const int CHAN_RIGHT = 2; 13 | 14 | // Indicates that sound is mono 15 | public const int CHAN_MONO = 4; 16 | 17 | // Indicates the length of the sound in frames 18 | private int totalLength; 19 | private int cyclePos; 20 | 21 | // The length of one cycle, in samples 22 | private int cycleLength; 23 | 24 | // Amplitude of the wave function 25 | private int amplitude; 26 | 27 | // Channel being played on. Combination of CHAN_LEFT and CHAN_RIGHT, or CHAN_MONO 28 | private int channel; 29 | 30 | // Sampling rate of the output channel 31 | private int sampleRate; 32 | 33 | // Initial value of the envelope 34 | private int initialEnvelope; 35 | private int numStepsEnvelope; 36 | 37 | // Whether the envelope is an increase/decrease in amplitude 38 | private bool increaseEnvelope; 39 | private int counterEnvelope; 40 | 41 | // Stores the random values emulating the polynomial generator (badly!) 42 | private bool[] randomValues; 43 | private int dividingRatio; 44 | private int polynomialSteps; 45 | private int shiftClockFreq; 46 | private int finalFreq; 47 | private int cycleOffset; 48 | private Random random = new Random(); 49 | 50 | // Creates a white noise generator with the specified wavelength, amplitude, channel, and sample rate 51 | public NoiseGenerator(int waveLength, int ampl, int chan, int rate) 52 | { 53 | cycleLength = waveLength; 54 | amplitude = ampl; 55 | cyclePos = 0; 56 | channel = chan; 57 | sampleRate = rate; 58 | cycleOffset = 0; 59 | 60 | randomValues = new bool[32767]; 61 | 62 | 63 | for (int r = 0; r < 32767; r++) 64 | randomValues [r] = random.NextDouble() < 0.5; 65 | 66 | cycleOffset = 0; 67 | } 68 | 69 | // Creates a white noise generator with the specified sample rate 70 | public NoiseGenerator(int rate) 71 | { 72 | cyclePos = 0; 73 | channel = CHAN_LEFT | CHAN_RIGHT; 74 | cycleLength = 2; 75 | totalLength = 0; 76 | sampleRate = rate; 77 | amplitude = 32; 78 | 79 | randomValues = new bool[32767]; 80 | 81 | for (int r = 0; r < 32767; r++) 82 | randomValues [r] = random.NextDouble() < 0.5; 83 | 84 | cycleOffset = 0; 85 | } 86 | 87 | public void SetSampleRate(int sr) 88 | { 89 | sampleRate = sr; 90 | } 91 | 92 | // Set the channel that the white noise is playing on 93 | public void SetChannel(int chan) 94 | { 95 | channel = chan; 96 | } 97 | 98 | // Setup the envelope, and restart it from the beginning 99 | public void SetEnvelope(int initialValue, int numSteps, bool increase) 100 | { 101 | initialEnvelope = initialValue; 102 | numStepsEnvelope = numSteps; 103 | increaseEnvelope = increase; 104 | amplitude = initialValue * 2; 105 | } 106 | 107 | // Set the length of the sound 108 | public void SetLength(int gbLength) 109 | { 110 | if (gbLength == -1) 111 | { 112 | totalLength = -1; 113 | } else 114 | { 115 | totalLength = (64 - gbLength) / 4; 116 | } 117 | } 118 | 119 | public void SetParameters(float dividingRatio, bool polynomialSteps, int shiftClockFreq) 120 | { 121 | this.dividingRatio = (int)dividingRatio; 122 | if (!polynomialSteps) 123 | { 124 | this.polynomialSteps = 32767; 125 | cycleLength = 32767 << 8; 126 | cycleOffset = 0; 127 | } else 128 | { 129 | this.polynomialSteps = 63; 130 | cycleLength = 63 << 8; 131 | 132 | cycleOffset = (int)(random.NextDouble() * 1000); 133 | } 134 | this.shiftClockFreq = shiftClockFreq; 135 | 136 | if (dividingRatio == 0) 137 | dividingRatio = 0.5f; 138 | 139 | finalFreq = ((int)(4194304 / 8 / dividingRatio)) >> (shiftClockFreq + 1); 140 | } 141 | 142 | // Output a single frame of samples, of specified length. Start at position indicated in the output array. 143 | 144 | public void Play(byte[] b, int numSamples, int numChannels) 145 | { 146 | int val; 147 | 148 | if (totalLength != 0) 149 | { 150 | totalLength--; 151 | 152 | counterEnvelope++; 153 | if (numStepsEnvelope != 0) 154 | { 155 | if (((counterEnvelope % numStepsEnvelope) == 0) && (amplitude > 0)) 156 | { 157 | if (!increaseEnvelope) 158 | { 159 | if (amplitude > 0) 160 | amplitude -= 2; 161 | } else 162 | { 163 | if (amplitude < 16) 164 | amplitude += 2; 165 | } 166 | } 167 | } 168 | 169 | 170 | int step = ((finalFreq) / (sampleRate >> 8)); 171 | 172 | for (int r = 0; r < numSamples; r++) 173 | { 174 | bool value = randomValues [((cycleOffset) + (cyclePos >> 8)) & 0x7FFF]; 175 | val = value ? (amplitude / 2) : (-amplitude / 2); 176 | 177 | if ((channel & CHAN_LEFT) != 0) 178 | b [r * numChannels] += (byte)val; 179 | if ((channel & CHAN_RIGHT) != 0) 180 | b [r * numChannels + 1] += (byte)val; 181 | if ((channel & CHAN_MONO) != 0) 182 | b [r * numChannels] = b [r * numChannels + 1] += (byte)val; 183 | 184 | cyclePos = (cyclePos + step) % cycleLength; 185 | } 186 | } 187 | } 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Audio/NoiseGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 0ef401450292395458df2c96f1c79638 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Audio/SoundChip.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace UnityGB 4 | { 5 | public class SoundChip 6 | { 7 | internal SquareWaveGenerator channel1; 8 | internal SquareWaveGenerator channel2; 9 | internal VoluntaryWaveGenerator channel3; 10 | internal NoiseGenerator channel4; 11 | internal bool soundEnabled = true; 12 | 13 | /** If true, channel is enabled */ 14 | internal bool channel1Enable = true, channel2Enable = true, 15 | channel3Enable = true, channel4Enable = true; 16 | 17 | /** Current sampling rate that sound is output at */ 18 | private int sampleRate = 44100; 19 | 20 | 21 | /** Initialize sound emulation, and allocate sound hardware */ 22 | public SoundChip() 23 | { 24 | channel1 = new SquareWaveGenerator(sampleRate); 25 | channel2 = new SquareWaveGenerator(sampleRate); 26 | channel3 = new VoluntaryWaveGenerator(sampleRate); 27 | channel4 = new NoiseGenerator(sampleRate); 28 | } 29 | 30 | /** Change the sample rate of the playback */ 31 | public void SetSampleRate(int sr) 32 | { 33 | sampleRate = sr; 34 | 35 | channel1.SetSampleRate(sr); 36 | channel2.SetSampleRate(sr); 37 | channel3.SetSampleRate(sr); 38 | channel4.SetSampleRate(sr); 39 | } 40 | 41 | /** Adds a single frame of sound data to the buffer */ 42 | public void OutputSound(IAudioOutput audioOutput) 43 | { 44 | if (soundEnabled) 45 | return; 46 | 47 | int numChannels = 2; // Always stereo for Game Boy 48 | int numSamples = audioOutput.GetSamplesAvailable(); 49 | 50 | byte[] b = new byte[numChannels * numSamples]; 51 | 52 | if (channel1Enable) 53 | channel1.Play(b, numSamples, numChannels); 54 | if (channel2Enable) 55 | channel2.Play(b, numSamples, numChannels); 56 | if (channel3Enable) 57 | channel3.Play(b, numSamples, numChannels); 58 | if (channel4Enable) 59 | channel4.Play(b, numSamples, numChannels); 60 | 61 | audioOutput.Play(b); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Audio/SoundChip.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 51c63d69f2458b642a830d2a100c2a3d 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Audio/SquareWaveGenerator.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0414 2 | using System.Collections; 3 | 4 | /// 5 | /// This class can mix a square wave signal with a sound buffer. 6 | /// It supports all features of the Gameboys sound channels 1 and 2. 7 | /// 8 | 9 | namespace UnityGB 10 | { 11 | public class SquareWaveGenerator 12 | { 13 | // Sound is to be played on the left channel of a stereo sound 14 | public const int CHAN_LEFT = 1; 15 | 16 | // Sound is to be played on the right channel of a stereo sound 17 | public const int CHAN_RIGHT = 2; 18 | 19 | // Sound is to be played back in mono 20 | public const int CHAN_MONO = 4; 21 | 22 | // Length of the sound (in frames) 23 | private int totalLength; 24 | 25 | // Current position in the waveform (in samples) 26 | private int cyclePos; 27 | 28 | // Length of the waveform (in samples) 29 | private int cycleLength; 30 | 31 | // Amplitude of the waveform 32 | private int amplitude; 33 | 34 | // Amount of time the sample stays high in a single waveform (in eighths) 35 | private int dutyCycle; 36 | 37 | // The channel that the sound is to be played back on 38 | private int channel; 39 | 40 | // Sample rate of the sound buffer 41 | private int sampleRate; 42 | 43 | // Initial amplitude 44 | private int initialEnvelope; 45 | 46 | // Number of envelope steps 47 | private int numStepsEnvelope; 48 | 49 | // If true, envelope will increase amplitude of sound, false indicates decrease 50 | private bool increaseEnvelope; 51 | 52 | // Current position in the envelope 53 | private int counterEnvelope; 54 | 55 | // Frequency of the sound in internal GB format 56 | private int gbFrequency; 57 | 58 | // Amount of time between sweep steps. 59 | private int timeSweep; 60 | 61 | // Number of sweep steps 62 | private int numSweep; 63 | 64 | // If true, sweep will decrease the sound frequency, otherwise, it will increase 65 | private bool decreaseSweep; 66 | 67 | // Current position in the sweep 68 | private int counterSweep; 69 | 70 | // Create a square wave generator with the supplied parameters 71 | public SquareWaveGenerator(int waveLength, int ampl, int duty, int chan, int rate) 72 | { 73 | cycleLength = waveLength; 74 | amplitude = ampl; 75 | cyclePos = 0; 76 | dutyCycle = duty; 77 | channel = chan; 78 | sampleRate = rate; 79 | } 80 | 81 | // Create a square wave generator at the specified sample rate 82 | public SquareWaveGenerator(int rate) 83 | { 84 | dutyCycle = 4; 85 | cyclePos = 0; 86 | channel = CHAN_LEFT | CHAN_RIGHT; 87 | cycleLength = 2; 88 | totalLength = 0; 89 | sampleRate = rate; 90 | amplitude = 32; 91 | counterSweep = 0; 92 | } 93 | 94 | // Set the sound buffer sample rate 95 | public void SetSampleRate(int sr) 96 | { 97 | sampleRate = sr; 98 | } 99 | 100 | // Set the duty cycle 101 | public void SetDutyCycle(int duty) 102 | { 103 | switch (duty) 104 | { 105 | case 0: 106 | dutyCycle = 1; 107 | break; 108 | case 1: 109 | dutyCycle = 2; 110 | break; 111 | case 2: 112 | dutyCycle = 4; 113 | break; 114 | case 3: 115 | dutyCycle = 6; 116 | break; 117 | } 118 | } 119 | 120 | // Set the sound frequency, in internal GB format */ 121 | public void SetFrequency(int gbFrequency) 122 | { 123 | try 124 | { 125 | float frequency = 131072 / 2048f; 126 | 127 | if (gbFrequency != 2048) 128 | { 129 | frequency = (131072f / (float)(2048 - gbFrequency)); 130 | } 131 | 132 | this.gbFrequency = gbFrequency; 133 | if (frequency != 0) 134 | { 135 | cycleLength = (256 * sampleRate) / (int)frequency; 136 | } else 137 | { 138 | cycleLength = 65535; 139 | } 140 | if (cycleLength == 0) 141 | cycleLength = 1; 142 | } catch 143 | { 144 | // Skip ip 145 | } 146 | } 147 | 148 | // Set the channel for playback 149 | public void SetChannel(int chan) 150 | { 151 | channel = chan; 152 | } 153 | 154 | // Set the envelope parameters 155 | public void SetEnvelope(int initialValue, int numSteps, bool increase) 156 | { 157 | initialEnvelope = initialValue; 158 | numStepsEnvelope = numSteps; 159 | increaseEnvelope = increase; 160 | amplitude = initialValue * 2; 161 | } 162 | 163 | // Set the frequency sweep parameters 164 | public void SetSweep(int time, int num, bool decrease) 165 | { 166 | timeSweep = (time + 1) / 2; 167 | numSweep = num; 168 | decreaseSweep = decrease; 169 | counterSweep = 0; 170 | } 171 | 172 | public void SetLength(int gbLength) 173 | { 174 | if (gbLength == -1) 175 | { 176 | totalLength = -1; 177 | } else 178 | { 179 | totalLength = (64 - gbLength) / 4; 180 | } 181 | } 182 | 183 | public void SetLength3(int gbLength) 184 | { 185 | if (gbLength == -1) 186 | { 187 | totalLength = -1; 188 | } else 189 | { 190 | totalLength = (256 - gbLength) / 4; 191 | } 192 | } 193 | 194 | public void SetVolume3(int volume) 195 | { 196 | switch (volume) 197 | { 198 | case 0: 199 | amplitude = 0; 200 | break; 201 | case 1: 202 | amplitude = 32; 203 | break; 204 | case 2: 205 | amplitude = 16; 206 | break; 207 | case 3: 208 | amplitude = 8; 209 | break; 210 | } 211 | } 212 | 213 | // Output a frame of sound data into the buffer using the supplied frame length and array offset. 214 | public void Play(byte[] b, int numSamples, int numChannels) 215 | { 216 | int val = 0; 217 | 218 | if (totalLength != 0) 219 | { 220 | totalLength--; 221 | 222 | if (timeSweep != 0) 223 | { 224 | counterSweep++; 225 | if (counterSweep > timeSweep) 226 | { 227 | if (decreaseSweep) 228 | { 229 | SetFrequency(gbFrequency - (gbFrequency >> numSweep)); 230 | } else 231 | { 232 | SetFrequency(gbFrequency + (gbFrequency >> numSweep)); 233 | } 234 | counterSweep = 0; 235 | } 236 | } 237 | 238 | counterEnvelope++; 239 | if (numStepsEnvelope != 0) 240 | { 241 | if (((counterEnvelope % numStepsEnvelope) == 0) && (amplitude > 0)) 242 | { 243 | if (!increaseEnvelope) 244 | { 245 | if (amplitude > 0) 246 | amplitude -= 2; 247 | } else 248 | { 249 | if (amplitude < 16) 250 | amplitude += 2; 251 | } 252 | } 253 | } 254 | 255 | for (int r = 0; r < numSamples; r++) 256 | { 257 | if (cycleLength != 0) 258 | { 259 | if (((8 * cyclePos) / cycleLength) >= dutyCycle) 260 | { 261 | val = amplitude; 262 | } else 263 | { 264 | val = -amplitude; 265 | } 266 | } 267 | 268 | if ((channel & CHAN_LEFT) != 0) 269 | b [r * numChannels] += (byte)val; 270 | if ((channel & CHAN_RIGHT) != 0) 271 | b [r * numChannels + 1] += (byte)val; 272 | if ((channel & CHAN_MONO) != 0) 273 | b [r * numChannels] = b [r * numChannels + 1] = (byte)val; 274 | 275 | cyclePos = (cyclePos + 256) % cycleLength; 276 | } 277 | } 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Audio/SquareWaveGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 7b2039431acf5654db4042effa1b082f 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Audio/VoluntaryWaveGenerator.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0414 2 | using System.Collections; 3 | 4 | /// 5 | /// Voluntary wave generator. 6 | /// 7 | 8 | namespace UnityGB 9 | { 10 | public class VoluntaryWaveGenerator 11 | { 12 | public const int CHAN_LEFT = 1; 13 | public const int CHAN_RIGHT = 2; 14 | public const int CHAN_MONO = 4; 15 | 16 | private int totalLength; 17 | private int cyclePos; 18 | private int cycleLength; 19 | private int amplitude; 20 | private int channel; 21 | private int sampleRate; 22 | private int volumeShift; 23 | private int[] waveform = new int[32]; 24 | 25 | public VoluntaryWaveGenerator(int waveLength, int ampl, int duty, int chan, int rate) 26 | { 27 | cycleLength = waveLength; 28 | amplitude = ampl; 29 | cyclePos = 0; 30 | channel = chan; 31 | sampleRate = rate; 32 | } 33 | 34 | public VoluntaryWaveGenerator(int rate) 35 | { 36 | cyclePos = 0; 37 | channel = CHAN_LEFT | CHAN_RIGHT; 38 | cycleLength = 2; 39 | totalLength = 0; 40 | sampleRate = rate; 41 | amplitude = 32; 42 | } 43 | 44 | public void SetSampleRate(int sr) 45 | { 46 | sampleRate = sr; 47 | } 48 | 49 | public void SetFrequency(int gbFrequency) 50 | { 51 | float frequency = 65536f / (float)(2048 - gbFrequency); 52 | cycleLength = (int)((float)(256f * sampleRate) / frequency); 53 | if (cycleLength == 0) 54 | cycleLength = 1; 55 | } 56 | 57 | public void SetChannel(int chan) 58 | { 59 | channel = chan; 60 | } 61 | 62 | public void SetLength(int gbLength) 63 | { 64 | if (gbLength == -1) 65 | { 66 | totalLength = -1; 67 | } else 68 | { 69 | totalLength = (256 - gbLength) / 4; 70 | } 71 | } 72 | 73 | public void SetSamplePair(int address, int value) 74 | { 75 | waveform [address * 2] = (value & 0xF0) >> 4; 76 | waveform [address * 2 + 1] = (value & 0x0F); 77 | } 78 | 79 | public void SetVolume(int volume) 80 | { 81 | switch (volume) 82 | { 83 | case 0: 84 | volumeShift = 5; 85 | break; 86 | case 1: 87 | volumeShift = 0; 88 | break; 89 | case 2: 90 | volumeShift = 1; 91 | break; 92 | case 3: 93 | volumeShift = 2; 94 | break; 95 | } 96 | } 97 | 98 | public void Play(byte[] b, int numSamples, int numChannels) 99 | { 100 | int val; 101 | 102 | if (totalLength > 0) 103 | { 104 | totalLength--; 105 | 106 | for (int r = 0; r < numSamples; r++) 107 | { 108 | int samplePos = (31 * cyclePos) / cycleLength; 109 | val = waveform [samplePos % 32] >> volumeShift << 1; 110 | 111 | if ((channel & CHAN_LEFT) != 0) 112 | b [r * numChannels] += (byte)val; 113 | if ((channel & CHAN_RIGHT) != 0) 114 | b [r * numChannels + 1] += (byte)val; 115 | //if ((channel & CHAN_MONO) != 0) 116 | //b [r * numChannels] = b [r * numChannels + 1] += (byte)val; 117 | 118 | cyclePos = (cyclePos + 256) % cycleLength; 119 | } 120 | } 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Audio/VoluntaryWaveGenerator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5a958d026d3fe0545aa317a290d3bbb5 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/CPU.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using UnityEngine; 6 | 7 | namespace UnityGB 8 | { 9 | public enum LcdcModeType 10 | { 11 | HBlank = 0, 12 | VBlank = 1, 13 | SearchingOamRam = 2, 14 | TransferingData = 3 15 | } 16 | 17 | public enum TimerFrequencyType 18 | { 19 | hz4096 = 0, 20 | hz262144 = 1, 21 | hz65536 = 2, 22 | hz16384 = 3 23 | } 24 | 25 | public interface ICartridge 26 | { 27 | int ReadByte(int address); 28 | 29 | void WriteByte(int address, int value); 30 | 31 | byte[] GetSavedData(); 32 | } 33 | 34 | public sealed class X80 35 | { 36 | 37 | public const uint WHITE = 0xFFFFFFFF; 38 | public const uint LIGHT_GRAY = 0xFFAAAAAA; 39 | public const uint DARK_GRAY = 0xFF555555; 40 | public const uint BLACK = 0xFF000000; 41 | public Emulator emulator; 42 | public ICartridge cartridge; 43 | private int A, B, C, D, E, H, L, PC, SP; 44 | private bool FZ, FC, FH, FN; 45 | public bool halted; 46 | public bool stopped; 47 | public bool interruptsEnabled = true; 48 | private bool stopCounting; 49 | public bool leftKeyPressed; 50 | public bool rightKeyPressed; 51 | public bool upKeyPressed; 52 | public bool downKeyPressed; 53 | public bool aButtonPressed; 54 | public bool bButtonPressed; 55 | public bool startButtonPressed; 56 | public bool selectButtonPressed; 57 | public bool keyP14, keyP15; 58 | public bool keyPressedInterruptRequested; 59 | public bool serialIOTransferCompleteInterruptRequested; 60 | public bool timerOverflowInterruptRequested; 61 | public bool lcdcInterruptRequested; 62 | public bool vBlankInterruptRequested; 63 | public bool keyPressedInterruptEnabled; 64 | public bool serialIOTransferCompleteInterruptEnabled; 65 | public bool timerOverflowInterruptEnabled; 66 | public bool lcdcInterruptEnabled; 67 | public bool vBlankInterruptEnabled; 68 | public bool lcdControlOperationEnabled; 69 | public bool windowTileMapDisplaySelect; 70 | public bool windowDisplayed; 71 | public bool backgroundAndWindowTileDataSelect; 72 | public bool backgroundTileMapDisplaySelect; 73 | public bool largeSprites; 74 | public bool spritesDisplayed; 75 | public bool backgroundDisplayed; 76 | public int scrollX, scrollY; 77 | public int windowX, windowY; 78 | public int lyCompare, ly; 79 | public uint[] backgroundPalette = { WHITE, LIGHT_GRAY, DARK_GRAY, BLACK }; 80 | public uint[] objectPalette0 = { WHITE, LIGHT_GRAY, DARK_GRAY, BLACK }; 81 | public uint[] objectPalette1 = { WHITE, LIGHT_GRAY, DARK_GRAY, BLACK }; 82 | public bool lcdcLycLyCoincidenceInterruptEnabled; 83 | public bool lcdcOamInterruptEnabled; 84 | public bool lcdcVBlankInterruptEnabled; 85 | public bool lcdcHBlankInterruptEnabled; 86 | public LcdcModeType lcdcMode; 87 | public bool timerRunning; 88 | public int timerCounter; 89 | public int timerModulo; 90 | public TimerFrequencyType timerFrequency; 91 | public int ticks; 92 | private byte[] highRam = new byte[256]; 93 | private byte[] videoRam = new byte[8 * 1024]; 94 | private byte[] workRam = new byte[8 * 1024]; 95 | public byte[] oam = new byte[256]; 96 | public uint[,] backgroundBuffer = new uint[256, 256]; 97 | private bool[,] backgroundTileInvalidated = new bool[32, 32]; 98 | private bool invalidateAllBackgroundTilesRequest; 99 | public uint[,,,] spriteTile = new uint[256, 8, 8, 2]; 100 | private bool[] spriteTileInvalidated = new bool[256]; 101 | private bool invalidateAllSpriteTilesRequest; 102 | public uint[,] windowBuffer = new uint[144, 168]; 103 | 104 | // Sound 105 | public SoundChip SoundChip; 106 | private int[] soundRegisters = new int[0x30]; 107 | 108 | public X80(Emulator emulator) 109 | { 110 | this.emulator = emulator; 111 | 112 | SoundChip = new SoundChip(); 113 | } 114 | 115 | public void Step() 116 | { 117 | if (interruptsEnabled) 118 | { 119 | if (vBlankInterruptEnabled && vBlankInterruptRequested) 120 | { 121 | vBlankInterruptRequested = false; 122 | Interrupt(0x0040); 123 | } else if (lcdcInterruptEnabled && lcdcInterruptRequested) 124 | { 125 | lcdcInterruptRequested = false; 126 | Interrupt(0x0048); 127 | } else if (timerOverflowInterruptEnabled && timerOverflowInterruptRequested) 128 | { 129 | timerOverflowInterruptRequested = false; 130 | Interrupt(0x0050); 131 | } else if (serialIOTransferCompleteInterruptEnabled && serialIOTransferCompleteInterruptRequested) 132 | { 133 | serialIOTransferCompleteInterruptRequested = false; 134 | Interrupt(0x0058); 135 | } else if (keyPressedInterruptEnabled && keyPressedInterruptRequested) 136 | { 137 | keyPressedInterruptRequested = false; 138 | Interrupt(0x0060); 139 | } 140 | } 141 | 142 | PC &= 0xFFFF; 143 | 144 | int opCode = 0x00; 145 | if (!halted) 146 | { 147 | opCode = ReadByte(PC); 148 | if (stopCounting) 149 | { 150 | stopCounting = false; 151 | } else 152 | { 153 | PC++; 154 | } 155 | } 156 | 157 | switch (opCode) 158 | { 159 | case 0x00: // NOP 160 | case 0xD3: 161 | case 0xDB: 162 | case 0xDD: 163 | case 0xE3: 164 | case 0xE4: 165 | case 0xEB: 166 | case 0xEC: 167 | case 0xF4: 168 | case 0xFC: 169 | case 0xFD: 170 | NoOperation(); 171 | break; 172 | case 0x01: // LD BC,NN 173 | LoadImmediate(ref B, ref C); 174 | break; 175 | case 0x02: // LD (BC),A 176 | WriteByte(B, C, A); 177 | break; 178 | case 0x03: // INC BC 179 | Increment(ref B, ref C); 180 | break; 181 | case 0x04: // INC B 182 | Increment(ref B); 183 | break; 184 | case 0x05: // DEC B 185 | Decrement(ref B); 186 | break; 187 | case 0x06: // LD B,N 188 | LoadImmediate(ref B); 189 | break; 190 | case 0x07: // RLCA 191 | RotateALeft(); 192 | break; 193 | case 0x08: // LD (word),SP 194 | WriteWordToImmediateAddress(SP); 195 | break; 196 | case 0x09: // ADD HL,BC 197 | Add(ref H, ref L, B, C); 198 | break; 199 | case 0x0A: // LD A,(BC) 200 | ReadByte(ref A, B, C); 201 | break; 202 | case 0x0B: // DEC BC 203 | Decrement(ref B, ref C); 204 | break; 205 | case 0x0C: // INC C 206 | Increment(ref C); 207 | break; 208 | case 0x0D: // DEC C 209 | Decrement(ref C); 210 | break; 211 | case 0x0E: // LD C,N 212 | LoadImmediate(ref C); 213 | break; 214 | case 0x0F: // RRCA 215 | RotateARight(); 216 | break; 217 | case 0x10: // STOP 218 | stopped = true; 219 | ticks += 4; 220 | break; 221 | case 0x11: // LD DE,NN 222 | LoadImmediate(ref D, ref E); 223 | break; 224 | case 0x12: // LD (DE),A 225 | WriteByte(D, E, A); 226 | break; 227 | case 0x13: // INC DE 228 | Increment(ref D, ref E); 229 | break; 230 | case 0x14: // INC D 231 | Increment(ref D); 232 | break; 233 | case 0x15: // DEC D 234 | Decrement(ref D); 235 | break; 236 | case 0x16: // LD D,N 237 | LoadImmediate(ref D); 238 | break; 239 | case 0x17: // RLA 240 | RotateALeftThroughCarry(); 241 | break; 242 | case 0x18: // JR N 243 | JumpRelative(); 244 | break; 245 | case 0x19: // ADD HL,DE 246 | Add(ref H, ref L, D, E); 247 | break; 248 | case 0x1A: // LD A,(DE) 249 | ReadByte(ref A, D, E); 250 | break; 251 | case 0x1B: // DEC DE 252 | Decrement(ref D, ref E); 253 | break; 254 | case 0x1C: // INC E 255 | Increment(ref E); 256 | break; 257 | case 0x1D: // DEC E 258 | Decrement(ref E); 259 | break; 260 | case 0x1E: // LD E,N 261 | LoadImmediate(ref E); 262 | break; 263 | case 0x1F: // RRA 264 | RotateARightThroughCarry(); 265 | break; 266 | case 0x20: // JR NZ,N 267 | JumpRelativeIfNotZero(); 268 | break; 269 | case 0x21: // LD HL,NN 270 | LoadImmediate(ref H, ref L); 271 | break; 272 | case 0x22: // LD (HLI),A 273 | WriteByte(H, L, A); 274 | Increment(ref H, ref L); 275 | break; 276 | case 0x23: // INC HL 277 | Increment(ref H, ref L); 278 | break; 279 | case 0x24: // INC H 280 | Increment(ref H); 281 | break; 282 | case 0x25: // DEC H 283 | Decrement(ref H); 284 | break; 285 | case 0x26: // LD H,N 286 | LoadImmediate(ref H); 287 | break; 288 | case 0x27: // DAA 289 | DecimallyAdjustA(); 290 | break; 291 | case 0x28: // JR Z,N 292 | JumpRelativeIfZero(); 293 | break; 294 | case 0x29: // ADD HL,HL 295 | Add(ref H, ref L, H, L); 296 | break; 297 | case 0x2A: // LD A,(HLI) 298 | ReadByte(ref A, H, L); 299 | Increment(ref H, ref L); 300 | break; 301 | case 0x2B: // DEC HL 302 | Decrement(ref H, ref L); 303 | break; 304 | case 0x2C: // INC L 305 | Increment(ref L); 306 | break; 307 | case 0x2D: // DEC L 308 | Decrement(ref L); 309 | break; 310 | case 0x2E: // LD L,N 311 | LoadImmediate(ref L); 312 | break; 313 | case 0x2F: // CPL 314 | ComplementA(); 315 | break; 316 | case 0x30: // JR NC,N 317 | JumpRelativeIfNotCarry(); 318 | break; 319 | case 0x31: // LD SP,NN 320 | LoadImmediateWord(ref SP); 321 | break; 322 | case 0x32: // LD (HLD),A 323 | WriteByte(H, L, A); 324 | Decrement(ref H, ref L); 325 | break; 326 | case 0x33: // INC SP 327 | IncrementWord(ref SP); 328 | break; 329 | case 0x34: // INC (HL) 330 | IncrementMemory(H, L); 331 | break; 332 | case 0x35: // DEC (HL) 333 | DecrementMemory(H, L); 334 | break; 335 | case 0x36: // LD (HL),N 336 | LoadImmediateIntoMemory(H, L); 337 | break; 338 | case 0x37: // SCF 339 | SetCarryFlag(); 340 | break; 341 | case 0x38: // JR C,N 342 | JumpRelativeIfCarry(); 343 | break; 344 | case 0x39: // ADD HL,SP 345 | AddSPToHL(); 346 | break; 347 | case 0x3A: // LD A,(HLD) 348 | ReadByte(ref A, H, L); 349 | Decrement(ref H, ref L); 350 | break; 351 | case 0x3B: // DEC SP 352 | DecrementWord(ref SP); 353 | break; 354 | case 0x3C: // INC A 355 | Increment(ref A); 356 | break; 357 | case 0x3D: // DEC A 358 | Decrement(ref A); 359 | break; 360 | case 0x3E: // LD A,N 361 | LoadImmediate(ref A); 362 | break; 363 | case 0x3F: // CCF 364 | ComplementCarryFlag(); 365 | break; 366 | case 0x40: // LD B,B 367 | Load(ref B, B); 368 | break; 369 | case 0x41: // LD B,C 370 | Load(ref B, C); 371 | break; 372 | case 0x42: // LD B,D 373 | Load(ref B, D); 374 | break; 375 | case 0x43: // LD B,E 376 | Load(ref B, E); 377 | break; 378 | case 0x44: // LD B,H 379 | Load(ref B, H); 380 | break; 381 | case 0x45: // LD B,L 382 | Load(ref B, L); 383 | break; 384 | case 0x46: // LD B,(HL) 385 | ReadByte(ref B, H, L); 386 | break; 387 | case 0x47: // LD B,A 388 | Load(ref B, A); 389 | break; 390 | case 0x48: // LD C,B 391 | Load(ref C, B); 392 | break; 393 | case 0x49: // LD C,C 394 | Load(ref C, C); 395 | break; 396 | case 0x4A: // LD C,D 397 | Load(ref C, D); 398 | break; 399 | case 0x4B: // LD C,E 400 | Load(ref C, E); 401 | break; 402 | case 0x4C: // LD C,H 403 | Load(ref C, H); 404 | break; 405 | case 0x4D: // LD C,L 406 | Load(ref C, L); 407 | break; 408 | case 0x4E: // LD C,(HL) 409 | ReadByte(ref C, H, L); 410 | break; 411 | case 0x4F: // LD C,A 412 | Load(ref C, A); 413 | break; 414 | case 0x50: // LD D,B 415 | Load(ref D, B); 416 | break; 417 | case 0x51: // LD D,C 418 | Load(ref D, C); 419 | break; 420 | case 0x52: // LD D,D 421 | Load(ref D, D); 422 | break; 423 | case 0x53: // LD D,E 424 | Load(ref D, E); 425 | break; 426 | case 0x54: // LD D,H 427 | Load(ref D, H); 428 | break; 429 | case 0x55: // LD D,L 430 | Load(ref D, L); 431 | break; 432 | case 0x56: // LD D,(HL) 433 | ReadByte(ref D, H, L); 434 | break; 435 | case 0x57: // LD D,A 436 | Load(ref D, A); 437 | break; 438 | case 0x58: // LD E,B 439 | Load(ref E, B); 440 | break; 441 | case 0x59: // LD E,C 442 | Load(ref E, C); 443 | break; 444 | case 0x5A: // LD E,D 445 | Load(ref E, D); 446 | break; 447 | case 0x5B: // LD E,E 448 | Load(ref E, E); 449 | break; 450 | case 0x5C: // LD E,H 451 | Load(ref E, H); 452 | break; 453 | case 0x5D: // LD E,L 454 | Load(ref E, L); 455 | break; 456 | case 0x5E: // LD E,(HL) 457 | ReadByte(ref E, H, L); 458 | break; 459 | case 0x5F: // LD E,A 460 | Load(ref E, A); 461 | break; 462 | case 0x60: // LD H,B 463 | Load(ref H, B); 464 | break; 465 | case 0x61: // LD H,C 466 | Load(ref H, C); 467 | break; 468 | case 0x62: // LD H,D 469 | Load(ref H, D); 470 | break; 471 | case 0x63: // LD H,E 472 | Load(ref H, E); 473 | break; 474 | case 0x64: // LD H,H 475 | Load(ref H, H); 476 | break; 477 | case 0x65: // LD H,L 478 | Load(ref H, L); 479 | break; 480 | case 0x66: // LD H,(HL) 481 | ReadByte(ref H, H, L); 482 | break; 483 | case 0x67: // LD H,A 484 | Load(ref H, A); 485 | break; 486 | case 0x68: // LD L,B 487 | Load(ref L, B); 488 | break; 489 | case 0x69: // LD L,C 490 | Load(ref L, C); 491 | break; 492 | case 0x6A: // LD L,D 493 | Load(ref L, D); 494 | break; 495 | case 0x6B: // LD L,E 496 | Load(ref L, E); 497 | break; 498 | case 0x6C: // LD L,H 499 | Load(ref L, H); 500 | break; 501 | case 0x6D: // LD L,L 502 | Load(ref L, L); 503 | break; 504 | case 0x6E: // LD L,(HL) 505 | ReadByte(ref L, H, L); 506 | break; 507 | case 0x6F: // LD L,A 508 | Load(ref L, A); 509 | break; 510 | case 0x70: // LD (HL),B 511 | WriteByte(H, L, B); 512 | break; 513 | case 0x71: // LD (HL),C 514 | WriteByte(H, L, C); 515 | break; 516 | case 0x72: // LD (HL),D 517 | WriteByte(H, L, D); 518 | break; 519 | case 0x73: // LD (HL),E 520 | WriteByte(H, L, E); 521 | break; 522 | case 0x74: // LD (HL),H 523 | WriteByte(H, L, H); 524 | break; 525 | case 0x75: // LD (HL),L 526 | WriteByte(H, L, L); 527 | break; 528 | case 0x76: // HALT 529 | Halt(); 530 | break; 531 | case 0x77: // LD (HL),A 532 | WriteByte(H, L, A); 533 | break; 534 | case 0x78: // LD A,B 535 | Load(ref A, B); 536 | break; 537 | case 0x79: // LD A,C 538 | Load(ref A, C); 539 | break; 540 | case 0x7A: // LD A,D 541 | Load(ref A, D); 542 | break; 543 | case 0x7B: // LD A,E 544 | Load(ref A, E); 545 | break; 546 | case 0x7C: // LD A,H 547 | Load(ref A, H); 548 | break; 549 | case 0x7D: // LD A,L 550 | Load(ref A, L); 551 | break; 552 | case 0x7E: // LD A,(HL) 553 | ReadByte(ref A, H, L); 554 | break; 555 | case 0x7F: // LD A,A 556 | Load(ref A, A); 557 | break; 558 | case 0x80: // ADD A,B 559 | Add(B); 560 | break; 561 | case 0x81: // ADD A,C 562 | Add(C); 563 | break; 564 | case 0x82: // ADD A,D 565 | Add(D); 566 | break; 567 | case 0x83: // ADD A,E 568 | Add(E); 569 | break; 570 | case 0x84: // ADD A,H 571 | Add(H); 572 | break; 573 | case 0x85: // ADD A,L 574 | Add(L); 575 | break; 576 | case 0x86: // ADD A,(HL) 577 | Add(H, L); 578 | break; 579 | case 0x87: // ADD A,A 580 | Add(A); 581 | break; 582 | case 0x88: // ADC A,B 583 | AddWithCarry(B); 584 | break; 585 | case 0x89: // ADC A,C 586 | AddWithCarry(C); 587 | break; 588 | case 0x8A: // ADC A,D 589 | AddWithCarry(D); 590 | break; 591 | case 0x8B: // ADC A,E 592 | AddWithCarry(E); 593 | break; 594 | case 0x8C: // ADC A,H 595 | AddWithCarry(H); 596 | break; 597 | case 0x8D: // ADC A,L 598 | AddWithCarry(L); 599 | break; 600 | case 0x8E: // ADC A,(HL) 601 | AddWithCarry(H, L); 602 | break; 603 | case 0x8F: // ADC A,A 604 | AddWithCarry(A); 605 | break; 606 | case 0x90: // SUB B 607 | Sub(B); 608 | break; 609 | case 0x91: // SUB C 610 | Sub(C); 611 | break; 612 | case 0x92: // SUB D 613 | Sub(D); 614 | break; 615 | case 0x93: // SUB E 616 | Sub(E); 617 | break; 618 | case 0x94: // SUB H 619 | Sub(H); 620 | break; 621 | case 0x95: // SUB L 622 | Sub(L); 623 | break; 624 | case 0x96: // SUB (HL) 625 | Sub(H, L); 626 | break; 627 | case 0x97: // SUB A 628 | Sub(A); 629 | break; 630 | case 0x98: // SBC B 631 | SubWithBorrow(B); 632 | break; 633 | case 0x99: // SBC C 634 | SubWithBorrow(C); 635 | break; 636 | case 0x9A: // SBC D 637 | SubWithBorrow(D); 638 | break; 639 | case 0x9B: // SBC E 640 | SubWithBorrow(E); 641 | break; 642 | case 0x9C: // SBC H 643 | SubWithBorrow(H); 644 | break; 645 | case 0x9D: // SBC L 646 | SubWithBorrow(L); 647 | break; 648 | case 0x9E: // SBC (HL) 649 | SubWithBorrow(H, L); 650 | break; 651 | case 0x9F: // SBC A 652 | SubWithBorrow(A); 653 | break; 654 | case 0xA0: // AND B 655 | And(B); 656 | break; 657 | case 0xA1: // AND C 658 | And(C); 659 | break; 660 | case 0xA2: // AND D 661 | And(D); 662 | break; 663 | case 0xA3: // AND E 664 | And(E); 665 | break; 666 | case 0xA4: // AND H 667 | And(H); 668 | break; 669 | case 0xA5: // AND L 670 | And(L); 671 | break; 672 | case 0xA6: // AND (HL) 673 | And(H, L); 674 | break; 675 | case 0xA7: // AND A 676 | And(A); 677 | break; 678 | case 0xA8: // XOR B 679 | Xor(B); 680 | break; 681 | case 0xA9: // XOR C 682 | Xor(C); 683 | break; 684 | case 0xAA: // XOR D 685 | Xor(D); 686 | break; 687 | case 0xAB: // XOR E 688 | Xor(E); 689 | break; 690 | case 0xAC: // XOR H 691 | Xor(H); 692 | break; 693 | case 0xAD: // XOR L 694 | Xor(L); 695 | break; 696 | case 0xAE: // XOR (HL) 697 | Xor(H, L); 698 | break; 699 | case 0xAF: // XOR A 700 | Xor(A); 701 | break; 702 | case 0xB0: // OR B 703 | Or(B); 704 | break; 705 | case 0xB1: // OR C 706 | Or(C); 707 | break; 708 | case 0xB2: // OR D 709 | Or(D); 710 | break; 711 | case 0xB3: // OR E 712 | Or(E); 713 | break; 714 | case 0xB4: // OR H 715 | Or(H); 716 | break; 717 | case 0xB5: // OR L 718 | Or(L); 719 | break; 720 | case 0xB6: // OR (HL) 721 | Or(H, L); 722 | break; 723 | case 0xB7: // OR A 724 | Or(A); 725 | break; 726 | case 0xB8: // CP B 727 | Compare(B); 728 | break; 729 | case 0xB9: // CP C 730 | Compare(C); 731 | break; 732 | case 0xBA: // CP D 733 | Compare(D); 734 | break; 735 | case 0xBB: // CP E 736 | Compare(E); 737 | break; 738 | case 0xBC: // CP H 739 | Compare(H); 740 | break; 741 | case 0xBD: // CP L 742 | Compare(L); 743 | break; 744 | case 0xBE: // CP (HL) 745 | Compare(H, L); 746 | break; 747 | case 0xBF: // CP A 748 | Compare(A); 749 | break; 750 | case 0xC0: // RET NZ 751 | ReturnIfNotZero(); 752 | break; 753 | case 0xC1: // POP BC 754 | Pop(ref B, ref C); 755 | break; 756 | case 0xC2: // JP NZ,N 757 | JumpIfNotZero(); 758 | break; 759 | case 0xC3: // JP N 760 | Jump(); 761 | break; 762 | case 0xC4: // CALL NZ,NN 763 | CallIfNotZero(); 764 | break; 765 | case 0xC5: // PUSH BC 766 | Push(B, C); 767 | break; 768 | case 0xC6: // ADD A,N 769 | AddImmediate(); 770 | break; 771 | case 0xC7: // RST 00H 772 | Restart(0); 773 | break; 774 | case 0xC8: // RET Z 775 | ReturnIfZero(); 776 | break; 777 | case 0xC9: // RET 778 | Return(); 779 | break; 780 | case 0xCA: // JP Z,N 781 | JumpIfZero(); 782 | break; 783 | case 0xCB: 784 | switch (ReadByte(PC++)) 785 | { 786 | case 0x00: // RLC B 787 | RotateLeft(ref B); 788 | break; 789 | case 0x01: // RLC C 790 | RotateLeft(ref C); 791 | break; 792 | case 0x02: // RLC D 793 | RotateLeft(ref D); 794 | break; 795 | case 0x03: // RLC E 796 | RotateLeft(ref E); 797 | break; 798 | case 0x04: // RLC H 799 | RotateLeft(ref H); 800 | break; 801 | case 0x05: // RLC L 802 | RotateLeft(ref L); 803 | break; 804 | case 0x06: // RLC (HL) 805 | RotateLeft(H, L); 806 | break; 807 | case 0x07: // RLC A 808 | RotateLeft(ref A); 809 | break; 810 | case 0x08: // RRC B 811 | RotateRight(ref B); 812 | break; 813 | case 0x09: // RRC C 814 | RotateRight(ref C); 815 | break; 816 | case 0x0A: // RRC D 817 | RotateRight(ref D); 818 | break; 819 | case 0x0B: // RRC E 820 | RotateRight(ref E); 821 | break; 822 | case 0x0C: // RRC H 823 | RotateRight(ref H); 824 | break; 825 | case 0x0D: // RRC L 826 | RotateRight(ref L); 827 | break; 828 | case 0x0E: // RRC (HL) 829 | RotateRight(H, L); 830 | break; 831 | case 0x0F: // RRC A 832 | RotateRight(ref A); 833 | break; 834 | case 0x10: // RL B 835 | RotateLeftThroughCarry(ref B); 836 | break; 837 | case 0x11: // RL C 838 | RotateLeftThroughCarry(ref C); 839 | break; 840 | case 0x12: // RL D 841 | RotateLeftThroughCarry(ref D); 842 | break; 843 | case 0x13: // RL E 844 | RotateLeftThroughCarry(ref E); 845 | break; 846 | case 0x14: // RL H 847 | RotateLeftThroughCarry(ref H); 848 | break; 849 | case 0x15: // RL L 850 | RotateLeftThroughCarry(ref L); 851 | break; 852 | case 0x16: // RL (HL) 853 | RotateLeftThroughCarry(H, L); 854 | break; 855 | case 0x17: // RL A 856 | RotateLeftThroughCarry(ref A); 857 | break; 858 | case 0x18: // RR B 859 | RotateRightThroughCarry(ref B); 860 | break; 861 | case 0x19: // RR C 862 | RotateRightThroughCarry(ref C); 863 | break; 864 | case 0x1A: // RR D 865 | RotateRightThroughCarry(ref D); 866 | break; 867 | case 0x1B: // RR E 868 | RotateRightThroughCarry(ref E); 869 | break; 870 | case 0x1C: // RR H 871 | RotateRightThroughCarry(ref H); 872 | break; 873 | case 0x1D: // RR L 874 | RotateRightThroughCarry(ref L); 875 | break; 876 | case 0x1E: // RR (HL) 877 | RotateRightThroughCarry(H, L); 878 | break; 879 | case 0x1F: // RR A 880 | RotateRightThroughCarry(ref A); 881 | break; 882 | case 0x20: // SLA B 883 | ShiftLeft(ref B); 884 | break; 885 | case 0x21: // SLA C 886 | ShiftLeft(ref C); 887 | break; 888 | case 0x22: // SLA D 889 | ShiftLeft(ref D); 890 | break; 891 | case 0x23: // SLA E 892 | ShiftLeft(ref E); 893 | break; 894 | case 0x24: // SLA H 895 | ShiftLeft(ref H); 896 | break; 897 | case 0x25: // SLA L 898 | ShiftLeft(ref L); 899 | break; 900 | case 0x26: // SLA (HL) 901 | ShiftLeft(H, L); 902 | break; 903 | case 0x27: // SLA A 904 | ShiftLeft(ref A); 905 | break; 906 | case 0x28: // SRA B 907 | SignedShiftRight(ref B); 908 | break; 909 | case 0x29: // SRA C 910 | SignedShiftRight(ref C); 911 | break; 912 | case 0x2A: // SRA D 913 | SignedShiftRight(ref D); 914 | break; 915 | case 0x2B: // SRA E 916 | SignedShiftRight(ref E); 917 | break; 918 | case 0x2C: // SRA H 919 | SignedShiftRight(ref H); 920 | break; 921 | case 0x2D: // SRA L 922 | SignedShiftRight(ref L); 923 | break; 924 | case 0x2E: // SRA (HL) 925 | SignedShiftRight(H, L); 926 | break; 927 | case 0x2F: // SRA A 928 | SignedShiftRight(ref A); 929 | break; 930 | case 0x30: // SWAP B 931 | Swap(ref B); 932 | break; 933 | case 0x31: // SWAP C 934 | Swap(ref C); 935 | break; 936 | case 0x32: // SWAP D 937 | Swap(ref D); 938 | break; 939 | case 0x33: // SWAP E 940 | Swap(ref E); 941 | break; 942 | case 0x34: // SWAP H 943 | Swap(ref H); 944 | break; 945 | case 0x35: // SWAP L 946 | Swap(ref L); 947 | break; 948 | case 0x36: // SWAP (HL) 949 | Swap(H, L); 950 | break; 951 | case 0x37: // SWAP A 952 | Swap(ref A); 953 | break; 954 | case 0x38: // SRL B 955 | UnsignedShiftRight(ref B); 956 | break; 957 | case 0x39: // SRL C 958 | UnsignedShiftRight(ref C); 959 | break; 960 | case 0x3A: // SRL D 961 | UnsignedShiftRight(ref D); 962 | break; 963 | case 0x3B: // SRL E 964 | UnsignedShiftRight(ref E); 965 | break; 966 | case 0x3C: // SRL H 967 | UnsignedShiftRight(ref H); 968 | break; 969 | case 0x3D: // SRL L 970 | UnsignedShiftRight(ref L); 971 | break; 972 | case 0x3E: // SRL (HL) 973 | UnsignedShiftRight(H, L); 974 | break; 975 | case 0x3F: // SRL A 976 | UnsignedShiftRight(ref A); 977 | break; 978 | case 0x40: // BIT 0,B 979 | TestBit(0, B); 980 | break; 981 | case 0x41: // BIT 0,C 982 | TestBit(0, C); 983 | break; 984 | case 0x42: // BIT 0,D 985 | TestBit(0, D); 986 | break; 987 | case 0x43: // BIT 0,E 988 | TestBit(0, E); 989 | break; 990 | case 0x44: // BIT 0,H 991 | TestBit(0, H); 992 | break; 993 | case 0x45: // BIT 0,L 994 | TestBit(0, L); 995 | break; 996 | case 0x46: // BIT 0,(HL) 997 | TestBit(0, H, L); 998 | break; 999 | case 0x47: // BIT 0,A 1000 | TestBit(0, A); 1001 | break; 1002 | case 0x48: // BIT 1,B 1003 | TestBit(1, B); 1004 | break; 1005 | case 0x49: // BIT 1,C 1006 | TestBit(1, C); 1007 | break; 1008 | case 0x4A: // BIT 1,D 1009 | TestBit(1, D); 1010 | break; 1011 | case 0x4B: // BIT 1,E 1012 | TestBit(1, E); 1013 | break; 1014 | case 0x4C: // BIT 1,H 1015 | TestBit(1, H); 1016 | break; 1017 | case 0x4D: // BIT 1,L 1018 | TestBit(1, L); 1019 | break; 1020 | case 0x4E: // BIT 1,(HL) 1021 | TestBit(1, H, L); 1022 | break; 1023 | case 0x4F: // BIT 1,A 1024 | TestBit(1, A); 1025 | break; 1026 | case 0x50: // BIT 2,B 1027 | TestBit(2, B); 1028 | break; 1029 | case 0x51: // BIT 2,C 1030 | TestBit(2, C); 1031 | break; 1032 | case 0x52: // BIT 2,D 1033 | TestBit(2, D); 1034 | break; 1035 | case 0x53: // BIT 2,E 1036 | TestBit(2, E); 1037 | break; 1038 | case 0x54: // BIT 2,H 1039 | TestBit(2, H); 1040 | break; 1041 | case 0x55: // BIT 2,L 1042 | TestBit(2, L); 1043 | break; 1044 | case 0x56: // BIT 2,(HL) 1045 | TestBit(2, H, L); 1046 | break; 1047 | case 0x57: // BIT 2,A 1048 | TestBit(2, A); 1049 | break; 1050 | case 0x58: // BIT 3,B 1051 | TestBit(3, B); 1052 | break; 1053 | case 0x59: // BIT 3,C 1054 | TestBit(3, C); 1055 | break; 1056 | case 0x5A: // BIT 3,D 1057 | TestBit(3, D); 1058 | break; 1059 | case 0x5B: // BIT 3,E 1060 | TestBit(3, E); 1061 | break; 1062 | case 0x5C: // BIT 3,H 1063 | TestBit(3, H); 1064 | break; 1065 | case 0x5D: // BIT 3,L 1066 | TestBit(3, L); 1067 | break; 1068 | case 0x5E: // BIT 3,(HL) 1069 | TestBit(3, H, L); 1070 | break; 1071 | case 0x5F: // BIT 3,A 1072 | TestBit(3, A); 1073 | break; 1074 | case 0x60: // BIT 4,B 1075 | TestBit(4, B); 1076 | break; 1077 | case 0x61: // BIT 4,C 1078 | TestBit(4, C); 1079 | break; 1080 | case 0x62: // BIT 4,D 1081 | TestBit(4, D); 1082 | break; 1083 | case 0x63: // BIT 4,E 1084 | TestBit(4, E); 1085 | break; 1086 | case 0x64: // BIT 4,H 1087 | TestBit(4, H); 1088 | break; 1089 | case 0x65: // BIT 4,L 1090 | TestBit(4, L); 1091 | break; 1092 | case 0x66: // BIT 4,(HL) 1093 | TestBit(4, H, L); 1094 | break; 1095 | case 0x67: // BIT 4,A 1096 | TestBit(4, A); 1097 | break; 1098 | case 0x68: // BIT 5,B 1099 | TestBit(5, B); 1100 | break; 1101 | case 0x69: // BIT 5,C 1102 | TestBit(5, C); 1103 | break; 1104 | case 0x6A: // BIT 5,D 1105 | TestBit(5, D); 1106 | break; 1107 | case 0x6B: // BIT 5,E 1108 | TestBit(5, E); 1109 | break; 1110 | case 0x6C: // BIT 5,H 1111 | TestBit(5, H); 1112 | break; 1113 | case 0x6D: // BIT 5,L 1114 | TestBit(5, L); 1115 | break; 1116 | case 0x6E: // BIT 5,(HL) 1117 | TestBit(5, H, L); 1118 | break; 1119 | case 0x6F: // BIT 5,A 1120 | TestBit(5, A); 1121 | break; 1122 | case 0x70: // BIT 6,B 1123 | TestBit(6, B); 1124 | break; 1125 | case 0x71: // BIT 6,C 1126 | TestBit(6, C); 1127 | break; 1128 | case 0x72: // BIT 6,D 1129 | TestBit(6, D); 1130 | break; 1131 | case 0x73: // BIT 6,E 1132 | TestBit(6, E); 1133 | break; 1134 | case 0x74: // BIT 6,H 1135 | TestBit(6, H); 1136 | break; 1137 | case 0x75: // BIT 6,L 1138 | TestBit(6, L); 1139 | break; 1140 | case 0x76: // BIT 6,(HL) 1141 | TestBit(6, H, L); 1142 | break; 1143 | case 0x77: // BIT 6,A 1144 | TestBit(6, A); 1145 | break; 1146 | case 0x78: // BIT 7,B 1147 | TestBit(7, B); 1148 | break; 1149 | case 0x79: // BIT 7,C 1150 | TestBit(7, C); 1151 | break; 1152 | case 0x7A: // BIT 7,D 1153 | TestBit(7, D); 1154 | break; 1155 | case 0x7B: // BIT 7,E 1156 | TestBit(7, E); 1157 | break; 1158 | case 0x7C: // BIT 7,H 1159 | TestBit(7, H); 1160 | break; 1161 | case 0x7D: // BIT 7,L 1162 | TestBit(7, L); 1163 | break; 1164 | case 0x7E: // BIT 7,(HL) 1165 | TestBit(7, H, L); 1166 | break; 1167 | case 0x7F: // BIT 7,A 1168 | TestBit(7, A); 1169 | break; 1170 | case 0x80: // RES 0,B 1171 | ResetBit(0, ref B); 1172 | break; 1173 | case 0x81: // RES 0,C 1174 | ResetBit(0, ref C); 1175 | break; 1176 | case 0x82: // RES 0,D 1177 | ResetBit(0, ref D); 1178 | break; 1179 | case 0x83: // RES 0,E 1180 | ResetBit(0, ref E); 1181 | break; 1182 | case 0x84: // RES 0,H 1183 | ResetBit(0, ref H); 1184 | break; 1185 | case 0x85: // RES 0,L 1186 | ResetBit(0, ref L); 1187 | break; 1188 | case 0x86: // RES 0,(HL) 1189 | ResetBit(0, H, L); 1190 | break; 1191 | case 0x87: // RES 0,A 1192 | ResetBit(0, ref A); 1193 | break; 1194 | case 0x88: // RES 1,B 1195 | ResetBit(1, ref B); 1196 | break; 1197 | case 0x89: // RES 1,C 1198 | ResetBit(1, ref C); 1199 | break; 1200 | case 0x8A: // RES 1,D 1201 | ResetBit(1, ref D); 1202 | break; 1203 | case 0x8B: // RES 1,E 1204 | ResetBit(1, ref E); 1205 | break; 1206 | case 0x8C: // RES 1,H 1207 | ResetBit(1, ref H); 1208 | break; 1209 | case 0x8D: // RES 1,L 1210 | ResetBit(1, ref L); 1211 | break; 1212 | case 0x8E: // RES 1,(HL) 1213 | ResetBit(1, H, L); 1214 | break; 1215 | case 0x8F: // RES 1,A 1216 | ResetBit(1, ref A); 1217 | break; 1218 | case 0x90: // RES 2,B 1219 | ResetBit(2, ref B); 1220 | break; 1221 | case 0x91: // RES 2,C 1222 | ResetBit(2, ref C); 1223 | break; 1224 | case 0x92: // RES 2,D 1225 | ResetBit(2, ref D); 1226 | break; 1227 | case 0x93: // RES 2,E 1228 | ResetBit(2, ref E); 1229 | break; 1230 | case 0x94: // RES 2,H 1231 | ResetBit(2, ref H); 1232 | break; 1233 | case 0x95: // RES 2,L 1234 | ResetBit(2, ref L); 1235 | break; 1236 | case 0x96: // RES 2,(HL) 1237 | ResetBit(2, H, L); 1238 | break; 1239 | case 0x97: // RES 2,A 1240 | ResetBit(2, ref A); 1241 | break; 1242 | case 0x98: // RES 3,B 1243 | ResetBit(3, ref B); 1244 | break; 1245 | case 0x99: // RES 3,C 1246 | ResetBit(3, ref C); 1247 | break; 1248 | case 0x9A: // RES 3,D 1249 | ResetBit(3, ref D); 1250 | break; 1251 | case 0x9B: // RES 3,E 1252 | ResetBit(3, ref E); 1253 | break; 1254 | case 0x9C: // RES 3,H 1255 | ResetBit(3, ref H); 1256 | break; 1257 | case 0x9D: // RES 3,L 1258 | ResetBit(3, ref L); 1259 | break; 1260 | case 0x9E: // RES 3,(HL) 1261 | ResetBit(3, H, L); 1262 | break; 1263 | case 0x9F: // RES 3,A 1264 | ResetBit(3, ref A); 1265 | break; 1266 | case 0xA0: // RES 4,B 1267 | ResetBit(4, ref B); 1268 | break; 1269 | case 0xA1: // RES 4,C 1270 | ResetBit(4, ref C); 1271 | break; 1272 | case 0xA2: // RES 4,D 1273 | ResetBit(4, ref D); 1274 | break; 1275 | case 0xA3: // RES 4,E 1276 | ResetBit(4, ref E); 1277 | break; 1278 | case 0xA4: // RES 4,H 1279 | ResetBit(4, ref H); 1280 | break; 1281 | case 0xA5: // RES 4,L 1282 | ResetBit(4, ref L); 1283 | break; 1284 | case 0xA6: // RES 4,(HL) 1285 | ResetBit(4, H, L); 1286 | break; 1287 | case 0xA7: // RES 4,A 1288 | ResetBit(4, ref A); 1289 | break; 1290 | case 0xA8: // RES 5,B 1291 | ResetBit(5, ref B); 1292 | break; 1293 | case 0xA9: // RES 5,C 1294 | ResetBit(5, ref C); 1295 | break; 1296 | case 0xAA: // RES 5,D 1297 | ResetBit(5, ref D); 1298 | break; 1299 | case 0xAB: // RES 5,E 1300 | ResetBit(5, ref E); 1301 | break; 1302 | case 0xAC: // RES 5,H 1303 | ResetBit(5, ref H); 1304 | break; 1305 | case 0xAD: // RES 5,L 1306 | ResetBit(5, ref L); 1307 | break; 1308 | case 0xAE: // RES 5,(HL) 1309 | ResetBit(5, H, L); 1310 | break; 1311 | case 0xAF: // RES 5,A 1312 | ResetBit(5, ref A); 1313 | break; 1314 | case 0xB0: // RES 6,B 1315 | ResetBit(6, ref B); 1316 | break; 1317 | case 0xB1: // RES 6,C 1318 | ResetBit(6, ref C); 1319 | break; 1320 | case 0xB2: // RES 6,D 1321 | ResetBit(6, ref D); 1322 | break; 1323 | case 0xB3: // RES 6,E 1324 | ResetBit(6, ref E); 1325 | break; 1326 | case 0xB4: // RES 6,H 1327 | ResetBit(6, ref H); 1328 | break; 1329 | case 0xB5: // RES 6,L 1330 | ResetBit(6, ref L); 1331 | break; 1332 | case 0xB6: // RES 6,(HL) 1333 | ResetBit(6, H, L); 1334 | break; 1335 | case 0xB7: // RES 6,A 1336 | ResetBit(6, ref A); 1337 | break; 1338 | case 0xB8: // RES 7,B 1339 | ResetBit(7, ref B); 1340 | break; 1341 | case 0xB9: // RES 7,C 1342 | ResetBit(7, ref C); 1343 | break; 1344 | case 0xBA: // RES 7,D 1345 | ResetBit(7, ref D); 1346 | break; 1347 | case 0xBB: // RES 7,E 1348 | ResetBit(7, ref E); 1349 | break; 1350 | case 0xBC: // RES 7,H 1351 | ResetBit(7, ref H); 1352 | break; 1353 | case 0xBD: // RES 7,L 1354 | ResetBit(7, ref L); 1355 | break; 1356 | case 0xBE: // RES 7,(HL) 1357 | ResetBit(7, H, L); 1358 | break; 1359 | case 0xBF: // RES 7,A 1360 | ResetBit(7, ref A); 1361 | break; 1362 | case 0xC0: // SET 0,B 1363 | SetBit(0, ref B); 1364 | break; 1365 | case 0xC1: // SET 0,C 1366 | SetBit(0, ref C); 1367 | break; 1368 | case 0xC2: // SET 0,D 1369 | SetBit(0, ref D); 1370 | break; 1371 | case 0xC3: // SET 0,E 1372 | SetBit(0, ref E); 1373 | break; 1374 | case 0xC4: // SET 0,H 1375 | SetBit(0, ref H); 1376 | break; 1377 | case 0xC5: // SET 0,L 1378 | SetBit(0, ref L); 1379 | break; 1380 | case 0xC6: // SET 0,(HL) 1381 | SetBit(0, H, L); 1382 | break; 1383 | case 0xC7: // SET 0,A 1384 | SetBit(0, ref A); 1385 | break; 1386 | case 0xC8: // SET 1,B 1387 | SetBit(1, ref B); 1388 | break; 1389 | case 0xC9: // SET 1,C 1390 | SetBit(1, ref C); 1391 | break; 1392 | case 0xCA: // SET 1,D 1393 | SetBit(1, ref D); 1394 | break; 1395 | case 0xCB: // SET 1,E 1396 | SetBit(1, ref E); 1397 | break; 1398 | case 0xCC: // SET 1,H 1399 | SetBit(1, ref H); 1400 | break; 1401 | case 0xCD: // SET 1,L 1402 | SetBit(1, ref L); 1403 | break; 1404 | case 0xCE: // SET 1,(HL) 1405 | SetBit(1, H, L); 1406 | break; 1407 | case 0xCF: // SET 1,A 1408 | SetBit(1, ref A); 1409 | break; 1410 | case 0xD0: // SET 2,B 1411 | SetBit(2, ref B); 1412 | break; 1413 | case 0xD1: // SET 2,C 1414 | SetBit(2, ref C); 1415 | break; 1416 | case 0xD2: // SET 2,D 1417 | SetBit(2, ref D); 1418 | break; 1419 | case 0xD3: // SET 2,E 1420 | SetBit(2, ref E); 1421 | break; 1422 | case 0xD4: // SET 2,H 1423 | SetBit(2, ref H); 1424 | break; 1425 | case 0xD5: // SET 2,L 1426 | SetBit(2, ref L); 1427 | break; 1428 | case 0xD6: // SET 2,(HL) 1429 | SetBit(2, H, L); 1430 | break; 1431 | case 0xD7: // SET 2,A 1432 | SetBit(2, ref A); 1433 | break; 1434 | case 0xD8: // SET 3,B 1435 | SetBit(3, ref B); 1436 | break; 1437 | case 0xD9: // SET 3,C 1438 | SetBit(3, ref C); 1439 | break; 1440 | case 0xDA: // SET 3,D 1441 | SetBit(3, ref D); 1442 | break; 1443 | case 0xDB: // SET 3,E 1444 | SetBit(3, ref E); 1445 | break; 1446 | case 0xDC: // SET 3,H 1447 | SetBit(3, ref H); 1448 | break; 1449 | case 0xDD: // SET 3,L 1450 | SetBit(3, ref L); 1451 | break; 1452 | case 0xDE: // SET 3,(HL) 1453 | SetBit(3, H, L); 1454 | break; 1455 | case 0xDF: // SET 3,A 1456 | SetBit(3, ref A); 1457 | break; 1458 | case 0xE0: // SET 4,B 1459 | SetBit(4, ref B); 1460 | break; 1461 | case 0xE1: // SET 4,C 1462 | SetBit(4, ref C); 1463 | break; 1464 | case 0xE2: // SET 4,D 1465 | SetBit(4, ref D); 1466 | break; 1467 | case 0xE3: // SET 4,E 1468 | SetBit(4, ref E); 1469 | break; 1470 | case 0xE4: // SET 4,H 1471 | SetBit(4, ref H); 1472 | break; 1473 | case 0xE5: // SET 4,L 1474 | SetBit(4, ref L); 1475 | break; 1476 | case 0xE6: // SET 4,(HL) 1477 | SetBit(4, H, L); 1478 | break; 1479 | case 0xE7: // SET 4,A 1480 | SetBit(4, ref A); 1481 | break; 1482 | case 0xE8: // SET 5,B 1483 | SetBit(5, ref B); 1484 | break; 1485 | case 0xE9: // SET 5,C 1486 | SetBit(5, ref C); 1487 | break; 1488 | case 0xEA: // SET 5,D 1489 | SetBit(5, ref D); 1490 | break; 1491 | case 0xEB: // SET 5,E 1492 | SetBit(5, ref E); 1493 | break; 1494 | case 0xEC: // SET 5,H 1495 | SetBit(5, ref H); 1496 | break; 1497 | case 0xED: // SET 5,L 1498 | SetBit(5, ref L); 1499 | break; 1500 | case 0xEE: // SET 5,(HL) 1501 | SetBit(5, H, L); 1502 | break; 1503 | case 0xEF: // SET 5,A 1504 | SetBit(5, ref A); 1505 | break; 1506 | case 0xF0: // SET 6,B 1507 | SetBit(6, ref B); 1508 | break; 1509 | case 0xF1: // SET 6,C 1510 | SetBit(6, ref C); 1511 | break; 1512 | case 0xF2: // SET 6,D 1513 | SetBit(6, ref D); 1514 | break; 1515 | case 0xF3: // SET 6,E 1516 | SetBit(6, ref E); 1517 | break; 1518 | case 0xF4: // SET 6,H 1519 | SetBit(6, ref H); 1520 | break; 1521 | case 0xF5: // SET 6,L 1522 | SetBit(6, ref L); 1523 | break; 1524 | case 0xF6: // SET 6,(HL) 1525 | SetBit(6, H, L); 1526 | break; 1527 | case 0xF7: // SET 6,A 1528 | SetBit(6, ref A); 1529 | break; 1530 | case 0xF8: // SET 7,B 1531 | SetBit(7, ref B); 1532 | break; 1533 | case 0xF9: // SET 7,C 1534 | SetBit(7, ref C); 1535 | break; 1536 | case 0xFA: // SET 7,D 1537 | SetBit(7, ref D); 1538 | break; 1539 | case 0xFB: // SET 7,E 1540 | SetBit(7, ref E); 1541 | break; 1542 | case 0xFC: // SET 7,H 1543 | SetBit(7, ref H); 1544 | break; 1545 | case 0xFD: // SET 7,L 1546 | SetBit(7, ref L); 1547 | break; 1548 | case 0xFE: // SET 7,(HL) 1549 | SetBit(7, H, L); 1550 | break; 1551 | case 0xFF: // SET 7,A 1552 | SetBit(7, ref A); 1553 | break; 1554 | } 1555 | break; 1556 | case 0xCC: // CALL Z,NN 1557 | CallIfZero(); 1558 | break; 1559 | case 0xCD: // CALL NN 1560 | Call(); 1561 | break; 1562 | case 0xCE: // ADC A,N 1563 | AddImmediateWithCarry(); 1564 | break; 1565 | case 0xCF: // RST 8H 1566 | Restart(0x08); 1567 | break; 1568 | case 0xD0: // RET NC 1569 | ReturnIfNotCarry(); 1570 | break; 1571 | case 0xD1: // POP DE 1572 | Pop(ref D, ref E); 1573 | break; 1574 | case 0xD2: // JP NC,N 1575 | JumpIfNotCarry(); 1576 | break; 1577 | case 0xD4: // CALL NC,NN 1578 | CallIfNotCarry(); 1579 | break; 1580 | case 0xD5: // PUSH DE 1581 | Push(D, E); 1582 | break; 1583 | case 0xD6: // SUB N 1584 | SubImmediate(); 1585 | break; 1586 | case 0xD7: // RST 10H 1587 | Restart(0x10); 1588 | break; 1589 | case 0xD8: // RET C 1590 | ReturnIfCarry(); 1591 | break; 1592 | case 0xD9: // RETI 1593 | ReturnFromInterrupt(); 1594 | break; 1595 | case 0xDA: // JP C,N 1596 | JumpIfCarry(); 1597 | break; 1598 | case 0xDC: // CALL C,NN 1599 | CallIfCarry(); 1600 | break; 1601 | case 0xDE: // SBC A,N 1602 | SubImmediateWithBorrow(); 1603 | break; 1604 | case 0xDF: // RST 18H 1605 | Restart(0x18); 1606 | break; 1607 | case 0xE0: // LD (FF00+byte),A 1608 | SaveAWithOffset(); 1609 | break; 1610 | case 0xE1: // POP HL 1611 | Pop(ref H, ref L); 1612 | break; 1613 | case 0xE2: // LD (FF00+C),A 1614 | SaveAToC(); 1615 | break; 1616 | case 0xE5: // PUSH HL 1617 | Push(H, L); 1618 | break; 1619 | case 0xE6: // AND N 1620 | AndImmediate(); 1621 | break; 1622 | case 0xE7: // RST 20H 1623 | Restart(0x20); 1624 | break; 1625 | case 0xE8: // ADD SP,offset 1626 | OffsetStackPointer(); 1627 | break; 1628 | case 0xE9: // JP (HL) 1629 | Jump(H, L); 1630 | break; 1631 | case 0xEA: // LD (word),A 1632 | SaveA(); 1633 | break; 1634 | case 0xEE: // XOR N 1635 | XorImmediate(); 1636 | break; 1637 | case 0xEF: // RST 28H 1638 | Restart(0x0028); 1639 | break; 1640 | case 0xF0: // LD A, (FF00 + n) 1641 | LoadAFromImmediate(); 1642 | break; 1643 | case 0xF1: // POP AF 1644 | PopAF(); 1645 | break; 1646 | case 0xF2: // LD A, (FF00 + C) 1647 | LoadAFromC(); 1648 | break; 1649 | case 0xF3: // DI 1650 | interruptsEnabled = false; 1651 | break; 1652 | case 0xF5: // PUSH AF 1653 | PushAF(); 1654 | break; 1655 | case 0xF6: // OR N 1656 | OrImmediate(); 1657 | break; 1658 | case 0xF7: // RST 30H 1659 | Restart(0x0030); 1660 | break; 1661 | case 0xF8: // LD HL, SP + dd 1662 | LoadHLWithSPPlusImmediate(); 1663 | break; 1664 | case 0xF9: // LD SP,HL 1665 | LoadSPWithHL(); 1666 | break; 1667 | case 0xFA: // LD A, (nn) 1668 | LoadFromImmediateAddress(ref A); 1669 | break; 1670 | case 0xFB: // EI 1671 | interruptsEnabled = true; 1672 | break; 1673 | case 0xFE: // CP N 1674 | CompareImmediate(); 1675 | break; 1676 | case 0xFF: // RST 38H 1677 | Restart(0x0038); 1678 | break; 1679 | default: 1680 | throw new Exception(string.Format("Unknown instruction: {0:X} at PC={1:X}", opCode, PC)); 1681 | } 1682 | } 1683 | 1684 | private void Load(ref int a, int b) 1685 | { 1686 | a = b; 1687 | ticks += 4; 1688 | } 1689 | 1690 | private void LoadSPWithHL() 1691 | { 1692 | SP = (H << 8) | L; 1693 | ticks += 6; 1694 | } 1695 | 1696 | private void LoadAFromImmediate() 1697 | { 1698 | A = ReadByte(0xFF00 | ReadByte(PC++)); 1699 | ticks += 19; 1700 | } 1701 | 1702 | private void LoadAFromC() 1703 | { 1704 | A = ReadByte(0xFF00 | C); 1705 | ticks += 19; 1706 | } 1707 | 1708 | private void LoadHLWithSPPlusImmediate() 1709 | { 1710 | int offset = ReadByte(PC++); 1711 | if (offset > 0x7F) 1712 | { 1713 | offset -= 256; 1714 | } 1715 | offset += SP; 1716 | H = 0xFF & (offset >> 8); 1717 | L = 0xFF & offset; 1718 | ticks += 20; 1719 | } 1720 | 1721 | private void ReturnFromInterrupt() 1722 | { 1723 | interruptsEnabled = true; 1724 | halted = false; 1725 | Return(); 1726 | ticks += 4; 1727 | } 1728 | 1729 | private void NegateA() 1730 | { 1731 | FC = A == 0; 1732 | FH = (A & 0x0F) != 0; 1733 | A = 0xFF & -A; 1734 | FZ = A == 0; 1735 | FN = true; 1736 | ticks += 8; 1737 | } 1738 | 1739 | private void NoOperation() 1740 | { 1741 | ticks += 4; 1742 | } 1743 | 1744 | private void OffsetStackPointer() 1745 | { 1746 | int value = ReadByte(PC++); 1747 | if (value > 0x7F) 1748 | { 1749 | value -= 256; 1750 | } 1751 | SP += value; 1752 | ticks += 20; 1753 | } 1754 | 1755 | private void SaveAToC() 1756 | { 1757 | WriteByte(0xFF00 | C, A); 1758 | ticks += 19; 1759 | } 1760 | 1761 | private void SaveA() 1762 | { 1763 | WriteByte(ReadWord(PC), A); 1764 | PC += 2; 1765 | ticks += 13; 1766 | } 1767 | 1768 | private void SaveAWithOffset() 1769 | { 1770 | WriteByte(0xFF00 | ReadByte(PC++), A); 1771 | ticks += 19; 1772 | } 1773 | 1774 | private void Swap(int ah, int al) 1775 | { 1776 | int address = (ah << 8) | al; 1777 | int value = ReadByte(address); 1778 | Swap(ref value); 1779 | WriteByte(address, value); 1780 | ticks += 7; 1781 | } 1782 | 1783 | private void Swap(ref int r) 1784 | { 1785 | r = 0xFF & ((r << 4) | (r >> 4)); 1786 | ticks += 8; 1787 | } 1788 | 1789 | private void SetBit(int bit, int ah, int al) 1790 | { 1791 | int address = (ah << 8) | al; 1792 | int value = ReadByte(address); 1793 | SetBit(bit, ref value); 1794 | WriteByte(address, value); 1795 | ticks += 7; 1796 | } 1797 | 1798 | private void SetBit(int bit, ref int a) 1799 | { 1800 | a |= (1 << bit); 1801 | ticks += 8; 1802 | } 1803 | 1804 | private void ResetBit(int bit, int ah, int al) 1805 | { 1806 | int address = (ah << 8) | al; 1807 | int value = ReadByte(address); 1808 | ResetBit(bit, ref value); 1809 | WriteByte(address, value); 1810 | ticks += 7; 1811 | } 1812 | 1813 | private void ResetBit(int bit, ref int a) 1814 | { 1815 | switch (bit) 1816 | { 1817 | case 0: // 1111 1110 1818 | a &= 0xFE; 1819 | break; 1820 | case 1: // 1111 1101 1821 | a &= 0xFD; 1822 | break; 1823 | case 2: // 1111 1011 1824 | a &= 0xFB; 1825 | break; 1826 | case 3: // 1111 0111 1827 | a &= 0xF7; 1828 | break; 1829 | case 4: // 1110 1111 1830 | a &= 0xEF; 1831 | break; 1832 | case 5: // 1101 1111 1833 | a &= 0xDF; 1834 | break; 1835 | case 6: // 1011 1111 1836 | a &= 0xBF; 1837 | break; 1838 | case 7: // 0111 1111 1839 | a &= 0x7F; 1840 | break; 1841 | } 1842 | ticks += 8; 1843 | } 1844 | 1845 | private void Halt() 1846 | { 1847 | if (interruptsEnabled) 1848 | { 1849 | halted = true; 1850 | } else 1851 | { 1852 | stopCounting = true; 1853 | } 1854 | ticks += 4; 1855 | } 1856 | 1857 | private void TestBit(int bit, int ah, int al) 1858 | { 1859 | int address = (ah << 8) | al; 1860 | int value = ReadByte(address); 1861 | TestBit(bit, value); 1862 | WriteByte(address, value); 1863 | ticks += 4; 1864 | } 1865 | 1866 | private void TestBit(int bit, int a) 1867 | { 1868 | FZ = (a & (1 << bit)) == 0; 1869 | FN = false; 1870 | FH = true; 1871 | ticks += 8; 1872 | } 1873 | 1874 | private void CallIfCarry() 1875 | { 1876 | if (FC) 1877 | { 1878 | Call(); 1879 | } else 1880 | { 1881 | PC += 2; 1882 | ticks++; 1883 | } 1884 | } 1885 | 1886 | private void CallIfNotCarry() 1887 | { 1888 | if (FC) 1889 | { 1890 | PC += 2; 1891 | ticks++; 1892 | } else 1893 | { 1894 | Call(); 1895 | } 1896 | } 1897 | 1898 | private void CallIfZero() 1899 | { 1900 | if (FZ) 1901 | { 1902 | Call(); 1903 | } else 1904 | { 1905 | PC += 2; 1906 | ticks++; 1907 | } 1908 | } 1909 | 1910 | private void CallIfNotZero() 1911 | { 1912 | if (FZ) 1913 | { 1914 | PC += 2; 1915 | ticks++; 1916 | } else 1917 | { 1918 | Call(); 1919 | } 1920 | } 1921 | 1922 | private void Interrupt(int address) 1923 | { 1924 | interruptsEnabled = false; 1925 | halted = false; 1926 | Push(PC); 1927 | PC = address; 1928 | } 1929 | 1930 | private void Restart(int address) 1931 | { 1932 | Push(PC); 1933 | PC = address; 1934 | } 1935 | 1936 | private void Call() 1937 | { 1938 | Push(0xFFFF & (PC + 2)); 1939 | PC = ReadWord(PC); 1940 | ticks += 17; 1941 | } 1942 | 1943 | private void JumpIfCarry() 1944 | { 1945 | if (FC) 1946 | { 1947 | Jump(); 1948 | } else 1949 | { 1950 | PC += 2; 1951 | ticks++; 1952 | } 1953 | } 1954 | 1955 | private void JumpIfNotCarry() 1956 | { 1957 | if (FC) 1958 | { 1959 | PC += 2; 1960 | ticks++; 1961 | } else 1962 | { 1963 | Jump(); 1964 | } 1965 | } 1966 | 1967 | private void JumpIfZero() 1968 | { 1969 | if (FZ) 1970 | { 1971 | Jump(); 1972 | } else 1973 | { 1974 | PC += 2; 1975 | ticks++; 1976 | } 1977 | } 1978 | 1979 | private void JumpIfNotZero() 1980 | { 1981 | if (FZ) 1982 | { 1983 | PC += 2; 1984 | ticks++; 1985 | } else 1986 | { 1987 | Jump(); 1988 | } 1989 | } 1990 | 1991 | private void Jump(int ah, int al) 1992 | { 1993 | PC = (ah << 8) | al; 1994 | ticks += 4; 1995 | } 1996 | 1997 | private void Jump() 1998 | { 1999 | PC = ReadWord(PC); 2000 | ticks += 10; 2001 | } 2002 | 2003 | private void ReturnIfCarry() 2004 | { 2005 | if (FC) 2006 | { 2007 | Return(); 2008 | ticks++; 2009 | } else 2010 | { 2011 | ticks += 5; 2012 | } 2013 | } 2014 | 2015 | private void ReturnIfNotCarry() 2016 | { 2017 | if (FC) 2018 | { 2019 | ticks += 5; 2020 | } else 2021 | { 2022 | Return(); 2023 | ticks++; 2024 | } 2025 | } 2026 | 2027 | private void ReturnIfZero() 2028 | { 2029 | if (FZ) 2030 | { 2031 | Return(); 2032 | ticks++; 2033 | } else 2034 | { 2035 | ticks += 5; 2036 | } 2037 | } 2038 | 2039 | private void ReturnIfNotZero() 2040 | { 2041 | if (FZ) 2042 | { 2043 | ticks += 5; 2044 | } else 2045 | { 2046 | Return(); 2047 | ticks++; 2048 | } 2049 | } 2050 | 2051 | private void Return() 2052 | { 2053 | Pop(ref PC); 2054 | } 2055 | 2056 | private void Pop(ref int a) 2057 | { 2058 | a = ReadWord(SP); 2059 | SP += 2; 2060 | ticks += 10; 2061 | } 2062 | 2063 | private void PopAF() 2064 | { 2065 | int F = 0; 2066 | Pop(ref A, ref F); 2067 | FZ = (F & 0x80) == 0x80; 2068 | FC = (F & 0x40) == 0x40; 2069 | FH = (F & 0x20) == 0x20; 2070 | FN = (F & 0x10) == 0x10; 2071 | } 2072 | 2073 | private void PushAF() 2074 | { 2075 | int F = 0; 2076 | if (FZ) 2077 | { 2078 | F |= 0x80; 2079 | } 2080 | if (FC) 2081 | { 2082 | F |= 0x40; 2083 | } 2084 | if (FH) 2085 | { 2086 | F |= 0x20; 2087 | } 2088 | if (FN) 2089 | { 2090 | F |= 0x10; 2091 | } 2092 | Push(A, F); 2093 | } 2094 | 2095 | private void Pop(ref int rh, ref int rl) 2096 | { 2097 | rl = ReadByte(SP++); 2098 | rh = ReadByte(SP++); 2099 | ticks += 10; 2100 | } 2101 | 2102 | private void Push(int rh, int rl) 2103 | { 2104 | WriteByte(--SP, rh); 2105 | WriteByte(--SP, rl); 2106 | ticks += 11; 2107 | } 2108 | 2109 | private void Push(int value) 2110 | { 2111 | SP -= 2; 2112 | WriteWord(SP, value); 2113 | ticks += 11; 2114 | } 2115 | 2116 | private void Or(int addressHigh, int addressLow) 2117 | { 2118 | Or(ReadByte((addressHigh << 8) | addressLow)); 2119 | ticks += 3; 2120 | } 2121 | 2122 | private void Or(int b) 2123 | { 2124 | A = 0xFF & (A | b); 2125 | FH = false; 2126 | FN = false; 2127 | FC = false; 2128 | FZ = A == 0; 2129 | ticks += 4; 2130 | } 2131 | 2132 | private void OrImmediate() 2133 | { 2134 | Or(ReadByte(PC++)); 2135 | ticks += 3; 2136 | } 2137 | 2138 | private void XorImmediate() 2139 | { 2140 | Xor(ReadByte(PC++)); 2141 | } 2142 | 2143 | private void Xor(int addressHigh, int addressLow) 2144 | { 2145 | Xor(ReadByte((addressHigh << 8) | addressLow)); 2146 | } 2147 | 2148 | private void Xor(int b) 2149 | { 2150 | A = 0xFF & (A ^ b); 2151 | FH = false; 2152 | FN = false; 2153 | FC = false; 2154 | FZ = A == 0; 2155 | } 2156 | 2157 | private void And(int addressHigh, int addressLow) 2158 | { 2159 | And(ReadByte((addressHigh << 8) | addressLow)); 2160 | ticks += 3; 2161 | } 2162 | 2163 | private void AndImmediate() 2164 | { 2165 | And(ReadByte(PC++)); 2166 | ticks += 3; 2167 | } 2168 | 2169 | private void And(int b) 2170 | { 2171 | A = 0xFF & (A & b); 2172 | FH = true; 2173 | FN = false; 2174 | FC = false; 2175 | FZ = A == 0; 2176 | ticks += 4; 2177 | } 2178 | 2179 | private void SetCarryFlag() 2180 | { 2181 | FH = false; 2182 | FC = true; 2183 | FN = false; 2184 | ticks += 4; 2185 | } 2186 | 2187 | private void ComplementCarryFlag() 2188 | { 2189 | FH = FC; 2190 | FC = !FC; 2191 | FN = false; 2192 | ticks += 4; 2193 | } 2194 | 2195 | private void LoadImmediateIntoMemory(int ah, int al) 2196 | { 2197 | WriteByte((ah << 8) | al, ReadByte(PC++)); 2198 | ticks += 10; 2199 | } 2200 | 2201 | private void ComplementA() 2202 | { 2203 | A ^= 0xFF; 2204 | FN = true; 2205 | FH = true; 2206 | ticks += 4; 2207 | } 2208 | 2209 | private void DecimallyAdjustA() 2210 | { 2211 | int highNibble = A >> 4; 2212 | int lowNibble = A & 0x0F; 2213 | bool _FC = true; 2214 | if (FN) 2215 | { 2216 | if (FC) 2217 | { 2218 | if (FH) 2219 | { 2220 | A += 0x9A; 2221 | } else 2222 | { 2223 | A += 0xA0; 2224 | } 2225 | } else 2226 | { 2227 | _FC = false; 2228 | if (FH) 2229 | { 2230 | A += 0xFA; 2231 | } else 2232 | { 2233 | A += 0x00; 2234 | } 2235 | } 2236 | } else if (FC) 2237 | { 2238 | if (FH || lowNibble > 9) 2239 | { 2240 | A += 0x66; 2241 | } else 2242 | { 2243 | A += 0x60; 2244 | } 2245 | } else if (FH) 2246 | { 2247 | if (highNibble > 9) 2248 | { 2249 | A += 0x66; 2250 | } else 2251 | { 2252 | A += 0x06; 2253 | _FC = false; 2254 | } 2255 | } else if (lowNibble > 9) 2256 | { 2257 | if (highNibble < 9) 2258 | { 2259 | _FC = false; 2260 | A += 0x06; 2261 | } else 2262 | { 2263 | A += 0x66; 2264 | } 2265 | } else if (highNibble > 9) 2266 | { 2267 | A += 0x60; 2268 | } else 2269 | { 2270 | _FC = false; 2271 | } 2272 | 2273 | A &= 0xFF; 2274 | FC = _FC; 2275 | FZ = A == 0; 2276 | ticks += 4; 2277 | } 2278 | 2279 | private void JumpRelativeIfNotCarry() 2280 | { 2281 | if (FC) 2282 | { 2283 | PC++; 2284 | ticks += 7; 2285 | } else 2286 | { 2287 | JumpRelative(); 2288 | } 2289 | } 2290 | 2291 | private void JumpRelativeIfCarry() 2292 | { 2293 | if (FC) 2294 | { 2295 | JumpRelative(); 2296 | } else 2297 | { 2298 | PC++; 2299 | ticks += 7; 2300 | } 2301 | } 2302 | 2303 | private void JumpRelativeIfNotZero() 2304 | { 2305 | if (FZ) 2306 | { 2307 | PC++; 2308 | ticks += 7; 2309 | } else 2310 | { 2311 | JumpRelative(); 2312 | } 2313 | } 2314 | 2315 | private void JumpRelativeIfZero() 2316 | { 2317 | if (FZ) 2318 | { 2319 | JumpRelative(); 2320 | } else 2321 | { 2322 | PC++; 2323 | ticks += 7; 2324 | } 2325 | } 2326 | 2327 | private void JumpRelative() 2328 | { 2329 | int relativeAddress = ReadByte(PC++); 2330 | if (relativeAddress > 0x7F) 2331 | { 2332 | relativeAddress -= 256; 2333 | } 2334 | PC += relativeAddress; 2335 | ticks += 12; 2336 | } 2337 | 2338 | private void Add(int addressHigh, int addressLow) 2339 | { 2340 | Add(ReadByte((addressHigh << 8) | addressLow)); 2341 | ticks += 3; 2342 | } 2343 | 2344 | private void AddImmediateWithCarry() 2345 | { 2346 | AddWithCarry(ReadByte(PC++)); 2347 | ticks += 3; 2348 | } 2349 | 2350 | private void AddWithCarry(int addressHigh, int addressLow) 2351 | { 2352 | AddWithCarry(ReadByte((addressHigh << 8) | addressLow)); 2353 | ticks += 3; 2354 | } 2355 | 2356 | private void AddWithCarry(int b) 2357 | { 2358 | int carry = FC ? 1 : 0; 2359 | FH = carry + (A & 0x0F) + (b & 0x0F) > 0x0F; 2360 | A += b + carry; 2361 | FC = A > 255; 2362 | A &= 0xFF; 2363 | FN = false; 2364 | FZ = A == 0; 2365 | ticks += 4; 2366 | } 2367 | 2368 | private void SubWithBorrow(int ah, int al) 2369 | { 2370 | SubWithBorrow(ReadByte((ah << 8) | al)); 2371 | ticks += 3; 2372 | } 2373 | 2374 | private void SubImmediateWithBorrow() 2375 | { 2376 | SubWithBorrow(ReadByte(PC++)); 2377 | ticks += 3; 2378 | } 2379 | 2380 | private void SubWithBorrow(int b) 2381 | { 2382 | if (FC) 2383 | { 2384 | Sub(b + 1); 2385 | } else 2386 | { 2387 | Sub(b); 2388 | } 2389 | } 2390 | 2391 | private void Sub(int ah, int al) 2392 | { 2393 | Sub(ReadByte((ah << 8) | al)); 2394 | ticks += 3; 2395 | } 2396 | 2397 | private void Compare(int ah, int al) 2398 | { 2399 | Compare(ReadByte((ah << 8) | al)); 2400 | ticks += 3; 2401 | } 2402 | 2403 | private void CompareImmediate() 2404 | { 2405 | Compare(ReadByte(PC++)); 2406 | ticks += 3; 2407 | } 2408 | 2409 | private void Compare(int b) 2410 | { 2411 | FH = (A & 0x0F) < (b & 0x0F); 2412 | FC = b > A; 2413 | FN = true; 2414 | FZ = A == b; 2415 | ticks += 4; 2416 | } 2417 | 2418 | private void SubImmediate() 2419 | { 2420 | Sub(ReadByte(PC++)); 2421 | ticks += 3; 2422 | } 2423 | 2424 | private void Sub(int b) 2425 | { 2426 | FH = (A & 0x0F) < (b & 0x0F); 2427 | FC = b > A; 2428 | A -= b; 2429 | A &= 0xFF; 2430 | FN = true; 2431 | FZ = A == 0; 2432 | ticks += 4; 2433 | } 2434 | 2435 | private void AddImmediate() 2436 | { 2437 | Add(ReadByte(PC++)); 2438 | ticks += 3; 2439 | } 2440 | 2441 | private void Add(int b) 2442 | { 2443 | FH = (A & 0x0F) + (b & 0x0F) > 0x0F; 2444 | A += b; 2445 | FC = A > 255; 2446 | A &= 0xFF; 2447 | FN = false; 2448 | FZ = A == 0; 2449 | ticks += 4; 2450 | } 2451 | 2452 | private void AddSPToHL() 2453 | { 2454 | Add(ref H, ref L, SP >> 8, SP & 0xFF); 2455 | } 2456 | 2457 | private void Add(ref int ah, ref int al, int bh, int bl) 2458 | { 2459 | al += bl; 2460 | int carry = (al > 0xFF) ? 1 : 0; 2461 | al &= 0xFF; 2462 | FH = carry + (ah & 0x0F) + (bh & 0x0F) > 0x0F; 2463 | ah += bh + carry; 2464 | FC = ah > 0xFF; 2465 | ah &= 0xFF; 2466 | FN = false; 2467 | ticks += 11; 2468 | } 2469 | 2470 | private void ShiftLeft(int ah, int al) 2471 | { 2472 | int address = (ah << 8) | al; 2473 | int value = ReadByte(address); 2474 | ShiftLeft(ref value); 2475 | WriteByte(address, value); 2476 | ticks += 7; 2477 | } 2478 | 2479 | private void ShiftLeft(ref int a) 2480 | { 2481 | FC = a > 0x7F; 2482 | a = 0xFF & (a << 1); 2483 | FZ = a == 0; 2484 | FN = false; 2485 | FH = false; 2486 | ticks += 8; 2487 | } 2488 | 2489 | private void UnsignedShiftRight(int ah, int al) 2490 | { 2491 | int address = (ah << 8) | al; 2492 | int value = ReadByte(address); 2493 | UnsignedShiftRight(ref value); 2494 | WriteByte(address, value); 2495 | ticks += 7; 2496 | } 2497 | 2498 | private void UnsignedShiftRight(ref int a) 2499 | { 2500 | FC = (a & 0x01) == 1; 2501 | a >>= 1; 2502 | FZ = a == 0; 2503 | FN = false; 2504 | FH = false; 2505 | ticks += 8; 2506 | } 2507 | 2508 | private void SignedShiftRight(int ah, int al) 2509 | { 2510 | int address = (ah << 8) | al; 2511 | int value = ReadByte(address); 2512 | SignedShiftRight(ref value); 2513 | WriteByte(address, value); 2514 | ticks += 7; 2515 | } 2516 | 2517 | private void SignedShiftRight(ref int a) 2518 | { 2519 | FC = (a & 0x01) == 1; 2520 | a = (a & 0x80) | (a >> 1); 2521 | FZ = a == 0; 2522 | FN = false; 2523 | FH = false; 2524 | ticks += 8; 2525 | } 2526 | 2527 | private void RotateARight() 2528 | { 2529 | int lowBit = A & 0x01; 2530 | FC = lowBit == 1; 2531 | A = (A >> 1) | (lowBit << 7); 2532 | FN = false; 2533 | FH = false; 2534 | ticks += 4; 2535 | } 2536 | 2537 | private void RotateARightThroughCarry() 2538 | { 2539 | int highBit = FC ? 0x80 : 0x00; 2540 | FC = (A & 0x01) == 0x01; 2541 | A = highBit | (A >> 1); 2542 | FN = false; 2543 | FH = false; 2544 | ticks += 4; 2545 | } 2546 | 2547 | private void RotateALeftThroughCarry() 2548 | { 2549 | int highBit = FC ? 1 : 0; 2550 | FC = A > 0x7F; 2551 | A = ((A << 1) & 0xFF) | highBit; 2552 | FN = false; 2553 | FH = false; 2554 | ticks += 4; 2555 | } 2556 | 2557 | private void RotateRight(ref int a) 2558 | { 2559 | int lowBit = a & 0x01; 2560 | FC = lowBit == 1; 2561 | a = (a >> 1) | (lowBit << 7); 2562 | FZ = a == 0; 2563 | FN = false; 2564 | FH = false; 2565 | ticks += 8; 2566 | } 2567 | 2568 | private void RotateRightThroughCarry(int ah, int al) 2569 | { 2570 | int address = (ah << 8) | al; 2571 | int value = ReadByte(address); 2572 | RotateRightThroughCarry(ref value); 2573 | WriteByte(address, value); 2574 | ticks += 7; 2575 | } 2576 | 2577 | private void RotateRightThroughCarry(ref int a) 2578 | { 2579 | int lowBit = FC ? 0x80 : 0; 2580 | FC = (a & 0x01) == 1; 2581 | a = (a >> 1) | lowBit; 2582 | FZ = a == 0; 2583 | FN = false; 2584 | FH = false; 2585 | ticks += 8; 2586 | } 2587 | 2588 | private void RotateLeftThroughCarry(int ah, int al) 2589 | { 2590 | int address = (ah << 8) | al; 2591 | int value = ReadByte(address); 2592 | RotateLeftThroughCarry(ref value); 2593 | WriteByte(address, value); 2594 | ticks += 7; 2595 | } 2596 | 2597 | private void RotateLeftThroughCarry(ref int a) 2598 | { 2599 | int highBit = FC ? 1 : 0; 2600 | FC = (a >> 7) == 1; 2601 | a = ((a << 1) & 0xFF) | highBit; 2602 | FZ = a == 0; 2603 | FN = false; 2604 | FH = false; 2605 | ticks += 8; 2606 | } 2607 | 2608 | private void RotateLeft(int ah, int al) 2609 | { 2610 | int address = (ah << 8) | al; 2611 | int value = ReadByte(address); 2612 | RotateLeft(ref value); 2613 | WriteByte(address, value); 2614 | ticks += 7; 2615 | } 2616 | 2617 | private void RotateRight(int ah, int al) 2618 | { 2619 | int address = (ah << 8) | al; 2620 | int value = ReadByte(address); 2621 | RotateRight(ref value); 2622 | WriteByte(address, value); 2623 | ticks += 7; 2624 | } 2625 | 2626 | private void RotateLeft(ref int a) 2627 | { 2628 | int highBit = a >> 7; 2629 | FC = highBit == 1; 2630 | a = ((a << 1) & 0xFF) | highBit; 2631 | FZ = a == 0; 2632 | FN = false; 2633 | FH = false; 2634 | ticks += 8; 2635 | } 2636 | 2637 | private void RotateALeft() 2638 | { 2639 | int highBit = A >> 7; 2640 | FC = highBit == 1; 2641 | A = ((A << 1) & 0xFF) | highBit; 2642 | FN = false; 2643 | FH = false; 2644 | ticks += 4; 2645 | } 2646 | 2647 | private void LoadFromImmediateAddress(ref int r) 2648 | { 2649 | r = ReadByte(ReadWord(PC)); 2650 | PC += 2; 2651 | ticks += 13; 2652 | } 2653 | 2654 | private void LoadImmediate(ref int r) 2655 | { 2656 | r = ReadByte(PC++); 2657 | ticks += 7; 2658 | } 2659 | 2660 | private void LoadImmediateWord(ref int r) 2661 | { 2662 | r = ReadWord(PC); 2663 | PC += 2; 2664 | ticks += 10; 2665 | } 2666 | 2667 | private void LoadImmediate(ref int rh, ref int rl) 2668 | { 2669 | rl = ReadByte(PC++); 2670 | rh = ReadByte(PC++); 2671 | ticks += 10; 2672 | } 2673 | 2674 | private void ReadByte(ref int r, int ah, int al) 2675 | { 2676 | r = ReadByte((ah << 8) | al); 2677 | ticks += 7; 2678 | } 2679 | 2680 | private void WriteByte(int ah, int al, int value) 2681 | { 2682 | WriteByte((ah << 8) | al, value); 2683 | ticks += 7; 2684 | } 2685 | 2686 | private void WriteWordToImmediateAddress(int value) 2687 | { 2688 | WriteWord(ReadWord(PC), value); 2689 | PC += 2; 2690 | ticks += 20; 2691 | } 2692 | 2693 | private void Decrement(ref int rh, ref int rl) 2694 | { 2695 | if (rl == 0) 2696 | { 2697 | rh = 0xFF & (rh - 1); 2698 | rl = 0xFF; 2699 | } else 2700 | { 2701 | rl--; 2702 | } 2703 | ticks += 6; 2704 | } 2705 | 2706 | private void IncrementWord(ref int r) 2707 | { 2708 | if (r == 0xFFFF) 2709 | { 2710 | r = 0; 2711 | } else 2712 | { 2713 | r++; 2714 | } 2715 | ticks += 6; 2716 | } 2717 | 2718 | private void DecrementWord(ref int r) 2719 | { 2720 | if (r == 0) 2721 | { 2722 | r = 0xFFFF; 2723 | } else 2724 | { 2725 | r--; 2726 | } 2727 | ticks += 6; 2728 | } 2729 | 2730 | private void DecrementMemory(int ah, int al) 2731 | { 2732 | int address = (ah << 8) | al; 2733 | int r = ReadByte(address); 2734 | Decrement(ref r); 2735 | WriteByte(address, r); 2736 | ticks += 7; 2737 | } 2738 | 2739 | private void IncrementMemory(int ah, int al) 2740 | { 2741 | int address = (ah << 8) | al; 2742 | int r = ReadByte(address); 2743 | Increment(ref r); 2744 | WriteByte(address, r); 2745 | ticks += 7; 2746 | } 2747 | 2748 | private void Increment(ref int rh, ref int rl) 2749 | { 2750 | if (rl == 255) 2751 | { 2752 | rh = 0xFF & (rh + 1); 2753 | rl = 0; 2754 | } else 2755 | { 2756 | rl++; 2757 | } 2758 | ticks += 6; 2759 | } 2760 | 2761 | private void Increment(ref int r) 2762 | { 2763 | FH = (r & 0x0F) == 0x0F; 2764 | r++; 2765 | r &= 0xFF; 2766 | FZ = r == 0; 2767 | FN = false; 2768 | ticks += 4; 2769 | } 2770 | 2771 | private void Decrement(ref int r) 2772 | { 2773 | FH = (r & 0x0F) == 0x00; 2774 | r--; 2775 | r &= 0xFF; 2776 | FZ = r == 0; 2777 | FN = true; 2778 | ticks += 4; 2779 | } 2780 | 2781 | public void PowerUp() 2782 | { 2783 | A = 0x01; 2784 | B = 0x00; 2785 | C = 0x13; 2786 | D = 0x00; 2787 | E = 0xD8; 2788 | H = 0x01; 2789 | L = 0x4D; 2790 | FZ = true; 2791 | FC = false; 2792 | FH = true; 2793 | FN = true; 2794 | SP = 0xFFFE; 2795 | PC = 0x0100; 2796 | 2797 | WriteByte(0xFF05, 0x00); // TIMA 2798 | WriteByte(0xFF06, 0x00); // TMA 2799 | WriteByte(0xFF07, 0x00); // TAC 2800 | WriteByte(0xFF10, 0x80); // NR10 2801 | WriteByte(0xFF11, 0xBF); // NR11 2802 | WriteByte(0xFF12, 0xF3); // NR12 2803 | WriteByte(0xFF14, 0xBF); // NR14 2804 | WriteByte(0xFF16, 0x3F); // NR21 2805 | WriteByte(0xFF17, 0x00); // NR22 2806 | WriteByte(0xFF19, 0xBF); // NR24 2807 | WriteByte(0xFF1A, 0x7F); // NR30 2808 | WriteByte(0xFF1B, 0xFF); // NR31 2809 | WriteByte(0xFF1C, 0x9F); // NR32 2810 | WriteByte(0xFF1E, 0xBF); // NR33 2811 | WriteByte(0xFF20, 0xFF); // NR41 2812 | WriteByte(0xFF21, 0x00); // NR42 2813 | WriteByte(0xFF22, 0x00); // NR43 2814 | WriteByte(0xFF23, 0xBF); // NR30 2815 | WriteByte(0xFF24, 0x77); // NR50 2816 | WriteByte(0xFF25, 0xF3); // NR51 2817 | WriteByte(0xFF26, 0xF1); // NR52 2818 | WriteByte(0xFF40, 0x91); // LCDC 2819 | WriteByte(0xFF42, 0x00); // SCY 2820 | WriteByte(0xFF43, 0x00); // SCX 2821 | WriteByte(0xFF45, 0x00); // LYC 2822 | WriteByte(0xFF47, 0xFC); // BGP 2823 | WriteByte(0xFF48, 0xFF); // OBP0 2824 | WriteByte(0xFF49, 0xFF); // OBP1 2825 | WriteByte(0xFF4A, 0x00); // WY 2826 | WriteByte(0xFF4B, 0x00); // WX 2827 | WriteByte(0xFFFF, 0x00); // IE 2828 | } 2829 | 2830 | public void WriteWord(int address, int value) 2831 | { 2832 | WriteByte(address, value & 0xFF); 2833 | WriteByte(address + 1, value >> 8); 2834 | } 2835 | 2836 | public int ReadWord(int address) 2837 | { 2838 | int low = ReadByte(address); 2839 | int high = ReadByte(address + 1); 2840 | return (high << 8) | low; 2841 | } 2842 | 2843 | public void WriteByte(int address, int value) 2844 | { 2845 | if (address >= 0xC000 && address <= 0xDFFF) 2846 | { 2847 | workRam [address - 0xC000] = (byte)value; 2848 | } else if (address >= 0xFE00 && address <= 0xFEFF) 2849 | { 2850 | oam [address - 0xFE00] = (byte)value; 2851 | } else if (address >= 0xFF80 && address <= 0xFFFE) 2852 | { 2853 | highRam [0xFF & address] = (byte)value; 2854 | } else if (address >= 0x8000 && address <= 0x9FFF) 2855 | { 2856 | int videoRamIndex = address - 0x8000; 2857 | videoRam [videoRamIndex] = (byte)value; 2858 | if (address < 0x9000) 2859 | { 2860 | spriteTileInvalidated [videoRamIndex >> 4] = true; 2861 | } 2862 | if (address < 0x9800) 2863 | { 2864 | invalidateAllBackgroundTilesRequest = true; 2865 | } else if (address >= 0x9C00) 2866 | { 2867 | int tileIndex = address - 0x9C00; 2868 | backgroundTileInvalidated [tileIndex >> 5, tileIndex & 0x1F] = true; 2869 | } else 2870 | { 2871 | int tileIndex = address - 0x9800; 2872 | backgroundTileInvalidated [tileIndex >> 5, tileIndex & 0x1F] = true; 2873 | } 2874 | } else if (address <= 0x7FFF || (address >= 0xA000 && address <= 0xBFFF)) 2875 | { 2876 | cartridge.WriteByte(address, value); 2877 | } else if (address >= 0xE000 && address <= 0xFDFF) 2878 | { 2879 | workRam [address - 0xE000] = (byte)value; 2880 | } else 2881 | { 2882 | switch (address) 2883 | { 2884 | case 0xFF00: // key pad 2885 | keyP14 = (value & 0x10) != 0x10; 2886 | keyP15 = (value & 0x20) != 0x20; 2887 | break; 2888 | case 0xFF04: // Timer divider 2889 | break; 2890 | case 0xFF05: // Timer counter 2891 | timerCounter = value; 2892 | break; 2893 | case 0xFF06: // Timer modulo 2894 | timerModulo = value; 2895 | break; 2896 | case 0xFF07: // Time Control 2897 | timerRunning = (value & 0x04) == 0x04; 2898 | timerFrequency = (TimerFrequencyType)(0x03 & value); 2899 | break; 2900 | case 0xFF0F: // Interrupt Flag (an interrupt request) 2901 | keyPressedInterruptRequested = (value & 0x10) == 0x10; 2902 | serialIOTransferCompleteInterruptRequested = (value & 0x08) == 0x08; 2903 | timerOverflowInterruptRequested = (value & 0x04) == 0x04; 2904 | lcdcInterruptRequested = (value & 0x02) == 0x02; 2905 | vBlankInterruptRequested = (value & 0x01) == 0x01; 2906 | break; 2907 | case 0xFF10: // Sound channel 1, sweep 2908 | SoundChip.channel1.SetSweep( 2909 | (value & 0x70) >> 4, 2910 | (value & 0x07), 2911 | (value & 0x08) == 1); 2912 | soundRegisters [0x10 - 0x10] = value; 2913 | break; 2914 | 2915 | case 0xFF11: // Sound channel 1, length and wave duty 2916 | SoundChip.channel1.SetDutyCycle((value & 0xC0) >> 6); 2917 | SoundChip.channel1.SetLength(value & 0x3F); 2918 | soundRegisters [0x11 - 0x10] = value; 2919 | break; 2920 | 2921 | case 0xFF12: // Sound channel 1, volume envelope 2922 | SoundChip.channel1.SetEnvelope( 2923 | (value & 0xF0) >> 4, 2924 | (value & 0x07), 2925 | (value & 0x08) == 8); 2926 | soundRegisters [0x12 - 0x10] = value; 2927 | break; 2928 | 2929 | case 0xFF13: // Sound channel 1, frequency low 2930 | soundRegisters [0x13 - 0x10] = value; 2931 | SoundChip.channel1.SetFrequency( 2932 | ((soundRegisters [0x14 - 0x10] & 0x07) << 8) + soundRegisters [0x13 - 0x10]); 2933 | break; 2934 | 2935 | case 0xFF14: // Sound channel 1, frequency high 2936 | soundRegisters [0x14 - 0x10] = value; 2937 | 2938 | if ((soundRegisters [0x14 - 0x10] & 0x80) != 0) 2939 | { 2940 | SoundChip.channel1.SetLength(soundRegisters [0x11 - 0x10] & 0x3F); 2941 | SoundChip.channel1.SetEnvelope( 2942 | (soundRegisters [0x12 - 0x10] & 0xF0) >> 4, 2943 | (soundRegisters [0x12 - 0x10] & 0x07), 2944 | (soundRegisters [0x12 - 0x10] & 0x08) == 8); 2945 | } 2946 | if ((soundRegisters [0x14 - 0x10] & 0x40) == 0) 2947 | { 2948 | SoundChip.channel1.SetLength(-1); 2949 | } 2950 | 2951 | SoundChip.channel1.SetFrequency( 2952 | ((soundRegisters [0x14 - 0x10] & 0x07) << 8) + soundRegisters [0x13 - 0x10]); 2953 | 2954 | break; 2955 | 2956 | case 0xFF17: // Sound channel 2, volume envelope 2957 | SoundChip.channel2.SetEnvelope( 2958 | (value & 0xF0) >> 4, 2959 | value & 0x07, 2960 | (value & 0x08) == 8); 2961 | soundRegisters [0x17 - 0x10] = value; 2962 | break; 2963 | 2964 | case 0xFF18: // Sound channel 2, frequency low 2965 | soundRegisters [0x18 - 0x10] = value; 2966 | SoundChip.channel2.SetFrequency( 2967 | ((soundRegisters [0x19 - 0x10] & 0x07) << 8) + soundRegisters [0x18 - 0x10]); 2968 | break; 2969 | 2970 | case 0xFF19: // Sound channel 2, frequency high 2971 | soundRegisters [0x19 - 0x10] = value; 2972 | 2973 | if ((value & 0x80) != 0) 2974 | { 2975 | SoundChip.channel2.SetLength(soundRegisters [0x21 - 0x10] & 0x3F); 2976 | SoundChip.channel2.SetEnvelope( 2977 | (soundRegisters [0x17 - 0x10] & 0xF0) >> 4, 2978 | (soundRegisters [0x17 - 0x10] & 0x07), 2979 | (soundRegisters [0x17 - 0x10] & 0x08) == 8); 2980 | } 2981 | if ((soundRegisters [0x19 - 0x10] & 0x40) == 0) 2982 | { 2983 | SoundChip.channel2.SetLength(-1); 2984 | } 2985 | SoundChip.channel2.SetFrequency( 2986 | ((soundRegisters [0x19 - 0x10] & 0x07) << 8) + soundRegisters [0x18 - 0x10]); 2987 | break; 2988 | 2989 | case 0xFF16: // Sound channel 2, length and wave duty 2990 | SoundChip.channel2.SetDutyCycle((value & 0xC0) >> 6); 2991 | SoundChip.channel2.SetLength(value & 0x3F); 2992 | soundRegisters [0x16 - 0x10] = value; 2993 | break; 2994 | 2995 | case 0xFF1A: // Sound channel 3, on/off 2996 | if ((value & 0x80) != 0) 2997 | { 2998 | SoundChip.channel3.SetVolume((soundRegisters [0x1C - 0x10] & 0x60) >> 5); 2999 | } else 3000 | { 3001 | SoundChip.channel3.SetVolume(0); 3002 | } 3003 | soundRegisters [0x1A - 0x10] = value; 3004 | break; 3005 | 3006 | case 0xFF1B: // Sound channel 3, length 3007 | soundRegisters [0x1B - 0x10] = value; 3008 | SoundChip.channel3.SetLength(value); 3009 | break; 3010 | 3011 | case 0xFF1C: // Sound channel 3, volume 3012 | soundRegisters [0x1C - 0x10] = value; 3013 | SoundChip.channel3.SetVolume((value & 0x60) >> 5); 3014 | break; 3015 | 3016 | case 0xFF1D: // Sound channel 3, frequency lower 8-bit 3017 | soundRegisters [0x1D - 0x10] = value; 3018 | SoundChip.channel3.SetFrequency( 3019 | ((soundRegisters [0x1E - 0x10] & 0x07) << 8) + soundRegisters [0x1D - 0x10]); 3020 | break; 3021 | 3022 | case 0xFF1E: // Sound channel 3, frequency higher 3-bit 3023 | soundRegisters [0x1E - 0x10] = value; 3024 | if ((soundRegisters [0x19 - 0x10] & 0x80) != 0) 3025 | { 3026 | SoundChip.channel3.SetLength(soundRegisters [0x1B - 0x10]); 3027 | } 3028 | SoundChip.channel3.SetFrequency( 3029 | ((soundRegisters [0x1E - 0x10] & 0x07) << 8) + soundRegisters [0x1D - 0x10]); 3030 | break; 3031 | 3032 | case 0xFF20: // Sound channel 4, length 3033 | SoundChip.channel4.SetLength(value & 0x3F); 3034 | soundRegisters [0x20 - 0x10] = value; 3035 | break; 3036 | 3037 | 3038 | case 0xFF21: // Sound channel 4, volume envelope 3039 | SoundChip.channel4.SetEnvelope( 3040 | (value & 0xF0) >> 4, 3041 | (value & 0x07), 3042 | (value & 0x08) == 8); 3043 | soundRegisters [0x21 - 0x10] = value; 3044 | break; 3045 | 3046 | case 0xFF22: // Sound channel 4, polynomial parameters 3047 | SoundChip.channel4.SetParameters( 3048 | (value & 0x07), 3049 | (value & 0x08) == 8, 3050 | (value & 0xF0) >> 4); 3051 | soundRegisters [0x22 - 0x10] = value; 3052 | break; 3053 | 3054 | case 0xFF23: // Sound channel 4, initial/consecutive 3055 | soundRegisters [0x23 - 0x10] = value; 3056 | if ((value & 0x80) != 0) 3057 | { 3058 | SoundChip.channel4.SetLength(soundRegisters [0x20 - 0x10] & 0x3F); 3059 | } else if (((value & 0x80) & 0x40) == 0) 3060 | { 3061 | SoundChip.channel4.SetLength(-1); 3062 | } 3063 | break; 3064 | case 0xFF24: 3065 | // TODO volume 3066 | break; 3067 | case 0xFF25: // Stereo select 3068 | int chanData; 3069 | soundRegisters [0x25 - 0x10] = value; 3070 | 3071 | chanData = 0; 3072 | if ((value & 0x01) != 0) 3073 | { 3074 | chanData |= SquareWaveGenerator.CHAN_LEFT; 3075 | } 3076 | if ((value & 0x10) != 0) 3077 | { 3078 | chanData |= SquareWaveGenerator.CHAN_RIGHT; 3079 | } 3080 | SoundChip.channel1.SetChannel(chanData); 3081 | 3082 | chanData = 0; 3083 | if ((value & 0x02) != 0) 3084 | { 3085 | chanData |= SquareWaveGenerator.CHAN_LEFT; 3086 | } 3087 | if ((value & 0x20) != 0) 3088 | { 3089 | chanData |= SquareWaveGenerator.CHAN_RIGHT; 3090 | } 3091 | SoundChip.channel2.SetChannel(chanData); 3092 | 3093 | chanData = 0; 3094 | if ((value & 0x04) != 0) 3095 | { 3096 | chanData |= VoluntaryWaveGenerator.CHAN_LEFT; 3097 | } 3098 | if ((value & 0x40) != 0) 3099 | { 3100 | chanData |= VoluntaryWaveGenerator.CHAN_RIGHT; 3101 | } 3102 | SoundChip.channel3.SetChannel(chanData); 3103 | 3104 | chanData = 0; 3105 | if ((value & 0x08) != 0) 3106 | { 3107 | chanData |= NoiseGenerator.CHAN_LEFT; 3108 | } 3109 | if ((value & 0x80) != 0) 3110 | { 3111 | chanData |= NoiseGenerator.CHAN_RIGHT; 3112 | } 3113 | SoundChip.channel4.SetChannel(chanData); 3114 | 3115 | break; 3116 | case 0xFF26: 3117 | SoundChip.soundEnabled = (value & 0x80) == 1; 3118 | break; 3119 | case 0xFF30: 3120 | case 0xFF31: 3121 | case 0xFF32: 3122 | case 0xFF33: 3123 | case 0xFF34: 3124 | case 0xFF35: 3125 | case 0xFF36: 3126 | case 0xFF37: 3127 | case 0xFF38: 3128 | case 0xFF39: 3129 | case 0xFF3A: 3130 | case 0xFF3B: 3131 | case 0xFF3C: 3132 | case 0xFF3D: 3133 | case 0xFF3E: 3134 | case 0xFF3F: 3135 | SoundChip.channel3.SetSamplePair(address - 0xFF30, value); 3136 | soundRegisters [address - 0xFF10] = value; 3137 | break; 3138 | case 0xFF40: 3139 | { // LCDC control 3140 | bool _backgroundAndWindowTileDataSelect = backgroundAndWindowTileDataSelect; 3141 | bool _backgroundTileMapDisplaySelect = backgroundTileMapDisplaySelect; 3142 | bool _windowTileMapDisplaySelect = windowTileMapDisplaySelect; 3143 | 3144 | lcdControlOperationEnabled = (value & 0x80) == 0x80; 3145 | windowTileMapDisplaySelect = (value & 0x40) == 0x40; 3146 | windowDisplayed = (value & 0x20) == 0x20; 3147 | backgroundAndWindowTileDataSelect = (value & 0x10) == 0x10; 3148 | backgroundTileMapDisplaySelect = (value & 0x08) == 0x08; 3149 | largeSprites = (value & 0x04) == 0x04; 3150 | spritesDisplayed = (value & 0x02) == 0x02; 3151 | backgroundDisplayed = (value & 0x01) == 0x01; 3152 | 3153 | if (_backgroundAndWindowTileDataSelect != backgroundAndWindowTileDataSelect 3154 | || _backgroundTileMapDisplaySelect != backgroundTileMapDisplaySelect 3155 | || _windowTileMapDisplaySelect != windowTileMapDisplaySelect) 3156 | { 3157 | invalidateAllBackgroundTilesRequest = true; 3158 | } 3159 | 3160 | break; 3161 | } 3162 | case 0xFF41: // LCDC Status 3163 | lcdcLycLyCoincidenceInterruptEnabled = (value & 0x40) == 0x40; 3164 | lcdcOamInterruptEnabled = (value & 0x20) == 0x20; 3165 | lcdcVBlankInterruptEnabled = (value & 0x10) == 0x10; 3166 | lcdcHBlankInterruptEnabled = (value & 0x08) == 0x08; 3167 | lcdcMode = (LcdcModeType)(value & 0x03); 3168 | break; 3169 | case 0xFF42: // Scroll Y; 3170 | scrollY = value; 3171 | break; 3172 | case 0xFF43: // Scroll X; 3173 | scrollX = value; 3174 | break; 3175 | case 0xFF44: // LY 3176 | ly = value; 3177 | break; 3178 | case 0xFF45: // LY Compare 3179 | lyCompare = value; 3180 | break; 3181 | case 0xFF46: // Memory Transfer 3182 | value <<= 8; 3183 | for (int i = 0; i < 0x8C; i++) 3184 | { 3185 | WriteByte(0xFE00 | i, ReadByte(value | i)); 3186 | } 3187 | break; 3188 | case 0xFF47: // Background palette 3189 | Console.WriteLine("[0xFF47] = {0:X}", value); 3190 | for (int i = 0; i < 4; i++) 3191 | { 3192 | switch (value & 0x03) 3193 | { 3194 | case 0: 3195 | backgroundPalette [i] = WHITE; 3196 | break; 3197 | case 1: 3198 | backgroundPalette [i] = LIGHT_GRAY; 3199 | break; 3200 | case 2: 3201 | backgroundPalette [i] = DARK_GRAY; 3202 | break; 3203 | case 3: 3204 | backgroundPalette [i] = BLACK; 3205 | break; 3206 | } 3207 | value >>= 2; 3208 | } 3209 | invalidateAllBackgroundTilesRequest = true; 3210 | break; 3211 | case 0xFF48: // Object palette 0 3212 | for (int i = 0; i < 4; i++) 3213 | { 3214 | switch (value & 0x03) 3215 | { 3216 | case 0: 3217 | objectPalette0 [i] = WHITE; 3218 | break; 3219 | case 1: 3220 | objectPalette0 [i] = LIGHT_GRAY; 3221 | break; 3222 | case 2: 3223 | objectPalette0 [i] = DARK_GRAY; 3224 | break; 3225 | case 3: 3226 | objectPalette0 [i] = BLACK; 3227 | break; 3228 | } 3229 | value >>= 2; 3230 | } 3231 | invalidateAllSpriteTilesRequest = true; 3232 | break; 3233 | case 0xFF49: // Object palette 1 3234 | for (int i = 0; i < 4; i++) 3235 | { 3236 | switch (value & 0x03) 3237 | { 3238 | case 0: 3239 | objectPalette1 [i] = WHITE; 3240 | break; 3241 | case 1: 3242 | objectPalette1 [i] = LIGHT_GRAY; 3243 | break; 3244 | case 2: 3245 | objectPalette1 [i] = DARK_GRAY; 3246 | break; 3247 | case 3: 3248 | objectPalette1 [i] = BLACK; 3249 | break; 3250 | } 3251 | value >>= 2; 3252 | } 3253 | invalidateAllSpriteTilesRequest = true; 3254 | break; 3255 | case 0xFF4A: // Window Y 3256 | windowY = value; 3257 | break; 3258 | case 0xFF4B: // Window X 3259 | windowX = value; 3260 | break; 3261 | case 0xFFFF: // Interrupt Enable 3262 | keyPressedInterruptEnabled = (value & 0x10) == 0x10; 3263 | serialIOTransferCompleteInterruptEnabled = (value & 0x08) == 0x08; 3264 | timerOverflowInterruptEnabled = (value & 0x04) == 0x04; 3265 | lcdcInterruptEnabled = (value & 0x02) == 0x02; 3266 | vBlankInterruptEnabled = (value & 0x01) == 0x01; 3267 | break; 3268 | } 3269 | } 3270 | } 3271 | 3272 | public int ReadByte(int address) 3273 | { 3274 | if (address <= 0x7FFF || (address >= 0xA000 && address <= 0xBFFF)) 3275 | { 3276 | return cartridge.ReadByte(address); 3277 | } else if (address >= 0x8000 && address <= 0x9FFF) 3278 | { 3279 | return videoRam [address - 0x8000]; 3280 | } else if (address >= 0xC000 && address <= 0xDFFF) 3281 | { 3282 | return workRam [address - 0xC000]; 3283 | } else if (address >= 0xE000 && address <= 0xFDFF) 3284 | { 3285 | return workRam [address - 0xE000]; 3286 | } else if (address >= 0xFE00 && address <= 0xFEFF) 3287 | { 3288 | return oam [address - 0xFE00]; 3289 | } else if (address >= 0xFF80 && address <= 0xFFFE) 3290 | { 3291 | return highRam [0xFF & address]; 3292 | } else if (address >= 0xFF10 && address < 0xFF3F) // Sound 3293 | { 3294 | return soundRegisters [address - 0xFF10]; 3295 | } else 3296 | { 3297 | switch (address) 3298 | { 3299 | case 0xFF00: // key pad 3300 | if (keyP14) 3301 | { 3302 | int value = 0; 3303 | if (!downKeyPressed) 3304 | { 3305 | value |= 0x08; 3306 | } 3307 | if (!upKeyPressed) 3308 | { 3309 | value |= 0x04; 3310 | } 3311 | if (!leftKeyPressed) 3312 | { 3313 | value |= 0x02; 3314 | } 3315 | if (!rightKeyPressed) 3316 | { 3317 | value |= 0x01; 3318 | } 3319 | return value; 3320 | } else if (keyP15) 3321 | { 3322 | int value = 0; 3323 | if (!startButtonPressed) 3324 | { 3325 | value |= 0x08; 3326 | } 3327 | if (!selectButtonPressed) 3328 | { 3329 | value |= 0x04; 3330 | } 3331 | if (!bButtonPressed) 3332 | { 3333 | value |= 0x02; 3334 | } 3335 | if (!aButtonPressed) 3336 | { 3337 | value |= 0x01; 3338 | } 3339 | return value; 3340 | } 3341 | break; 3342 | case 0xFF04: // Timer divider 3343 | return ticks & 0xFF; 3344 | case 0xFF05: // Timer counter 3345 | return timerCounter & 0xFF; 3346 | case 0xFF06: // Timer modulo 3347 | return timerModulo & 0xFF; 3348 | case 0xFF07: 3349 | { // Time Control 3350 | int value = 0; 3351 | if (timerRunning) 3352 | { 3353 | value |= 0x04; 3354 | } 3355 | value |= (int)timerFrequency; 3356 | return value; 3357 | } 3358 | case 0xFF0F: 3359 | { // Interrupt Flag (an interrupt request) 3360 | int value = 0; 3361 | if (keyPressedInterruptRequested) 3362 | { 3363 | value |= 0x10; 3364 | } 3365 | if (serialIOTransferCompleteInterruptRequested) 3366 | { 3367 | value |= 0x08; 3368 | } 3369 | if (timerOverflowInterruptRequested) 3370 | { 3371 | value |= 0x04; 3372 | } 3373 | if (lcdcInterruptRequested) 3374 | { 3375 | value |= 0x02; 3376 | } 3377 | if (vBlankInterruptRequested) 3378 | { 3379 | value |= 0x01; 3380 | } 3381 | return value; 3382 | } 3383 | case 0xFF40: 3384 | { // LCDC control 3385 | int value = 0; 3386 | if (lcdControlOperationEnabled) 3387 | { 3388 | value |= 0x80; 3389 | } 3390 | if (windowTileMapDisplaySelect) 3391 | { 3392 | value |= 0x40; 3393 | } 3394 | if (windowDisplayed) 3395 | { 3396 | value |= 0x20; 3397 | } 3398 | if (backgroundAndWindowTileDataSelect) 3399 | { 3400 | value |= 0x10; 3401 | } 3402 | if (backgroundTileMapDisplaySelect) 3403 | { 3404 | value |= 0x08; 3405 | } 3406 | if (largeSprites) 3407 | { 3408 | value |= 0x04; 3409 | } 3410 | if (spritesDisplayed) 3411 | { 3412 | value |= 0x02; 3413 | } 3414 | if (backgroundDisplayed) 3415 | { 3416 | value |= 0x01; 3417 | } 3418 | return value; 3419 | } 3420 | case 0xFF41: 3421 | {// LCDC Status 3422 | int value = 0; 3423 | if (lcdcLycLyCoincidenceInterruptEnabled) 3424 | { 3425 | value |= 0x40; 3426 | } 3427 | if (lcdcOamInterruptEnabled) 3428 | { 3429 | value |= 0x20; 3430 | } 3431 | if (lcdcVBlankInterruptEnabled) 3432 | { 3433 | value |= 0x10; 3434 | } 3435 | if (lcdcHBlankInterruptEnabled) 3436 | { 3437 | value |= 0x08; 3438 | } 3439 | if (ly == lyCompare) 3440 | { 3441 | value |= 0x04; 3442 | } 3443 | value |= (int)lcdcMode; 3444 | return value; 3445 | } 3446 | case 0xFF42: // Scroll Y 3447 | return scrollY; 3448 | case 0xFF43: // Scroll X 3449 | return scrollX; 3450 | case 0xFF44: // LY 3451 | return ly; 3452 | case 0xFF45: // LY Compare 3453 | return lyCompare; 3454 | case 0xFF47: 3455 | { // Background palette 3456 | invalidateAllBackgroundTilesRequest = true; 3457 | int value = 0; 3458 | for (int i = 3; i >= 0; i--) 3459 | { 3460 | value <<= 2; 3461 | switch (backgroundPalette [i]) 3462 | { 3463 | case BLACK: 3464 | value |= 3; 3465 | break; 3466 | case DARK_GRAY: 3467 | value |= 2; 3468 | break; 3469 | case LIGHT_GRAY: 3470 | value |= 1; 3471 | break; 3472 | case WHITE: 3473 | break; 3474 | } 3475 | } 3476 | return value; 3477 | } 3478 | case 0xFF48: 3479 | { // Object palette 0 3480 | invalidateAllSpriteTilesRequest = true; 3481 | int value = 0; 3482 | for (int i = 3; i >= 0; i--) 3483 | { 3484 | value <<= 2; 3485 | switch (objectPalette0 [i]) 3486 | { 3487 | case BLACK: 3488 | value |= 3; 3489 | break; 3490 | case DARK_GRAY: 3491 | value |= 2; 3492 | break; 3493 | case LIGHT_GRAY: 3494 | value |= 1; 3495 | break; 3496 | case WHITE: 3497 | break; 3498 | } 3499 | } 3500 | return value; 3501 | } 3502 | case 0xFF49: 3503 | { // Object palette 1 3504 | invalidateAllSpriteTilesRequest = true; 3505 | int value = 0; 3506 | for (int i = 3; i >= 0; i--) 3507 | { 3508 | value <<= 2; 3509 | switch (objectPalette1 [i]) 3510 | { 3511 | case BLACK: 3512 | value |= 3; 3513 | break; 3514 | case DARK_GRAY: 3515 | value |= 2; 3516 | break; 3517 | case LIGHT_GRAY: 3518 | value |= 1; 3519 | break; 3520 | case WHITE: 3521 | break; 3522 | } 3523 | } 3524 | return value; 3525 | } 3526 | case 0xFF4A: // Window Y 3527 | return windowY; 3528 | case 0xFF4B: // Window X 3529 | return windowX; 3530 | case 0xFFFF: 3531 | { // Interrupt Enable 3532 | int value = 0; 3533 | if (keyPressedInterruptEnabled) 3534 | { 3535 | value |= 0x10; 3536 | } 3537 | if (serialIOTransferCompleteInterruptEnabled) 3538 | { 3539 | value |= 0x08; 3540 | } 3541 | if (timerOverflowInterruptEnabled) 3542 | { 3543 | value |= 0x04; 3544 | } 3545 | if (lcdcInterruptEnabled) 3546 | { 3547 | value |= 0x02; 3548 | } 3549 | if (vBlankInterruptEnabled) 3550 | { 3551 | value |= 0x01; 3552 | } 3553 | return value; 3554 | } 3555 | } 3556 | } 3557 | return 0; 3558 | } 3559 | 3560 | public void KeyChanged(char keyCode, bool pressed) 3561 | { 3562 | switch (keyCode) 3563 | { 3564 | case 'b': 3565 | bButtonPressed = pressed; 3566 | break; 3567 | case 'a': 3568 | aButtonPressed = pressed; 3569 | break; 3570 | case 's': 3571 | startButtonPressed = pressed; 3572 | break; 3573 | case 'c': 3574 | selectButtonPressed = pressed; 3575 | break; 3576 | case 'u': 3577 | upKeyPressed = pressed; 3578 | break; 3579 | case 'd': 3580 | downKeyPressed = pressed; 3581 | break; 3582 | case 'l': 3583 | leftKeyPressed = pressed; 3584 | break; 3585 | case 'r': 3586 | rightKeyPressed = pressed; 3587 | break; 3588 | } 3589 | 3590 | if (keyPressedInterruptEnabled) 3591 | { 3592 | keyPressedInterruptRequested = true; 3593 | } 3594 | } 3595 | 3596 | public override string ToString() 3597 | { 3598 | return String.Format( 3599 | "PC={8:X} A={0:X} B={1:X} C={2:X} D={3:X} E={4:X} H={5:X} L={6:X} halted={7} SP={9:X} FZ={10} FH={11} FC={12} FN={13} IV={14} IL={15} IK={16} IT={17} INT={18} scrollX={19} scrollY={20} ly={21} lyCompare={22} LHIE={23} LYIE={24} LOIE={25}", 3600 | A, B, C, D, E, H, L, halted, PC, SP, FZ, FH, FC, FN, vBlankInterruptEnabled, lcdcInterruptEnabled, keyPressedInterruptEnabled, 3601 | timerOverflowInterruptEnabled, interruptsEnabled, scrollX, scrollY, ly, lyCompare, 3602 | lcdcHBlankInterruptEnabled, lcdcLycLyCoincidenceInterruptEnabled, lcdcOamInterruptEnabled); 3603 | } 3604 | 3605 | public void CheckForBadState() 3606 | { 3607 | if (A > 0xFF || A < 0 || B > 0xFF || B < 0 || C > 0xFF || C < 0 || D > 0xFF || D < 0 3608 | || E > 0xFF || E < 0 || H > 0xFF || H < 0 || SP > 0xFFFF || SP < 0 || PC > 0xFFFF || PC < 0) 3609 | { 3610 | throw new Exception(ToString()); 3611 | } 3612 | } 3613 | 3614 | public void UpdateSpriteTiles() 3615 | { 3616 | 3617 | for (int i = 0; i < 256; i++) 3618 | { 3619 | if (spriteTileInvalidated [i] || invalidateAllSpriteTilesRequest) 3620 | { 3621 | spriteTileInvalidated [i] = false; 3622 | int address = i << 4; 3623 | for (int y = 0; y < 8; y++) 3624 | { 3625 | int lowByte = videoRam [address++]; 3626 | int highByte = videoRam [address++] << 1; 3627 | for (int x = 7; x >= 0; x--) 3628 | { 3629 | int paletteIndex = (0x02 & highByte) | (0x01 & lowByte); 3630 | lowByte >>= 1; 3631 | highByte >>= 1; 3632 | if (paletteIndex > 0) 3633 | { 3634 | spriteTile [i, y, x, 0] = objectPalette0 [paletteIndex]; 3635 | spriteTile [i, y, x, 1] = objectPalette1 [paletteIndex]; 3636 | } else 3637 | { 3638 | spriteTile [i, y, x, 0] = 0; 3639 | spriteTile [i, y, x, 1] = 0; 3640 | } 3641 | } 3642 | } 3643 | } 3644 | } 3645 | 3646 | invalidateAllSpriteTilesRequest = false; 3647 | } 3648 | 3649 | public void UpdateWindow() 3650 | { 3651 | 3652 | int tileMapAddress = windowTileMapDisplaySelect ? 0x1C00 : 0x1800; 3653 | 3654 | if (backgroundAndWindowTileDataSelect) 3655 | { 3656 | for (int i = 0; i < 18; i++) 3657 | { 3658 | for (int j = 0; j < 21; j++) 3659 | { 3660 | if (backgroundTileInvalidated [i, j] || invalidateAllBackgroundTilesRequest) 3661 | { 3662 | int tileDataAddress = videoRam [tileMapAddress + ((i << 5) | j)] << 4; 3663 | int y = i << 3; 3664 | int x = j << 3; 3665 | for (int k = 0; k < 8; k++) 3666 | { 3667 | int lowByte = videoRam [tileDataAddress++]; 3668 | int highByte = videoRam [tileDataAddress++] << 1; 3669 | for (int b = 7; b >= 0; b--) 3670 | { 3671 | windowBuffer [y + k, x + b] = backgroundPalette [(0x02 & highByte) | (0x01 & lowByte)]; 3672 | lowByte >>= 1; 3673 | highByte >>= 1; 3674 | } 3675 | } 3676 | } 3677 | } 3678 | } 3679 | } else 3680 | { 3681 | for (int i = 0; i < 18; i++) 3682 | { 3683 | for (int j = 0; j < 21; j++) 3684 | { 3685 | if (backgroundTileInvalidated [i, j] || invalidateAllBackgroundTilesRequest) 3686 | { 3687 | int tileDataAddress = videoRam [tileMapAddress + ((i << 5) | j)]; 3688 | if (tileDataAddress > 127) 3689 | { 3690 | tileDataAddress -= 256; 3691 | } 3692 | tileDataAddress = 0x1000 + (tileDataAddress << 4); 3693 | int y = i << 3; 3694 | int x = j << 3; 3695 | for (int k = 0; k < 8; k++) 3696 | { 3697 | int lowByte = videoRam [tileDataAddress++]; 3698 | int highByte = videoRam [tileDataAddress++] << 1; 3699 | for (int b = 7; b >= 0; b--) 3700 | { 3701 | windowBuffer [y + k, x + b] = backgroundPalette [(0x02 & highByte) | (0x01 & lowByte)]; 3702 | lowByte >>= 1; 3703 | highByte >>= 1; 3704 | } 3705 | } 3706 | } 3707 | } 3708 | } 3709 | } 3710 | } 3711 | 3712 | public void UpdateBackground() 3713 | { 3714 | 3715 | int tileMapAddress = backgroundTileMapDisplaySelect ? 0x1C00 : 0x1800; 3716 | 3717 | if (backgroundAndWindowTileDataSelect) 3718 | { 3719 | for (int i = 0; i < 32; i++) 3720 | { 3721 | for (int j = 0; j < 32; j++, tileMapAddress++) 3722 | { 3723 | if (backgroundTileInvalidated [i, j] || invalidateAllBackgroundTilesRequest) 3724 | { 3725 | backgroundTileInvalidated [i, j] = false; 3726 | int tileDataAddress = videoRam [tileMapAddress] << 4; 3727 | int y = i << 3; 3728 | int x = j << 3; 3729 | for (int k = 0; k < 8; k++) 3730 | { 3731 | int lowByte = videoRam [tileDataAddress++]; 3732 | int highByte = videoRam [tileDataAddress++] << 1; 3733 | for (int b = 7; b >= 0; b--) 3734 | { 3735 | backgroundBuffer [y + k, x + b] = backgroundPalette [(0x02 & highByte) | (0x01 & lowByte)]; 3736 | lowByte >>= 1; 3737 | highByte >>= 1; 3738 | } 3739 | } 3740 | } 3741 | } 3742 | } 3743 | } else 3744 | { 3745 | for (int i = 0; i < 32; i++) 3746 | { 3747 | for (int j = 0; j < 32; j++, tileMapAddress++) 3748 | { 3749 | if (backgroundTileInvalidated [i, j] || invalidateAllBackgroundTilesRequest) 3750 | { 3751 | backgroundTileInvalidated [i, j] = false; 3752 | int tileDataAddress = videoRam [tileMapAddress]; 3753 | if (tileDataAddress > 127) 3754 | { 3755 | tileDataAddress -= 256; 3756 | } 3757 | tileDataAddress = 0x1000 + (tileDataAddress << 4); 3758 | int y = i << 3; 3759 | int x = j << 3; 3760 | for (int k = 0; k < 8; k++) 3761 | { 3762 | int lowByte = videoRam [tileDataAddress++]; 3763 | int highByte = videoRam [tileDataAddress++] << 1; 3764 | for (int b = 7; b >= 0; b--) 3765 | { 3766 | backgroundBuffer [y + k, x + b] = backgroundPalette [(0x02 & highByte) | (0x01 & lowByte)]; 3767 | lowByte >>= 1; 3768 | highByte >>= 1; 3769 | } 3770 | } 3771 | } 3772 | } 3773 | } 3774 | } 3775 | 3776 | invalidateAllBackgroundTilesRequest = false; 3777 | } 3778 | } 3779 | } 3780 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/CPU.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: dc55e065a2ccbec41b35e0ed8eeb1544 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Emulator.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0414 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.ComponentModel; 6 | using System.Text; 7 | using System.Diagnostics; 8 | using System.Runtime.InteropServices; 9 | using System.Threading; 10 | 11 | namespace UnityGB 12 | { 13 | public class Emulator : EmulatorBase 14 | { 15 | const float FRAMES_PER_SECOND = 59.7f; 16 | const int WIDTH = 160; 17 | const int HEIGHT = 144; 18 | int MAX_FRAMES_SKIPPED = 10; 19 | public long FREQUENCY = Stopwatch.Frequency; 20 | public long TICKS_PER_FRAME = (long)(Stopwatch.Frequency / FRAMES_PER_SECOND); 21 | private Stopwatch stopwatch = new Stopwatch(); 22 | private X80 x80; 23 | private double scanLineTicks; 24 | private uint[] pixels = new uint[WIDTH * HEIGHT]; 25 | private Game game; 26 | 27 | public Emulator(IVideoOutput video, IAudioOutput audio = null, ISaveMemory saveMemory = null) : base(video, audio, saveMemory) 28 | { 29 | for (int i = 0; i < pixels.Length; i++) 30 | { 31 | pixels [i] = 0xFF000000; 32 | } 33 | 34 | Video.SetSize(WIDTH, HEIGHT); 35 | } 36 | 37 | public override void RunNextStep() 38 | { 39 | if (stopwatch.ElapsedTicks > TICKS_PER_FRAME) 40 | { 41 | UpdateModel(true); 42 | Video.SetPixels(pixels); 43 | stopwatch.Reset(); 44 | stopwatch.Start(); 45 | } else 46 | { 47 | UpdateModel(false); 48 | } 49 | } 50 | 51 | public override void LoadRom(byte[] fileData) 52 | { 53 | game = Game.Load(fileData, SaveMemory); 54 | x80 = new X80(this); 55 | if (Audio != null) 56 | x80.SoundChip.SetSampleRate(Audio.GetOutputSampleRate()); 57 | x80.cartridge = game.cartridge; 58 | x80.PowerUp(); 59 | 60 | stopwatch.Reset(); 61 | stopwatch.Start(); 62 | } 63 | 64 | public override void SetInput(Button button, bool pressed) 65 | { 66 | char keyCode = ' '; 67 | switch (button) 68 | { 69 | case Button.Up: 70 | keyCode = 'u'; 71 | break; 72 | case Button.Down: 73 | keyCode = 'd'; 74 | break; 75 | case Button.Left: 76 | keyCode = 'l'; 77 | break; 78 | case Button.Right: 79 | keyCode = 'r'; 80 | break; 81 | case Button.A: 82 | keyCode = 'a'; 83 | break; 84 | case Button.B: 85 | keyCode = 'b'; 86 | break; 87 | case Button.Start: 88 | keyCode = 's'; 89 | break; 90 | case Button.Select: 91 | keyCode = 'c'; 92 | break; 93 | } 94 | 95 | x80.KeyChanged(keyCode, pressed); 96 | } 97 | 98 | private void UpdateModel(bool updateBitmap) 99 | { 100 | if (updateBitmap) 101 | { 102 | uint[,] backgroundBuffer = x80.backgroundBuffer; 103 | uint[,] windowBuffer = x80.windowBuffer; 104 | byte[] oam = x80.oam; 105 | 106 | for (int y = 0, pixelIndex = 0; y < HEIGHT; ++y) 107 | { 108 | x80.ly = y; 109 | x80.lcdcMode = LcdcModeType.SearchingOamRam; 110 | if (x80.lcdcInterruptEnabled 111 | && (x80.lcdcOamInterruptEnabled 112 | || (x80.lcdcLycLyCoincidenceInterruptEnabled && x80.lyCompare == y))) 113 | { 114 | x80.lcdcInterruptRequested = true; 115 | } 116 | ExecuteProcessor(800); 117 | x80.lcdcMode = LcdcModeType.TransferingData; 118 | ExecuteProcessor(1720); 119 | 120 | x80.UpdateWindow(); 121 | x80.UpdateBackground(); 122 | x80.UpdateSpriteTiles(); 123 | 124 | bool backgroundDisplayed = x80.backgroundDisplayed; 125 | int scrollX = x80.scrollX; 126 | int scrollY = x80.scrollY; 127 | bool windowDisplayed = x80.windowDisplayed; 128 | int windowX = x80.windowX - 7; 129 | int windowY = x80.windowY; 130 | 131 | for (int x = 0; x < WIDTH; ++x, ++pixelIndex) 132 | { 133 | uint intensity = 0; 134 | 135 | if (backgroundDisplayed) 136 | { 137 | intensity = backgroundBuffer [0xFF & (scrollY + y), 0xFF & (scrollX + x)]; 138 | } 139 | 140 | if (windowDisplayed && y >= windowY && y < windowY + HEIGHT && x >= windowX && x < windowX + WIDTH 141 | && windowX >= -7 && windowX < WIDTH && windowY >= 0 && windowY < HEIGHT) 142 | { 143 | intensity = windowBuffer [y - windowY, x - windowX]; 144 | } 145 | 146 | pixels [pixelIndex] = intensity; 147 | } 148 | 149 | if (x80.spritesDisplayed) 150 | { 151 | uint[, , ,] spriteTile = x80.spriteTile; 152 | if (x80.largeSprites) 153 | { 154 | for (int address = 0; address < WIDTH; address += 4) 155 | { 156 | int spriteY = oam [address]; 157 | int spriteX = oam [address + 1]; 158 | if (spriteY == 0 || spriteX == 0 || spriteY >= 160 || spriteX >= 168) 159 | { 160 | continue; 161 | } 162 | spriteY -= 16; 163 | if (spriteY > y || spriteY + 15 < y) 164 | { 165 | continue; 166 | } 167 | spriteX -= 8; 168 | 169 | int spriteTileIndex0 = 0xFE & oam [address + 2]; 170 | int spriteTileIndex1 = spriteTileIndex0 | 0x01; 171 | int spriteFlags = oam [address + 3]; 172 | bool spritePriority = (0x80 & spriteFlags) == 0x80; 173 | bool spriteYFlipped = (0x40 & spriteFlags) == 0x40; 174 | bool spriteXFlipped = (0x20 & spriteFlags) == 0x20; 175 | int spritePalette = (0x10 & spriteFlags) == 0x10 ? 1 : 0; 176 | 177 | if (spriteYFlipped) 178 | { 179 | int temp = spriteTileIndex0; 180 | spriteTileIndex0 = spriteTileIndex1; 181 | spriteTileIndex1 = temp; 182 | } 183 | 184 | int spriteRow = y - spriteY; 185 | if (spriteRow >= 0 && spriteRow < 8) 186 | { 187 | int screenAddress = (y << 7) + (y << 5) + spriteX; 188 | for (int x = 0; x < 8; ++x, ++screenAddress) 189 | { 190 | int screenX = spriteX + x; 191 | if (screenX >= 0 && screenX < WIDTH) 192 | { 193 | uint color = spriteTile [spriteTileIndex0, 194 | spriteYFlipped ? 7 - spriteRow : spriteRow, 195 | spriteXFlipped ? 7 - x : x, spritePalette]; 196 | if (color > 0) 197 | { 198 | if (spritePriority) 199 | { 200 | if (pixels [screenAddress] == 0xFFFFFFFF) 201 | { 202 | pixels [screenAddress] = color; 203 | } 204 | } else 205 | { 206 | pixels [screenAddress] = color; 207 | } 208 | } 209 | } 210 | } 211 | continue; 212 | } 213 | 214 | spriteY += 8; 215 | 216 | spriteRow = y - spriteY; 217 | if (spriteRow >= 0 && spriteRow < 8) 218 | { 219 | int screenAddress = (y << 7) + (y << 5) + spriteX; 220 | for (int x = 0; x < 8; ++x, ++screenAddress) 221 | { 222 | int screenX = spriteX + x; 223 | if (screenX >= 0 && screenX < WIDTH) 224 | { 225 | uint color = spriteTile [spriteTileIndex1, 226 | spriteYFlipped ? 7 - spriteRow : spriteRow, 227 | spriteXFlipped ? 7 - x : x, spritePalette]; 228 | if (color > 0) 229 | { 230 | if (spritePriority) 231 | { 232 | if (pixels [screenAddress] == 0xFFFFFFFF) 233 | { 234 | pixels [screenAddress] = color; 235 | } 236 | } else 237 | { 238 | pixels [screenAddress] = color; 239 | } 240 | } 241 | } 242 | } 243 | } 244 | } 245 | } else 246 | { 247 | for (int address = 0; address < WIDTH; address += 4) 248 | { 249 | int spriteY = oam [address]; 250 | int spriteX = oam [address + 1]; 251 | if (spriteY == 0 || spriteX == 0 || spriteY >= 160 || spriteX >= 168) 252 | { 253 | continue; 254 | } 255 | spriteY -= 16; 256 | if (spriteY > y || spriteY + 7 < y) 257 | { 258 | continue; 259 | } 260 | spriteX -= 8; 261 | 262 | int spriteTileIndex = oam [address + 2]; 263 | int spriteFlags = oam [address + 3]; 264 | bool spritePriority = (0x80 & spriteFlags) == 0x80; 265 | bool spriteYFlipped = (0x40 & spriteFlags) == 0x40; 266 | bool spriteXFlipped = (0x20 & spriteFlags) == 0x20; 267 | int spritePalette = (0x10 & spriteFlags) == 0x10 ? 1 : 0; 268 | 269 | int spriteRow = y - spriteY; 270 | int screenAddress = (y << 7) + (y << 5) + spriteX; 271 | for (int x = 0; x < 8; ++x, ++screenAddress) 272 | { 273 | int screenX = spriteX + x; 274 | if (screenX >= 0 && screenX < WIDTH) 275 | { 276 | uint color = spriteTile [spriteTileIndex, 277 | spriteYFlipped ? 7 - spriteRow : spriteRow, 278 | spriteXFlipped ? 7 - x : x, spritePalette]; 279 | if (color > 0) 280 | { 281 | if (spritePriority) 282 | { 283 | if (pixels [screenAddress] == 0xFFFFFFFF) 284 | { 285 | pixels [screenAddress] = color; 286 | } 287 | } else 288 | { 289 | pixels [screenAddress] = color; 290 | } 291 | } 292 | } 293 | } 294 | } 295 | } 296 | } 297 | 298 | x80.lcdcMode = LcdcModeType.HBlank; 299 | if (x80.lcdcInterruptEnabled && x80.lcdcHBlankInterruptEnabled) 300 | { 301 | x80.lcdcInterruptRequested = true; 302 | } 303 | ExecuteProcessor(2040); 304 | AddTicksPerScanLine(); 305 | } 306 | } else 307 | { 308 | for (int y = 0; y < HEIGHT; ++y) 309 | { 310 | x80.ly = y; 311 | x80.lcdcMode = LcdcModeType.SearchingOamRam; 312 | if (x80.lcdcInterruptEnabled 313 | && (x80.lcdcOamInterruptEnabled 314 | || (x80.lcdcLycLyCoincidenceInterruptEnabled && x80.lyCompare == y))) 315 | { 316 | x80.lcdcInterruptRequested = true; 317 | } 318 | ExecuteProcessor(800); 319 | x80.lcdcMode = LcdcModeType.TransferingData; 320 | ExecuteProcessor(1720); 321 | x80.lcdcMode = LcdcModeType.HBlank; 322 | if (x80.lcdcInterruptEnabled && x80.lcdcHBlankInterruptEnabled) 323 | { 324 | x80.lcdcInterruptRequested = true; 325 | } 326 | ExecuteProcessor(2040); 327 | AddTicksPerScanLine(); 328 | } 329 | } 330 | 331 | x80.lcdcMode = LcdcModeType.VBlank; 332 | if (x80.vBlankInterruptEnabled) 333 | { 334 | x80.vBlankInterruptRequested = true; 335 | } 336 | if (x80.lcdcInterruptEnabled && x80.lcdcVBlankInterruptEnabled) 337 | { 338 | x80.lcdcInterruptRequested = true; 339 | } 340 | for (int y = 144; y <= 153; ++y) 341 | { 342 | x80.ly = y; 343 | if (x80.lcdcInterruptEnabled && x80.lcdcLycLyCoincidenceInterruptEnabled 344 | && x80.lyCompare == y) 345 | { 346 | x80.lcdcInterruptRequested = true; 347 | } 348 | ExecuteProcessor(4560); 349 | AddTicksPerScanLine(); 350 | } 351 | 352 | if (Audio != null) 353 | x80.SoundChip.OutputSound(Audio); 354 | } 355 | 356 | private void AddTicksPerScanLine() 357 | { 358 | switch (x80.timerFrequency) 359 | { 360 | case TimerFrequencyType.hz4096: 361 | scanLineTicks += 0.44329004329004329004329004329004; 362 | break; 363 | case TimerFrequencyType.hz16384: 364 | scanLineTicks += 1.7731601731601731601731601731602; 365 | break; 366 | case TimerFrequencyType.hz65536: 367 | scanLineTicks += 7.0926406926406926406926406926407; 368 | break; 369 | case TimerFrequencyType.hz262144: 370 | scanLineTicks += 28.370562770562770562770562770563; 371 | break; 372 | } 373 | while (scanLineTicks >= 1.0) 374 | { 375 | scanLineTicks -= 1.0; 376 | if (x80.timerCounter == 0xFF) 377 | { 378 | x80.timerCounter = x80.timerModulo; 379 | if (x80.lcdcInterruptEnabled && x80.timerOverflowInterruptEnabled) 380 | { 381 | x80.timerOverflowInterruptRequested = true; 382 | } 383 | } else 384 | { 385 | x80.timerCounter++; 386 | } 387 | } 388 | } 389 | 390 | private void ExecuteProcessor(int maxTicks) 391 | { 392 | do 393 | { 394 | x80.Step(); 395 | if (x80.halted) 396 | { 397 | x80.ticks = ((maxTicks - x80.ticks) & 0x03); 398 | return; 399 | } 400 | } while (x80.ticks < maxTicks); 401 | x80.ticks -= maxTicks; 402 | } 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/Emulator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 05606e56e3652c44cb481e0ee9d39ae7 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/EmulatorBase.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | 4 | namespace UnityGB 5 | { 6 | public abstract class EmulatorBase 7 | { 8 | public enum Button 9 | { 10 | Up, 11 | Down, 12 | Left, 13 | Right, 14 | A, 15 | B, 16 | Start, 17 | Select} 18 | ; 19 | 20 | public IVideoOutput Video 21 | { 22 | get; 23 | private set; 24 | } 25 | 26 | public IAudioOutput Audio 27 | { 28 | get; 29 | private set; 30 | } 31 | 32 | public ISaveMemory SaveMemory 33 | { 34 | get; 35 | private set; 36 | } 37 | 38 | public EmulatorBase(IVideoOutput video, IAudioOutput audio = null, ISaveMemory saveMemory = null) 39 | { 40 | Video = video; 41 | Audio = audio; 42 | SaveMemory = saveMemory; 43 | } 44 | 45 | public abstract void LoadRom(byte[] data); 46 | 47 | public abstract void RunNextStep(); 48 | 49 | public abstract void SetInput(Button button, bool pressed); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/EmulatorBase.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ca0bb7dd5fca5104b9dcf1d6410039de 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/IAudioOutput.cs: -------------------------------------------------------------------------------- 1 | namespace UnityGB 2 | { 3 | public interface IAudioOutput 4 | { 5 | int GetOutputSampleRate(); 6 | 7 | int GetSamplesAvailable(); 8 | 9 | void Play(byte[] data); 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/IAudioOutput.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: e52f72e0a0b83264aa80497b2bf3d88f 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/ISaveMemory.cs: -------------------------------------------------------------------------------- 1 | namespace UnityGB 2 | { 3 | public interface ISaveMemory 4 | { 5 | void Save(string name, byte[] data); 6 | 7 | byte[] Load(string name); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/ISaveMemory.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: adbb0f66cd2f77e4e9f1e8f22a91b421 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/IVideoOutput.cs: -------------------------------------------------------------------------------- 1 | namespace UnityGB 2 | { 3 | public interface IVideoOutput 4 | { 5 | void SetSize(int w, int h); 6 | 7 | void SetPixels(uint[] colors); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/IVideoOutput.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4abbf8450cbe0e54c869b284b18ae956 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/ROM.cs: -------------------------------------------------------------------------------- 1 | #pragma warning disable 0414 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | 8 | namespace UnityGB 9 | { 10 | enum RomType 11 | { 12 | ROM = 0x00, 13 | ROM_MBC1 = 0x01, 14 | ROM_MBC1_RAM = 0x02, 15 | ROM_MBC1_RAM_BATT = 0x03, 16 | ROM_MBC2 = 0x05, 17 | ROM_MBC2_BATTERY = 0x06, 18 | ROM_RAM = 0x08, 19 | ROM_RAM_BATTERY = 0x09, 20 | ROM_MMM01 = 0x0B, 21 | ROM_MMM01_SRAM = 0x0C, 22 | ROM_MMM01_SRAM_BATT = 0x0D, 23 | ROM_MBC3_TIMER_BATT = 0x0F, 24 | ROM_MBC3_TIMER_RAM_BATT = 0x10, 25 | ROM_MBC3 = 0x11, 26 | ROM_MBC3_RAM = 0x12, 27 | ROM_MBC3_RAM_BATT = 0x13, 28 | ROM_MBC5 = 0x19, 29 | ROM_MBC5_RAM = 0x1A, 30 | ROM_MBC5_RAM_BATT = 0x1B, 31 | ROM_MBC5_RUMBLE = 0x1C, 32 | ROM_MBC5_RUMBLE_SRAM = 0x1D, 33 | ROM_MBC5_RUMBLE_SRAM_BATT = 0x1E, 34 | PocketCamera = 0x1F, 35 | BandaiTAMA5 = 0xFD, 36 | HudsonHuC3 = 0xFE, 37 | HudsonHuC1 = 0xFF, 38 | } 39 | 40 | class Game 41 | { 42 | public string title; 43 | public bool gameBoyColorGame; 44 | public int licenseCode; 45 | public bool gameBoy; 46 | public RomType romType; 47 | public int romSize; 48 | public int romBanks; 49 | public int ramSize; 50 | public int ramBanks; 51 | public bool japanese; 52 | public int oldLicenseCode; 53 | public int maskRomVersion; 54 | public int checksum; 55 | public int actualChecksum; 56 | public int headerChecksum; 57 | public int actualHeaderChecksum; 58 | public bool noVerticalBlankInterruptHandler; 59 | public bool noLCDCStatusInterruptHandler; 60 | public bool noTimerOverflowInterruptHandler; 61 | public bool noSerialTransferCompletionInterruptHandler; 62 | public bool noHighToLowOfP10ToP13InterruptHandler; 63 | public ICartridge cartridge; 64 | private ISaveMemory saveMemory; 65 | 66 | internal static Game Load(byte[] fileData, ISaveMemory saveMemory) 67 | { 68 | return new Game(fileData, saveMemory); 69 | } 70 | 71 | public Game(byte[] fileData, ISaveMemory saveMemory) 72 | { 73 | this.saveMemory = saveMemory; 74 | title = ExtractGameTitle(fileData); 75 | gameBoyColorGame = fileData [0x0143] == 0x80; 76 | licenseCode = (((int)fileData [0x0144]) << 4) | fileData [0x0145]; 77 | gameBoy = fileData [0x0146] == 0x00; 78 | romType = (RomType)fileData [0x0147]; 79 | 80 | switch (fileData [0x0148]) 81 | { 82 | case 0x00: 83 | romSize = 32 * 1024; 84 | romBanks = 2; 85 | break; 86 | case 0x01: 87 | romSize = 64 * 1024; 88 | romBanks = 4; 89 | break; 90 | case 0x02: 91 | romSize = 128 * 1024; 92 | romBanks = 8; 93 | break; 94 | case 0x03: 95 | romSize = 256 * 1024; 96 | romBanks = 16; 97 | break; 98 | case 0x04: 99 | romSize = 512 * 1024; 100 | romBanks = 32; 101 | break; 102 | case 0x05: 103 | romSize = 1024 * 1024; 104 | romBanks = 64; 105 | break; 106 | case 0x06: 107 | romSize = 2 * 1024 * 1024; 108 | romBanks = 128; 109 | break; 110 | case 0x52: 111 | romSize = 1179648; 112 | romBanks = 72; 113 | break; 114 | case 0x53: 115 | romSize = 1310720; 116 | romBanks = 80; 117 | break; 118 | case 0x54: 119 | romSize = 1572864; 120 | romBanks = 96; 121 | break; 122 | } 123 | 124 | switch (fileData [0x0149]) 125 | { 126 | case 0x00: 127 | ramSize = 0; 128 | ramBanks = 0; 129 | break; 130 | case 0x01: 131 | ramSize = 2 * 1024; 132 | ramBanks = 1; 133 | break; 134 | case 0x02: 135 | ramSize = 8 * 1024; 136 | ramBanks = 1; 137 | break; 138 | case 0x03: 139 | ramSize = 32 * 1024; 140 | ramBanks = 4; 141 | break; 142 | case 0x04: 143 | ramSize = 128 * 1024; 144 | ramBanks = 16; 145 | break; 146 | } 147 | 148 | japanese = fileData [0x014A] == 0x00; 149 | oldLicenseCode = fileData [0x014B]; 150 | maskRomVersion = fileData [0x014C]; 151 | 152 | headerChecksum = fileData [0x014D]; 153 | for (int i = 0x0134; i <= 0x014C; i++) 154 | { 155 | actualHeaderChecksum = actualHeaderChecksum - fileData [i] - 1; 156 | } 157 | actualHeaderChecksum &= 0xFF; 158 | 159 | checksum = (((int)fileData [0x014E]) << 8) | fileData [0x014F]; 160 | for (int i = 0; i < fileData.Length; i++) 161 | { 162 | if (i != 0x014E && i != 0x014F) 163 | { 164 | actualChecksum += fileData [i]; 165 | } 166 | } 167 | actualChecksum &= 0xFFFF; 168 | 169 | noVerticalBlankInterruptHandler = fileData [0x0040] == 0xD9; 170 | noLCDCStatusInterruptHandler = fileData [0x0048] == 0xD9; 171 | noTimerOverflowInterruptHandler = fileData [0x0050] == 0xD9; 172 | noSerialTransferCompletionInterruptHandler = fileData [0x0058] == 0xD9; 173 | noHighToLowOfP10ToP13InterruptHandler = fileData [0x0060] == 0xD9; 174 | 175 | byte[] savedData = null; 176 | if (saveMemory != null) 177 | savedData = saveMemory.Load(title); 178 | 179 | switch (romType) 180 | { 181 | case RomType.ROM: 182 | cartridge = new ROM(fileData); 183 | break; 184 | case RomType.ROM_MBC1: 185 | case RomType.ROM_MBC1_RAM: 186 | case RomType.ROM_MBC1_RAM_BATT: 187 | cartridge = new MBC1(fileData, savedData, romType, romSize, romBanks); 188 | break; 189 | case RomType.ROM_MBC2: 190 | case RomType.ROM_MBC2_BATTERY: 191 | cartridge = new MBC2(fileData, savedData, romType, romSize, romBanks); 192 | break; 193 | case RomType.ROM_MBC3: 194 | case RomType.ROM_MBC3_RAM: 195 | case RomType.ROM_MBC3_RAM_BATT: 196 | case RomType.ROM_MBC3_TIMER_BATT: 197 | case RomType.ROM_MBC3_TIMER_RAM_BATT: 198 | case RomType.ROM_MBC5: 199 | case RomType.ROM_MBC5_RAM: 200 | case RomType.ROM_MBC5_RAM_BATT: 201 | 202 | cartridge = new MBC3(fileData, savedData, romType, romSize, romBanks); 203 | break; 204 | default: 205 | throw new Exception(string.Format("Cannot emulate cartridge type {0}.", romType)); 206 | } 207 | } 208 | 209 | ~Game() // destructor 210 | { 211 | if (saveMemory != null) 212 | saveMemory.Save(title, cartridge.GetSavedData()); 213 | } 214 | 215 | private string ExtractGameTitle(byte[] fileData) 216 | { 217 | StringBuilder sb = new StringBuilder(); 218 | for (int i = 0x0134; i <= 0x0142; i++) 219 | { 220 | if (fileData [i] == 0x00) 221 | { 222 | break; 223 | } 224 | sb.Append((char)fileData [i]); 225 | } 226 | return sb.ToString(); 227 | } 228 | 229 | public override string ToString() 230 | { 231 | return "title = " + title + "\n" 232 | + "game boy color game = " + gameBoyColorGame + "\n" 233 | + "license code = " + licenseCode + "\n" 234 | + "game boy = " + gameBoy + "\n" 235 | + "rom type = " + romType + "\n" 236 | + "rom size = " + romSize + "\n" 237 | + "rom banks = " + romBanks + "\n" 238 | + "ram size = " + ramSize + "\n" 239 | + "ram banks = " + ramBanks + "\n" 240 | + "japanese = " + japanese + "\n" 241 | + "old license code = " + oldLicenseCode + "\n" 242 | + "mask rom version = " + maskRomVersion + "\n" 243 | + "header checksum = " + headerChecksum + "\n" 244 | + "actual header checksum = " + actualHeaderChecksum + "\n" 245 | + "checksum = " + checksum + "\n" 246 | + "actual checksum = " + actualChecksum + "\n" 247 | + "no vertical blank interrupt handler = " + noVerticalBlankInterruptHandler + "\n" 248 | + "no lcd status interrupt handler = " + noLCDCStatusInterruptHandler + "\n" 249 | + "no timer overflow interrupt handler = " + noTimerOverflowInterruptHandler + "\n" 250 | + "no serial transfer completion interrupt handler = " + noSerialTransferCompletionInterruptHandler + "\n" 251 | + "no high to lower of P10-P13 interrupt handler = " + noHighToLowOfP10ToP13InterruptHandler + "\n"; 252 | } 253 | } 254 | 255 | class ROM : ICartridge 256 | { 257 | 258 | private byte[] fileData; 259 | 260 | public ROM(byte[] fileData) 261 | { 262 | this.fileData = fileData; 263 | } 264 | 265 | public int ReadByte(int address) 266 | { 267 | return fileData [0x7FFF & address]; 268 | } 269 | 270 | public void WriteByte(int address, int value) 271 | { 272 | } 273 | 274 | public byte[] GetSavedData() 275 | { 276 | return null; 277 | } 278 | } 279 | 280 | class MBC1 : ICartridge 281 | { 282 | private const int RAM_BANK = 4; 283 | private const int RAM_BANK_SIZE = 8192; 284 | private RomType romType; 285 | private bool ramBankingMode; 286 | private int selectedRomBank = 1; 287 | private int selectedRamBank; 288 | private byte[,] ram = new byte[RAM_BANK, RAM_BANK_SIZE]; 289 | private byte[,] rom; 290 | 291 | public MBC1(byte[] fileData, RomType romType, int romSize, int romBanks) 292 | { 293 | this.romType = romType; 294 | int bankSize = romSize / romBanks; 295 | rom = new byte[romBanks, bankSize]; 296 | for (int i = 0, k = 0; i < romBanks; ++i) 297 | { 298 | for (int j = 0; j < bankSize; ++j, ++k) 299 | { 300 | rom [i, j] = fileData [k]; 301 | } 302 | } 303 | } 304 | 305 | public MBC1(byte[] fileData, byte[] savedData, RomType romType, int romSize, int romBanks) 306 | : this (fileData, romType, romSize, romBanks) 307 | { 308 | // Load the RAM 309 | if (savedData != null) 310 | { 311 | // Load the RAM 312 | for (int i = 0, k = 0; i < RAM_BANK; ++i) 313 | { 314 | for (int j = 0; j < RAM_BANK_SIZE; ++j, ++k) 315 | { 316 | ram [i, j] = savedData [k]; 317 | } 318 | } 319 | } 320 | } 321 | 322 | public int ReadByte(int address) 323 | { 324 | if (address <= 0x3FFF) 325 | { 326 | return rom [0, address]; 327 | } else if (address >= 0x4000 && address <= 0x7FFF) 328 | { 329 | return rom [selectedRomBank, address - 0x4000]; 330 | } else if (address >= 0xA000 && address <= 0xBFFF) 331 | { 332 | return ram [selectedRamBank, address - 0xA000]; 333 | } 334 | throw new Exception(string.Format("Invalid cartridge read: {0:X}", address)); 335 | } 336 | 337 | public void WriteByte(int address, int value) 338 | { 339 | if (address >= 0xA000 && address <= 0xBFFF) 340 | { 341 | ram [selectedRamBank, address - 0xA000] = (byte)(0xFF & value); 342 | } else if (address >= 0x6000 && address <= 0x7FFF) 343 | { 344 | ramBankingMode = (value & 0x01) == 0x01; 345 | } else if (address >= 0x2000 && address <= 0x3FFF) 346 | { 347 | int selectedRomBankLow = 0x1F & value; 348 | if (selectedRomBankLow == 0x00) 349 | { 350 | selectedRomBankLow++; 351 | } 352 | selectedRomBank = (selectedRomBank & 0x60) | selectedRomBankLow; 353 | } else if (address >= 0x4000 && address <= 0x5FFF) 354 | { 355 | if (ramBankingMode) 356 | { 357 | selectedRamBank = 0x03 & value; 358 | } else 359 | { 360 | selectedRomBank = (selectedRomBank & 0x1F) | ((0x03 & value) << 5); 361 | } 362 | } 363 | } 364 | 365 | public byte[] GetSavedData() 366 | { 367 | byte[] savedData = new byte[RAM_BANK * RAM_BANK_SIZE]; 368 | 369 | for (int i = 0, k = 0; i < RAM_BANK; ++i) 370 | { 371 | for (int j = 0; j < RAM_BANK_SIZE; ++j, ++k) 372 | { 373 | savedData [k] = ram [i, j]; 374 | } 375 | } 376 | 377 | return savedData; 378 | } 379 | } 380 | 381 | class MBC2 : ICartridge 382 | { 383 | private const int RAM_SIZE = 512; 384 | private RomType romType; 385 | private int selectedRomBank = 1; 386 | private byte[] ram = new byte[RAM_SIZE]; 387 | private byte[,] rom; 388 | 389 | public MBC2(byte[] fileData, RomType romType, int romSize, int romBanks) 390 | { 391 | this.romType = romType; 392 | int bankSize = romSize / romBanks; 393 | rom = new byte[romBanks, bankSize]; 394 | for (int i = 0, k = 0; i < romBanks; ++i) 395 | { 396 | for (int j = 0; j < bankSize; ++j, ++k) 397 | { 398 | rom [i, j] = fileData [k]; 399 | } 400 | } 401 | } 402 | 403 | public MBC2(byte[] fileData, byte[] savedData, RomType romType, int romSize, int romBanks) : this (fileData, romType, romSize, romBanks) 404 | { 405 | // Load RAM data 406 | if (savedData != null) 407 | { 408 | for (int i = 0; i < RAM_SIZE; ++i) 409 | { 410 | ram [i] = fileData [i]; 411 | } 412 | } 413 | } 414 | 415 | public int ReadByte(int address) 416 | { 417 | if (address <= 0x3FFF) 418 | { 419 | return rom [0, address]; 420 | } else if (address >= 0x4000 && address <= 0x7FFF) 421 | { 422 | return rom [selectedRomBank, address - 0x4000]; 423 | } else if (address >= 0xA000 && address <= 0xA1FF) 424 | { 425 | return ram [address - 0xA000]; 426 | } 427 | throw new Exception(string.Format("Invalid cartridge address: {0}", address)); 428 | } 429 | 430 | public void WriteByte(int address, int value) 431 | { 432 | if (address >= 0xA000 && address <= 0xA1FF) 433 | { 434 | ram [address - 0xA000] = (byte)(0x0F & value); 435 | } else if (address >= 0x2000 && address <= 0x3FFF) 436 | { 437 | selectedRomBank = 0x0F & value; 438 | } 439 | } 440 | 441 | public byte[] GetSavedData() 442 | { 443 | return ram; 444 | } 445 | } 446 | 447 | class MBC3 : ICartridge 448 | { 449 | private const int RAM_BANK = 4; 450 | private const int RAM_BANK_SIZE = 8192; 451 | private RomType romType; 452 | private int selectedRomBank = 1; 453 | private int selectedRamBank = 0; 454 | private byte[,] ram = new byte[RAM_BANK, RAM_BANK_SIZE]; 455 | private byte[,] rom; 456 | private bool ramRTCEnambleFlag = false; 457 | private System.DateTime latchClock; 458 | private int latchClockData = 0x01; 459 | 460 | public MBC3(byte[] fileData, RomType romType, int romSize, int romBanks) 461 | { 462 | this.romType = romType; 463 | int bankSize = romSize / romBanks; 464 | rom = new byte[romBanks, bankSize]; 465 | 466 | // Load the ROM 467 | for (int i = 0, k = 0; i < romBanks; i++) 468 | { 469 | for (int j = 0; j < bankSize; j++, k++) 470 | { 471 | rom [i, j] = fileData [k]; 472 | } 473 | } 474 | 475 | } 476 | 477 | public MBC3(byte[] fileData, byte[] savedData, RomType romType, int romSize, int romBanks) 478 | : this (fileData, romType, romSize, romBanks) 479 | { 480 | // Load the RAM 481 | if (savedData != null) 482 | { 483 | for (int i = 0, k = 0; i < RAM_BANK; ++i) 484 | { 485 | for (int j = 0; j < RAM_BANK_SIZE; ++j, ++k) 486 | { 487 | ram [i, j] = savedData [k]; 488 | } 489 | } 490 | } 491 | } 492 | 493 | public int ReadByte(int address) 494 | { 495 | if (address <= 0x3FFF) 496 | { 497 | return rom [0, address]; 498 | } else if (address >= 0x4000 && address <= 0x7FFF) 499 | { 500 | return rom [selectedRomBank, address - 0x4000]; 501 | } else if (address >= 0xA000 && address <= 0xBFFF) 502 | { 503 | if (ramRTCEnambleFlag) 504 | { 505 | if (selectedRamBank < 4) 506 | { 507 | return ram [selectedRamBank, address - 0xA000]; 508 | } else 509 | { 510 | UnityEngine.Debug.Log("Get RTC register"); 511 | if (selectedRamBank == 0x08) 512 | { 513 | return latchClock.Second; 514 | } else if (selectedRamBank == 0x09) 515 | { 516 | return latchClock.Minute; 517 | } else if (selectedRamBank == 0x0A) 518 | { 519 | return latchClock.Hour; 520 | } else if (selectedRamBank == 0x0B) 521 | { 522 | return latchClock.DayOfYear & 0x00FF; 523 | } else if (selectedRamBank == 0x0C) 524 | { 525 | return (latchClock.DayOfYear & 0x01FF) >> 8; 526 | } 527 | } 528 | } 529 | } 530 | throw new Exception(string.Format("Invalid cartridge read: {0:X}", address)); 531 | } 532 | 533 | public void WriteByte(int address, int value) 534 | { 535 | if (address <= 0x1FFF) 536 | { 537 | ramRTCEnambleFlag = (value & 0x0F) == 0x0A; 538 | } else if (address >= 0x2000 && address <= 0x3FFF) 539 | { 540 | selectedRomBank = 0x7F & value; 541 | if (selectedRomBank == 0x00) 542 | { 543 | selectedRomBank = 0x01; 544 | } 545 | } else if (address >= 0x4000 && address <= 0x5FFF) 546 | { 547 | selectedRamBank = 0x0F & value; 548 | } else if (address >= 0x6000 && address <= 0x7FFF) 549 | { 550 | if (((0x01 & value) == 0x01) && (latchClockData == 0x00)) 551 | latchClock = System.DateTime.Now; 552 | latchClockData = 0x01 & value; 553 | } else if (address >= 0xA000 && address <= 0xBFFF) 554 | { 555 | if (ramRTCEnambleFlag && selectedRamBank < 4) 556 | { 557 | ram [selectedRamBank, address - 0xA000] = (byte)(0xFF & value); 558 | } 559 | } else 560 | { 561 | throw new Exception(string.Format("Invalid cartridge write: {0:X}", address)); 562 | } 563 | } 564 | 565 | public byte[] GetSavedData() 566 | { 567 | byte[] savedData = new byte[RAM_BANK * RAM_BANK_SIZE]; 568 | 569 | for (int i = 0, k = 0; i < RAM_BANK; ++i) 570 | { 571 | for (int j = 0; j < RAM_BANK_SIZE; ++j, ++k) 572 | { 573 | savedData [k] = ram [i, j]; 574 | } 575 | } 576 | 577 | return savedData; 578 | } 579 | } 580 | } 581 | -------------------------------------------------------------------------------- /Assets/UnityGB/Scripts/UnityGB/ROM.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 80f9cb7272127c84ba885f556db03a98 3 | MonoImporter: 4 | serializedVersion: 2 5 | defaultReferences: [] 6 | executionOrder: 0 7 | icon: {instanceID: 0} 8 | userData: 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) Takohi, Jonathan Odul 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /ProjectSettings/AudioManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/AudioManager.asset -------------------------------------------------------------------------------- /ProjectSettings/DynamicsManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/DynamicsManager.asset -------------------------------------------------------------------------------- /ProjectSettings/EditorBuildSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/EditorBuildSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/EditorSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/EditorSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/GraphicsSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/GraphicsSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/InputManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/InputManager.asset -------------------------------------------------------------------------------- /ProjectSettings/NavMeshAreas.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/NavMeshAreas.asset -------------------------------------------------------------------------------- /ProjectSettings/NavMeshLayers.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/NavMeshLayers.asset -------------------------------------------------------------------------------- /ProjectSettings/NetworkManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/NetworkManager.asset -------------------------------------------------------------------------------- /ProjectSettings/Physics2DSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/Physics2DSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/ProjectSettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/ProjectSettings.asset -------------------------------------------------------------------------------- /ProjectSettings/ProjectVersion.txt: -------------------------------------------------------------------------------- 1 | m_EditorVersion: 2017.1.0f3 2 | -------------------------------------------------------------------------------- /ProjectSettings/QualitySettings.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/QualitySettings.asset -------------------------------------------------------------------------------- /ProjectSettings/TagManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/TagManager.asset -------------------------------------------------------------------------------- /ProjectSettings/TimeManager.asset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KonsomeJona/UnityGB/538bec2f2b2e08434c91e909f6a6a4a370795cb8/ProjectSettings/TimeManager.asset -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UnityGB 2 | 3 | UnityGB allows you to run a Game Boy ROM inside your Unity project. 4 | It has been written in C# and is only using Mono allowing you to export the project to any platform supported by Unity. 5 | 6 | UnityGB became the main subject of several articles like [CNET, Gameboy emulator on the Oculus Rift: Gameception](http://www.cnet.com/news/gameboy-emulator-on-the-oculus-rift-gameception/). 7 | 8 | UnityGB is still in alpha version, many bugs and glitches can occur. Also, the code is far from being perfect and needs a lot of optimization. Any and all help welcome! Please feel free to contribute to this project. 9 | 10 | Please also take some time to take a look at our other applications on our website [Takohi.com](http://www.takohi.com) and our other [Unity assets](https://www.assetstore.unity3d.com/en/?gclid=CjwKEAjwuoOpBRCSy6yQm66J1g8SJABrXW48qCgi3rfBrvrAQu55uQeS0U3YH51O-Ybf6N1ZDwJVQRoCqBrw_wcB#!/search/page=1/sortby=popularity/query=publisher:4241). :+1: 11 | 12 | ## Compatibilities 13 | 14 | ### What is supported 15 | 16 | * Game Boy games with cartridge <= MBC5 17 | * Input 18 | * Sound 19 | * Save (save files are compatible with Visual Boy Advance and other emulators) 20 | 21 | ### Planned features 22 | 23 | * Game Boy Color compatibility 24 | * Link Cable 25 | * Oculus Rift 26 | 27 | ### Known issues 28 | 29 | * Sound synchronization 30 | * Pokemon Yellow: Pikachu voices are not played 31 | * Some glitches with sprites order 32 | 33 | ## Demonstrations 34 | 35 | **Hilarious Gameboy Emulator with Leap Motion in Unity**, by [Pierce Wolcott](https://piercewolcott.com/) 36 | [![Gameboy Emulator with Leap Motion in Unity](https://i.vimeocdn.com/video/514669012_590x332.jpg)](https://vimeo.com/124805471 "Leap Boy") 37 | 38 | **Oculus Rift Gameboy Emulator in Unity**, by [Shane O'Brien](http://www.youtube.com/watch?v=wby8pMrYYaM) 39 | [![Oculus Rift Gameboy Emulator in Unity](http://img.youtube.com/vi/wby8pMrYYaM/0.jpg)](http://www.youtube.com/watch?v=wby8pMrYYaM "Oculus Rift Gameboy Emulator in Unity") 40 | 41 | **UnityGB4BB10, GameBoy Emulator for BlackBerry 10** 42 | [![UnityGB4BB10](http://www.filearchivehaven.com/wp-content/uploads/2014/08/UnityGB4BB-Games-1024x576.png)](http://www.filearchivehaven.com/2014/08/17/proud-to-announce-another-gameboy-emulator-for-blackberry-10-unitygb4bb10/ "UnityGB4BB10") 43 | 44 | **Official Web Demo**, by [Takohi](http://www.takohi.com) 45 | [![Official Demo](https://bitbucket.org/repo/8MjKzK/images/2954418396-unitygb_demo_screenshot_2.png)](http://www.takohi.com/data/unity/unitygb/ "Official Demo") 46 | 47 | ## Usage 48 | **UnityGB** can work in several ways. What you have to do is to make your own implementation about how to manage inputs (controls) and outputs (video and sound). 49 | In the Unity package and the sources, you will already find one classic scene outputing the video in a classic texture, the audio trough the [OnAudioFilterRead](http://docs.unity3d.com/Documentation/ScriptReference/MonoBehaviour.OnAudioFilterRead.html) method, and joypad input from the keyboard. 50 | 51 | ### Joypad 52 | 53 | An example of how to implement the controls can be found in *DefaultEmulatorManager.cs*. 54 | 55 | When you want to emulate an input from the player into the game, you just have to call the *SetInput* method from the emulator reference: 56 | ``` 57 | void SetInput(Button button, bool pressed); 58 | ``` 59 | Button is an enum with these values: 60 | 61 | ``` 62 | public enum Button {Up, Down, Left, Right, A, B, Start, Select}; 63 | ``` 64 | For example, if you want to press the button start when the player is hitting the space button: 65 | 66 | ``` 67 | if(Input.GetKeyDown(KeyCode.Space)) 68 | Emulator.SetInput(EmulatorBase.Button.Start, true); 69 | else if(Input.GetKeyUp(KeyCode.Space)) 70 | Emulator.SetInput(EmulatorBase.Button.Start, false); 71 | ``` 72 | 73 | ### Video 74 | 75 | An example of how to implement the video can be found in *DefaultVideoOutput.cs*. 76 | 77 | In order to output the video from the emulator or in other words, to display the Game Boy screen, you will have to make your own class implementing the *IVideoOutput* interface. 78 | 79 | This interface has only two methods: 80 | 81 | ``` 82 | void SetSize(int w, int h); 83 | void SetPixels(uint[] colors); 84 | 85 | ``` 86 | 87 | The *SetSize* method will be called after the rom is loaded. It will tell you what is the size of the screen in pixels (160x144, it should never change) so you can initialize your way to display. 88 | 89 | The method *SetPixels* will be called every time the frame has been updated. As parameter, an array of colors that corresponds to the color of each pixel. The size of the array will be *width x height*. 90 | 91 | Here is a method to convert a uint color into a Unity color: 92 | 93 | ``` 94 | private Color UIntToColor(uint color) 95 | { 96 | byte a = (byte)(color >> 24); 97 | byte r = (byte)(color >> 16); 98 | byte g = (byte)(color >> 8); 99 | byte b = (byte)(color >> 0); 100 | return new Color(r / 255f, g / 255f, b / 255f, a / 255f); 101 | } 102 | ``` 103 | 104 | ### Audio 105 | 106 | An example of how to implement the audio can be found in *DefaultAudioOutput.cs*. 107 | 108 | With Unity, the only way to dynamically produce audio is by using the method [OnAudioFilterRead(float[] data, int channels)](https://docs.unity3d.com/Documentation/ScriptReference/MonoBehaviour.OnAudioFilterRead.html) from a *MonoBehaviour* object. 109 | 110 | In the package, we already provide a way to produce the audio trough the *DefaultAudioOutput* class. If you want to use it, just attach that script to a game object and add to it an *AudioSource* component. 111 | 112 | If you still want to make your own implementation for outputing audio, you will have to make a class that implements the interface *IAudioOutput*. 113 | 114 | This interface has three methods: 115 | ``` 116 | int GetOutputSampleRate(); 117 | int GetSamplesAvailable(); 118 | void Play(byte[] data, int offset, int count); 119 | ``` 120 | 121 | * *GetOutputSampleRare()* has to return the sample rate (44100Hz, ...). 122 | * *GetSamplesAvailable()* returns the number of samples to return during the next step. 123 | * *Play(byte[] data)* gives you the samples to play. 124 | 125 | Samples are interleaved (%0 = Left, %1 = Right). This means for each sample: data[i] = left sample, data[i + 1] = right sample. 126 | 127 | ### Save 128 | 129 | An example of how to manage save files can be found in *DefaultSaveMemory.cs*. 130 | 131 | In order to manage save files, you will have to make your own class implementing the *ISaveMemory* interface. 132 | 133 | This interface has only two methods: 134 | 135 | ``` 136 | void Save(string name, byte[] data); 137 | byte[] Load(string name); 138 | ``` 139 | 140 | The *Save* method is called when the user stops the game, and the *Load* method is called when the game is loaded. The *name* parameter is the name of the game that is currently played. 141 | 142 | Globally, when the *Save* method is called, you simply have to store somewhere the byte array given as parameter, then load and return this same byte array when the *Load* method is called for the same game. 143 | 144 | ### Make the emulator run 145 | 146 | An example of how to use the emulator can be found in *DefaultEmulatorManager.cs*. 147 | 148 | The code below is a simple way showing how to initialize and make run UnityGB with a *MonoBehaviour* script. 149 | 150 | First, we create an instance of the emulator and load the ROM in the *Start* method. 151 | 152 | ``` 153 | void Start() 154 | { 155 | // Load emulator 156 | IVideoOutput drawable = ...; // Reference to your IVideoOutput implementation. 157 | IAudioOutput audio = ...; // Reference to your IAudioOutput implementation. 158 | ISaveMemory saveMemory = ...; // Reference to your ISaveMemory implementation. 159 | Emulator = new Emulator(drawable, audio, saveMemory); // Instantiate emulator. 160 | 161 | byte[] rom = ...; // Load ROM binary. 162 | Emulator.LoadRom(www.bytes); // Load ROM into emulator. 163 | } 164 | ``` 165 | Then we make the emulator running during the *Update* method of the *MonoBehaviour*. We also use this method to manage inputs from the user and send them to the emulator. 166 | 167 | ``` 168 | void Update() 169 | { 170 | // Input 171 | if(Input.GetKeyDown(KeyCode.Space)) 172 | Emulator.SetInput(EmulatorBase.Button.Start, true); 173 | else if(Input.GetKeyUp(KeyCode.Space)) 174 | Emulator.SetInput(EmulatorBase.Button.Start, false); 175 | ... 176 | 177 | // Run 178 | Emulator.RunNextStep(); 179 | } 180 | ``` 181 | 182 | Don't forget to attach the previous MonoBehaviour to a game object in order to make it run. 183 | 184 | ## Reference Material 185 | 186 | * [Official UnityGB thread on Unity forum](http://forum.unity3d.com/threads/245974-unityGB-Emulator-Game-Boy-for-Unity-RELEASED) 187 | * http://meatfighter.com/gameboy/ 188 | * http://www.millstone.demon.co.uk/download/javaboy/ 189 | --------------------------------------------------------------------------------