From 93ad387db835f4ca32a8c00672345db1d4880ad6 Mon Sep 17 00:00:00 2001 From: goyuken Date: Mon, 19 Nov 2012 22:43:34 +0000 Subject: [PATCH] stuff, don't use --- BizHawk.Emulation/BizHawk.Emulation.csproj | 2 + .../Consoles/Nintendo/GBA/LibMeteor.cs | 67 + .../Consoles/Nintendo/GBA/Meteor.cs | 199 +++ BizHawk.Emulation/Database/Database.cs | 4 + BizHawk.MultiClient/MainForm.cs | 7 + BizHawk.MultiClient/movie/InputAdapters.cs | 4 + libmeteor/cinterface.cpp | 96 ++ libmeteor/include/ameteor.hpp | 80 + libmeteor/include/ameteor/audio/dsound.hpp | 78 + libmeteor/include/ameteor/audio/sound1.hpp | 75 + libmeteor/include/ameteor/audio/sound2.hpp | 70 + libmeteor/include/ameteor/audio/sound4.hpp | 78 + libmeteor/include/ameteor/audio/speaker.hpp | 173 ++ libmeteor/include/ameteor/bios.hpp | 59 + libmeteor/include/ameteor/cartmem.hpp | 57 + libmeteor/include/ameteor/clock.hpp | 111 ++ libmeteor/include/ameteor/cpu.hpp | 148 ++ .../ameteor/disassembler/argimmediate.hpp | 44 + .../ameteor/disassembler/argmulregisters.hpp | 67 + .../include/ameteor/disassembler/argpsr.hpp | 47 + .../ameteor/disassembler/argregister.hpp | 56 + .../ameteor/disassembler/argrelative.hpp | 50 + .../include/ameteor/disassembler/argshift.hpp | 56 + .../ameteor/disassembler/arguimmediate.hpp | 45 + .../include/ameteor/disassembler/argument.hpp | 39 + .../ameteor/disassembler/arguments.hpp | 48 + .../ameteor/disassembler/instruction.hpp | 76 + libmeteor/include/ameteor/dma.hpp | 112 ++ libmeteor/include/ameteor/eeprom.hpp | 79 + libmeteor/include/ameteor/flash.hpp | 63 + .../include/ameteor/graphics/bglayer.hpp | 97 ++ libmeteor/include/ameteor/graphics/object.hpp | 100 ++ .../include/ameteor/graphics/objects.hpp | 53 + .../include/ameteor/graphics/renderer.hpp | 52 + libmeteor/include/ameteor/graphics/screen.hpp | 148 ++ libmeteor/include/ameteor/interpreter.hpp | 133 ++ libmeteor/include/ameteor/io.hpp | 216 +++ libmeteor/include/ameteor/keypad.hpp | 105 ++ libmeteor/include/ameteor/lcd.hpp | 129 ++ libmeteor/include/ameteor/memory.hpp | 149 ++ libmeteor/include/ameteor/sound.hpp | 117 ++ libmeteor/include/ameteor/sram.hpp | 56 + libmeteor/include/ameteor/swap.hpp | 36 + libmeteor/include/ameteor/timer.hpp | 86 + libmeteor/include/syg/signal.hpp | 389 +++++ libmeteor/source/ameteor.cpp | 204 +++ libmeteor/source/audio/dsound.cpp | 82 + libmeteor/source/audio/sound1.cpp | 202 +++ libmeteor/source/audio/sound2.cpp | 145 ++ libmeteor/source/audio/sound4.cpp | 185 +++ libmeteor/source/audio/speaker.cpp | 169 ++ libmeteor/source/bios.cpp | 919 ++++++++++ libmeteor/source/cartmem.cpp | 57 + libmeteor/source/clock.cpp | 123 ++ libmeteor/source/cpu.cpp | 328 ++++ libmeteor/source/cpu_globals.hpp | 41 + libmeteor/source/debug.cpp | 85 + libmeteor/source/debug.hpp | 92 + .../source/disassembler/argimmediate.cpp | 39 + .../source/disassembler/argmulregisters.cpp | 83 + libmeteor/source/disassembler/argpsr.cpp | 53 + libmeteor/source/disassembler/argregister.cpp | 53 + libmeteor/source/disassembler/argrelative.cpp | 71 + libmeteor/source/disassembler/argshift.cpp | 71 + .../source/disassembler/arguimmediate.cpp | 39 + libmeteor/source/disassembler/arguments.cpp | 51 + libmeteor/source/disassembler/instruction.cpp | 691 ++++++++ libmeteor/source/dma.cpp | 307 ++++ libmeteor/source/eeprom.cpp | 272 +++ libmeteor/source/flash.cpp | 178 ++ libmeteor/source/globals.hpp | 140 ++ libmeteor/source/graphics/bglayer.cpp | 451 +++++ libmeteor/source/graphics/object.cpp | 680 ++++++++ libmeteor/source/graphics/objects.cpp | 168 ++ libmeteor/source/graphics/renderer.cpp | 35 + libmeteor/source/graphics/screen.cpp | 402 +++++ libmeteor/source/interpreter.cpp | 193 +++ libmeteor/source/interpreter_arm.cpp | 1477 +++++++++++++++++ libmeteor/source/interpreter_thumb.cpp | 1222 ++++++++++++++ libmeteor/source/io.cpp | 493 ++++++ libmeteor/source/keypad.cpp | 96 ++ libmeteor/source/lcd.cpp | 148 ++ libmeteor/source/memory.cpp | 823 +++++++++ libmeteor/source/sound.cpp | 111 ++ libmeteor/source/sram.cpp | 62 + libmeteor/source/timer.cpp | 146 ++ 86 files changed, 15143 insertions(+) create mode 100644 BizHawk.Emulation/Consoles/Nintendo/GBA/LibMeteor.cs create mode 100644 BizHawk.Emulation/Consoles/Nintendo/GBA/Meteor.cs create mode 100644 libmeteor/cinterface.cpp create mode 100644 libmeteor/include/ameteor.hpp create mode 100644 libmeteor/include/ameteor/audio/dsound.hpp create mode 100644 libmeteor/include/ameteor/audio/sound1.hpp create mode 100644 libmeteor/include/ameteor/audio/sound2.hpp create mode 100644 libmeteor/include/ameteor/audio/sound4.hpp create mode 100644 libmeteor/include/ameteor/audio/speaker.hpp create mode 100644 libmeteor/include/ameteor/bios.hpp create mode 100644 libmeteor/include/ameteor/cartmem.hpp create mode 100644 libmeteor/include/ameteor/clock.hpp create mode 100644 libmeteor/include/ameteor/cpu.hpp create mode 100644 libmeteor/include/ameteor/disassembler/argimmediate.hpp create mode 100644 libmeteor/include/ameteor/disassembler/argmulregisters.hpp create mode 100644 libmeteor/include/ameteor/disassembler/argpsr.hpp create mode 100644 libmeteor/include/ameteor/disassembler/argregister.hpp create mode 100644 libmeteor/include/ameteor/disassembler/argrelative.hpp create mode 100644 libmeteor/include/ameteor/disassembler/argshift.hpp create mode 100644 libmeteor/include/ameteor/disassembler/arguimmediate.hpp create mode 100644 libmeteor/include/ameteor/disassembler/argument.hpp create mode 100644 libmeteor/include/ameteor/disassembler/arguments.hpp create mode 100644 libmeteor/include/ameteor/disassembler/instruction.hpp create mode 100644 libmeteor/include/ameteor/dma.hpp create mode 100644 libmeteor/include/ameteor/eeprom.hpp create mode 100644 libmeteor/include/ameteor/flash.hpp create mode 100644 libmeteor/include/ameteor/graphics/bglayer.hpp create mode 100644 libmeteor/include/ameteor/graphics/object.hpp create mode 100644 libmeteor/include/ameteor/graphics/objects.hpp create mode 100644 libmeteor/include/ameteor/graphics/renderer.hpp create mode 100644 libmeteor/include/ameteor/graphics/screen.hpp create mode 100644 libmeteor/include/ameteor/interpreter.hpp create mode 100644 libmeteor/include/ameteor/io.hpp create mode 100644 libmeteor/include/ameteor/keypad.hpp create mode 100644 libmeteor/include/ameteor/lcd.hpp create mode 100644 libmeteor/include/ameteor/memory.hpp create mode 100644 libmeteor/include/ameteor/sound.hpp create mode 100644 libmeteor/include/ameteor/sram.hpp create mode 100644 libmeteor/include/ameteor/swap.hpp create mode 100644 libmeteor/include/ameteor/timer.hpp create mode 100644 libmeteor/include/syg/signal.hpp create mode 100644 libmeteor/source/ameteor.cpp create mode 100644 libmeteor/source/audio/dsound.cpp create mode 100644 libmeteor/source/audio/sound1.cpp create mode 100644 libmeteor/source/audio/sound2.cpp create mode 100644 libmeteor/source/audio/sound4.cpp create mode 100644 libmeteor/source/audio/speaker.cpp create mode 100644 libmeteor/source/bios.cpp create mode 100644 libmeteor/source/cartmem.cpp create mode 100644 libmeteor/source/clock.cpp create mode 100644 libmeteor/source/cpu.cpp create mode 100644 libmeteor/source/cpu_globals.hpp create mode 100644 libmeteor/source/debug.cpp create mode 100644 libmeteor/source/debug.hpp create mode 100644 libmeteor/source/disassembler/argimmediate.cpp create mode 100644 libmeteor/source/disassembler/argmulregisters.cpp create mode 100644 libmeteor/source/disassembler/argpsr.cpp create mode 100644 libmeteor/source/disassembler/argregister.cpp create mode 100644 libmeteor/source/disassembler/argrelative.cpp create mode 100644 libmeteor/source/disassembler/argshift.cpp create mode 100644 libmeteor/source/disassembler/arguimmediate.cpp create mode 100644 libmeteor/source/disassembler/arguments.cpp create mode 100644 libmeteor/source/disassembler/instruction.cpp create mode 100644 libmeteor/source/dma.cpp create mode 100644 libmeteor/source/eeprom.cpp create mode 100644 libmeteor/source/flash.cpp create mode 100644 libmeteor/source/globals.hpp create mode 100644 libmeteor/source/graphics/bglayer.cpp create mode 100644 libmeteor/source/graphics/object.cpp create mode 100644 libmeteor/source/graphics/objects.cpp create mode 100644 libmeteor/source/graphics/renderer.cpp create mode 100644 libmeteor/source/graphics/screen.cpp create mode 100644 libmeteor/source/interpreter.cpp create mode 100644 libmeteor/source/interpreter_arm.cpp create mode 100644 libmeteor/source/interpreter_thumb.cpp create mode 100644 libmeteor/source/io.cpp create mode 100644 libmeteor/source/keypad.cpp create mode 100644 libmeteor/source/lcd.cpp create mode 100644 libmeteor/source/memory.cpp create mode 100644 libmeteor/source/sound.cpp create mode 100644 libmeteor/source/sram.cpp create mode 100644 libmeteor/source/timer.cpp diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj index 02c5015e4d..d7aa635616 100644 --- a/BizHawk.Emulation/BizHawk.Emulation.csproj +++ b/BizHawk.Emulation/BizHawk.Emulation.csproj @@ -216,6 +216,8 @@ + + diff --git a/BizHawk.Emulation/Consoles/Nintendo/GBA/LibMeteor.cs b/BizHawk.Emulation/Consoles/Nintendo/GBA/LibMeteor.cs new file mode 100644 index 0000000000..9c73e83766 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Nintendo/GBA/LibMeteor.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; + +namespace BizHawk.Emulation.Consoles.Nintendo.GBA +{ + /// + /// bindings into libmeteor.dll + /// + public static class LibMeteor + { + /// + /// reset the emulation core + /// + [DllImport("libmeteor.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void libmeteor_reset(); + + /// + /// signal that you are removing data from the sound buffer. + /// the next time frameadvance() is called, writing will start from the beginning + /// + /// the valid length of the buffer, in bytes + [DllImport("libmeteor.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern uint libmeteor_emptysound(); + + /// + /// set up buffers for libmeteor to dump data to. these must be valid before every frameadvance + /// + /// buffer to hold video data as BGRA32 + /// length in bytes. must be at least 240 * 160 * 4 + /// buffer to hold audio data as stereo s16le + /// length in bytes. must be 0 mod 4 (hold a full stereo sample set) + /// false if some problem. buffers will not be valid in this case + [DllImport("libmeteor.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern bool libmeteor_setbuffers(IntPtr vid, uint vidlen, IntPtr aud, uint audlen); + + /// + /// initialize the library + /// + [DllImport("libmeteor.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void libmeteor_init(); + + /// + /// run emulation for one frame, updating sound and video along the way + /// + [DllImport("libmeteor.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void libmeteor_frameadvance(); + + /// + /// load a rom image + /// + /// raw rom data. need not persist past this call + /// length of data in bytes + [DllImport("libmeteor.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void libmeteor_loadrom(byte[] data, uint datalen); + + /// + /// load a bios image + /// + /// raw bios data. need not persist past this call + /// length of data in bytes + [DllImport("libmeteor.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern void libmeteor_loadbios(byte[] data, uint datalen); + } +} diff --git a/BizHawk.Emulation/Consoles/Nintendo/GBA/Meteor.cs b/BizHawk.Emulation/Consoles/Nintendo/GBA/Meteor.cs new file mode 100644 index 0000000000..a0f3da4b41 --- /dev/null +++ b/BizHawk.Emulation/Consoles/Nintendo/GBA/Meteor.cs @@ -0,0 +1,199 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Runtime.InteropServices; +using System.IO; + +namespace BizHawk.Emulation.Consoles.Nintendo.GBA +{ + public class GBA : IEmulator, IVideoProvider, ISyncSoundProvider + { + public static readonly ControllerDefinition GBAController = + new ControllerDefinition + { + Name = "GBA Controller", + BoolButtons = + { + "Up", "Down", "Left", "Right", "Select", "Start", "B", "A", "L", "R"//, "Reset", "Power", + } + }; + public ControllerDefinition ControllerDefinition { get { return GBAController; } } + public IController Controller { get; set; } + + public void Load(byte[] rom) + { + Init(); + LibMeteor.libmeteor_reset(); + LibMeteor.libmeteor_loadbios(File.ReadAllBytes("gbabios.rom"), 16384); + LibMeteor.libmeteor_loadrom(rom, (uint)rom.Length); + } + + public void FrameAdvance(bool render, bool rendersound = true) + { + LibMeteor.libmeteor_frameadvance(); + } + + public int Frame + { + get { return 0; } + } + + public int LagCount + { + get; + set; + } + + public bool IsLagFrame + { + get { return false; } + } + + public string SystemId + { + get { return "GBA"; } + } + + public bool DeterministicEmulation + { + get { return true; } + } + + public byte[] ReadSaveRam() + { + return new byte[0]; + } + + public void StoreSaveRam(byte[] data) + { + } + + public void ClearSaveRam() + { + } + + public bool SaveRamModified { get { return false; } set { } } + + public void ResetFrameCounter() + { + } + + public void SaveStateText(System.IO.TextWriter writer) + { + } + + public void LoadStateText(System.IO.TextReader reader) + { + } + + public void SaveStateBinary(System.IO.BinaryWriter writer) + { + } + + public void LoadStateBinary(System.IO.BinaryReader reader) + { + } + + public byte[] SaveStateBinary() + { + MemoryStream ms = new MemoryStream(); + BinaryWriter bw = new BinaryWriter(ms); + SaveStateBinary(bw); + bw.Flush(); + return ms.ToArray(); + } + + public CoreInputComm CoreInputComm { get; set; } + + CoreOutputComm _CoreOutputComm = new CoreOutputComm + { + VsyncNum = 262144, + VsyncDen = 4389 + }; + + public CoreOutputComm CoreOutputComm { get { return _CoreOutputComm; } } + + public IList MemoryDomains + { + get { return null; } + } + + public MemoryDomain MainMemory + { + get { return null; } + } + + static GBA attachedcore; + + void Init() + { + if (attachedcore != null) + attachedcore.Dispose(); + + LibMeteor.libmeteor_init(); + videobuffer = new int[240 * 160]; + videohandle = GCHandle.Alloc(videobuffer, GCHandleType.Pinned); + soundbuffer = new short[2048]; + soundhandle = GCHandle.Alloc(soundbuffer, GCHandleType.Pinned); + + if (!LibMeteor.libmeteor_setbuffers + (videohandle.AddrOfPinnedObject(), (uint)(sizeof(int) * videobuffer.Length), + soundhandle.AddrOfPinnedObject(), (uint)(sizeof(short) * soundbuffer.Length))) + throw new Exception("libmeteor_setbuffers() returned false!"); + + attachedcore = this; + } + + bool disposed = false; + public void Dispose() + { + if (!disposed) + { + disposed = true; + videohandle.Free(); + soundhandle.Free(); + } + } + + #region IVideoProvider + + public IVideoProvider VideoProvider { get { return this; } } + + int[] videobuffer; + GCHandle videohandle; + + public int[] GetVideoBuffer() { return videobuffer; } + public int VirtualWidth { get { return 240; } } + public int BufferWidth { get { return 240; } } + public int BufferHeight { get { return 160; } } + public int BackgroundColor { get { return unchecked((int)0xff000000); } } + + #endregion + + #region ISoundProvider + + short[] soundbuffer; + GCHandle soundhandle; + + public ISoundProvider SoundProvider { get { return null; } } + public ISyncSoundProvider SyncSoundProvider { get { return this; } } + public bool StartAsyncSound() { return false; } + public void EndAsyncSound() { } + + public void GetSamples(out short[] samples, out int nsamp) + { + uint nbytes = LibMeteor.libmeteor_emptysound(); + samples = soundbuffer; + nsamp = (int)(nbytes / 4); + + } + + public void DiscardSamples() + { + LibMeteor.libmeteor_emptysound(); + } + + #endregion + } +} diff --git a/BizHawk.Emulation/Database/Database.cs b/BizHawk.Emulation/Database/Database.cs index f6da3bfe52..2cb9be03b5 100644 --- a/BizHawk.Emulation/Database/Database.cs +++ b/BizHawk.Emulation/Database/Database.cs @@ -164,6 +164,10 @@ namespace BizHawk case ".CRT": Game.System = "C64"; break; + + case ".GBA": + Game.System = "GBA"; + break; } Game.Name = Path.GetFileNameWithoutExtension(fileName).Replace('_', ' '); diff --git a/BizHawk.MultiClient/MainForm.cs b/BizHawk.MultiClient/MainForm.cs index 03526ba038..d416acbb8d 100644 --- a/BizHawk.MultiClient/MainForm.cs +++ b/BizHawk.MultiClient/MainForm.cs @@ -17,6 +17,7 @@ using BizHawk.MultiClient.tools; using System.Collections.Generic; using BizHawk.Emulation.Consoles.Intellivision; using BizHawk.Emulation.Consoles.GB; +using BizHawk.Emulation.Consoles.Nintendo.GBA; using BizHawk.Emulation.Computers.Commodore64; namespace BizHawk.MultiClient @@ -1301,6 +1302,7 @@ namespace BizHawk.MultiClient case "A78": str += "Atari 7800"; break; case "C64": str += "Commodore 64"; break; case "Coleco": str += "ColecoVision"; break; + case "GBA": str += "Game Boy Advance"; break; } if (INTERIM) str += " (interim)"; @@ -1822,6 +1824,11 @@ namespace BizHawk.MultiClient c64.HardReset(); nextEmulator = c64; break; + case "GBA": + GBA gba = new GBA(); + gba.Load(rom.RomData); + nextEmulator = gba; + break; } } diff --git a/BizHawk.MultiClient/movie/InputAdapters.cs b/BizHawk.MultiClient/movie/InputAdapters.cs index c86d892a25..fae5a3cfbd 100644 --- a/BizHawk.MultiClient/movie/InputAdapters.cs +++ b/BizHawk.MultiClient/movie/InputAdapters.cs @@ -437,6 +437,10 @@ namespace BizHawk.MultiClient { return GetC64ControllersAsMnemonic(); } + else if (ControlType == "GBA Controller") + { + return "EAT AT JOE'S"; + } StringBuilder input = new StringBuilder("|"); diff --git a/libmeteor/cinterface.cpp b/libmeteor/cinterface.cpp new file mode 100644 index 0000000000..aea61202ba --- /dev/null +++ b/libmeteor/cinterface.cpp @@ -0,0 +1,96 @@ +#include "ameteor.hpp" +#include "ameteor/cartmem.hpp" + +#define EXPORT extern "C" __declspec(dllexport) + +EXPORT void libmeteor_reset() +{ + AMeteor::Reset(AMeteor::UNIT_ALL ^ AMeteor::UNIT_MEMORY_BIOS); +} + +uint32_t *videobuff; + +void videocb(const uint16_t *frame) +{ + uint32_t *dest = videobuff; + const uint16_t *src = frame; + for (int i = 0; i < 240 * 160; i++, src++, dest++) + { + uint16_t c = *frame; + uint16_t b = c >> 10 & 31; + uint16_t g = c >> 5 & 31; + uint16_t r = c & 31; + b = b << 3 | b >> 2; + g = g << 3 | g >> 2; + r = r << 3 | r >> 2; + *dest = b | g << 8 | r << 16 | 0xff000000; + } + AMeteor::Stop(); // to the end of frame only +} + +int16_t *soundbuff; +int16_t *soundbuffcur; +int16_t *soundbuffend; + +void soundcb(const int16_t *samples) +{ + if (soundbuffcur < soundbuffend) + { + *soundbuffcur++ = *samples++; + *soundbuffcur++ = *samples++; + } +} + +EXPORT unsigned libmeteor_emptysound() +{ + unsigned ret = (soundbuffcur - soundbuff) * sizeof(int16_t); + soundbuffcur = soundbuff; + return ret; +} + +EXPORT int libmeteor_setbuffers(uint32_t *vid, unsigned vidlen, int16_t *aud, unsigned audlen) +{ + if (vidlen < 240 * 160 * sizeof(uint32_t)) + return 0; + if (audlen < 4 || audlen % 4 != 0) + return 0; + videobuff = vid; + soundbuff = aud; + soundbuffend = soundbuff + audlen; + libmeteor_emptysound(); + return 1; +} + +EXPORT void libmeteor_init() +{ + static bool first = true; + if (first) + { + //AMeteor::_memory.LoadCartInferred(); + AMeteor::_lcd.GetScreen().GetRenderer().SetFrameSlot(syg::ptr_fun(videocb)); + AMeteor::_sound.GetSpeaker().SetFrameSlot(syg::ptr_fun(soundcb)); + // TODO: input + first = false; + } +} + +EXPORT void libmeteor_frameadvance() +{ + AMeteor::_keypad.SetPadState(0x3ff); + AMeteor::Run(10000000); +} + +// TODO: serialize, unserialize + +EXPORT void libmeteor_loadrom(const void *data, unsigned size) +{ + AMeteor::_memory.LoadRom((const uint8_t*)data, size); +} + +EXPORT void libmeteor_loadbios(const void *data, unsigned size) +{ + AMeteor::_memory.LoadBios((const uint8_t*)data, size); +} + +// TODO: memory domains + diff --git a/libmeteor/include/ameteor.hpp b/libmeteor/include/ameteor.hpp new file mode 100644 index 0000000000..27dafb1fd8 --- /dev/null +++ b/libmeteor/include/ameteor.hpp @@ -0,0 +1,80 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __AMETEOR_H__ +#define __AMETEOR_H__ + +#include "ameteor/memory.hpp" +#include "ameteor/io.hpp" +#include "ameteor/dma.hpp" +#include "ameteor/interpreter.hpp" +#include "ameteor/lcd.hpp" +#include "ameteor/clock.hpp" +#include "ameteor/timer.hpp" +#include "ameteor/sound.hpp" +#include "ameteor/keypad.hpp" + +namespace AMeteor +{ + extern Clock _clock; + extern Io _io; + extern Interpreter _cpu; + extern Memory _memory; + extern Dma _dma; + extern Lcd _lcd; + extern Sound _sound; + extern Keypad _keypad; + extern Timer _timer0; + extern Timer _timer1; + extern Timer _timer2; + extern Timer _timer3; + + const uint32_t UNIT_CLOCK = 0x0001; + const uint32_t UNIT_IO = 0x0002; + const uint32_t UNIT_CPU = 0x0004; + const uint32_t UNIT_MEMORY = 0x0008; + const uint32_t UNIT_DMA = 0x0010; + const uint32_t UNIT_LCD = 0x0020; + const uint32_t UNIT_SOUND = 0x0040; + const uint32_t UNIT_KEYPAD = 0x0080; + const uint32_t UNIT_TIMER0 = 0x0100; + const uint32_t UNIT_TIMER1 = 0x0200; + const uint32_t UNIT_TIMER2 = 0x0400; + const uint32_t UNIT_TIMER3 = 0x0800; + const uint32_t UNIT_MEMORY_ROM = 0x1000; + const uint32_t UNIT_MEMORY_BIOS = 0x2000; + const uint32_t UNIT_ALL = 0x3FFF; + + void Reset (uint32_t units); + + bool SaveState (const char* filename); + bool LoadState (const char* filename); + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + inline void Run (unsigned int cycles) + { + _cpu.Run(cycles); + } + + inline void Stop () + { + _cpu.Stop(); + } +} + +#endif diff --git a/libmeteor/include/ameteor/audio/dsound.hpp b/libmeteor/include/ameteor/audio/dsound.hpp new file mode 100644 index 0000000000..85dc320b13 --- /dev/null +++ b/libmeteor/include/ameteor/audio/dsound.hpp @@ -0,0 +1,78 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __AUDIO_D_SOUND_H__ +#define __AUDIO_D_SOUND_H__ + +#include +#include +#include + +namespace AMeteor +{ + namespace Audio + { + class DSound + { + public : + static const int BUFFER_SIZE = 32; + + DSound (); + + void FillFifo (int8_t* buffer); + void FillFifo (int8_t sample); + + void NextSample () + { + // if the buffer is empty, there is nothing to do + if (m_size) + // if this was the last sample, we reset all and send 0 to all next + // GetSample()s until the buffer is refilled + if (--m_size == 0) + Reset(); + // else, we go on to next sample and we go to the first if we got + // to the last + else if (++m_rpos >= BUFFER_SIZE) + m_rpos = 0; + } + + void Reset () + { + m_buffer[0] = m_size = m_rpos = m_wpos = 0; + } + + int8_t GetSample() + { + return m_buffer[m_rpos]; + } + + uint8_t GetSize () + { + return m_size; + }; + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + int8_t m_buffer[BUFFER_SIZE]; + uint8_t m_rpos, m_wpos; + uint8_t m_size; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/audio/sound1.hpp b/libmeteor/include/ameteor/audio/sound1.hpp new file mode 100644 index 0000000000..282ff2c125 --- /dev/null +++ b/libmeteor/include/ameteor/audio/sound1.hpp @@ -0,0 +1,75 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __AUDIO_SOUND_1_H__ +#define __AUDIO_SOUND_1_H__ + +#include +#include +#include + +namespace AMeteor +{ + namespace Audio + { + class Sound1 + { + public : + Sound1 (uint16_t& cntl, uint16_t& cnth, uint16_t& cntx, + uint16_t freq); + + void Reset (); + + // call this at the frequence given in constructor + void SoundTick (); + + void ResetSound (); + void ResetEnvelope () + { + m_envelope = 0; + } + + int8_t GetSample () const + { + return m_sample; + } + + bool IsOn () const + { + return m_on; + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + uint16_t &m_cntl, &m_cnth, &m_cntx; + bool m_on; + // positions in Period, Sweep time and Envelope step time + uint32_t m_posP, m_posS, m_posE; + int8_t m_sample; + // sample period in cycles + uint16_t m_speriod; + // envelope level + uint8_t m_envelope; + // sound length in cycles + uint32_t m_length; + bool m_timed; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/audio/sound2.hpp b/libmeteor/include/ameteor/audio/sound2.hpp new file mode 100644 index 0000000000..d4c18d8aaa --- /dev/null +++ b/libmeteor/include/ameteor/audio/sound2.hpp @@ -0,0 +1,70 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __AUDIO_SOUND_2_H__ +#define __AUDIO_SOUND_2_H__ + +#include +#include +#include + +namespace AMeteor +{ + namespace Audio + { + class Sound2 + { + public : + Sound2 (uint16_t& cntl, uint16_t& cnth, uint16_t freq); + + void Reset (); + + // call this at the frequence given in constructor + void SoundTick (); + + void ResetSound (); + void ResetEnvelope () + { + m_envelope = 0; + } + + int8_t GetSample () const + { + return m_sample; + } + + bool IsOn () const + { + return m_on; + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + uint16_t &m_cntl, &m_cnth; + bool m_on; + uint32_t m_posP, m_posE; + int8_t m_sample; + uint16_t m_speriod; + uint8_t m_envelope; + uint32_t m_length; + bool m_timed; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/audio/sound4.hpp b/libmeteor/include/ameteor/audio/sound4.hpp new file mode 100644 index 0000000000..ab51d627d9 --- /dev/null +++ b/libmeteor/include/ameteor/audio/sound4.hpp @@ -0,0 +1,78 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __AUDIO_SOUND_4_H__ +#define __AUDIO_SOUND_4_H__ + +#include +#include +#include + +namespace AMeteor +{ + namespace Audio + { + void InitNoise (); + + class Sound4 + { + public : + Sound4 (uint16_t& cntl, uint16_t& cnth, uint16_t freq); + + void Reset (); + + // call this at the frequence given in constructor + void SoundTick (); + + void ResetSound (); + void ResetEnvelope () + { + m_envelope = 0; + } + + int8_t GetSample () const + { + return m_sample; + } + + bool IsOn () const + { + return m_on; + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + uint16_t &m_cntl, &m_cnth; + bool m_on; + // positions in Period, position in noise and Envelope step time + uint32_t m_posP, m_posN, m_posE; + int8_t m_sample; + // sample period in cycles + uint16_t m_speriod; + // envelope level + uint8_t m_envelope; + // sound length in cycles + uint32_t m_length; + bool m_timed; + // clock divider + uint8_t m_div; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/audio/speaker.hpp b/libmeteor/include/ameteor/audio/speaker.hpp new file mode 100644 index 0000000000..15d37f955b --- /dev/null +++ b/libmeteor/include/ameteor/audio/speaker.hpp @@ -0,0 +1,173 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __AUDIO_SPEAKER_H__ +#define __AUDIO_SPEAKER_H__ + +#include +#include +#include +#include +#include "sound1.hpp" +#include "sound2.hpp" +#include "sound4.hpp" +#include "dsound.hpp" + +namespace AMeteor +{ + namespace Audio + { + class Speaker + { + public : + typedef syg::slot1 FrameSlot; + + Speaker (uint16_t& cnt1l, uint16_t& cnt1h, uint16_t& cnt1x, + uint16_t& cnt2l, uint16_t& cnt2h, + uint16_t& cnt4l, uint16_t& cnt4h, + uint16_t& cntl, uint16_t& cnth, uint16_t& cntx, uint16_t& bias); + ~Speaker (); + + inline void SetFrameSlot (const FrameSlot& slot); + + void Reset (); + + inline void ResetSound1 (); + inline void ResetSound2 (); + inline void ResetSound4 (); + inline void ResetSound1Envelope (); + inline void ResetSound2Envelope (); + inline void ResetSound4Envelope (); + + inline void FillFifoA (int8_t* buffer); + inline void FillFifoB (int8_t* buffer); + inline void FillFifoA (int8_t sample); + inline void FillFifoB (int8_t sample); + + inline void ResetFifoA (); + inline void ResetFifoB (); + + inline void NextSampleA (); + inline void NextSampleB (); + + inline uint8_t GetSizeA(); + inline uint8_t GetSizeB(); + + void SoundTick (); + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + Sound1 m_sound1; + Sound2 m_sound2; + Sound4 m_sound4; + DSound m_dsa, m_dsb; + uint16_t &m_cntl, &m_cnth, &m_cntx, &m_bias; + + FrameSlot m_sig_frame; + + int16_t MixSample (uint16_t cntl, uint8_t cnth); + }; + + inline void Speaker::SetFrameSlot (const FrameSlot& slot) + { + m_sig_frame = slot; + } + + inline void Speaker::ResetSound1 () + { + m_sound1.ResetSound (); + } + + inline void Speaker::ResetSound2 () + { + m_sound2.ResetSound (); + } + + inline void Speaker::ResetSound4 () + { + m_sound4.ResetSound (); + } + + inline void Speaker::ResetSound1Envelope () + { + m_sound1.ResetEnvelope(); + } + + inline void Speaker::ResetSound2Envelope () + { + m_sound2.ResetEnvelope(); + } + + inline void Speaker::ResetSound4Envelope () + { + m_sound4.ResetEnvelope(); + } + + inline void Speaker::FillFifoA (int8_t* buffer) + { + m_dsa.FillFifo(buffer); + } + + inline void Speaker::FillFifoB (int8_t* buffer) + { + m_dsb.FillFifo(buffer); + } + + inline void Speaker::FillFifoA (int8_t sample) + { + m_dsa.FillFifo(sample); + } + + inline void Speaker::FillFifoB (int8_t sample) + { + m_dsb.FillFifo(sample); + } + + inline void Speaker::ResetFifoA () + { + m_dsa.Reset(); + } + + inline void Speaker::ResetFifoB () + { + m_dsb.Reset(); + } + + inline void Speaker::NextSampleA () + { + m_dsa.NextSample(); + } + + inline void Speaker::NextSampleB () + { + m_dsb.NextSample(); + } + + inline uint8_t Speaker::GetSizeA() + { + return m_dsa.GetSize(); + } + + inline uint8_t Speaker::GetSizeB() + { + return m_dsb.GetSize(); + } + } +} + +#endif diff --git a/libmeteor/include/ameteor/bios.hpp b/libmeteor/include/ameteor/bios.hpp new file mode 100644 index 0000000000..05a3b7db70 --- /dev/null +++ b/libmeteor/include/ameteor/bios.hpp @@ -0,0 +1,59 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __BIOS_H__ +#define __BIOS_H__ + +#include "ameteor.hpp" + +namespace AMeteor +{ + namespace Bios + { + // Entry point + void Bios000h (); + // Software IRQ + void Bios008h (); + void Bios168h (); + // Return from IntrWait (after the IRQ) + void Bios338h (); + // IRQ + void Bios018h (); + void Bios130h (); + + void SoftReset (); // 00 + void RegisterRamReset (); // 01 + void Halt (); // 02 + void IntrWait (); // 04 + void VBlankIntrWait (); // 05 + void Div (); // 06 + void DivArm (); // 07 + void Sqrt (); // 08 + void ArcTan (); // 09 + void ArcTan2 (); // 0A + void CpuSet (); // 0B + void CpuFastSet (); // 0C + void BgAffineSet (); // 0E + void ObjAffineSet (); // 0F + void LZ77UnCompWram (); // 11 + void LZ77UnCompVram (); // 12 + void HuffUnComp (); // 13 + void RLUnCompWram (); // 14 + void RLUnCompVram (); // 15 + } +} + +#endif diff --git a/libmeteor/include/ameteor/cartmem.hpp b/libmeteor/include/ameteor/cartmem.hpp new file mode 100644 index 0000000000..e7ee43678a --- /dev/null +++ b/libmeteor/include/ameteor/cartmem.hpp @@ -0,0 +1,57 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __CART_MEM_H__ +#define __CART_MEM_H__ + +#include +#include +#include +#include + +namespace AMeteor +{ + class CartMem + { + public: + static const unsigned int MAX_SIZE = 0x20000; + + CartMem(); + virtual ~CartMem(); + + virtual void Reset () = 0; + + virtual bool Load (std::istream& stream) = 0; + virtual bool Save (std::ostream& stream) = 0; + + virtual uint8_t Read (uint16_t add) = 0; + // returns true if memory has been updated + virtual bool Write (uint16_t add, uint8_t val) = 0; + + virtual bool SaveState (std::ostream& stream); + virtual bool LoadState (std::istream& stream); + + protected: + uint8_t* m_data; + uint32_t m_size; + }; + +#ifdef __LIBRETRO__ + extern uint8_t CartMemData[CartMem::MAX_SIZE+4]; +#endif +} + +#endif diff --git a/libmeteor/include/ameteor/clock.hpp b/libmeteor/include/ameteor/clock.hpp new file mode 100644 index 0000000000..8d001f962b --- /dev/null +++ b/libmeteor/include/ameteor/clock.hpp @@ -0,0 +1,111 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __CLOCK_H__ +#define __CLOCK_H__ + +#include +#include +#include +#include +#include + +namespace AMeteor +{ + class Clock + { + public : + Clock () + { + Reset(); + } + + void Reset (); + + void ResetCounter() + { + m_count = 0; + } + unsigned int GetCounter() const + { + return m_count; + } + + void TimePass (unsigned short cycles) + { + m_cycles += cycles; + } + void Commit (); + void WaitForNext (); + + void AddLcd (uint32_t cycles) + { + // The lcd clock is always enabled + m_lcd += cycles; + SetFirst(); + } + + void AddTimer (uint8_t num, uint32_t cycles) + { + if (m_timer[num] == INT_MAX) + m_timer[num] = cycles; + else + m_timer[num] += cycles; + SetFirst(); + } + void SetTimer (uint8_t num, uint32_t cycles) + { + m_timer[num] = cycles; + SetFirst(); + } + void DisableTimer (uint8_t num) + { + m_timer[num] = INT_MAX; + SetFirst(); + } + int GetTimer (uint8_t num) + { + return m_timer[num]; + } + + void SetBattery (uint32_t cycles) + { + m_battery = cycles; + } + void DisableBattery () + { + m_battery = INT_MAX; + // no need to SetFirst since battery will be disabled only in TimeEvent + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + // XXX freq + static const int SOUND_PERIOD = 380; + + unsigned short m_cycles; + unsigned short m_first; + int m_lcd, m_timer[4], m_sound, m_battery; + + unsigned int m_count; + + void SetFirst (); + }; +} + +#endif diff --git a/libmeteor/include/ameteor/cpu.hpp b/libmeteor/include/ameteor/cpu.hpp new file mode 100644 index 0000000000..3f8e820ab5 --- /dev/null +++ b/libmeteor/include/ameteor/cpu.hpp @@ -0,0 +1,148 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __CPU_H__ +#define __CPU_H__ + +#include +#include +#include + +namespace AMeteor +{ + class Cpu + { + public : + union Psr + { + uint32_t dw; + struct + { + unsigned int mode : 5; + bool thumb : 1; + bool fiq_d : 1; + bool irq_d : 1; + unsigned int reserved : 19; + bool s_overflow : 1; + bool f_overflow : 1; + bool f_carry : 1; + bool f_zero : 1; + bool f_sign : 1; + } b; + }; + struct IPsr + { + uint8_t mode; + bool thumb; + bool fiq_d; + bool irq_d; + bool s_overflow; + bool f_overflow; + bool f_carry; + bool f_zero; + bool f_sign; + }; + enum Modes + { + M_USR = 0x10, + M_FIQ = 0x11, + M_IRQ = 0x12, + M_SVC = 0x13, + M_ABT = 0x17, + M_UND = 0x1B, + M_SYS = 0x1F + }; + + Cpu (); + virtual ~Cpu () {} + + virtual void Reset (); + virtual void SoftReset (); + + void UpdateICpsr (); + void UpdateCpsr (); + void SwitchToMode (uint8_t newmode); + void SwitchModeBack (); + + virtual void SendInterrupt (uint16_t interrupt) = 0; + virtual void CheckInterrupt () = 0; + void Interrupt (); + void SoftwareInterrupt (uint32_t comment); + void SoftwareInterrupt (); + + uint32_t& Reg(uint8_t r) + { + return m_st.r[r]; + } + + Psr& Cpsr() + { + return m_st.cpsr; + } + IPsr& ICpsr() + { + return m_st.icpsr; + } + Psr& Spsr() + { + return m_st.spsr; + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + protected : + struct CPUState + { + // Current registers + uint32_t r[16]; + Psr cpsr, spsr; + IPsr icpsr; + + // System/User + uint32_t usr_r[7]; // from 8 to 14 + + // FIQ + uint32_t fiq_r[7]; // from 8 to 14 + Psr fiq_spsr; + + // Supervisor + uint32_t svc_r[2]; // 13 and 14 + Psr svc_spsr; + + // Abort + uint32_t abt_r[2]; // 13 and 14 + Psr abt_spsr; + + // IRQ + uint32_t irq_r[2]; // 13 and 14 + Psr irq_spsr; + + // Undefined + uint32_t und_r[2]; // 13 and 14 + Psr und_spsr; + }; + + CPUState m_st; + + virtual void SetInterrupt (bool interrupt) = 0; + + private : + void SaveMode (uint8_t mode); + }; +} + +#endif diff --git a/libmeteor/include/ameteor/disassembler/argimmediate.hpp b/libmeteor/include/ameteor/disassembler/argimmediate.hpp new file mode 100644 index 0000000000..cb372f1067 --- /dev/null +++ b/libmeteor/include/ameteor/disassembler/argimmediate.hpp @@ -0,0 +1,44 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __ARG_IMMEDIATE_H__ +#define __ARG_IMMEDIATE_H__ + +#include "argument.hpp" +#include + +namespace AMeteor +{ + namespace Disassembler + { + class ArgImmediate : public Argument + { + public : + ArgImmediate (int32_t imm) : + m_imm(imm) + { } + + Argument* Clone () const; + + std::string GetString () const; + + private : + int32_t m_imm; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/disassembler/argmulregisters.hpp b/libmeteor/include/ameteor/disassembler/argmulregisters.hpp new file mode 100644 index 0000000000..73a6c241a1 --- /dev/null +++ b/libmeteor/include/ameteor/disassembler/argmulregisters.hpp @@ -0,0 +1,67 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __ARG_MUL_REGISTERS_H__ +#define __ARG_MUL_REGISTERS_H__ + +#include "argument.hpp" + +#include +#include + +namespace AMeteor +{ + namespace Disassembler + { + enum SpecialRegister + { + SPREG_NONE = 0, + SPREG_LR = 1, + SPREG_PC = 2 + }; + + class ArgMulRegisters : public Argument + { + public : + ArgMulRegisters (bool forceuser) : + m_lastreg(SPREG_NONE), + m_forceuser(forceuser) + { } + + Argument* Clone () const; + + void AddRegister(uint8_t reg) + { + m_regs.push_back(reg); + } + void AddLastRegister(SpecialRegister reg) + { + m_lastreg = reg; + } + + std::string GetString () const; + + private : + typedef std::vector Registers; + + Registers m_regs; + SpecialRegister m_lastreg; + bool m_forceuser; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/disassembler/argpsr.hpp b/libmeteor/include/ameteor/disassembler/argpsr.hpp new file mode 100644 index 0000000000..5557615a99 --- /dev/null +++ b/libmeteor/include/ameteor/disassembler/argpsr.hpp @@ -0,0 +1,47 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __ARG_PSR_H__ +#define __ARG_PSR_H__ + +#include "argument.hpp" + +#include + +namespace AMeteor +{ + namespace Disassembler + { + class ArgPsr : public Argument + { + public : + ArgPsr (bool spsr, uint8_t fields = 0xFF) : + m_spsr(spsr), + m_fields(fields) + { } + + Argument* Clone () const; + + std::string GetString () const; + + private : + bool m_spsr; + uint8_t m_fields; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/disassembler/argregister.hpp b/libmeteor/include/ameteor/disassembler/argregister.hpp new file mode 100644 index 0000000000..78a26b44e1 --- /dev/null +++ b/libmeteor/include/ameteor/disassembler/argregister.hpp @@ -0,0 +1,56 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __ARG_REGISTER_H__ +#define __ARG_REGISTER_H__ + +#include "argument.hpp" + +#include + +namespace AMeteor +{ + namespace Disassembler + { + class ArgRegister : public Argument + { + public : + ArgRegister (uint8_t reg, bool writeback = false, + bool special = false, bool memory = false) : + m_reg(reg), + m_writeback(writeback), + m_special(special), + m_memory(memory) + { } + + Argument* Clone () const; + + std::string GetString () const; + uint8_t GetRegister () const + { + return m_reg; + } + + private : + uint8_t m_reg; + bool m_writeback; + bool m_special; + bool m_memory; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/disassembler/argrelative.hpp b/libmeteor/include/ameteor/disassembler/argrelative.hpp new file mode 100644 index 0000000000..c7ccc9e212 --- /dev/null +++ b/libmeteor/include/ameteor/disassembler/argrelative.hpp @@ -0,0 +1,50 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __ARG_RELATIVE_H__ +#define __ARG_RELATIVE_H__ + +#include "argument.hpp" +#include "argregister.hpp" +#include "argimmediate.hpp" + +namespace AMeteor +{ + namespace Disassembler + { + class ArgRelative : public Argument + { + public : + ArgRelative (const ArgRegister& reg, const Argument& off, + bool pre, bool up, bool writeback); + ArgRelative (const ArgRelative& arg); + ~ArgRelative (); + + Argument* Clone () const; + + std::string GetString () const; + + private : + ArgRegister m_reg; + const Argument* m_off; + bool m_pre; + bool m_up; + bool m_writeback; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/disassembler/argshift.hpp b/libmeteor/include/ameteor/disassembler/argshift.hpp new file mode 100644 index 0000000000..1ba586b6d7 --- /dev/null +++ b/libmeteor/include/ameteor/disassembler/argshift.hpp @@ -0,0 +1,56 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __ARG_SHIFT_H__ +#define __ARG_SHIFT_H__ + +#include "argument.hpp" + +namespace AMeteor +{ + namespace Disassembler + { + enum ShiftType + { + SHIFT_LSL = 0, + SHIFT_LSR, + SHIFT_ASR, + SHIFT_ROR, + SHIFT_RRX + }; + + class ArgShift : public Argument + { + public : + ArgShift (const Argument& arg1, const Argument& arg2, + ShiftType type, bool memory); + ArgShift (const ArgShift& arg); + ~ArgShift (); + + Argument* Clone () const; + + std::string GetString () const; + + private : + const Argument* m_arg1; + const Argument* m_arg2; + ShiftType m_type; + bool m_memory; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/disassembler/arguimmediate.hpp b/libmeteor/include/ameteor/disassembler/arguimmediate.hpp new file mode 100644 index 0000000000..450322eba3 --- /dev/null +++ b/libmeteor/include/ameteor/disassembler/arguimmediate.hpp @@ -0,0 +1,45 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __ARG_U_IMMEDIATE_H__ +#define __ARG_U_IMMEDIATE_H__ + +#include "argument.hpp" + +#include + +namespace AMeteor +{ + namespace Disassembler + { + class ArgUImmediate : public Argument + { + public : + ArgUImmediate (uint32_t imm) : + m_imm(imm) + { } + + Argument* Clone () const; + + std::string GetString () const; + + private : + uint32_t m_imm; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/disassembler/argument.hpp b/libmeteor/include/ameteor/disassembler/argument.hpp new file mode 100644 index 0000000000..de007d0d7f --- /dev/null +++ b/libmeteor/include/ameteor/disassembler/argument.hpp @@ -0,0 +1,39 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __ARGUMENT_H__ +#define __ARGUMENT_H__ + +#include + +namespace AMeteor +{ + namespace Disassembler + { + class Argument + { + public : + virtual ~Argument () + { } + + virtual Argument* Clone () const = 0; + + virtual std::string GetString () const = 0; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/disassembler/arguments.hpp b/libmeteor/include/ameteor/disassembler/arguments.hpp new file mode 100644 index 0000000000..dd078da2ef --- /dev/null +++ b/libmeteor/include/ameteor/disassembler/arguments.hpp @@ -0,0 +1,48 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __ARGUMENTS_H__ +#define __ARGUMENTS_H__ + +#include "argument.hpp" + +#include + +namespace AMeteor +{ + namespace Disassembler + { + class Arguments + { + public : + ~Arguments (); + + void Clear (); + + void AddArgument(const Argument& arg) + { + m_args.push_back(arg.Clone()); + } + + std::string GetString () const; + + private : + std::vector m_args; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/disassembler/instruction.hpp b/libmeteor/include/ameteor/disassembler/instruction.hpp new file mode 100644 index 0000000000..3da87b8be1 --- /dev/null +++ b/libmeteor/include/ameteor/disassembler/instruction.hpp @@ -0,0 +1,76 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __INSTRUCTION_H__ +#define __INSTRUCTION_H__ + +#include "arguments.hpp" + +#include +#include + +namespace AMeteor +{ + namespace Disassembler + { + class Instruction + { + public : + Instruction () + { + } + + explicit Instruction (uint32_t offset, uint32_t code) + { + ParseArm (offset, code); + } + + explicit Instruction (uint32_t offset, uint16_t code) + { + ParseThumb(offset, code); + } + + void Clear (); + + void ParseArm (uint32_t offset, uint32_t code); + void ParseThumb (uint32_t offset, uint16_t code); + + const std::string& GetOperator () const + { + return m_operator; + } + + std::string GetArguments () const + { + return m_args.GetString(); + } + + std::string ToString () const + { + return GetOperator() + ' ' + GetArguments(); + } + + private : + std::string m_operator; + Arguments m_args; + + void ParseArmDataProc (uint32_t code); + void ParseArmCondition (uint32_t code); + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/dma.hpp b/libmeteor/include/ameteor/dma.hpp new file mode 100644 index 0000000000..571c44c27a --- /dev/null +++ b/libmeteor/include/ameteor/dma.hpp @@ -0,0 +1,112 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __DMA_H__ +#define __DMA_H__ + +#include +#include +#include + +namespace AMeteor +{ + class Dma + { + public : + enum Reason + { + Immediately = 0, + VBlank, + HBlank, + Special + }; + + Dma () : + m_graphic(false) + { } + + void Reset (); + + bool GraphicDma () const + { + return m_graphic; + } + + void SetReload(uint8_t channum, uint16_t reload) + { + m_chans[channum].reload = reload; + } + + void UpdateCnt (uint8_t channum); + void Check(uint8_t channum, uint8_t reason); + inline void CheckAll(uint8_t reason) + { + Check(0, reason); + Check(1, reason); + Check(2, reason); + Check(3, reason); + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + struct Channel + { + Channel () : + reload(0), + src(0), + dest(0), + count(0), + control(0) + { } + + uint16_t reload; + uint32_t src; + uint32_t dest; + uint16_t count; + union Control + { + Control(uint16_t v) : + w(v) + { } + + uint16_t w; + struct + { + unsigned int unused : 5; + unsigned int dest : 2; + unsigned int src : 2; + unsigned int repeat : 1; + unsigned int type : 1; + unsigned int drq : 1; + unsigned int start : 2; + unsigned int irq : 1; + unsigned int enable : 1; + } b; + } control; + }; + + Channel m_chans[4]; + bool m_graphic; + + void Process(uint8_t channel); + void Copy (uint32_t& src, uint32_t& dest, int8_t s_inc, int8_t d_inc, + uint32_t count, bool word); + }; +} + +#endif diff --git a/libmeteor/include/ameteor/eeprom.hpp b/libmeteor/include/ameteor/eeprom.hpp new file mode 100644 index 0000000000..8052a5b393 --- /dev/null +++ b/libmeteor/include/ameteor/eeprom.hpp @@ -0,0 +1,79 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __EEPROM_H__ +#define __EEPROM_H__ + +#include "cartmem.hpp" +#include +#include +#include + +namespace AMeteor +{ + class Eeprom : public CartMem + { + public : + Eeprom (bool big); + + void Reset (); + + uint16_t GetSize () const + { + return m_size; + } + + bool Load (std::istream& f); + bool Save (std::ostream& f); + + uint8_t Read (uint16_t add); + bool Write (uint16_t add, uint8_t val); + + uint16_t Read (); + //bool Write (uint16_t val); + + bool Write (uint16_t* data, uint16_t size); + //XXX +#if 0 + void Read (uint16_t* pOut); +#endif + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + enum State + { + IDLE, + //WAITING, + + //READ_ADD, + //READ_END, + READ_GARBAGE, + READ_DATA + + /*WRITE_ADD, + WRITE_DATA, + WRITE_END*/ + }; + + uint8_t m_state; + uint16_t m_add; + uint8_t m_pos; + }; +} + +#endif diff --git a/libmeteor/include/ameteor/flash.hpp b/libmeteor/include/ameteor/flash.hpp new file mode 100644 index 0000000000..3c56d89747 --- /dev/null +++ b/libmeteor/include/ameteor/flash.hpp @@ -0,0 +1,63 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __FLASH_H__ +#define __FLASH_H__ + +#include "cartmem.hpp" +#include +#include +#include + +namespace AMeteor +{ + class Flash : public CartMem + { + public : + Flash (bool big); + + void Reset (); + + bool Load (std::istream& f); + bool Save (std::ostream& f); + + uint8_t Read (uint16_t add); + bool Write (uint16_t add, uint8_t val); + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + uint8_t m_device_id; + uint8_t m_manufacturer_id; + + enum State + { + NORMAL, + CMD1, + CMD2, + ID, + ERASE1, + ERASE2, + ERASE3, + WRITE + }; + + State m_state; + }; +} + +#endif diff --git a/libmeteor/include/ameteor/graphics/bglayer.hpp b/libmeteor/include/ameteor/graphics/bglayer.hpp new file mode 100644 index 0000000000..1409929041 --- /dev/null +++ b/libmeteor/include/ameteor/graphics/bglayer.hpp @@ -0,0 +1,97 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __GRAPHICS_BG_LAYER_H__ +#define __GRAPHICS_BG_LAYER_H__ + +#include "ameteor/memory.hpp" +#include "ameteor/io.hpp" + +#include +#include + +namespace AMeteor +{ + namespace Graphics + { + class BgLayer + { + public : + BgLayer (int8_t num, Memory& memory, Io& io, uint16_t* pPalette); + ~BgLayer (); + + inline uint8_t GetNum () const; + inline uint8_t GetPriority () const; + + void DrawLine0 (uint8_t line, uint16_t* ptr); + void DrawLine2 (uint16_t* ptr, + int32_t refX, int32_t refY, + int16_t dx, int16_t dy); + void DrawLine3 (uint16_t* ptr, + int32_t refX, int32_t refY, + int16_t dx, int16_t dy); + void DrawLine4 (uint8_t line, uint16_t* ptr, + int32_t curX, int32_t curY, + int16_t dx, int16_t dmx, int16_t dy, int16_t dmy, bool frame1); + void FillList (); + + void UpdateCnt (uint16_t cnt); + inline void UpdateXOff (uint16_t off); + inline void UpdateYOff (uint16_t off); + + private : + Memory& m_memory; + Io& m_io; + + const uint8_t m_num; + uint8_t m_priority; + + uint16_t m_cnt; + bool m_hicolor; + uint16_t m_xoff, m_yoff; + // in text mode + uint8_t m_tWidth, m_tHeight; + // in rotation/scale mode + uint8_t m_rWidth, m_rHeight; + + uint32_t m_mapAdd; + uint32_t m_charAdd; + uint16_t* m_pPalette; + }; + + inline uint8_t BgLayer::GetNum () const + { + return m_num; + } + + inline uint8_t BgLayer::GetPriority () const + { + return m_priority; + } + + inline void BgLayer::UpdateXOff (uint16_t off) + { + m_xoff = off; + } + + inline void BgLayer::UpdateYOff (uint16_t off) + { + m_yoff = off; + } + } +} + +#endif diff --git a/libmeteor/include/ameteor/graphics/object.hpp b/libmeteor/include/ameteor/graphics/object.hpp new file mode 100644 index 0000000000..c1611c149b --- /dev/null +++ b/libmeteor/include/ameteor/graphics/object.hpp @@ -0,0 +1,100 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __GRAPHICS_OBJECT_H__ +#define __GRAPHICS_OBJECT_H__ + +#include +#include +#include + +namespace AMeteor +{ + namespace Graphics + { + class Object + { + public : + static const uint8_t FLIP_HORIZONTAL = 1; + static const uint8_t FLIP_VERTICAL = 2; + + Object (uint16_t* pPalette, uint8_t* pChar); + // Warning : this copy constructor must not be used on an used object + // use it only on just created objects + Object (const Object& obj); + + inline uint8_t GetPriority () const; + inline int8_t GetRotationParam () const; + inline uint16_t GetTileNum () const; + inline bool IsWindow () const; + + void DrawLine (uint8_t line, uint32_t* surface, bool oneDim, + uint8_t mosaic); + void DrawLineRot (uint8_t line, uint32_t* surface, bool oneDim, + int16_t a, int16_t b, int16_t c, int16_t d, uint8_t mosaic); + void DrawWindow (uint8_t line, uint8_t* surface, bool oneDim, + uint8_t mask); + void DrawWindowRot (uint8_t line, uint8_t* surface, + bool oneDim, int16_t a, int16_t b, int16_t c, int16_t d, + uint8_t mask); + + void UpdateAttrs (uint16_t attr0, uint16_t attr1, uint16_t attr2); + void UpdateAttr0 (uint16_t attr); + void UpdateAttr1 (uint16_t attr); + void UpdateAttr2 (uint16_t attr); + + private : + inline void SetSize (); + + enum Shape + { + SHAPE_SQUARE = 0, + SHAPE_HORIZONTAL, + SHAPE_VERTICAL, + SHAPE_PROHIBITED + }; + + uint16_t m_attr0, m_attr1, m_attr2; + uint8_t m_width, m_height; + uint16_t* m_pPalette; + uint8_t* m_pChar; + uint32_t m_charBegin; + uint32_t m_charEnd; + }; + + inline int8_t Object::GetRotationParam () const + { + return (m_attr0 & (0x1 << 8)) ? (m_attr1 >> 9) & 0x1F : -1; + } + + inline uint8_t Object::GetPriority () const + { + return (m_attr2 >> 10) & 0x3; + } + + inline bool Object::IsWindow () const + { + return (m_attr0 & (0x3 << 10)) == (0x2 << 10); + } + + inline uint16_t Object::GetTileNum () const + { + return (m_attr2 & 0x3FF); + } + } +} + +#endif diff --git a/libmeteor/include/ameteor/graphics/objects.hpp b/libmeteor/include/ameteor/graphics/objects.hpp new file mode 100644 index 0000000000..a398080bd7 --- /dev/null +++ b/libmeteor/include/ameteor/graphics/objects.hpp @@ -0,0 +1,53 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __GRAPHICS_OBJECTS_H__ +#define __GRAPHICS_OBJECTS_H__ + +#include "object.hpp" +#include "ameteor/memory.hpp" +#include "ameteor/io.hpp" + +#include + +namespace AMeteor +{ + namespace Graphics + { + class Objects + { + public : + Objects (Memory& memory, Io& io, uint16_t* pPalette); + + void DrawLine (uint8_t line, uint32_t* surface); + void DrawLineHighOnly (uint8_t line, uint32_t* surface); + void DrawWindow (uint8_t line, uint8_t* surface); + + void OamWrite (uint32_t begin, uint32_t end); + void OamWrite16 (uint32_t add); + void OamWrite32 (uint32_t add); + + private : + typedef std::vector Objs; + + Io& m_io; + Objs m_objs; + uint16_t* m_pOam; + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/graphics/renderer.hpp b/libmeteor/include/ameteor/graphics/renderer.hpp new file mode 100644 index 0000000000..d94b63c253 --- /dev/null +++ b/libmeteor/include/ameteor/graphics/renderer.hpp @@ -0,0 +1,52 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __GRAPHICS_RENDERER_H__ +#define __GRAPHICS_RENDERER_H__ + +#include + +#include + +namespace AMeteor +{ + namespace Graphics + { + class Renderer + { + public: + typedef syg::slot1 FrameSlot; + + Renderer(const uint16_t* surface); + + inline void SetFrameSlot(const FrameSlot& slot); + + void VBlank(); + + private : + const uint16_t* m_base; + + FrameSlot m_sig_frame; + }; + + void Renderer::SetFrameSlot(const FrameSlot& slot) + { + m_sig_frame = slot; + } + } +} + +#endif diff --git a/libmeteor/include/ameteor/graphics/screen.hpp b/libmeteor/include/ameteor/graphics/screen.hpp new file mode 100644 index 0000000000..4482a7ac68 --- /dev/null +++ b/libmeteor/include/ameteor/graphics/screen.hpp @@ -0,0 +1,148 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __GRAPHICS_SCREEN_H__ +#define __GRAPHICS_SCREEN_H__ + +#include "bglayer.hpp" +#include "objects.hpp" +#include "renderer.hpp" + +#include +#include +#include + +namespace AMeteor +{ + namespace Graphics + { + class Screen + { + public : + static const uint8_t WIDTH = 240; + static const uint8_t HEIGHT = 160; + + // we skip x frames every FRMSKIP_TOTAL frames + static const uint8_t FRMSKIP_TOTAL = 10; + + Screen (Memory& memory, Io& io); + ~Screen (); + + Renderer& GetRenderer() + { + return m_renderer; + } + const Renderer& GetRenderer() const + { + return m_renderer; + } + + const uint16_t* GetSurface () const + { + return m_surface; + } + + void SetFrameskip (uint8_t skip) + { + m_frameskip = skip; + m_curframe = 0; + } + + void DrawLine (uint8_t line); + + void UpdateDispCnt (uint16_t dispcnt) + { + m_dispcnt = dispcnt; + } + +#define UPDATE_BG_CNT(num) \ + void UpdateBg##num##Cnt (uint16_t cnt) \ + { \ + m_bgLayer##num.UpdateCnt(cnt); \ + } + UPDATE_BG_CNT(0) + UPDATE_BG_CNT(1) + UPDATE_BG_CNT(2) + UPDATE_BG_CNT(3) +#undef UPDATE_BG_CNT + +#define UPDATE_BG_OFF(num, coord) \ + void UpdateBg##num##coord##Off (uint16_t off) \ + { \ + m_bgLayer##num.Update##coord##Off(off); \ + } + UPDATE_BG_OFF(0, X) + UPDATE_BG_OFF(0, Y) + UPDATE_BG_OFF(1, X) + UPDATE_BG_OFF(1, Y) + UPDATE_BG_OFF(2, X) + UPDATE_BG_OFF(2, Y) + UPDATE_BG_OFF(3, X) + UPDATE_BG_OFF(3, Y) +#undef UPDATE_BG_OFF + +#define UPDATE_BG_REF(num, coord) \ + void UpdateBg##num##Ref##coord(int32_t x) \ + { \ + m_ref##coord##num = (x & (0x1 << 27)) ? x | 0xF0000000 : x & 0x07FFFFFF; \ + } + UPDATE_BG_REF(2, X) + UPDATE_BG_REF(2, Y) + UPDATE_BG_REF(3, X) + UPDATE_BG_REF(3, Y) +#undef UPDATE_BG_REF + + void OamWrite (uint32_t begin, uint32_t end) + { + m_objs.OamWrite (begin, end); + } + void OamWrite16 (uint32_t add) + { + m_objs.OamWrite16 (add); + } + void OamWrite32 (uint32_t add) + { + m_objs.OamWrite32 (add); + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + Io& m_io; + + uint16_t* m_surface; + + Renderer m_renderer; + + uint8_t m_frameskip, m_curframe; + + uint16_t m_dispcnt; + int32_t m_refX2, m_refY2, m_refX3, m_refY3; + uint16_t* m_pPalette; + + // FIXME is this REALLY useful ? + static BgLayer Screen::* const BgLayers [4]; + BgLayer m_bgLayer0, m_bgLayer1, m_bgLayer2, m_bgLayer3; + Objects m_objs; + + void DrawWindow (uint8_t line, uint8_t* surface, + uint16_t win0v, uint16_t win0h, uint8_t mask); + }; + } +} + +#endif diff --git a/libmeteor/include/ameteor/interpreter.hpp b/libmeteor/include/ameteor/interpreter.hpp new file mode 100644 index 0000000000..5f02df4686 --- /dev/null +++ b/libmeteor/include/ameteor/interpreter.hpp @@ -0,0 +1,133 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __INTERPRETER_H__ +#define __INTERPRETER_H__ + +#include +#include +#include +#include "cpu.hpp" + +#define ARM(name) \ + inline void a##name () +#define NIARM(name) \ + void a##name () +#define THUMB(name) \ + inline void t##name () +#define NITHUMB(name) \ + void t##name () + +namespace AMeteor +{ + class Interpreter : public Cpu + { + public : + Interpreter(); + + void Reset () + { + m_interrupt = m_interrupt_ = false; + m_run = false; + Cpu::Reset(); + } + void SoftReset () + { + m_interrupt_ = m_interrupt = false; + Cpu::SoftReset(); + } + + void SendInterrupt (uint16_t interrupt); + void CheckInterrupt (); + + void Run (unsigned int cycles); + void Stop () + { + m_run = false; + } + bool IsRunning () + { + return m_run; + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + protected : + void SetInterrupt (bool interrupt) + { + m_interrupt = interrupt; + } + + private : + bool m_run; + + bool m_interrupt; + bool m_interrupt_; + uint32_t code; + uint8_t& m_haltcnt; + // only for use in halt mode, in normal mode we use m_interrupt + uint16_t& m_if; + uint16_t& m_ie; + + NIARM(_Code); + inline bool a_CheckCondition (uint8_t cond); + inline void a_DataProcCore(uint8_t rd, uint32_t op1, uint32_t op2, + bool shiftcarry); + + ARM(BXBLX); + ARM(BBL); + NIARM(_DataProcShiftImm); + NIARM(_DataProcShiftReg); + NIARM(_DataProcImm); + ARM(PSR); + ARM(_Multiply); + ARM(LDRSTR); + ARM(STRLDR_HD); + ARM(LDMSTM); + ARM(SWP); + ARM(SWI); + + NITHUMB(_Code); + + THUMB(_Shift); + THUMB(ADDSUB); + THUMB(_Imm); + THUMB(_ALU); + THUMB(_HiRegOp); + THUMB(LDRimm); + THUMB(STRLDRreg); + THUMB(STRLDRoff); + THUMB(LDRHSTRHoff); + THUMB(STRLDRsp); + THUMB(ADDpcsp); + THUMB(ADDsp); + THUMB(PUSHPOP); + THUMB(STMLDM); + THUMB(_CondBranch); + THUMB(SWI); + THUMB(B); + THUMB(_BL1); + THUMB(_BL2); + }; +} + +#undef ARM +#undef NIARM +#undef THUMB +#undef NITHUMB + +#endif diff --git a/libmeteor/include/ameteor/io.hpp b/libmeteor/include/ameteor/io.hpp new file mode 100644 index 0000000000..5f553c016c --- /dev/null +++ b/libmeteor/include/ameteor/io.hpp @@ -0,0 +1,216 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __IO_H__ +#define __IO_H__ + +#include +#include +#include + +namespace AMeteor +{ + class Io + { + public : + enum IoAddress + { + // LCD I/O Registers + DISPCNT = 0x000, + DISPSTAT = 0x004, + VCOUNT = 0x006, + BG0CNT = 0x008, + BG1CNT = 0x00A, + BG2CNT = 0x00C, + BG3CNT = 0x00E, + BG0HOFS = 0x010, + BG0VOFS = 0x012, + BG1HOFS = 0x014, + BG1VOFS = 0x016, + BG2HOFS = 0x018, + BG2VOFS = 0x01A, + BG3HOFS = 0x01C, + BG3VOFS = 0x01E, + BG2PA = 0x020, + BG2PB = 0x022, + BG2PC = 0x024, + BG2PD = 0x026, + BG2X_L = 0x028, + BG2X_H = 0x02A, + BG2Y_L = 0x02C, + BG2Y_H = 0x02E, + BG3PA = 0x030, + BG3PB = 0x032, + BG3PC = 0x034, + BG3PD = 0x036, + BG3X_L = 0x038, + BG3X_H = 0x03A, + BG3Y_L = 0x03C, + BG3Y_H = 0x03E, + WIN0H = 0x040, + WIN1H = 0x042, + WIN0V = 0x044, + WIN1V = 0x046, + WININ = 0x048, + WINOUT = 0x04A, + MOSAIC = 0x04C, + BLDCNT = 0x050, + BLDALPHA = 0x052, + BLDY = 0x054, + // Sound Registers + SOUND1CNT_L = 0x060, NR10 = 0x060, + SOUND1CNT_H = 0x062, NR11 = 0x062, + NR12 = 0x063, + SOUND1CNT_X = 0x064, NR13 = 0x064, + NR14 = 0x065, + SOUND2CNT_L = 0x068, NR21 = 0x068, + NR22 = 0x069, + SOUND2CNT_H = 0x06C, NR23 = 0x06C, + NR24 = 0x06D, + SOUND4CNT_L = 0x078, NR41 = 0x078, + NR42 = 0x079, + SOUND4CNT_H = 0x07C, NR43 = 0x07C, + NR44 = 0x07D, + SOUNDCNT_L = 0x080, NR50 = 0x080, + NR51 = 0x081, + SOUNDCNT_H = 0x082, + SOUNDCNT_X = 0x084, NR52 = 0x084, + SOUNDBIAS = 0x088, + FIFO_A = 0x0A0, + FIFO_B = 0x0A4, + // DMA Transfer Channels + DMA0SAD = 0x0B0, + DMA0DAD = 0x0B4, + DMA0CNT_L = 0x0B8, + DMA0CNT_H = 0x0BA, + DMA1SAD = 0x0BC, + DMA1DAD = 0x0C0, + DMA1CNT_L = 0x0C4, + DMA1CNT_H = 0x0C6, + DMA2SAD = 0x0C8, + DMA2DAD = 0x0CC, + DMA2CNT_L = 0x0D0, + DMA2CNT_H = 0x0D2, + DMA3SAD = 0x0D4, + DMA3DAD = 0x0D8, + DMA3CNT_L = 0x0DC, + DMA3CNT_H = 0x0DE, + // Timer Registers + TM0CNT_L = 0x100, + TM0CNT_H = 0x102, + TM1CNT_L = 0x104, + TM1CNT_H = 0x106, + TM2CNT_L = 0x108, + TM2CNT_H = 0x10A, + TM3CNT_L = 0x10C, + TM3CNT_H = 0x10E, + // Keypad Input + KEYINPUT = 0x130, + KEYCNT = 0x132, + // Serial Communication (2) + RCNT = 0x134, + // Interrupt, WaitState, and Power-Down Control + IE = 0x200, + IF = 0x202, + WAITCNT = 0x204, + IME = 0x208, + POSTFLG = 0x300, + HALTCNT = 0x301, + + DMA_CHANSIZE = 0x00C, + TIMER_SIZE = 0x004, + // TODO make tests and everything in Write*() functions so that we can + // make IO_SIZE 0x804 (don't forget mirrors) + IO_SIZE = 0x1000 + }; + + Io (); + ~Io (); + + void Reset (); + void ClearSio (); + void ClearSound (); + void ClearOthers (); + + uint8_t Read8 (uint32_t add); + uint16_t Read16 (uint32_t add); + uint32_t Read32 (uint32_t add); + + void Write8 (uint32_t add, uint8_t val); + void Write16 (uint32_t add, uint16_t val); + void Write32 (uint32_t add, uint32_t val); + + // Direct read and write + // Using theses functions will write directly on IO memory without + // doing anything else (they won't call Dma::Check for example) + // add must be the real address & 0xFFF + // No check is done on the memory, these functions may segfault if + // you give them wrong values ! + + uint8_t DRead8 (uint16_t add) + { + return m_iomem[add]; + } + + uint16_t DRead16 (uint16_t add) + { + return *(uint16_t*)(m_iomem+add); + } + + uint32_t DRead32 (uint16_t add) + { + return *(uint32_t*)(m_iomem+add); + } + + void DWrite8 (uint16_t add, uint8_t val) + { + m_iomem[add] = val; + } + + void DWrite16 (uint16_t add, uint16_t val) + { + *(uint16_t*)(m_iomem+add) = val; + } + + void DWrite32 (uint16_t add, uint32_t val) + { + *(uint32_t*)(m_iomem+add) = val; + } + + uint8_t& GetRef8 (uint16_t add) + { + return m_iomem[add]; + } + + uint16_t& GetRef16 (uint16_t add) + { + return *(uint16_t*)(m_iomem+add); + } + + uint32_t& GetRef32 (uint16_t add) + { + return *(uint32_t*)(m_iomem+add); + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + uint8_t* m_iomem; + }; +} + +#endif diff --git a/libmeteor/include/ameteor/keypad.hpp b/libmeteor/include/ameteor/keypad.hpp new file mode 100644 index 0000000000..9c6493c55b --- /dev/null +++ b/libmeteor/include/ameteor/keypad.hpp @@ -0,0 +1,105 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __KEYPAD_H__ +#define __KEYPAD_H__ + +#include +#include + +namespace AMeteor +{ + class Keypad + { + public : + enum Button + { + BTN_A = 0x001, + BTN_B = 0x002, + BTN_SELECT = 0x004, + BTN_START = 0x008, + BTN_RIGHT = 0x010, + BTN_LEFT = 0x020, + BTN_UP = 0x040, + BTN_DOWN = 0x080, + BTN_R = 0x100, + BTN_L = 0x200 + }; + + Keypad (); + + void Reset () + { + } + + void BindKey(int code, Button btn) + { + m_keys[code] = (uint16_t)btn; + } + void UnbindKey(int code) + { + m_keys.erase(code); + } + void BindJoy(uint16_t joyid, uint16_t button, Button btn) + { + m_joys[((int)joyid) << 16 | button] = (uint16_t)btn; + } + void UnbindJoy(uint16_t joyid, uint16_t button) + { + m_joys.erase(((int)joyid) << 16 | button); + } + void BindAxis(uint16_t joyid, uint16_t axis, Button btn) + { + m_axis[((int)joyid) << 16 | axis] = (uint16_t)btn; + } + void UnbindAxis(uint16_t joyid, uint16_t axis) + { + m_axis.erase(((int)joyid) << 16 | axis); + } + + void ResetBindings() + { + m_keys.clear(); + m_joys.clear(); + m_axis.clear(); + } + + inline void SetPadState(uint16_t keys); + + void KeyPressed(int code); + void KeyReleased(int code); + void JoyButtonPressed (uint16_t joyid, uint16_t button); + void JoyButtonReleased (uint16_t joyid, uint16_t button); + void JoyMoved (uint16_t joyid, uint16_t axis, float pos); + + void VBlank (); + + private : + uint16_t& m_keyinput; + uint16_t& m_keycnt; + + std::map m_keys; + std::map m_joys; + std::map m_axis; + }; + + void Keypad::SetPadState(uint16_t keys) + { + m_keyinput = keys; + } +} + +#endif diff --git a/libmeteor/include/ameteor/lcd.hpp b/libmeteor/include/ameteor/lcd.hpp new file mode 100644 index 0000000000..0068aad295 --- /dev/null +++ b/libmeteor/include/ameteor/lcd.hpp @@ -0,0 +1,129 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __LCD_H__ +#define __LCD_H__ + +#include "clock.hpp" + +#include "memory.hpp" +#include "io.hpp" +#include "graphics/screen.hpp" + +#include +#include +#include + +#include + +namespace AMeteor +{ + class Lcd + { + public : + Lcd (); + + void Reset (); + + const uint16_t* GetSurface () const + { + return m_screen.GetSurface(); + } + + Graphics::Screen& GetScreen() + { + return m_screen; + } + const Graphics::Screen& GetScreen() const + { + return m_screen; + } + + void SetFrameskip (uint8_t skip) + { + m_screen.SetFrameskip(skip); + } + + void UpdateDispCnt (uint16_t dispcnt) + { + m_screen.UpdateDispCnt(dispcnt); + } + +#define UPDATE_BG_CNT(num) \ + void UpdateBg##num##Cnt (uint16_t cnt) \ + { \ + m_screen.UpdateBg##num##Cnt(cnt); \ + } + UPDATE_BG_CNT(0) + UPDATE_BG_CNT(1) + UPDATE_BG_CNT(2) + UPDATE_BG_CNT(3) +#undef UPDATE_BG_CNT + +#define UPDATE_BG_OFF(num, coord) \ + void UpdateBg##num##coord##Off (uint16_t cnt) \ + { \ + m_screen.UpdateBg##num##coord##Off(cnt); \ + } + UPDATE_BG_OFF(0, X) + UPDATE_BG_OFF(0, Y) + UPDATE_BG_OFF(1, X) + UPDATE_BG_OFF(1, Y) + UPDATE_BG_OFF(2, X) + UPDATE_BG_OFF(2, Y) + UPDATE_BG_OFF(3, X) + UPDATE_BG_OFF(3, Y) +#undef UPDATE_BG_OFF + +#define UPDATE_BG_REF(num, coord) \ + void UpdateBg##num##Ref##coord (int32_t cnt) \ + { \ + m_screen.UpdateBg##num##Ref##coord (cnt); \ + } + UPDATE_BG_REF(2, X) + UPDATE_BG_REF(2, Y) + UPDATE_BG_REF(3, X) + UPDATE_BG_REF(3, Y) +#undef UPDATE_BG_REF + + void OamWrite (uint32_t begin, uint32_t end) + { + m_screen.OamWrite(begin, end); + } + void OamWrite16 (uint32_t add) + { + m_screen.OamWrite16(add); + } + void OamWrite32 (uint32_t add) + { + m_screen.OamWrite32(add); + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + //syg::signal sig_vblank; + + private : + Graphics::Screen m_screen; + + void TimeEvent (); + + friend void Clock::Commit(); + }; +} + +#endif diff --git a/libmeteor/include/ameteor/memory.hpp b/libmeteor/include/ameteor/memory.hpp new file mode 100644 index 0000000000..b3b919aaee --- /dev/null +++ b/libmeteor/include/ameteor/memory.hpp @@ -0,0 +1,149 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __MEMORY_H__ +#define __MEMORY_H__ + +#include "cartmem.hpp" +//XXX +#include "eeprom.hpp" + +#include +#include +#include +#include + +namespace AMeteor +{ + class Memory + { + public : + // in cycles + static const uint32_t CART_SAVE_TIME = 16*1024*1024; // 1 second + + enum CartType + { + CTYPE_UNKNOWN, + CTYPE_EEPROM512, + CTYPE_EEPROM8192, + CTYPE_FLASH64, + CTYPE_FLASH128, + CTYPE_SRAM + }; + enum CartError + { + CERR_NO_ERROR, + CERR_NOT_FOUND, + CERR_FAIL + }; + + Memory (); + ~Memory (); + + uint8_t GetCartType () const + { + return m_carttype; + } + // erases cartridge memory + void SetCartTypeFromSize (uint32_t size); + void SetCartType (uint8_t type); + void SetCartFile (const char* filename) + { + m_cartfile = filename; + } + + void Reset (uint32_t params = ~0); + void ClearWbram (); + void ClearWcram (); + void ClearPalette (); + void ClearVram (); + void ClearOam (); + void SoftReset (); + + bool LoadBios (const char* filename); + void LoadBios (const uint8_t* data, uint32_t size); + void UnloadBios () + { + if (m_brom) + { + delete [] m_brom; + m_brom = NULL; + } + } + bool LoadRom (const char* filename); + void LoadRom (const uint8_t* data, uint32_t size); + CartError LoadCart (); +#ifdef __LIBRETRO__ + bool LoadCartInferred (); +#endif + + bool HasBios () const + { + return m_brom; + } + + uint8_t GetCycles16NoSeq (uint32_t add, uint32_t count); + uint8_t GetCycles16Seq (uint32_t add, uint32_t count); + uint8_t GetCycles32NoSeq (uint32_t add, uint32_t count); + uint8_t GetCycles32Seq (uint32_t add, uint32_t count); + void UpdateWaitStates (uint16_t waitcnt); + + uint8_t* GetRealAddress(uint32_t add, uint8_t size = 0); + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + // TODO make const members + uint8_t Read8 (uint32_t add); + uint16_t Read16 (uint32_t add); + uint32_t Read32 (uint32_t add); + + void Write8 (uint32_t add, uint8_t val); + void Write16 (uint32_t add, uint16_t val); + void Write32 (uint32_t add, uint32_t val); + + void WriteEepromDma (uint32_t src, uint16_t size); + //void ReadEepromDma (uint32_t dest, uint16_t size); + + void TimeEvent (); + + private : + // times for a 8 or 16 bits access + uint8_t m_memtime[0xF]; + // times for a sequential 8 or 16 bits access in GamePak ROM + uint8_t m_memtimeseq[0x3]; + + // General Internal Memory + uint8_t* m_brom; // BIOS - System ROM + uint8_t* m_wbram; // WRAM - On-board Work RAM + uint8_t* m_wcram; // WRAM - In-chip Work RAM + // Internal Display Memory + uint8_t* m_pram; // BG/OBJ Palette RAM + uint8_t* m_vram; // VRAM - Video RAM + uint8_t* m_oram; // OAM - OBJ Attributes + // External Memory (Game Pak) + uint8_t* m_rom; // Game Pake ROM/FlashROM (max 32MB) + + uint8_t m_carttype; + CartMem* m_cart; + std::string m_cartfile; + + uint8_t ReadCart (uint16_t add); + void WriteCart (uint16_t add, uint8_t val); + }; +} + +#endif diff --git a/libmeteor/include/ameteor/sound.hpp b/libmeteor/include/ameteor/sound.hpp new file mode 100644 index 0000000000..5575048970 --- /dev/null +++ b/libmeteor/include/ameteor/sound.hpp @@ -0,0 +1,117 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __SOUND_H__ +#define __SOUND_H__ + +#include "audio/speaker.hpp" +#include "clock.hpp" +#include +#include +#include + +namespace AMeteor +{ + class Sound + { + public : + Sound (); + + void Reset (); + + inline Audio::Speaker& GetSpeaker(); + + void UpdateCntH1 (uint8_t val); + + inline void ResetSound1 (); + inline void ResetSound2 (); + inline void ResetSound4 (); + + inline void ResetSound1Envelope (); + inline void ResetSound2Envelope (); + inline void ResetSound4Envelope (); + + void TimerOverflow (uint8_t timernum); + + inline void SendDigitalA (uint8_t* buffer); + inline void SendDigitalB (uint8_t* buffer); + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + Audio::Speaker m_speaker; + + uint8_t m_fATimer, m_fBTimer; + + inline void TimerOverflowA (); + inline void TimerOverflowB (); + + void TimeEvent () + { + m_speaker.SoundTick(); + } + + friend void Clock::Commit (); + }; + + inline Audio::Speaker& Sound::GetSpeaker() + { + return m_speaker; + } + + inline void Sound::ResetSound1 () + { + m_speaker.ResetSound1(); + } + + inline void Sound::ResetSound2 () + { + m_speaker.ResetSound2(); + } + + inline void Sound::ResetSound4 () + { + m_speaker.ResetSound4(); + } + + inline void Sound::ResetSound1Envelope () + { + m_speaker.ResetSound1Envelope(); + } + + inline void Sound::ResetSound2Envelope () + { + m_speaker.ResetSound2Envelope(); + } + + inline void Sound::ResetSound4Envelope () + { + m_speaker.ResetSound4Envelope(); + } + + inline void Sound::SendDigitalA (uint8_t* buffer) + { + m_speaker.FillFifoA((int8_t*)buffer); + } + + inline void Sound::SendDigitalB (uint8_t* buffer) + { + m_speaker.FillFifoB((int8_t*)buffer); + } +} + +#endif diff --git a/libmeteor/include/ameteor/sram.hpp b/libmeteor/include/ameteor/sram.hpp new file mode 100644 index 0000000000..d937d75df7 --- /dev/null +++ b/libmeteor/include/ameteor/sram.hpp @@ -0,0 +1,56 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __SRAM_H__ +#define __SRAM_H__ + +#include "cartmem.hpp" +#include +#include +#include +#include + +namespace AMeteor +{ + class Sram : public CartMem + { + public : + Sram (); + + void Reset (); + + bool Load (std::istream& f); + bool Save (std::ostream& f); + + uint8_t Read (uint16_t add) + { + return m_data[add % SIZE]; + } + bool Write (uint16_t add, uint8_t val) + { + m_data[add % SIZE] = val; + return true; + } + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + static const uint16_t SIZE = 0x8000; + }; +} + +#endif diff --git a/libmeteor/include/ameteor/swap.hpp b/libmeteor/include/ameteor/swap.hpp new file mode 100644 index 0000000000..ec9d992626 --- /dev/null +++ b/libmeteor/include/ameteor/swap.hpp @@ -0,0 +1,36 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __SWAP_H__ +#define __SWAP_H__ + +namespace AMeteor +{ + inline uint16_t Swap16(uint16_t val) + { + return (val << 8) | (val >> 8); + } + + inline uint32_t Swap32(uint32_t val) + { + return (val << 24) | + ((val & 0x0000FF00) << 8) | + ((val & 0x00FF0000) >> 8) | + (val >> 24); + } +} + +#endif diff --git a/libmeteor/include/ameteor/timer.hpp b/libmeteor/include/ameteor/timer.hpp new file mode 100644 index 0000000000..4d51730258 --- /dev/null +++ b/libmeteor/include/ameteor/timer.hpp @@ -0,0 +1,86 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __TIMER_H__ +#define __TIMER_H__ + +#include "clock.hpp" + +#include +#include +#include + +namespace AMeteor +{ + class Timer + { + public : + Timer (int8_t num, Timer* next) : + m_num(num), + m_reload(0), + m_count(0), + m_control(0), + m_next(next) + { + } + + void Reset (); + + void SetReload (uint16_t rel) + { + m_reload = rel; + } + void Reload (); + + uint16_t GetCount () const; + + bool SaveState (std::ostream& stream); + bool LoadState (std::istream& stream); + + private : + union Control + { + Control(uint16_t v) : + w(v) + { } + + uint16_t w; + struct + { + unsigned int prescaler : 2; + bool countup : 1; + unsigned int unused1 : 3; + bool irq : 1; + bool start : 1; + unsigned int unused2 : 8; + } b; + }; + + const int8_t m_num; + uint16_t m_reload; + uint32_t m_count; + Control m_control; + + Timer* m_next; + + void TimeEvent (); + void Countup (); + + friend void Clock::Commit(); + }; +} + +#endif diff --git a/libmeteor/include/syg/signal.hpp b/libmeteor/include/syg/signal.hpp new file mode 100644 index 0000000000..2a3dab12d8 --- /dev/null +++ b/libmeteor/include/syg/signal.hpp @@ -0,0 +1,389 @@ +#ifndef SYG_connection_HPP +#define SYG_connection_HPP + +#include + +namespace syg +{ + +//------------------------------ +// connections +//------------------------------ + +template +class connection_base +{ + public: + virtual Tret call() const = 0; + virtual connection_base* clone() const = 0; + virtual ~connection_base() {} +}; + +template +class connection_base1 +{ + public: + virtual Tret call(Targ0) const = 0; + virtual connection_base1* clone() const = 0; + virtual ~connection_base1() {} +}; + +template +class connection_func : public connection_base +{ + public: + typedef Tret (*FuncPtr)(); + + connection_func(FuncPtr ptr): + _func(ptr) + {} + + Tret call() const + { + return _func(); + } + + connection_base* clone() const + { + return new connection_func(*this); + } + + private: + FuncPtr _func; +}; + +template +class connection_func1 : public connection_base1 +{ + public: + typedef Tret (*FuncPtr)(Targ0); + + connection_func1(FuncPtr ptr): + _func(ptr) + {} + + Tret call(Targ0 arg0) const + { + return _func(arg0); + } + + connection_base1* clone() const + { + return new connection_func1(*this); + } + + private: + FuncPtr _func; +}; + +template +class connection_meth : public connection_base +{ + public: + typedef Tobj TypeObj; + typedef Tret (Tobj::*FuncPtr)(); + + connection_meth(TypeObj& obj, FuncPtr ptr): + _obj(obj), + _meth(ptr) + {} + + Tret call() const + { + return (_obj.*_meth)(); + } + + connection_base* clone() const + { + return new connection_meth(*this); + } + + private: + Tobj& _obj; + FuncPtr _meth; +}; + +template +class connection_meth1 : public connection_base1 +{ + public: + typedef Tobj TypeObj; + typedef Tret (Tobj::*FuncPtr)(Targ0); + + connection_meth1(TypeObj& obj, FuncPtr ptr): + _obj(obj), + _meth(ptr) + {} + + Tret call(Targ0 arg0) const + { + return (_obj.*_meth)(arg0); + } + + connection_base1* clone() const + { + return new connection_meth1(*this); + } + + private: + Tobj& _obj; + FuncPtr _meth; +}; + +//------------------------------ +// slots +//------------------------------ + +template +class slot; + +template +class slot1; + +template +slot ptr_fun(Tret (*fun)()); + +template +slot mem_fun(Tobj& obj, Tret (Tobj::*fun)()); + +template +slot1 ptr_fun(Tret (*fun)(Targ0)); + +template +slot1 mem_fun(Tobj& obj, Tret (Tobj::*fun)(Targ0)); + +template +class slot +{ + public: + slot(): + _conn(0) + {} + slot(const slot& s) + { + *this = s; + } + ~slot() + { + delete _conn; + } + + slot& operator=(const slot& s) + { + if (s._conn) + _conn = s._conn->clone(); + else + _conn = 0; + + return *this; + } + + Tret call() const + { + return _conn->call(); + } + + Tret operator()() const + { + return call(); + } + + operator bool() const + { + return _conn; + } + + private: + connection_base* _conn; + + slot(const connection_base& conn): + _conn(conn.clone()) + {} + + friend slot ptr_fun(Tret (*fun)()); + + template + friend slot mem_fun(Tobj2& obj, Tret2 (Tobj2::*fun)()); +}; + +template +class slot1 +{ + public: + typedef slot1 SlotType; + + slot1(): + _conn(0) + {} + slot1(const SlotType& s) + { + *this = s; + } + ~slot1() + { + delete _conn; + } + + slot1& operator=(const SlotType& s) + { + if (s._conn) + _conn = s._conn->clone(); + else + _conn = 0; + + return *this; + } + + Tret call(Targ0 arg0) const + { + return _conn->call(arg0); + } + + Tret operator()(Targ0 arg0) const + { + return call(arg0); + } + + operator bool() const + { + return _conn; + } + + private: + typedef connection_base1 Connection; + + Connection* _conn; + + slot1(const Connection& conn): + _conn(conn.clone()) + {} + + friend SlotType ptr_fun(Tret (*fun)(Targ0)); + + template + friend slot1 mem_fun(Tobj2& obj, + Tret2 (Tobj2::*fun)(Targ02)); +}; + +template +inline slot ptr_fun(Tret (*fun)()) +{ + return slot(connection_func(fun)); +} + +template +inline slot mem_fun(Tobj& obj, Tret (Tobj::*fun)()) +{ + return slot(connection_meth(obj, fun)); +} + +template +inline slot1 ptr_fun(Tret (*fun)(Targ0)) +{ + return slot1(connection_func1(fun)); +} + +template +inline slot1 mem_fun(Tobj& obj, Tret (Tobj::*fun)(Targ0)) +{ + return slot1(connection_meth1(obj, fun)); +} + +//------------------------------ +// signals +//------------------------------ +/* +template +class signal; + +template +class connection +{ + public: + connection(): + _list(0) + { + } + + void disconnect() + { + _list->erase(_iter); + } + + private: + typedef std::list > List; + typedef typename List::iterator Iterator; + + List* _list; + Iterator _iter; + + connection(List* list, Iterator iter): + _list(list), + _iter(iter) + {} + + friend class signal; +}; + +template +class signal +{ + public: + connection connect(const slot s) + { + _slots.push_back(s); + return connection(&_slots, (++_slots.rbegin()).base()); + } + + Tret emit() const + { + for (typename Slots::const_iterator iter = _slots.begin(), + end = (++_slots.rbegin()).base(); + iter != end; ++iter) + iter->call(); + + return _slots.back().call(); + } + + Tret operator()() const + { + return emit(); + } + + private: + typedef std::list > Slots; + + Slots _slots; +}; + +template +class signal1 +{ + public: + void connect(const slot s) + { + _slots.push_back(s); + } + + Tret emit(Targ0 arg0) const + { + for (typename Slots::const_iterator iter = _slots.begin(); iter != _slots.end() - 1; ++iter) + iter->call(); + + return _slots.back().call(arg0); + } + + Tret operator()(Targ0 arg0) const + { + return emit(arg0); + } + + private: + typedef std::list > Slots; + + Slots _slots; +}; +*/ +} // namespace syg + +#endif diff --git a/libmeteor/source/ameteor.cpp b/libmeteor/source/ameteor.cpp new file mode 100644 index 0000000000..76d9b187e4 --- /dev/null +++ b/libmeteor/source/ameteor.cpp @@ -0,0 +1,204 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor.hpp" +#include "debug.hpp" +#include "globals.hpp" +#include +#include + +// TODO add version +#define SS_MAGIC_STRING ("AMeteor SaveState") +#define SS_MS_SIZE (sizeof(SS_MAGIC_STRING)-1) + +namespace AMeteor +{ + namespace + { + class AMeteor + { + public : + AMeteor () + { + Audio::InitNoise(); + } + } __ameteor; + } + + // the clock must be initialized first since there are devices like + // lcd which needs to set the timer + Clock _clock; + Io _io; + // the interpreter (which is in the cpu) takes io addresses, thus the + // cpu must be initialized after io + Interpreter _cpu; + Memory _memory; + Dma _dma; + // the lcd must be initialized after the memory since it takes + // pointers from it + Lcd _lcd; + // the sound must be initialized after the io since it takes references + // from it + Sound _sound; + // the keypad needs to take the vblank event from lcd, so it must be + // initialized after lcd + // it must also be initialized after io since it takes the keyinput + // reference + Keypad _keypad; + Timer _timer3(3, NULL); + Timer _timer2(2, &_timer3); + Timer _timer1(1, &_timer2); + Timer _timer0(0, &_timer1); + + void Reset (uint32_t units) + { +#define RESET(u, e) \ + if (units & UNIT_##e) \ + _##u.Reset(); + RESET(clock, CLOCK); + RESET(io, IO); + RESET(cpu, CPU); + RESET(dma, DMA); + RESET(lcd, LCD); + RESET(sound, SOUND); + RESET(keypad, KEYPAD); + RESET(timer0, TIMER0); + RESET(timer1, TIMER1); + RESET(timer2, TIMER2); + RESET(timer3, TIMER3); +#undef RESET + if (units & UNIT_MEMORY) + _memory.Reset(units); + } + + bool SaveState (const char* filename) + { + if (_cpu.IsRunning()) + return false; + + std::ostringstream ss; + + if (!SaveState(ss)) + return false; + + std::ofstream file(filename); + + if (!file) + return false; + + std::string buf = ss.str(); + if (!file.write(buf.c_str(), buf.length())) + return false; + + file.close(); + if (file.bad()) + return false; + + return true; + } + + bool LoadState (const char* filename) + { + if (_cpu.IsRunning()) + return false; + + std::istringstream ss; + { + std::ifstream file(filename); + if (!file) + return false; + + // 1Mo + std::vector buf(0x100000); + if (file.read((char*)&buf[0], 0x100000).bad()) + return false; + int nread = file.gcount(); + + file.close(); + if (file.bad()) + return false; + + ss.str(std::string((char*)&buf[0], nread)); + } + + return LoadState(ss); + } + + bool SaveState (std::ostream& stream) + { + if (_cpu.IsRunning()) + return false; + + SS_WRITE_DATA(SS_MAGIC_STRING, SS_MS_SIZE); + +#define SAVE(dev) \ + if (!dev.SaveState(stream)) \ + return false + SAVE(_clock); + SAVE(_io); + SAVE(_cpu); + SAVE(_memory); + SAVE(_dma); + SAVE(_lcd); + SAVE(_sound); + //SAVE(_keypad); + SAVE(_timer0); + SAVE(_timer1); + SAVE(_timer2); + SAVE(_timer3); +#undef SAVE + + return true; + } + + bool LoadState (std::istream& stream) + { + if (_cpu.IsRunning()) + return false; + + { + char buf[SS_MS_SIZE]; + SS_READ_DATA(buf, SS_MS_SIZE); + if (std::memcmp(buf, SS_MAGIC_STRING, SS_MS_SIZE)) + return false; + } + + +#define LOAD(dev) \ + if (!dev.LoadState(stream)) \ + return false + LOAD(_clock); + LOAD(_io); + LOAD(_cpu); + LOAD(_memory); + LOAD(_dma); + LOAD(_lcd); + LOAD(_sound); + //LOAD(_keypad); + LOAD(_timer0); + LOAD(_timer1); + LOAD(_timer2); + LOAD(_timer3); +#undef LOAD + + uint8_t xxx; + // if there is garbage at end of file + if (stream.read((char*)&xxx, 1)) + return false; + + return true; + } +} diff --git a/libmeteor/source/audio/dsound.cpp b/libmeteor/source/audio/dsound.cpp new file mode 100644 index 0000000000..1f06f6a690 --- /dev/null +++ b/libmeteor/source/audio/dsound.cpp @@ -0,0 +1,82 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/audio/dsound.hpp" +#include "../globals.hpp" +#include + +namespace AMeteor +{ + namespace Audio + { + DSound::DSound () : + m_rpos(0), + m_wpos(0), + m_size(0) + { + std::memset(m_buffer, 0, sizeof(m_buffer)); + } + + void DSound::FillFifo (int8_t* buffer) + { + int8_t* pmbuf = m_buffer + m_wpos; + // we copy 16 bytes of data + for (int8_t* pbuf = buffer; + pbuf < buffer + BUFFER_SIZE/2 && m_size < 32; ++pbuf, ++pmbuf) + { + if (pmbuf >= m_buffer + BUFFER_SIZE) + pmbuf = m_buffer; + + *pmbuf = *pbuf; + ++m_size; + } + + m_wpos = pmbuf - m_buffer; + } + + void DSound::FillFifo (int8_t sample) + { + if (m_size == 32) + return; + if (m_wpos == BUFFER_SIZE) + m_wpos = 0; + m_buffer[m_wpos++] = sample; + ++m_size; + } + + bool DSound::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_rpos); + SS_WRITE_VAR(m_wpos); + SS_WRITE_VAR(m_size); + + SS_WRITE_ARRAY(m_buffer); + + return true; + } + + bool DSound::LoadState (std::istream& stream) + { + SS_READ_VAR(m_rpos); + SS_READ_VAR(m_wpos); + SS_READ_VAR(m_size); + + SS_READ_ARRAY(m_buffer); + + return true; + } + } +} diff --git a/libmeteor/source/audio/sound1.cpp b/libmeteor/source/audio/sound1.cpp new file mode 100644 index 0000000000..da7b587e51 --- /dev/null +++ b/libmeteor/source/audio/sound1.cpp @@ -0,0 +1,202 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/audio/sound1.hpp" +#include "../globals.hpp" +#include + +namespace AMeteor +{ + namespace Audio + { + Sound1::Sound1 (uint16_t& cntl, uint16_t& cnth, uint16_t& cntx, + uint16_t freq) : + m_cntl(cntl), + m_cnth(cnth), + m_cntx(cntx), + m_on(false), + m_posP(0), + m_posS(0), + m_posE(0), + m_sample(0), + m_speriod(16*1024*1024/freq), + m_envelope(0), + m_length(0), + m_timed(false) + { + } + + void Sound1::Reset () + { + m_on = false; + m_timed = false; + m_length = 0; + m_envelope = 0; + m_posP = m_posE = m_posS = 0; + m_sample = 0; + } + + void Sound1::ResetSound () + { + m_on = true; + m_timed = (m_cntx & (0x1 << 14)); + m_length = (64 - (m_cnth & 0x3F)) * ((16*1024*1024)/256); + m_envelope = m_cnth >> 12; + m_posE = m_posS = 0; + } + + void Sound1::SoundTick () + { + // remember here that the processors runs at 16MHz = 16,777,216 cycles/s + // and this function is called normally at 44,100 Hz + + m_posP += m_speriod; + m_posS += m_speriod; + m_posE += m_speriod; + if (m_length > m_speriod) + m_length -= m_speriod; + else + { + if (m_timed) + m_on = false; + m_length = 0; + } + + // sweep time in cycles + // maximum is 917,504, so we need a 32 bits int + uint32_t sweeptime = ((m_cntl >> 4) & 0x7) * ((16*1024*1024)/128); + // period in cycles + // period = 16M/freq + // freq = 128K/(2048 - (SOUND1CNT_X & 0x7FF)) + // maximum is 262,144, so we need a 32 bits int + uint32_t period = + ((16*1024*1024) / (128*1024)) * (2048 - (m_cntx & 0x7FF)); + // frequency as contained in SOUND1CNT_X + uint16_t freq = m_cntx & 0x7FF; + + // we rewind posP + m_posP %= period; + + // the envelope now + // envelope step time in cycles + uint32_t steptime = ((m_cnth >> 8) & 0x7) * ((16*1024*1024)/64); + // the envelope can't do two steps between to calls of SoundTick + if (steptime && m_posE > steptime) + { + if (m_cnth & (0x1 << 11)) + { + if (m_envelope < 15) + ++m_envelope; + } + else + { + if (m_envelope > 0) + --m_envelope; + } + + m_posE -= steptime; + } + + // if the envelope is null or the sound is finished, no need to calculate + // anything + if (m_on && m_envelope) + { + // we set the sample according to the position in the current period + // and the wave duty cycle + switch ((m_cnth >> 6) & 0x3) + { + case 0: // 12.5% + m_sample = m_posP < period/8 ? 112 : -16; + break; + case 1: // 25% + m_sample = m_posP < period/4 ? 96 : -32; + break; + case 2: // 50% + m_sample = m_posP < period/2 ? 64 : -64; + break; + case 3: // 75% + m_sample = m_posP < (3*period)/4 ? 32 : -96; + break; + } + + m_sample = (((int16_t)m_sample) * m_envelope)/15; + } + else + m_sample = 0; + + // there can't have been more than one sweep between two call of + // SoundTick since SoundTick is called at least at a frequency of 4,000Hz + // (alsa can't output at a lower samplerate on my sound card) and sweeps + // happen at maximum at a frequency of 128Hz + + // if the channel is on and sweep is enabled and it's time to sweep + if (m_on && sweeptime && m_posS > sweeptime) + { + // n = sweep shifts (in SOUND1CNT_L) + if (m_cntl & (0x1 << 3)) + // F(t+1) = F(t) - F(t) / 2^n + freq = freq - freq / (1 << (m_cntl & 0x7)); + // freq won't go under 1 since when freq = 2, freq - freq / 2 (the + // minimum sweep shift) = 1 and then freq - freq / 2 = 1 + // because 1/2 = 0 + else + { + // F(t+1) = F(t) + F(t) / 2^n + freq = freq + freq / (1 << (m_cntl & 0x7)); + if (freq > 2047) + { + m_on = false; + freq = 2047; + } + } + + // we update the frequency in the cntx register + m_cntx = (m_cntx & 0xF800) | freq; + + // now we rewind posS + m_posS -= sweeptime; + } + } + + bool Sound1::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_on); + SS_WRITE_VAR(m_posP); + SS_WRITE_VAR(m_posS); + SS_WRITE_VAR(m_posE); + SS_WRITE_VAR(m_sample); + SS_WRITE_VAR(m_envelope); + SS_WRITE_VAR(m_length); + SS_WRITE_VAR(m_timed); + + return true; + } + + bool Sound1::LoadState (std::istream& stream) + { + SS_READ_VAR(m_on); + SS_READ_VAR(m_posP); + SS_READ_VAR(m_posS); + SS_READ_VAR(m_posE); + SS_READ_VAR(m_sample); + SS_READ_VAR(m_envelope); + SS_READ_VAR(m_length); + SS_READ_VAR(m_timed); + + return true; + } + } +} diff --git a/libmeteor/source/audio/sound2.cpp b/libmeteor/source/audio/sound2.cpp new file mode 100644 index 0000000000..fe1dd05a4b --- /dev/null +++ b/libmeteor/source/audio/sound2.cpp @@ -0,0 +1,145 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/audio/sound2.hpp" +#include "../globals.hpp" +#include + +namespace AMeteor +{ + namespace Audio + { + Sound2::Sound2 (uint16_t& cntl, uint16_t& cnth, uint16_t freq) : + m_cntl(cntl), + m_cnth(cnth), + m_on(false), + m_posP(0), + m_posE(0), + m_sample(0), + m_speriod(16*1024*1024/freq), + m_envelope(0), + m_length(0), + m_timed(false) + { + } + + void Sound2::Reset () + { + m_on = false; + m_timed = false; + m_length = 0; + m_envelope = 0; + m_posP = m_posE = 0; + m_sample = 0; + } + + void Sound2::ResetSound () + { + m_on = true; + m_timed = (m_cnth & (0x1 << 14)); + m_length = (64 - (m_cntl & 0x3F)) * ((16*1024*1024)/256); + m_envelope = m_cntl >> 12; + m_posE = 0; + } + + void Sound2::SoundTick () + { + // refer at sound1 to know how sound2 works + + m_posP += m_speriod; + m_posE += m_speriod; + if (m_length > m_speriod) + m_length -= m_speriod; + else + { + if (m_timed) + m_on = false; + m_length = 0; + } + + uint32_t period = + ((16*1024*1024) / (128*1024)) * (2048 - (m_cnth & 0x7FF)); + + m_posP %= period; + + uint32_t steptime = ((m_cntl >> 8) & 0x7) * ((16*1024*1024)/64); + if (steptime && m_posE > steptime) + { + if (m_cntl & (0x1 << 11)) + { + if (m_envelope < 15) + ++m_envelope; + } + else + { + if (m_envelope > 0) + --m_envelope; + } + + m_posE -= steptime; + } + + if (m_on && m_envelope) + { + switch ((m_cntl >> 6) & 0x3) + { + case 0: // 12.5% + m_sample = m_posP < period/8 ? 112 : -16; + break; + case 1: // 25% + m_sample = m_posP < period/4 ? 96 : -32; + break; + case 2: // 50% + m_sample = m_posP < period/2 ? 64 : -64; + break; + case 3: // 75% + m_sample = m_posP < (3*period)/4 ? 32 : -96; + break; + } + + m_sample = (((int16_t)m_sample) * m_envelope)/15; + } + else + m_sample = 0; + } + + bool Sound2::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_on); + SS_WRITE_VAR(m_posP); + SS_WRITE_VAR(m_posE); + SS_WRITE_VAR(m_sample); + SS_WRITE_VAR(m_envelope); + SS_WRITE_VAR(m_length); + SS_WRITE_VAR(m_timed); + + return true; + } + + bool Sound2::LoadState (std::istream& stream) + { + SS_READ_VAR(m_on); + SS_READ_VAR(m_posP); + SS_READ_VAR(m_posE); + SS_READ_VAR(m_sample); + SS_READ_VAR(m_envelope); + SS_READ_VAR(m_length); + SS_READ_VAR(m_timed); + + return true; + } + } +} diff --git a/libmeteor/source/audio/sound4.cpp b/libmeteor/source/audio/sound4.cpp new file mode 100644 index 0000000000..ef963ecd6e --- /dev/null +++ b/libmeteor/source/audio/sound4.cpp @@ -0,0 +1,185 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/audio/sound4.hpp" +#include "../globals.hpp" + +namespace AMeteor +{ + namespace Audio + { + static bool Noise7Stages[127]; + static bool Noise15Stages[32767]; + + void InitNoise () + { + unsigned short i = 0x7f; + bool* pNoise = Noise7Stages; + do + { + *pNoise++ = i & 1; + i = (i >> 1) | (((i & 1) << 6) ^ ((i & 2) << 5)); + } while (i != 0x7f); + + i = 0x7fff; + pNoise = Noise15Stages; + do + { + *pNoise++ = i & 1; + i = (i >> 1) | (((i & 1) << 14) ^ ((i & 2) << 13)); + } while (i != 0x7fff); + } + + Sound4::Sound4 (uint16_t& cntl, uint16_t& cnth, uint16_t freq) : + m_cntl(cntl), + m_cnth(cnth), + m_on(false), + m_posP(0), + m_posN(0), + m_posE(0), + m_sample(0), + m_speriod(16*1024*1024/freq), + m_envelope(0), + m_length(0), + m_timed(false), + m_div(4*8/2) + { + } + + void Sound4::Reset () + { + m_on = false; + m_timed = false; + m_length = 0; + m_envelope = 0; + m_posP = m_posE = m_posN = 0; + m_sample = 0; + m_div = 4*8/2; + } + + void Sound4::ResetSound () + { + m_on = true; + m_timed = (m_cnth & (0x1 << 14)); + m_length = (64 - (m_cntl & 0x3F)) * ((16*1024*1024)/256); + m_envelope = m_cntl >> 12; + m_div = ((m_cnth & 0x7) ? 4*8*(m_cnth & 0x7) : 4*8/2); + m_posE = m_posP = 0; + } + + void Sound4::SoundTick () + { + // rest is the number of processor clock ticks that were not yet taken by + // the noise clock divider (if the total divider is 8 and we have 10 + // ticks of the processor clock, only 8 were taken by the noise clock), + // the rest will be taken by the next call of SoundTick() + uint16_t rest = m_posP + m_speriod; + // advance is the number of noise ticks that have passed + uint16_t advance = m_posP + m_speriod; + + // time of one sound tick in cycles + uint32_t tick = m_div; + // if shift is 111X in binary + if (((m_cnth >> 5) & 0x7) == 0x7) + // not used + // assume 13 + tick *= 1 << 14; + else + tick *= (2 << ((m_cnth >> 4) & 0xF)); + + rest %= tick; + advance /= tick; + + m_posP = rest; + m_posN += advance; + // we have this modulo on posN so that when you switch from 15 stages to + // 7 stages and then you switch back, you won't restart the 15 stages + // pattern from the beginning + // don't know if GBA handle this like that + m_posN %= 32768; + + m_posE += m_speriod; + if (m_length > m_speriod) + m_length -= m_speriod; + else + { + if (m_timed) + m_on = false; + m_length = 0; + } + + uint32_t steptime = ((m_cntl >> 8) & 0x7) * ((16*1024*1024)/64); + if (steptime && m_posE > steptime) + { + if (m_cnth & (0x1 << 11)) + { + if (m_envelope < 15) + ++m_envelope; + } + else + { + if (m_envelope > 0) + --m_envelope; + } + + m_posE -= steptime; + } + + if (m_on && m_envelope) + { + if (m_cnth & (0x1 << 3)) + m_sample = Noise7Stages[m_posN % 128]; + else + m_sample = Noise15Stages[m_posN]; + + m_sample = m_sample ? 127 : -127; + m_sample = (((int16_t)m_sample) * m_envelope)/15; + } + else + m_sample = 0; + } + + bool Sound4::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_on); + SS_WRITE_VAR(m_posP); + SS_WRITE_VAR(m_posE); + SS_WRITE_VAR(m_posN); + SS_WRITE_VAR(m_sample); + SS_WRITE_VAR(m_envelope); + SS_WRITE_VAR(m_length); + SS_WRITE_VAR(m_timed); + SS_WRITE_VAR(m_div); + + return true; + } + + bool Sound4::LoadState (std::istream& stream) + { + SS_READ_VAR(m_on); + SS_READ_VAR(m_posP); + SS_READ_VAR(m_posE); + SS_READ_VAR(m_posN); + SS_READ_VAR(m_sample); + SS_READ_VAR(m_envelope); + SS_READ_VAR(m_length); + SS_READ_VAR(m_timed); + SS_READ_VAR(m_div); + + return true; + } + } +} diff --git a/libmeteor/source/audio/speaker.cpp b/libmeteor/source/audio/speaker.cpp new file mode 100644 index 0000000000..13e6f1a3d3 --- /dev/null +++ b/libmeteor/source/audio/speaker.cpp @@ -0,0 +1,169 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/audio/speaker.hpp" +#include "../debug.hpp" + +namespace AMeteor +{ + namespace Audio + { + Speaker::Speaker (uint16_t& cnt1l, uint16_t& cnt1h, uint16_t& cnt1x, + uint16_t& cnt2l, uint16_t& cnt2h, + uint16_t& cnt4l, uint16_t& cnt4h, + uint16_t& cntl, uint16_t& cnth, uint16_t& cntx, uint16_t& bias) : + // XXX freq + m_sound1(cnt1l, cnt1h, cnt1x, 44100), + m_sound2(cnt2l, cnt2h, 44100), + m_sound4(cnt4l, cnt4h, 44100), + m_cntl(cntl), + m_cnth(cnth), + m_cntx(cntx), + m_bias(bias) + { + } + + Speaker::~Speaker () + { + } + + void Speaker::Reset () + { + m_sound1.Reset(); + m_sound2.Reset(); + m_sound4.Reset(); + m_dsa.Reset(); + m_dsb.Reset(); + } + + void Speaker::SoundTick () + { + int16_t f[2]; + + // if master is enabled + if (m_cntx & (0x1 << 7)) + { + m_sound1.SoundTick(); + if (m_sound1.IsOn()) + m_cntx |= 0x0001; + else + m_cntx &= 0xFFFE; + m_sound2.SoundTick(); + if (m_sound2.IsOn()) + m_cntx |= 0x0002; + else + m_cntx &= 0xFFFD; + m_sound4.SoundTick(); + if (m_sound4.IsOn()) + m_cntx |= 0x0008; + else + m_cntx &= 0xFFF7; + } + + // left + f[0] = MixSample (m_cntl >> 4, m_cnth >> 9); + // right + f[1] = MixSample (m_cntl, m_cnth >> 8); + + m_sig_frame(f); + } + + int16_t Speaker::MixSample (uint16_t cntl, uint8_t cnth) + { + int16_t sample; + + // if master is enabled + if (m_cntx & (0x1 << 7)) + { + int8_t s1, s2, s4; + s1 = (cntl & (0x1 << 8)) ? m_sound1.GetSample() : 0; + s2 = (cntl & (0x1 << 9)) ? m_sound2.GetSample() : 0; + s4 = (cntl & (0x1 << 11)) ? m_sound4.GetSample() : 0; + + int16_t dmg = s1 + s2 + s4; + dmg = (dmg * (cntl & 0x7)) / 7; + switch (m_cnth & 0x3) + { + case 0: // 25% + dmg /= 4; + break; + case 1: // 50% + dmg /= 2; + break; + case 2: // 100% + break; + case 3: // Prohibited + met_abort("Invalid SOUNDCNT_H sound # 1-4 volume"); + break; + } + + int16_t sA, sB; + sA = (cnth & (0x1 )) ? m_dsa.GetSample() : 0; + sB = (cnth & (0x1 << 4)) ? m_dsb.GetSample() : 0; + + if (!(m_cnth & (0x1 << 2))) + sA /= 2; + if (!(m_cnth & (0x1 << 3))) + sB /= 2; + + // TODO when finished put this all together on one line + sample = (sA + sB) * 4 + dmg; + } + else + sample = 0; + + sample += m_bias & 0x3FF; + if (sample < 0) + sample = 0; + else if (sample >= 0x400) + sample = 0x3FF; + sample -= 0x200; + sample <<= 6; + + return sample; + } + + bool Speaker::SaveState (std::ostream& stream) + { +#define WRITE(var) \ + if (!var.SaveState(stream)) \ + return false + WRITE(m_sound1); + WRITE(m_sound2); + WRITE(m_sound4); + WRITE(m_dsa); + WRITE(m_dsb); +#undef WRITE + + return true; + } + + bool Speaker::LoadState (std::istream& stream) + { +#define READ(var) \ + if (!var.LoadState(stream)) \ + return false + READ(m_sound1); + READ(m_sound2); + READ(m_sound4); + READ(m_dsa); + READ(m_dsb); +#undef READ + + return true; + } + } +} diff --git a/libmeteor/source/bios.cpp b/libmeteor/source/bios.cpp new file mode 100644 index 0000000000..2480fb692c --- /dev/null +++ b/libmeteor/source/bios.cpp @@ -0,0 +1,919 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/bios.hpp" +#include "ameteor/cpu.hpp" +#include "ameteor/memory.hpp" + +#include "globals.hpp" + +#include "debug.hpp" + +#include + +namespace AMeteor +{ + namespace Bios + { + static const int16_t sineTable[256] = { + (int16_t)0x0000, (int16_t)0x0192, (int16_t)0x0323, (int16_t)0x04B5, + (int16_t)0x0645, (int16_t)0x07D5, (int16_t)0x0964, (int16_t)0x0AF1, + (int16_t)0x0C7C, (int16_t)0x0E05, (int16_t)0x0F8C, (int16_t)0x1111, + (int16_t)0x1294, (int16_t)0x1413, (int16_t)0x158F, (int16_t)0x1708, + (int16_t)0x187D, (int16_t)0x19EF, (int16_t)0x1B5D, (int16_t)0x1CC6, + (int16_t)0x1E2B, (int16_t)0x1F8B, (int16_t)0x20E7, (int16_t)0x223D, + (int16_t)0x238E, (int16_t)0x24DA, (int16_t)0x261F, (int16_t)0x275F, + (int16_t)0x2899, (int16_t)0x29CD, (int16_t)0x2AFA, (int16_t)0x2C21, + (int16_t)0x2D41, (int16_t)0x2E5A, (int16_t)0x2F6B, (int16_t)0x3076, + (int16_t)0x3179, (int16_t)0x3274, (int16_t)0x3367, (int16_t)0x3453, + (int16_t)0x3536, (int16_t)0x3612, (int16_t)0x36E5, (int16_t)0x37AF, + (int16_t)0x3871, (int16_t)0x392A, (int16_t)0x39DA, (int16_t)0x3A82, + (int16_t)0x3B20, (int16_t)0x3BB6, (int16_t)0x3C42, (int16_t)0x3CC5, + (int16_t)0x3D3E, (int16_t)0x3DAE, (int16_t)0x3E14, (int16_t)0x3E71, + (int16_t)0x3EC5, (int16_t)0x3F0E, (int16_t)0x3F4E, (int16_t)0x3F84, + (int16_t)0x3FB1, (int16_t)0x3FD3, (int16_t)0x3FEC, (int16_t)0x3FFB, + (int16_t)0x4000, (int16_t)0x3FFB, (int16_t)0x3FEC, (int16_t)0x3FD3, + (int16_t)0x3FB1, (int16_t)0x3F84, (int16_t)0x3F4E, (int16_t)0x3F0E, + (int16_t)0x3EC5, (int16_t)0x3E71, (int16_t)0x3E14, (int16_t)0x3DAE, + (int16_t)0x3D3E, (int16_t)0x3CC5, (int16_t)0x3C42, (int16_t)0x3BB6, + (int16_t)0x3B20, (int16_t)0x3A82, (int16_t)0x39DA, (int16_t)0x392A, + (int16_t)0x3871, (int16_t)0x37AF, (int16_t)0x36E5, (int16_t)0x3612, + (int16_t)0x3536, (int16_t)0x3453, (int16_t)0x3367, (int16_t)0x3274, + (int16_t)0x3179, (int16_t)0x3076, (int16_t)0x2F6B, (int16_t)0x2E5A, + (int16_t)0x2D41, (int16_t)0x2C21, (int16_t)0x2AFA, (int16_t)0x29CD, + (int16_t)0x2899, (int16_t)0x275F, (int16_t)0x261F, (int16_t)0x24DA, + (int16_t)0x238E, (int16_t)0x223D, (int16_t)0x20E7, (int16_t)0x1F8B, + (int16_t)0x1E2B, (int16_t)0x1CC6, (int16_t)0x1B5D, (int16_t)0x19EF, + (int16_t)0x187D, (int16_t)0x1708, (int16_t)0x158F, (int16_t)0x1413, + (int16_t)0x1294, (int16_t)0x1111, (int16_t)0x0F8C, (int16_t)0x0E05, + (int16_t)0x0C7C, (int16_t)0x0AF1, (int16_t)0x0964, (int16_t)0x07D5, + (int16_t)0x0645, (int16_t)0x04B5, (int16_t)0x0323, (int16_t)0x0192, + (int16_t)0x0000, (int16_t)0xFE6E, (int16_t)0xFCDD, (int16_t)0xFB4B, + (int16_t)0xF9BB, (int16_t)0xF82B, (int16_t)0xF69C, (int16_t)0xF50F, + (int16_t)0xF384, (int16_t)0xF1FB, (int16_t)0xF074, (int16_t)0xEEEF, + (int16_t)0xED6C, (int16_t)0xEBED, (int16_t)0xEA71, (int16_t)0xE8F8, + (int16_t)0xE783, (int16_t)0xE611, (int16_t)0xE4A3, (int16_t)0xE33A, + (int16_t)0xE1D5, (int16_t)0xE075, (int16_t)0xDF19, (int16_t)0xDDC3, + (int16_t)0xDC72, (int16_t)0xDB26, (int16_t)0xD9E1, (int16_t)0xD8A1, + (int16_t)0xD767, (int16_t)0xD633, (int16_t)0xD506, (int16_t)0xD3DF, + (int16_t)0xD2BF, (int16_t)0xD1A6, (int16_t)0xD095, (int16_t)0xCF8A, + (int16_t)0xCE87, (int16_t)0xCD8C, (int16_t)0xCC99, (int16_t)0xCBAD, + (int16_t)0xCACA, (int16_t)0xC9EE, (int16_t)0xC91B, (int16_t)0xC851, + (int16_t)0xC78F, (int16_t)0xC6D6, (int16_t)0xC626, (int16_t)0xC57E, + (int16_t)0xC4E0, (int16_t)0xC44A, (int16_t)0xC3BE, (int16_t)0xC33B, + (int16_t)0xC2C2, (int16_t)0xC252, (int16_t)0xC1EC, (int16_t)0xC18F, + (int16_t)0xC13B, (int16_t)0xC0F2, (int16_t)0xC0B2, (int16_t)0xC07C, + (int16_t)0xC04F, (int16_t)0xC02D, (int16_t)0xC014, (int16_t)0xC005, + (int16_t)0xC000, (int16_t)0xC005, (int16_t)0xC014, (int16_t)0xC02D, + (int16_t)0xC04F, (int16_t)0xC07C, (int16_t)0xC0B2, (int16_t)0xC0F2, + (int16_t)0xC13B, (int16_t)0xC18F, (int16_t)0xC1EC, (int16_t)0xC252, + (int16_t)0xC2C2, (int16_t)0xC33B, (int16_t)0xC3BE, (int16_t)0xC44A, + (int16_t)0xC4E0, (int16_t)0xC57E, (int16_t)0xC626, (int16_t)0xC6D6, + (int16_t)0xC78F, (int16_t)0xC851, (int16_t)0xC91B, (int16_t)0xC9EE, + (int16_t)0xCACA, (int16_t)0xCBAD, (int16_t)0xCC99, (int16_t)0xCD8C, + (int16_t)0xCE87, (int16_t)0xCF8A, (int16_t)0xD095, (int16_t)0xD1A6, + (int16_t)0xD2BF, (int16_t)0xD3DF, (int16_t)0xD506, (int16_t)0xD633, + (int16_t)0xD767, (int16_t)0xD8A1, (int16_t)0xD9E1, (int16_t)0xDB26, + (int16_t)0xDC72, (int16_t)0xDDC3, (int16_t)0xDF19, (int16_t)0xE075, + (int16_t)0xE1D5, (int16_t)0xE33A, (int16_t)0xE4A3, (int16_t)0xE611, + (int16_t)0xE783, (int16_t)0xE8F8, (int16_t)0xEA71, (int16_t)0xEBED, + (int16_t)0xED6C, (int16_t)0xEEEF, (int16_t)0xF074, (int16_t)0xF1FB, + (int16_t)0xF384, (int16_t)0xF50F, (int16_t)0xF69C, (int16_t)0xF82B, + (int16_t)0xF9BB, (int16_t)0xFB4B, (int16_t)0xFCDD, (int16_t)0xFE6E + }; + + void Bios000h () + { + debug("Bios entry point"); + R(13) = 0x03007FE0; + R(15) = 0x08000004; + CPU.SwitchToMode(Cpu::M_IRQ); + R(13) = 0x03007FA0; + CPU.SwitchToMode(Cpu::M_SYS); + R(13) = 0x03007F00; + ICPSR.irq_d = false; + IO.Write8(Io::POSTFLG, 0x01); + } + + void Bios008h () + { + // if we are here, we should be in SVC mode (0x13) + // store the spsr, r11, r12 and r14 on the stack + uint32_t baseadd = R(13) - (4*4), add = (baseadd & 0xFFFFFFFC); + MEM.Write32(add , SPSR); + MEM.Write32(add += 4, R(11)); + MEM.Write32(add += 4, R(12)); + MEM.Write32(add + 4, R(14)); + R(13) = baseadd; + + uint8_t swiComment = MEM.Read8(R(14) - 2); + + // put 0x1F in cpsr but don't touch to the irq disable bit + CPU.SwitchToMode(0x1F); + CPSR = 0x0000001F | (CPSR & (0x1 << 7)); + CPU.UpdateICpsr(); + + // store r11 and r14 (of the user mode) on the stack + baseadd = R(13) - (2*4); add = (baseadd & 0xFFFFFFFC); + MEM.Write32(add , R(11)); + MEM.Write32(add + 4, R(14)); + R(13) = baseadd; + + R(14) = 0x168; + + debug("Software IRQ start"); + switch (swiComment) + { + case 0x04: + IntrWait(); + break; + case 0x05: + VBlankIntrWait(); + break; + default: + met_abort("not implemented : " << (int)swiComment); + break; + } + } + + void Bios168h () + { + uint32_t add = R(13) & 0xFFFFFFFC; + R( 2) = MEM.Read32(add ); + R(14) = MEM.Read32(add += 4); + R(13) += 2*4; + + // SVC with fiq and irq disabled + CPU.SwitchToMode(0x13); // SVC + CPSR = 0x000000D3; + CPU.UpdateICpsr(); + add = R(13) & 0xFFFFFFFC; + + SPSR = MEM.Read32(add); + + R(11) = MEM.Read32(add += 4); + R(12) = MEM.Read32(add += 4); + R(14) = MEM.Read32(add + 4); + R(13) += 4*4; + + // FIXME this works (for thumb) ? + if (CPU.Spsr().b.thumb) + R(15) = R(14) + 2; + else + R(15) = R(14) + 4; + + debug("Software IRQ end"); + CPU.SwitchModeBack(); + } + + void Bios018h () + { + debug("IRQ start"); + // stmfd r13!,r0-r3,r12,r14 + uint32_t baseadd = R(13) - (6*4), add = (baseadd & 0xFFFFFFFC); + MEM.Write32(add , R( 0)); + MEM.Write32(add += 4, R( 1)); + MEM.Write32(add += 4, R( 2)); + MEM.Write32(add += 4, R( 3)); + MEM.Write32(add += 4, R(12)); + MEM.Write32(add + 4, R(14)); + + R(13) = baseadd; + + // add r14,r15,0h + R(14) = 0x00000130; + + R(15) = MEM.Read32(0x03007FFC) + 4; + } + + void Bios130h () + { + debug("IRQ end"); + // ldmfd r13!,r0-r3,r12,r14 + uint32_t add = R(13) & 0xFFFFFFFC; + R( 0) = MEM.Read32(add ); + R( 1) = MEM.Read32(add += 4); + R( 2) = MEM.Read32(add += 4); + R( 3) = MEM.Read32(add += 4); + R(12) = MEM.Read32(add += 4); + R(14) = MEM.Read32(add + 4); + + R(13) += 6*4; + + // subs r15,r14,4h + R(15) = R(14); + if (CPU.Spsr().b.thumb) + R(15) -= 2; + + CPU.SwitchModeBack(); + + // XXX FIXME, usefull ? test on breath of fire ! + /*if (FLAG_T) + R(15) &= 0xFFFFFFFE; + else + R(15) &= 0xFFFFFFFC;*/ + } + + void SoftReset () + { + CPU.SoftReset (); + if (MEM.Read8(0x03007FFA)) + R(15) = 0x02000004; + else + R(15) = 0x08000004; + + MEM.SoftReset (); + } + + void RegisterRamReset () + { + IO.Write16(Io::DISPCNT, 0x0080); + uint8_t flagRes = R(0); + if (flagRes & (0x1 )) + MEM.ClearWbram(); + if (flagRes & (0x1 << 1)) + MEM.ClearWcram(); + if (flagRes & (0x1 << 2)) + MEM.ClearPalette(); + if (flagRes & (0x1 << 3)) + MEM.ClearVram(); + if (flagRes & (0x1 << 4)) + MEM.ClearOam(); + if (flagRes & (0x1 << 5)) + IO.ClearSio (); + if (flagRes & (0x1 << 6)) + IO.ClearSound (); + if (flagRes & (0x1 << 7)) + IO.ClearOthers (); + } + + void Halt () + { + IO.Write8(Io::HALTCNT, 0); + } + + void IntrWait () + { + // FIXME ugly + R(13) -= 8; + MEM.Write32(R(13) & 0xFFFFFFFC, R(4)); + MEM.Write32((R(13)+4) & 0xFFFFFFFC, R(14)); + + uint16_t& intFlags = *(uint16_t*)MEM.GetRealAddress(0x03007FF8); + + if (R(0)) + { + if (intFlags & R(1)) + intFlags = (intFlags & R(1)) ^ intFlags; + else + FLAG_Z = 1; + IO.Write16(Io::IME, 1); + } + + IO.Write8(Io::HALTCNT, 0); + + // return address (after IRQ) + R(15) = 0x33C; + + debug("IntrWait start"); + } + + void Bios338h () + { + uint16_t& intFlags = *(uint16_t*)MEM.GetRealAddress(0x03007FF8); + + if (!(intFlags & R(1))) + { + IO.Write16(Io::IME, 1); + IO.Write8(Io::HALTCNT, 0); + } + else + { + intFlags = (intFlags & R(1)) ^ intFlags; + IO.Write16(Io::IME, 1); + + // FIXME ugly + R(4) = MEM.Read32(R(13) & 0xFFFFFFFC); + R(14) = MEM.Read32((R(13)+4) & 0xFFFFFFFC); + R(13) += 8; + + // should lead to 0x168 + R(15) = R(14)+4; + } + + debug("IntWait end"); + } + + void VBlankIntrWait () + { + R(0) = 1; + R(1) = 1; + IntrWait(); + } + + void Div () + { + if (!R(1)) + met_abort("Div by 0"); + + int32_t number = R(0), denom = R(1); + + int32_t div = number / denom; + R(0) = div; + R(1) = number % denom; + R(3) = div < 0 ? -div : div; + } + + void DivArm () + { + uint32_t tmp = R(0); + R(0) = R(1); + R(1) = tmp; + Div(); + } + + void Sqrt () + { + R(0) = (uint16_t)sqrt((float)R(0)); + } + + void ArcTan () + { + int32_t a = -(((int32_t)R(0) * R(0)) >> 14); + int32_t b = 0xA9; + b = ((a * b) >> 14) + 0x0390; + b = ((a * b) >> 14) + 0x091C; + b = ((a * b) >> 14) + 0x0FB6; + b = ((a * b) >> 14) + 0X16AA; + b = ((a * b) >> 14) + 0X2081; + b = ((a * b) >> 14) + 0X3651; + b = ((a * b) >> 14) + 0XA2F9; + + R(0) = (R(0) * b) >> 16; + } + + void ArcTan2 () + { + int16_t x = R(0), y = R(1); + if (y) + if (x) + if (abs(x) < abs(y)) + { + R(0) <<= 14; + Div(); + ArcTan(); + R(0) = 0x4000 - R(0); + if (y < 0) + R(0) += 0x8000; + } + else + { + uint32_t r1 = R(1); + R(1) = R(0); + R(0) = r1 << 14; + Div(); + ArcTan(); + if (x < 0) + R(0) += 0x8000; + else if (y < 0) + R(0) += 0x10000; + } + else + if (y < 0) + R(0) = 0xc000; + else + R(0) = 0x4000; + else + if (x < 0) + R(0) = 0x8000; + else + R(0) = 0x0000; + } + + void CpuSet () + { + if (R(2) & (0x1 << 26)) // 32 bits + { + if (R(2) & (0x1 << 24)) // fixed source address + { + uint32_t source = MEM.Read32(R(0) & 0xFFFFFFFC); + uint32_t address = R(1) & 0xFFFFFFFC; + for (uint32_t count = (R(2) & 0x001FFFFF); count; --count) + { + MEM.Write32(address, source); + address += 4; + } + } + else // copy + { + uint32_t src = R(0) & 0xFFFFFFFC; + uint32_t dest = R(1) & 0xFFFFFFFC; + for (uint32_t count = (R(2) & 0x001FFFFF); count; --count) + { + MEM.Write32(dest, MEM.Read32(src)); + src += 4; + dest += 4; + } + } + } + else // 16 bits + { + if (R(2) & (0x1 << 24)) // fixed source address + { + uint16_t source = MEM.Read16(R(0)); + uint32_t address = R(1); + for (uint32_t count = (R(2) & 0x001FFFFF); count; --count) + { + MEM.Write16(address, source); + address += 2; + } + } + else // copy + { + uint32_t src = R(0); + uint32_t dest = R(1); + for (uint32_t count = (R(2) & 0x001FFFFF); count; --count) + { + MEM.Write16(dest, MEM.Read16(src)); + src += 2; + dest += 2; + } + } + } + } + + void CpuFastSet () + { + if (R(2) & (0x1 << 24)) // fixed source address + { + uint32_t source = MEM.Read32(R(0)); + uint32_t address = R(1); + for (uint32_t count = (R(2) & 0x001FFFFF); count; --count) + { + MEM.Write32(address, source); + address += 4; + } + } + else // copy + { + uint32_t src = R(0); + uint32_t dest = R(1); + for (uint32_t count = (R(2) & 0x001FFFFF); count; --count) + { + MEM.Write32(dest, MEM.Read32(src)); + src += 4; + dest += 4; + } + } + } + + void BgAffineSet () + { + uint32_t src = R(0); + uint32_t dest = R(1); + uint32_t num = R(2); + + int32_t cx, cy; + int16_t dix, diy, rx, ry; + uint16_t alpha; + + int32_t cos, sin; + int16_t dx, dmx, dy, dmy; + + while (num--) + { + cx = MEM.Read32(src); + src += 4; + cy = MEM.Read32(src); + src += 4; + dix = MEM.Read16(src); + src += 2; + diy = MEM.Read16(src); + src += 2; + rx = MEM.Read16(src); + src += 2; + ry = MEM.Read16(src); + src += 2; + alpha = MEM.Read16(src) >> 8; + src += 2; + + sin = sineTable[alpha]; + cos = sineTable[(alpha + 0x40) & 0xFF]; + + dx = (rx * cos) >> 14; + dmx = -((rx * sin) >> 14); + dy = (ry * sin) >> 14; + dmy = (ry * cos) >> 14; + + MEM.Write16(dest, dx); + dest += 2; + MEM.Write16(dest, dmx); + dest += 2; + MEM.Write16(dest, dy); + dest += 2; + MEM.Write16(dest, dmy); + dest += 2; + + MEM.Write32(dest, cx - dx * dix - dmx * diy); + dest += 4; + MEM.Write32(dest, cy - dy * dix - dmy * diy); + dest += 4; + } + } + + void ObjAffineSet () + { + uint32_t src = R(0); + uint32_t dest = R(1); + uint32_t num = R(2); + uint32_t off = R(3); + + int16_t rx, ry; + uint16_t alpha; + + int32_t cos, sin; + int16_t dx, dmx, dy, dmy; + + while (num--) + { + rx = MEM.Read16(src); + src += 2; + ry = MEM.Read16(src); + src += 2; + alpha = MEM.Read16(src) >> 8; + src += 4; + + sin = sineTable[alpha]; + cos = sineTable[(alpha + 0x40) & 0xFF]; + + dx = (rx * cos) >> 14; + dmx = -((rx * sin) >> 14); + dy = (ry * sin) >> 14; + dmy = (ry * cos) >> 14; + + MEM.Write16(dest, dx); + dest += off; + MEM.Write16(dest, dmx); + dest += off; + MEM.Write16(dest, dy); + dest += off; + MEM.Write16(dest, dmy); + dest += off; + } + } + + void LZ77UnCompWram () + { + uint32_t src = R(0); + uint32_t header = MEM.Read32(src); + src += 4; + if (((header >> 4) & 0xF) != 1) + met_abort("This is not LZ77 data"); + uint32_t size = header >> 8; + debug("LZ77UnCompWram from " << IOS_ADD << R(0) << " to " << IOS_ADD << R(1) << ", len : " << size); + uint32_t dest = R(1); + uint8_t flags; + uint16_t block; + uint8_t blocklen; + uint32_t realaddr; + + // for each block of a flags byte + 8 blocks + while (true) + { + flags = MEM.Read8(src++); + + for (uint8_t i = 0; i < 8; ++i) + { + // compressed block of 2 bytes + if (flags & 0x80) + { + block = MEM.Read8(src) << 8 | MEM.Read8(src+1); + src += 2; + blocklen = (block >> 12) + 3; + realaddr = dest - (block & 0x0FFF) - 1; + for(uint16_t j = 0; j < blocklen; ++j) + { + MEM.Write8(dest++, MEM.Read8(realaddr++)); + + --size; + if(size == 0) + { + size = header >> 8; + return; + } + } + } + // uncompressed block of 1 byte + else + { + MEM.Write8(dest++, MEM.Read8(src++)); + + --size; + if (size == 0) + { + size = header >> 8; + return; + } + } + + flags <<= 1; + } + } + } + + void LZ77UnCompVram () + { + uint32_t src = R(0); + uint32_t header = MEM.Read32(src); + src += 4; + if (((header >> 4) & 0xF) != 1) + met_abort("This is not LZ77 data"); + uint32_t size = header >> 8; + debug("LZ77UnCompVram from " << IOS_ADD << R(0) << " to " << IOS_ADD << R(1) << ", len : " << size); + uint32_t dest = R(1); + uint8_t flags; + uint16_t out = 0; + uint8_t shift = 0; + uint16_t block; + uint8_t blocklen; + uint32_t realaddr; + + // for each block of a flags byte + 8 blocks + while (true) + { + flags = MEM.Read8(src++); + + for (uint8_t i = 0; i < 8; ++i) + { + // compressed block of 2 bytes + if (flags & 0x80) + { + block = MEM.Read8(src) << 8 | MEM.Read8(src+1); + src += 2; + blocklen = (block >> 12) + 3; + realaddr = dest + (shift/8) - (block & 0x0FFF) - 1; + for(uint16_t j = 0; j < blocklen; ++j) { + out |= MEM.Read8(realaddr++) << shift; + shift += 8; + + if(shift == 16) { + MEM.Write16(dest, out); + dest += 2; + out = 0; + shift = 0; + } + + --size; + if(size == 0) + { + size = header >> 8; + return; + } + } + } + // uncompressed block of 1 byte + else + { + out |= MEM.Read8(src++) << shift; + shift += 8; + + if (shift == 16) + { + MEM.Write16(dest, out); + dest += 2; + shift = 0; + out = 0; + } + + --size; + if (size == 0) + { + size = header >> 8; + return; + } + } + + flags <<= 1; + } + } + } + + void HuffUnComp () + { + uint32_t src = R(0) & 0xFFFFFFFC; + uint32_t dest = R(1); + uint32_t header = MEM.Read32(src); + src += 4; + if (((header >> 4) & 0xF) != 2) + met_abort("This is not Huffman data"); + uint8_t blockLen = header & 0xF; + uint32_t size = header >> 8; + if (size % 4) + met_abort("Size not multiple of 4 in HuffUnComp"); + uint32_t treeStart = src + 1; + src += 2 + MEM.Read8(src) * 2; + + uint32_t cData = MEM.Read32(src); + src += 4; + uint32_t mask = 0x80000000; + uint32_t treePos = treeStart; + uint8_t node = MEM.Read8(treePos); + bool endNode = false; + uint32_t oData = 0; + uint8_t oShift = 0; + + while (size) + { + treePos = (treePos & 0xFFFFFFFE) + (node & 0x3F) * 2 + 2; + if (cData & mask) + { + ++treePos; + if (node & (0x1 << 6)) + endNode = true; + } + else + { + if (node & (0x1 << 7)) + endNode = true; + } + node = MEM.Read8(treePos); + + if (endNode) + { + oData |= ((uint32_t)node) << oShift; + oShift += blockLen; + + if (oShift >= 32) + { + MEM.Write32(dest, oData); + dest += 4; + size -= 4; + + oShift -= 32; + if (oShift) + oData = node >> (8 - oShift); + else + oData = 0; + } + endNode = false; + treePos = treeStart; + node = MEM.Read8(treePos); + } + + mask >>= 1; + if (!mask) + { + cData = MEM.Read32(src); + src += 4; + mask = 0x80000000; + } + } + } + + void RLUnCompWram () + { + uint32_t src = R(0); + uint32_t header = MEM.Read32(src); + src += 4; + if (((header >> 4) & 0xF) != 3) + met_abort("This is not RL data"); + uint32_t size = header >> 8; + debug("RLUnCompWram from " << IOS_ADD << R(0) << " to " << IOS_ADD << R(1) << ", len : " << size); + uint32_t dest = R(1); + uint8_t flags; + uint8_t block; + uint8_t blocklen; + + // for each block + while (true) + { + flags = MEM.Read8(src++); + blocklen = flags & 0x7F; + + // compressed block + if (flags & 0x80) + { + blocklen += 3; + block = MEM.Read8(src++); + + for(uint8_t i = 0; i < blocklen; ++i) { + MEM.Write8(dest++, block); + + --size; + if(size == 0) + { + size = header >> 8; + return; + } + } + } + // uncompressed block + else + { + blocklen += 1; + + for (uint8_t i = 0; i < blocklen; ++i) + { + MEM.Write8(dest++, MEM.Read8(src++)); + + --size; + if (size == 0) + { + size = header >> 8; + return; + } + } + } + } + } + + void RLUnCompVram () + { + uint32_t src = R(0); + uint32_t header = MEM.Read32(src); + src += 4; + if (((header >> 4) & 0xF) != 3) + met_abort("This is not RL data"); + uint32_t size = header >> 8; + debug("RLUnCompVram from " << IOS_ADD << R(0) << " to " << IOS_ADD << R(1) << ", len : " << size); + uint32_t dest = R(1); + uint8_t flags; + uint16_t out = 0; + uint8_t shift = 0; + uint8_t block; + uint8_t blocklen; + + // for each block + while (true) + { + flags = MEM.Read8(src++); + blocklen = flags & 0x7F; + + // compressed block + if (flags & 0x80) + { + blocklen += 3; + block = MEM.Read8(src++); + + for(uint8_t i = 0; i < blocklen; ++i) { + out |= block << shift; + shift += 8; + + if(shift == 16) { + MEM.Write16(dest, out); + dest += 2; + out = 0; + shift = 0; + } + + --size; + if(size == 0) + { + size = header >> 8; + return; + } + } + } + // uncompressed block + else + { + blocklen += 1; + + for (uint8_t i = 0; i < blocklen; ++i) + { + out |= MEM.Read8(src++) << shift; + shift += 8; + + if (shift == 16) + { + MEM.Write16(dest, out); + dest += 2; + shift = 0; + out = 0; + } + + --size; + if (size == 0) + { + size = header >> 8; + return; + } + } + } + } + } + } +} diff --git a/libmeteor/source/cartmem.cpp b/libmeteor/source/cartmem.cpp new file mode 100644 index 0000000000..37fa9d78ad --- /dev/null +++ b/libmeteor/source/cartmem.cpp @@ -0,0 +1,57 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/cartmem.hpp" +#include "globals.hpp" + +namespace AMeteor +{ +#ifdef __LIBRETRO__ + uint8_t CartMemData[CartMem::MAX_SIZE+4]; +#endif + + CartMem::CartMem() : +#ifdef __LIBRETRO__ + m_data(CartMemData) +#else + m_data(new uint8_t[MAX_SIZE+4]) +#endif + { + } + + CartMem::~CartMem() + { +#ifndef __LIBRETRO__ + delete [] m_data; +#endif + } + + bool CartMem::SaveState (std::ostream& stream) + { + stream.write((char*)m_data, MAX_SIZE); + SS_WRITE_VAR(m_size); + + return true; + } + + bool CartMem::LoadState (std::istream& stream) + { + stream.read((char*)m_data, MAX_SIZE); + SS_READ_VAR(m_size); + + return true; + } +} diff --git a/libmeteor/source/clock.cpp b/libmeteor/source/clock.cpp new file mode 100644 index 0000000000..7f21980ed3 --- /dev/null +++ b/libmeteor/source/clock.cpp @@ -0,0 +1,123 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/clock.hpp" +#include "globals.hpp" +#include "ameteor.hpp" + +#include "debug.hpp" + +namespace AMeteor +{ + void Clock::Reset () + { + // lcd is enabled by default + m_first = m_count = m_cycles = m_lcd = m_sound = 0; + // timers and battery are disabled by default + m_battery = m_timer[0] = m_timer[1] = m_timer[2] = m_timer[3] = + INT_MAX; + } + + void Clock::Commit () + { + unsigned short tocommit; + + m_count += m_cycles; + + // this loop is here because a timer can trigger a dma which will take a + // long time, during this time the lcd must draw and the timers continue + while (m_cycles >= m_first) + { + tocommit = m_cycles; + m_cycles = 0; + + m_lcd -= tocommit; + while (m_lcd <= 0) + LCD.TimeEvent(); + + m_sound -= tocommit; + while (m_sound <= 0) + { + SOUND.TimeEvent(); + // XXX freq + m_sound += SOUND_PERIOD; + } + +#define COMMIT(dev, obj) \ + if (m_##dev != INT_MAX) \ + { \ + m_##dev -= tocommit; \ + while (m_##dev <= 0) \ + obj.TimeEvent(); \ + } + COMMIT(timer[0], TIMER0) + COMMIT(timer[1], TIMER1) + COMMIT(timer[2], TIMER2) + COMMIT(timer[3], TIMER3) + COMMIT(battery, MEM) +#undef COMMIT + + SetFirst(); + } + } + + void Clock::WaitForNext () + { + m_cycles = m_first; + Commit(); + } + +#define SETFIRST(dev) \ + if (m_##dev < m_first) \ + m_first = m_##dev + void Clock::SetFirst () + { + m_first = m_lcd; + SETFIRST(timer[0]); + SETFIRST(timer[1]); + SETFIRST(timer[2]); + SETFIRST(timer[3]); + SETFIRST(sound); + SETFIRST(battery); + } +#undef SETFIRST + + bool Clock::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_cycles); + SS_WRITE_VAR(m_first); + SS_WRITE_VAR(m_lcd); + SS_WRITE_VAR(m_sound); + SS_WRITE_VAR(m_battery); + + SS_WRITE_ARRAY(m_timer); + + return true; + } + + bool Clock::LoadState (std::istream& stream) + { + SS_READ_VAR(m_cycles); + SS_READ_VAR(m_first); + SS_READ_VAR(m_lcd); + SS_READ_VAR(m_sound); + SS_READ_VAR(m_battery); + + SS_READ_ARRAY(m_timer); + + return true; + } +} diff --git a/libmeteor/source/cpu.cpp b/libmeteor/source/cpu.cpp new file mode 100644 index 0000000000..bb405b709a --- /dev/null +++ b/libmeteor/source/cpu.cpp @@ -0,0 +1,328 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/cpu.hpp" +#include "ameteor/bios.hpp" +#include "globals.hpp" +#include "cpu_globals.hpp" +#include "ameteor.hpp" + +#include "debug.hpp" + +#include + +namespace AMeteor +{ + Cpu::Cpu () + { + Reset(); + } + + void Cpu::Reset () + { + std::memset(&m_st, 0, sizeof(m_st)); + R(15) = 0x00000004; + m_st.icpsr.mode = M_SVC; + m_st.icpsr.fiq_d = true; + m_st.icpsr.irq_d = true; + } + + void Cpu::SoftReset () + { + std::memset(&m_st, 0, sizeof(m_st)); + R(13) = 0x03007F00; + R(15) = 0x08000004; + m_st.irq_r[0] = 0x03007FA0; // R13 + m_st.svc_r[0] = 0x03007FE0; // R13 + m_st.icpsr.mode = 0x1F; + m_st.icpsr.fiq_d = true; + } + + void Cpu::UpdateICpsr () + { + m_st.icpsr.mode = m_st.cpsr.b.mode; + m_st.icpsr.irq_d = m_st.cpsr.b.irq_d; + m_st.icpsr.fiq_d = m_st.cpsr.b.fiq_d; + m_st.icpsr.thumb = m_st.cpsr.b.thumb; + m_st.icpsr.s_overflow = m_st.cpsr.b.s_overflow; + m_st.icpsr.f_overflow = m_st.cpsr.b.f_overflow; + m_st.icpsr.f_carry = m_st.cpsr.b.f_carry; + m_st.icpsr.f_zero = m_st.cpsr.b.f_zero; + m_st.icpsr.f_sign = m_st.cpsr.b.f_sign; + } + + void Cpu::UpdateCpsr () + { + m_st.cpsr.b.mode = m_st.icpsr.mode; + m_st.cpsr.b.irq_d = m_st.icpsr.irq_d; + m_st.cpsr.b.fiq_d = m_st.icpsr.fiq_d; + m_st.cpsr.b.thumb = m_st.icpsr.thumb; + m_st.cpsr.b.s_overflow = m_st.icpsr.s_overflow; + m_st.cpsr.b.f_overflow = m_st.icpsr.f_overflow; + m_st.cpsr.b.f_carry = m_st.icpsr.f_carry; + m_st.cpsr.b.f_zero = m_st.icpsr.f_zero; + m_st.cpsr.b.f_sign = m_st.icpsr.f_sign; + } + + void Cpu::SwitchToMode (uint8_t newmode) + { + SaveMode(m_st.icpsr.mode); + + switch (newmode) + { + case 0x10: // User (non-privileged) + case 0x1F: // System (privileged 'User' mode) + switch (m_st.icpsr.mode) + { + case 0x10: + case 0x1F: + case 0x11: + std::memcpy(m_st.r + 8, m_st.usr_r, sizeof(m_st.usr_r)); + break; + default: + std::memcpy(m_st.r + 13, m_st.usr_r + 5, 2*4); + break; + } + break; + case 0x11: // FIQ + std::memcpy(m_st.r + 8, m_st.fiq_r, sizeof(m_st.fiq_r)); + break; + case 0x12: // IRQ + std::memcpy(m_st.r + 13, m_st.irq_r, sizeof(m_st.irq_r)); + break; + case 0x13: // Supervisor (SWI) + std::memcpy(m_st.r + 13, m_st.svc_r, sizeof(m_st.svc_r)); + break; + case 0x17: // Abort + std::memcpy(m_st.r + 13, m_st.abt_r, sizeof(m_st.abt_r)); + break; + case 0x1B: // Undefined + std::memcpy(m_st.r + 13, m_st.und_r, sizeof(m_st.und_r)); + break; + default: + met_abort("Unknown CPU mode : " << IOS_ADD << (int)newmode); + break; + } + + UpdateCpsr(); + m_st.spsr.dw = m_st.cpsr.dw; + m_st.icpsr.mode = newmode; + } + + void Cpu::SwitchModeBack () + { + // oldmode is the mode on which we want to switch back + uint8_t oldmode = m_st.spsr.b.mode, curmode = m_st.icpsr.mode; + // we don't care if the spsr of the mode we are using is modified + SaveMode(curmode); + + m_st.cpsr.dw = m_st.spsr.dw; + UpdateICpsr(); + CheckInterrupt(); + switch (oldmode) + { + case 0x10: // User (non-privileged) + case 0x1F: // System (privileged 'User' mode) + switch (curmode) + { + case 0x10: + case 0x1F: + case 0x11: + std::memcpy(m_st.r + 8, m_st.usr_r, sizeof(m_st.usr_r)); + break; + default: + std::memcpy(m_st.r + 13, m_st.usr_r + 5, 2*4); + break; + } + break; + case 0x11: // FIQ + std::memcpy(m_st.r + 8, m_st.fiq_r, sizeof(m_st.fiq_r)); + m_st.spsr.dw = m_st.fiq_spsr.dw; + break; + case 0x12: // IRQ + std::memcpy(m_st.r + 13, m_st.irq_r, sizeof(m_st.irq_r)); + m_st.spsr.dw = m_st.irq_spsr.dw; + break; + case 0x13: // Supervisor (SWI) + std::memcpy(m_st.r + 13, m_st.svc_r, sizeof(m_st.svc_r)); + m_st.spsr.dw = m_st.svc_spsr.dw; + break; + case 0x17: // Abort + std::memcpy(m_st.r + 13, m_st.abt_r, sizeof(m_st.abt_r)); + m_st.spsr.dw = m_st.abt_spsr.dw; + break; + case 0x1B: // Undefined + std::memcpy(m_st.r + 13, m_st.und_r, sizeof(m_st.und_r)); + m_st.spsr.dw = m_st.und_spsr.dw; + break; + default: + met_abort("Unknown CPU mode : " << IOS_ADD << (int)oldmode); + break; + } + } + + void Cpu::SaveMode (uint8_t mode) + { + switch (mode) + { + case 0x10: // User (non-privileged) + case 0x1F: // System (privileged 'User' mode) + std::memcpy(m_st.usr_r, m_st.r + 8, sizeof(m_st.usr_r)); + break; + case 0x11: // FIQ + std::memcpy(m_st.fiq_r, m_st.r + 8, sizeof(m_st.fiq_r)); + m_st.fiq_spsr.dw = m_st.spsr.dw; + break; + case 0x12: // IRQ + std::memcpy(m_st.irq_r, m_st.r + 13, sizeof(m_st.irq_r)); + m_st.irq_spsr.dw = m_st.spsr.dw; + break; + case 0x13: // Supervisor (SWI) + std::memcpy(m_st.svc_r, m_st.r + 13, sizeof(m_st.svc_r)); + m_st.svc_spsr.dw = m_st.spsr.dw; + break; + case 0x17: // Abort + std::memcpy(m_st.abt_r, m_st.r + 13, sizeof(m_st.abt_r)); + m_st.abt_spsr.dw = m_st.spsr.dw; + break; + case 0x1B: // Undefined + std::memcpy(m_st.und_r, m_st.r + 13, sizeof(m_st.und_r)); + m_st.und_spsr.dw = m_st.spsr.dw; + break; + default: + met_abort("Unknown CPU mode : " << IOS_ADD << (int)mode); + break; + } + } + + void Cpu::Interrupt () + { + // Switch mode + SwitchToMode(0x12); // IRQ + // Save PC + R(14) = R(15); + // FIXME : why ? this seems to be USELESS ! (look at bios irq end) + if (m_st.icpsr.thumb) + R(14) += 2; + // Switch to ARM + m_st.icpsr.thumb = false; + // Disable IRQ + m_st.icpsr.irq_d = true; + SetInterrupt(false); + // Branch on 0x18 + R(15) = 0x1C; + } + + void Cpu::SoftwareInterrupt () + { + // Switch mode + SwitchToMode(0x13); // Supervisor + // Save PC + R(14) = R(15) - (m_st.icpsr.thumb ? 2 : 4); + // Switch to ARM + m_st.icpsr.thumb = false; + // Disable IRQ + m_st.icpsr.irq_d = true; + SetInterrupt(false); + // Branch on 0x8 + R(15) = 0xC; + } + + // TODO put this in Bios, no ? + void Cpu::SoftwareInterrupt (uint32_t comment) + { + if (MEM.HasBios()) + SoftwareInterrupt(); + else + switch (comment) + { + case 0x00: + Bios::SoftReset(); + break; + case 0x01: + Bios::RegisterRamReset(); + break; + case 0x02: + Bios::Halt(); + break; + case 0x04: + case 0x05: + SoftwareInterrupt(); + break; + case 0x06: + Bios::Div(); + break; + case 0x07: + Bios::DivArm(); + break; + case 0x08: + Bios::Sqrt(); + break; + case 0x09: + Bios::ArcTan(); + break; + case 0x0A: + Bios::ArcTan2(); + break; + case 0x0B: + Bios::CpuSet(); + break; + case 0x0C: + Bios::CpuFastSet(); + break; + case 0x0E: + Bios::BgAffineSet(); + break; + case 0x0F: + Bios::ObjAffineSet(); + break; + case 0x11: + Bios::LZ77UnCompWram(); + break; + case 0x12: + Bios::LZ77UnCompVram(); + break; + case 0x13: + Bios::HuffUnComp(); + break; + case 0x14: + Bios::RLUnCompWram(); + break; + case 0x15: + Bios::RLUnCompVram(); + break; + default: + met_abort("Unknown software interrupt : " << IOS_ADD << comment); + break; + } + } + + bool Cpu::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_st); + + return true; + } + + bool Cpu::LoadState (std::istream& stream) + { + SS_READ_VAR(m_st); + + CheckInterrupt(); + + return true; + } +} diff --git a/libmeteor/source/cpu_globals.hpp b/libmeteor/source/cpu_globals.hpp new file mode 100644 index 0000000000..e0f73bdd77 --- /dev/null +++ b/libmeteor/source/cpu_globals.hpp @@ -0,0 +1,41 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __CPU_GLOBALS_H__ +#define __CPU_GLOBALS_H__ + +#undef R +#define R(reg) m_st.r[reg] + +#undef CPU +#define CPU (*this) + +#undef CPSR +#undef SPSR +#undef FLAG_Z +#undef FLAG_N +#undef FLAG_C +#undef FLAG_V +#undef FLAG_T +#define CPSR (m_st.cpsr.dw) +#define SPSR (m_st.spsr.dw) +#define FLAG_Z (m_st.icpsr.f_zero) +#define FLAG_N (m_st.icpsr.f_sign) +#define FLAG_C (m_st.icpsr.f_carry) +#define FLAG_V (m_st.icpsr.f_overflow) +#define FLAG_T (m_st.icpsr.thumb) + +#endif diff --git a/libmeteor/source/debug.cpp b/libmeteor/source/debug.cpp new file mode 100644 index 0000000000..50365d1a59 --- /dev/null +++ b/libmeteor/source/debug.cpp @@ -0,0 +1,85 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "debug.hpp" +#include "ameteor/cpu.hpp" +#include "globals.hpp" +#include "ameteor.hpp" + +#include +#include + +namespace AMeteor +{ + // TODO make this more guidelined (like the above assert) + void debug_bits(uint32_t u) + { +#if defined METDEBUG && defined METDEBUGLOG + for (register int8_t c = 31; c >= 0; --c) + { + STDBG << !!(u & (((uint32_t)0x1) << c)); + if (!(c % 8)) + STDBG << ' '; + } + STDBG << std::endl; +#else + (void)u; +#endif + } + + void debug_bits_16(uint16_t u) + { +#if defined METDEBUG && defined METDEBUGLOG + for (register int8_t c = 15; c >= 0; --c) + { + STDBG << !!(u & (((uint32_t)0x1) << c)); + if (!(c % 8)) + STDBG << ' '; + } + STDBG << std::endl; +#else + (void)u; +#endif + } + +#ifdef MET_REGS_DEBUG + void PrintRegs () + { + static uint32_t regs[17] = {0}; + + for (uint8_t c = 0; c <= 15; ++c) + if (R(c) != regs[c]) + { + STDBG << "R" << std::setbase(10) << (int)c << " = " << IOS_ADD << R(c) << '\n'; + regs[c] = R(c); + } + CPU.UpdateCpsr(); + if (CPSR != regs[16]) + { + STDBG << "R16 = " << IOS_ADD << CPSR << '\n'; + regs[16] = CPSR; + } + } + + void PrintStack (uint32_t stackadd) + { + uint32_t add = stackadd; + debug("Stack : " << IOS_ADD << add); + for (; add < 0x03008000; add += 4) + debug(IOS_ADD << MEM.Read32(add)); + } +#endif +} diff --git a/libmeteor/source/debug.hpp b/libmeteor/source/debug.hpp new file mode 100644 index 0000000000..350cacd45c --- /dev/null +++ b/libmeteor/source/debug.hpp @@ -0,0 +1,92 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __DEBUG_H__ +#define __DEBUG_H__ + +#include +#include +#include +#include + +// for abort macro +#include "ameteor.hpp" + +#if 0 +#define met_abort(str) \ + { \ + std::cerr << IOS_NOR << "Fatal error :\n" << str << "\nFile : " \ + << __FILE__ << "\nLine : " << __LINE__ << "\nr15 = " \ + << IOS_ADD << ::AMeteor::_cpu.Reg(15) << "\n[r15] = " << IOS_ADD \ + << ::AMeteor::_memory.Read32(::AMeteor::_cpu.Reg(15)) \ + << "\nFlag T : " << ::AMeteor::_cpu.ICpsr().thumb << std::endl; \ + abort(); \ + } +#endif +#ifdef METDEBUG +#include +extern "C" int __stdcall MessageBoxA(int, const char *, const char *, int); +#define met_abort(_str) if(true)\ + { \ + std::stringstream _zisrny; \ + _zisrny << IOS_NOR << "Fatal error :\n" << _str << "\nFile : " \ + << __FILE__ << "\nLine : " << __LINE__ << "\nr15 = " \ + << IOS_ADD << ::AMeteor::_cpu.Reg(15) << "\n[r15] = " << IOS_ADD \ + << ::AMeteor::_memory.Read32(::AMeteor::_cpu.Reg(15)) \ + << "\nFlag T : " << ::AMeteor::_cpu.ICpsr().thumb << std::endl; \ + MessageBoxA(NULL, _zisrny.str().c_str(), "FUCK!", 0); \ + } + +#else +#define met_abort(str) {} +#endif + +#define STDBG std::cout +//#define STDBG debug_stream + +#if defined METDEBUG && defined METDEBUGLOG +//XXX +# define MYDEBUG +# define debug(str) \ + STDBG << str << std::endl +# define debug_(str) \ + STDBG << str +#else +# define debug(s) {} +# define debug_(s) {} +#endif + +#define IOS_ADD \ + "0x" << std::setbase(16) << std::setw(8) << std::setfill('0') \ + << std::uppercase +#define IOS_NOR \ + std::setbase(10) << std::setw(0) << std::setfill(' ') + +namespace AMeteor +{ + void debug_bits(uint32_t u); + void debug_bits_16(uint16_t u); + +#if defined MET_REGS_DEBUG + void PrintRegs (); + void PrintStack (uint32_t stackadd); +#else +#define PrintRegs() +#define PrintStack(b) +#endif +} + +#endif diff --git a/libmeteor/source/disassembler/argimmediate.cpp b/libmeteor/source/disassembler/argimmediate.cpp new file mode 100644 index 0000000000..cf17a5ccf1 --- /dev/null +++ b/libmeteor/source/disassembler/argimmediate.cpp @@ -0,0 +1,39 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/disassembler/argimmediate.hpp" + +#include +#include + +namespace AMeteor +{ + namespace Disassembler + { + std::string ArgImmediate::GetString () const + { + std::ostringstream ss; + ss << '#'; + ss << std::hex << std::setw(2) << std::setfill('0') << m_imm << 'h'; + return ss.str(); + } + + Argument* ArgImmediate::Clone () const + { + return new ArgImmediate(*this); + } + } +} diff --git a/libmeteor/source/disassembler/argmulregisters.cpp b/libmeteor/source/disassembler/argmulregisters.cpp new file mode 100644 index 0000000000..5e0bae0822 --- /dev/null +++ b/libmeteor/source/disassembler/argmulregisters.cpp @@ -0,0 +1,83 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/disassembler/argmulregisters.hpp" + +#include + +namespace AMeteor +{ + namespace Disassembler + { + std::string ArgMulRegisters::GetString () const + { + std::ostringstream ss; + ss << '{'; + + bool open = false; + + for (Registers::const_iterator iter = m_regs.begin(); + iter != m_regs.end(); ++iter) + { + if (open && + iter + 1 < m_regs.end() && + *(iter + 1) == (*iter) + 1) + { + continue; + } + else if (iter + 2 < m_regs.end() && + *(iter + 1) == (*iter) + 1 && + *(iter + 2) == (*iter) + 2) + { + ss << 'r' << (int)*iter << '-'; + open = true; + } + else + { + ss << 'r' << (int)*iter; + open = false; + if (iter + 1 != m_regs.end()) + ss << ", "; + } + } + + if (m_lastreg == SPREG_LR) + { + if (ss.width() != 1) + ss << ", "; + ss << "lr"; + } + else if (m_lastreg == SPREG_PC) + { + if (ss.width() != 1) + ss << ", "; + ss << "pc"; + } + + ss << '}'; + + if (m_forceuser) + ss << '^'; + + return ss.str(); + } + + Argument* ArgMulRegisters::Clone () const + { + return new ArgMulRegisters(*this); + } + } +} diff --git a/libmeteor/source/disassembler/argpsr.cpp b/libmeteor/source/disassembler/argpsr.cpp new file mode 100644 index 0000000000..c03a5e20d6 --- /dev/null +++ b/libmeteor/source/disassembler/argpsr.cpp @@ -0,0 +1,53 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/disassembler/argpsr.hpp" + +namespace AMeteor +{ + namespace Disassembler + { + std::string ArgPsr::GetString () const + { + std::string out; + + if (m_spsr) + out = "SPSR"; + else + out = "CPSR"; + + if (m_fields <= 0xF) + { + out += '_'; + if (m_fields & 0x1) + out += 'c'; + if (m_fields & 0x2) + out += 'x'; + if (m_fields & 0x4) + out += 's'; + if (m_fields & 0x8) + out += 'f'; + } + + return out; + } + + Argument* ArgPsr::Clone () const + { + return new ArgPsr(*this); + } + } +} diff --git a/libmeteor/source/disassembler/argregister.cpp b/libmeteor/source/disassembler/argregister.cpp new file mode 100644 index 0000000000..5b00ba1937 --- /dev/null +++ b/libmeteor/source/disassembler/argregister.cpp @@ -0,0 +1,53 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/disassembler/argregister.hpp" + +#include + +namespace AMeteor +{ + namespace Disassembler + { + std::string ArgRegister::GetString () const + { + static const char* SpeRegisters[] = {"SP", "LR", "PC"}; + + std::ostringstream ss; + + if (m_memory) + ss << '['; + + if (m_special) + ss << SpeRegisters[m_reg-13]; + else + ss << 'r' << (int)m_reg; + + if (m_memory) + ss << ']'; + + if (m_writeback) + ss << '!'; + + return ss.str(); + } + + Argument* ArgRegister::Clone () const + { + return new ArgRegister(*this); + } + } +} diff --git a/libmeteor/source/disassembler/argrelative.cpp b/libmeteor/source/disassembler/argrelative.cpp new file mode 100644 index 0000000000..16355d104c --- /dev/null +++ b/libmeteor/source/disassembler/argrelative.cpp @@ -0,0 +1,71 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/disassembler/argrelative.hpp" + +#include + +namespace AMeteor +{ + namespace Disassembler + { + ArgRelative::ArgRelative (const ArgRegister& reg, const Argument& off, + bool pre, bool up, bool writeback) : + Argument(), + m_reg(reg), + m_off(off.Clone()), + m_pre(pre), + m_up(up), + m_writeback(writeback) + { } + + ArgRelative::ArgRelative (const ArgRelative& arg) : + Argument(), + m_reg(arg.m_reg), + m_off(arg.m_off->Clone()), + m_pre(arg.m_pre), + m_up(arg.m_up), + m_writeback(arg.m_writeback) + { } + + ArgRelative::~ArgRelative () + { + delete m_off; + } + + Argument* ArgRelative::Clone () const + { + return new ArgRelative(*this); + } + + std::string ArgRelative::GetString () const + { + std::ostringstream ss; + ss << "[r" << (int)m_reg.GetRegister(); + + if (m_pre) + { + ss << (m_up ? ", +" : ", -") << m_off->GetString() << ']'; + if (m_writeback) + ss << '!'; + } + else + ss << (m_up ? "], +" : "], -") << m_off->GetString(); + + return ss.str(); + } + } +} diff --git a/libmeteor/source/disassembler/argshift.cpp b/libmeteor/source/disassembler/argshift.cpp new file mode 100644 index 0000000000..5fa4522537 --- /dev/null +++ b/libmeteor/source/disassembler/argshift.cpp @@ -0,0 +1,71 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/disassembler/argshift.hpp" + +#include + +namespace AMeteor +{ + namespace Disassembler + { + ArgShift::ArgShift (const Argument& arg1, const Argument& arg2, + ShiftType type, bool memory) : + Argument(), + m_arg1(arg1.Clone()), + m_arg2(arg2.Clone()), + m_type(type), + m_memory(memory) + { } + + ArgShift::ArgShift (const ArgShift& arg) : + Argument(), + m_arg1(arg.m_arg1->Clone()), + m_arg2(arg.m_arg2->Clone()), + m_type(arg.m_type), + m_memory(arg.m_memory) + { } + + ArgShift::~ArgShift () + { + delete m_arg1; + delete m_arg2; + } + + Argument* ArgShift::Clone () const + { + return new ArgShift(*this); + } + + std::string ArgShift::GetString () const + { + static const char* Shifts[] = {", LSL ", ", LSR ", ", ASR ", ", ROR ", + ", RRX "}; + + std::ostringstream ss; + + if (m_memory) + ss << '['; + + ss << m_arg1->GetString() << Shifts[m_type] << m_arg2->GetString(); + + if (m_memory) + ss << ']'; + + return ss.str(); + } + } +} diff --git a/libmeteor/source/disassembler/arguimmediate.cpp b/libmeteor/source/disassembler/arguimmediate.cpp new file mode 100644 index 0000000000..9d4ce1fd05 --- /dev/null +++ b/libmeteor/source/disassembler/arguimmediate.cpp @@ -0,0 +1,39 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/disassembler/arguimmediate.hpp" + +#include +#include + +namespace AMeteor +{ + namespace Disassembler + { + std::string ArgUImmediate::GetString () const + { + std::ostringstream ss; + ss << '#'; + ss << std::hex << std::setw(8) << std::setfill('0') << m_imm << 'h'; + return ss.str(); + } + + Argument* ArgUImmediate::Clone () const + { + return new ArgUImmediate(*this); + } + } +} diff --git a/libmeteor/source/disassembler/arguments.cpp b/libmeteor/source/disassembler/arguments.cpp new file mode 100644 index 0000000000..714ac9a881 --- /dev/null +++ b/libmeteor/source/disassembler/arguments.cpp @@ -0,0 +1,51 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/disassembler/arguments.hpp" + +namespace AMeteor +{ + namespace Disassembler + { + Arguments::~Arguments () + { + Clear(); + } + + void Arguments::Clear () + { + for (std::vector::iterator iter = m_args.begin(); + iter != m_args.end(); ++iter) + { + delete *iter; + } + m_args.clear(); + } + + std::string Arguments::GetString () const + { + std::string out; + for (std::vector::const_iterator iter = m_args.begin(); + iter != m_args.end(); ++iter) + { + if (!out.empty()) + out += ", "; + out += (*iter)->GetString(); + } + return out; + } + } +} diff --git a/libmeteor/source/disassembler/instruction.cpp b/libmeteor/source/disassembler/instruction.cpp new file mode 100644 index 0000000000..eef4248219 --- /dev/null +++ b/libmeteor/source/disassembler/instruction.cpp @@ -0,0 +1,691 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/disassembler/instruction.hpp" + +#include "ameteor/disassembler/argregister.hpp" +#include "ameteor/disassembler/argrelative.hpp" +#include "ameteor/disassembler/argimmediate.hpp" +#include "ameteor/disassembler/arguimmediate.hpp" +#include "ameteor/disassembler/argshift.hpp" +#include "ameteor/disassembler/argpsr.hpp" +#include "ameteor/disassembler/argmulregisters.hpp" + +#include "../globals.hpp" // for ROR + +namespace AMeteor +{ + namespace Disassembler + { + void Instruction::Clear () + { + m_operator.clear(); + m_args.Clear(); + } + +#define Rn ((code >> 16) & 0xF) +#define Rd ((code >> 12) & 0xF) +#define Rs ((code >> 8) & 0xF) +#define Rm (code & 0xF) + + void Instruction::ParseArm (uint32_t offset, uint32_t code) + { + Clear(); + + if ((code & 0x0FFFFFD0) == 0x12FFF10) + { + if (code & (0x1 << 5)) + m_operator = "BLX"; + else + m_operator = "BX"; + m_args.AddArgument(ArgRegister(Rm)); + ParseArmCondition(code); + } + else if (((code >> 25) & 0x7) == 0x5) + { + if (((code >> 28) & 0xF) == 0xF) + { + m_operator = "BLX"; + m_args.AddArgument(ArgImmediate(offset + 8 + + (((code & (0x1 << 23)) ? 0xFF000000 | (code & 0x00FFFFFF) + : (code & 0x00FFFFFF)) << 2) + + ((code & (0x1 << 24)) ? 2 : 0))); + } + else + { + if (code & (0x1 << 24)) + m_operator = "BL"; + else + m_operator = "B"; + m_args.AddArgument(ArgImmediate(offset + 8 + + (((code & (0x1 << 23)) ? 0xFF000000 | (code & 0x00FFFFFF) + : (code & 0x00FFFFFF)) << 2))); + ParseArmCondition(code); + } + } + else if (((code >> 25) & 0x7) == 0x1) + { + ParseArmDataProc(code); + } + else if (((code >> 26) & 0x3) == 0x1) + { + if ((code & 0xF0100000) != 0xF0100000) // not PLD + m_args.AddArgument(ArgRegister(Rd)); + if (code & (0x1 << 25)) // register offset + { + switch ((code >> 5) & 0x3) + { + case 0: // Logical Shift Left + m_args.AddArgument(ArgRelative(Rn, ArgShift(ArgRegister(Rm), + ArgImmediate((code >> 7) & 0x1F), SHIFT_LSL, false), + code & (0x1 << 24), code & (0x1 << 23), + code & (0x1 << 21))); + break; + case 1: // Logical Shift Right + if (code & (0x1F << 7)) + m_args.AddArgument(ArgRelative(Rn, ArgShift(ArgRegister(Rm), + ArgImmediate((code >> 7) & 0x1F), SHIFT_LSR, false), + code & (0x1 << 24), code & (0x1 << 23), + code & (0x1 << 21))); + else + m_args.AddArgument(ArgRelative(Rn, ArgShift(ArgRegister(Rm), + ArgImmediate(32), SHIFT_LSR, false), + code & (0x1 << 24), code & (0x1 << 23), + code & (0x1 << 21))); + break; + case 2: // Arithmetic Shift Right + if (code & (0x1F << 7)) + m_args.AddArgument(ArgRelative(Rn, ArgShift(ArgRegister(Rm), + ArgImmediate((code >> 7) & 0x1F), SHIFT_ASR, false), + code & (0x1 << 24), code & (0x1 << 23), + code & (0x1 << 21))); + else + m_args.AddArgument(ArgRelative(Rn, ArgShift(ArgRegister(Rm), + ArgImmediate(32), SHIFT_ASR, false), + code & (0x1 << 24), code & (0x1 << 23), + code & (0x1 << 21))); + break; + case 3: // ROtate Right + if (code & (0x1F << 7)) + m_args.AddArgument(ArgRelative(Rn, ArgShift(ArgRegister(Rm), + ArgImmediate((code >> 7) & 0x1F), SHIFT_ROR, false), + code & (0x1 << 24), code & (0x1 << 23), + code & (0x1 << 21))); + else + m_args.AddArgument(ArgRelative(Rn, ArgShift(ArgRegister(Rm), + ArgImmediate(1), SHIFT_RRX, false), + code & (0x1 << 24), code & (0x1 << 23), + code & (0x1 << 21))); + break; + } + } + else // immediate offset + { + m_args.AddArgument(ArgRelative(Rn, ArgImmediate(code & 0xFFF), + code & (0x1 << 24), code & (0x1 << 23), + code & (0x1 << 21))); + } + + if ((code & 0xF0100000) == 0xF0100000) + m_operator = "PLD"; + else + { + if (code & (0x1 << 20)) + m_operator = "LDR"; + else + m_operator = "STR"; + if (code & (0x1 << 22)) + m_operator += "B"; + + ParseArmCondition(code); + } + } + else if (((code >> 25) & 0x7) == 0x4) + { + if (code & (0x1 << 20)) + m_operator = "LDM"; + else + m_operator = "STM"; + + if (code & (0x1 << 23)) + m_operator += 'I'; + else + m_operator += 'D'; + + if (code & (0x1 << 24)) + m_operator += 'B'; + else + m_operator += 'A'; + + m_args.AddArgument(ArgRegister(Rn, code & (0x1 << 21))); + + ArgMulRegisters argRegs(code & (0x1 << 22)); + for (register uint8_t n = 0; n < 16; ++n) + if (code & (0x1 << n)) + argRegs.AddRegister(n); + m_args.AddArgument(argRegs); + + ParseArmCondition(code); + } + else if (((code >> 25) & 0x7) == 0x0) + { + if ((code & 0x0FC000F0) == 0x00000090 || + (code & 0x0F8000F0) == 0x00800090 || + (code & 0x0F900090) == 0x01000080) + { + // NOTE : In this instruction Rn and Rd are inverted + static const char* Instructions[] = {"MUL", "MLA", "Reserved", + "Reserved", "UMULL", "UMLAL", "SMULL", "SMLAL", "SMLAxy", + "", // This is for SMLAWy and SMULWy + "SMLALxy", "SMULxy", "Reserved", "Reserved", "Reserved", + "Reserved"}; + + uint8_t opcode = (code >> 21) & 0xF; + if (opcode == 0x9) + m_operator = (code & (0x1 << 5)) ? "SMULWy" : "SMLAWy"; + else + m_operator = Instructions[opcode]; + if (!(opcode & (0x1 << 4)) && (code & (0x1 << 20))) + m_operator += 'S'; + + ParseArmCondition(code); + + if ((opcode & 0xC) == 0x4 || opcode == 0xA) + m_args.AddArgument(ArgRegister(Rd)); + m_args.AddArgument(ArgRegister(Rn)); + m_args.AddArgument(ArgRegister(Rm)); + m_args.AddArgument(ArgRegister(Rs)); + if ((opcode & 0xE) == 0x8 || opcode == 0x1) + m_args.AddArgument(ArgRegister(Rd)); + } + else if ((code & (0x1 << 7)) && (code & (0x1 << 4))) + { + if (((code >> 23) & 0x3) == 0x2 && ((code >> 20) & 0x3) == 0x0 + && ((code >> 4) & 0xFF) == 0x09) + { + if (code & (0x1 << 22)) // SWPB + m_operator = "SWPB"; + else // SWP + m_operator = "SWP"; + + ParseArmCondition(code); + + m_args.AddArgument(ArgRegister(Rd)); + m_args.AddArgument(ArgRegister(Rm)); + m_args.AddArgument(ArgRegister(Rn, false, false, true)); + } + else + { + static const char* Instructions[] = {"Reserved", "STRH", "LDRD", + "STRD", "Reserved", "LDRH", "LDRSB", "LDRSH"}; + + m_operator = Instructions[((code >> 18) & 0x4) + | ((code >> 5) & 0x3)]; + + ParseArmCondition(code); + + m_args.AddArgument(ArgRegister(Rd)); + + if (code & (0x1 << 22)) // immediate + { + m_args.AddArgument(ArgRelative(ArgRegister(Rn), + ArgImmediate(((code >> 4) & 0xF0) | (code & 0xF)), + code & (0x1 << 24), code & (0x1 << 23), + code & (0x1 << 21))); + } + else + { + m_args.AddArgument(ArgRelative(ArgRegister(Rn), + ArgRegister(code & 0xF), + code & (0x1 << 24), code & (0x1 << 23), + code & (0x1 << 21))); + } + } + } + else if (((code >> 23) & 0x3) == 0x2) + { + if (!((code >> 20) & 0x1)) + { + if (code & (0x1 << 21)) + { + m_operator = "MSR"; + + m_args.AddArgument(ArgPsr(code & (0x1 << 22), + (code >> 16) & 0xF)); + + if (code & (0x1 << 25)) // immediate + { + m_args.AddArgument(ArgUImmediate( + ROR(code & 0xFF, (code >> 8) & 0xF))); + } + else + { + m_args.AddArgument(ArgRegister(Rm)); + } + } + else + { + m_operator = "MRS"; + + m_args.AddArgument(ArgRegister(Rd)); + m_args.AddArgument(ArgPsr(code & (0x1 << 22))); + } + + ParseArmCondition(code); + } + else + { + ParseArmDataProc(code); + } + } + else + { + ParseArmDataProc(code); + } + } + else + { + m_operator = "Unknown"; + } + } + + void Instruction::ParseArmDataProc (uint32_t code) + { + static const char* ops[] = {"AND", "EOR", "SUB", "RSB", "ADD", "ADC", + "SBC", "RSC", "TST", "TEQ", "CMP", "CMN", "ORR", "MOV", "BIC", "MVN"}; + + uint8_t opcode = (code >> 21) & 0xF; + + if (opcode < 0x8 || opcode > 0xB) + m_args.AddArgument(ArgRegister(Rd)); + if (opcode != 0xD && opcode != 0xF) + m_args.AddArgument(ArgRegister(Rn)); + + if (code & (0x1 << 25)) // Immediate operand 2 + { + /*if (code & (0xF << 8)) + m_args.AddArgument(ArgShift(ArgImmediate(code & 0xFF), + ArgImmediate(((code >> 8) & 0xF) << 1), SHIFT_ROR, false)); + else + m_args.AddArgument(ArgImmediate(code & 0xFF));*/ + m_args.AddArgument(ArgUImmediate( + ROR(code & 0xFF, (code >> 7) & 0x1E))); + } + else + { + switch ((code >> 5) & 0x3) + { + case 0: // Logical Shift Left + if (code & (0x1 << 4)) // Shift by register + { + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgRegister(Rs), SHIFT_LSL, false)); + } + else // Shift by immediate + { + if (code & (0x1F << 7)) + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgImmediate((code >> 7) & 0x1F), SHIFT_LSL, false)); + else + m_args.AddArgument(ArgRegister(Rm)); + } + break; + case 1: // Logical Shift Right + if (code & (0x1 << 4)) // Shift by register + { + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgRegister(Rs), SHIFT_LSR, false)); + } + else // Shift by immediate + { + if (code & (0x1F << 7)) + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgImmediate((code >> 7) & 0x1F), SHIFT_LSR, false)); + else + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgImmediate(32), SHIFT_LSR, false)); + } + break; + case 2: // Arithmetic Shift Right + if (code & (0x1 << 4)) // Shift by register + { + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgRegister(Rs), SHIFT_ASR, false)); + } + else // Shift by immediate + { + if (code & (0x1F << 7)) + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgImmediate((code >> 7) & 0x1F), SHIFT_ASR, false)); + else + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgImmediate(32), SHIFT_ASR, false)); + } + break; + case 3: // ROtate Right + if (code & (0x1 << 4)) // Shift by register + { + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgRegister(Rs), SHIFT_ROR, false)); + } + else // Shift by immediate + { + if (code & (0x1F << 7)) + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgImmediate((code >> 7) & 0x1F), SHIFT_ROR, false)); + else + m_args.AddArgument(ArgShift(ArgRegister(Rm), + ArgImmediate(1), SHIFT_RRX, false)); + } + break; + } + } + + m_operator = ops[opcode]; + + if (code & (0x1 << 20) && (opcode < 0x8 || opcode > 0xB)) + { + m_operator += "S"; + } + ParseArmCondition(code); + } + + void Instruction::ParseArmCondition (uint32_t code) + { + static const char* Conditions[] = {"EQ", "NE", "CS", "CC", "MI", "PL", + "VS", "VC", "HI", "LS", "GE", "LT", "GT", "LE", "", "NV"}; + + m_operator += Conditions[code >> 28]; + } + +#undef Rn +#undef Rd +#undef Rs +#undef Rm + +#define Rb ((code >> 8) & 0x7) +#define Ro ((code >> 6) & 0x7) +#define Rs ((code >> 3) & 0x7) +#define Rd ((code ) & 0x7) +#define Imm (code & 0xFF) +#define Off ((code >> 6) & 0x1F) + +#define HiRs ((code >> 3) & 0xF) +#define HiRd (((code & (0x1 << 7)) >> 4) | Rd) + + void Instruction::ParseThumb (uint32_t offset, uint16_t code) + { + Clear (); + + if ((code >> 12) == 0xB && ((code >> 9) & 0x3) == 0x2) // 1011x10 + { + if (code & (0x1 << 11)) + m_operator = "POP"; + else + m_operator = "PUSH"; + + ArgMulRegisters argRegs(false); + for (register uint8_t n = 0; n < 8; ++n) + if (Imm & (0x1 << n)) + argRegs.AddRegister(n); + + if (code & (0x1 << 8)) + { + if (code & (0x1 << 11)) + argRegs.AddLastRegister(SPREG_PC); + else + argRegs.AddLastRegister(SPREG_LR); + } + m_args.AddArgument(argRegs); + } + else if ((code >> 11) == 0x9) // 01001 + { + m_operator = "LDR"; + + m_args.AddArgument(ArgRegister(Rb)); + m_args.AddArgument(ArgRelative(15, ArgImmediate(Imm << 2), + true, true, false)); + } + else if ((code >> 12) == 0x8) // 1000 + { + if (code & (0x1 << 11)) + m_operator = "LDRH"; + else + m_operator = "STRH"; + + m_args.AddArgument(ArgRegister(Rd)); + m_args.AddArgument(ArgRelative(Rs, ArgImmediate(Off << 1), + true, true, false)); + } + else if ((code >> 10) == 0x10) // 010000 + { + static const char* Instructions[] = {"AND", "EOR", "LSL", "LSR", "ASR", + "ADC", "SBC", "ROR", "TST", "NEG", "CMP", "CMN", "ORR", "MUL", "BIC", + "MVN"}; + + m_operator = Instructions[(code >> 6) & 0xF]; + + m_args.AddArgument(ArgRegister(Rd)); + m_args.AddArgument(ArgRegister(Rs)); + } + else if ((code >> 10) == 0x11) // 010001 + { + switch ((code >> 8) & 0x3) + { + case 0x0: // ADD + m_operator = "ADD"; + m_args.AddArgument(ArgRegister(HiRd)); + m_args.AddArgument(ArgRegister(HiRs)); + break; + case 0x1: // CMP + m_operator = "CMP"; + m_args.AddArgument(ArgRegister(HiRd)); + m_args.AddArgument(ArgRegister(HiRs)); + break; + case 0x2: + if (HiRd != 8 || HiRs != 8) // MOV + { + m_operator = "MOV"; + m_args.AddArgument(ArgRegister(HiRd)); + m_args.AddArgument(ArgRegister(HiRs)); + } + else + m_operator = "NOP"; + break; + case 0x3: + if (code & (0x1 << 7)) // BLX + { + m_operator = "BLX"; + m_args.AddArgument(ArgRegister(HiRs)); + } + else // BX + { + m_operator = "BX"; + m_args.AddArgument(ArgRegister(HiRs)); + } + break; + } + } + else if ((code >> 13) == 0x1) // 001 + { + static const char* Instructions[] = {"MOV", "CMP", "ADD", "SUB"}; + + m_operator = Instructions[(code >> 11) & 0x3]; + + m_args.AddArgument(ArgRegister(Rb)); + m_args.AddArgument(ArgImmediate(Imm)); + } + else if ((code >> 13) == 0x3) // 011 + { + static const char* Instructions[] = {"STR", "LDR", "STRB", "LDRB"}; + + m_operator = Instructions[(code >> 11) & 0x3]; + + m_args.AddArgument(ArgRegister(Rd)); + if (code & (0x1 << 12)) + m_args.AddArgument(ArgRelative(Rs, ArgImmediate(Off), true, + true, false)); + else + m_args.AddArgument(ArgRelative(Rs, ArgImmediate(Off << 2), true, + true, false)); + } + else if ((code >> 12) == 0xC) // 1100 + { + if (code & (0x1 << 11)) + m_operator = "LDMIA"; + else + m_operator = "STMIA"; + + m_args.AddArgument(ArgRegister(Rb, true)); + + ArgMulRegisters argRegs(false); + for (register uint8_t n = 0; n < 8; ++n) + if (Imm & (0x1 << n)) + argRegs.AddRegister(n); + m_args.AddArgument(argRegs); + } + else if ((code >> 13) == 0x0) // 000 + { + if ((code >> 11) == 0x3) // 00011 + { + if ((code >> 9) & 0x1) + m_operator = "SUB"; + else + m_operator = "ADD"; + + m_args.AddArgument(ArgRegister(Rd)); + m_args.AddArgument(ArgRegister(Rs)); + + if ((code >> 10) & 0x1) // imm + m_args.AddArgument(ArgImmediate(Ro)); + else // reg + m_args.AddArgument(ArgRegister(Ro)); + } + else // 000 + { + static const char* Instructions[] = {"LSL", "LSR", "ASR", "Reserved"}; + + m_operator = Instructions[(code >> 11) & 0x3]; + + m_args.AddArgument(ArgRegister(Rd)); + m_args.AddArgument(ArgRegister(Rs)); + m_args.AddArgument(ArgImmediate(Off)); + } + } + else if ((code >> 11) == 0x1E) // 11110 + { + m_operator = "BL.W1"; + + m_args.AddArgument(ArgImmediate(offset + 4 + ((code & 0x7FF) << 12))); + } + else if ((code >> 13) == 0x7 && (code & (0x1 << 11))) // 111x1 + { + m_operator = "BL.W2"; + + m_args.AddArgument(ArgImmediate((code & 0x7FF) << 1)); + } + else if ((code >> 11) == 0x1C) // 11100 + { + m_operator = "B"; + + if (code & (0x1 << 10)) + m_args.AddArgument(ArgUImmediate(offset + 4 + + ((int32_t)(((code & 0x3FF) << 1) | 0xFFFFF800)))); + else + m_args.AddArgument(ArgUImmediate(offset + 4 + ((code & 0x3FF) << 1))); + } + else if ((code >> 12) == 0xD) // 1101 + { + if (((code >> 8) & 0xF) == 0xF) // 11011111 + { + m_operator = "SWI"; + + m_args.AddArgument(ArgImmediate(code & 0xFF)); + } + else // 1101 + { + static const char* Conditions[] = {"EQ", "NE", "CS", "CC", "MI", "PL", + "VS", "VC", "HI", "LS", "GE", "LT", "GT", "LE", "__", "**"}; + + m_operator = "B"; + m_operator += Conditions[(code >> 8) & 0xF]; + + m_args.AddArgument(ArgUImmediate(offset + 4 + + (((int32_t)(int8_t)Imm) << 1))); + } + } + else if ((code >> 8) == 0xB0) // 10110000 + { + m_operator = "ADD"; + + m_args.AddArgument(ArgRegister(13, false, true)); + if (code & (0x1 << 7)) // substract + m_args.AddArgument(ArgImmediate(-((code & 0x7F) << 2))); + else // add + m_args.AddArgument(ArgImmediate((code & 0x7F) << 2)); + } + else if ((code >> 12) == 0x5) // 0101 + { + if (code & (0x1 << 11)) + m_operator = "LDR"; + else + m_operator = "STR"; + + if (code & (0x1 << 10)) + m_operator += 'B'; + + m_args.AddArgument(ArgRegister(Rd)); + m_args.AddArgument(ArgRelative(Rs, ArgRegister(Ro), true, true, false)); + } + else if ((code >> 12) == 0x9) // 1001 + { + if (code & (0x1 << 11)) + m_operator = "LDR"; + else + m_operator = "STR"; + + m_args.AddArgument(ArgRegister(Rb)); + m_args.AddArgument(ArgRelative(ArgRegister(13, false, true), + ArgImmediate(Imm << 2), true, true, false)); + } + else if ((code >> 12) == 0xA) // 1010 + { + m_operator = "ADD"; + + m_args.AddArgument(ArgRegister(Rb)); + if (code & (0x1 << 11)) // with SP + m_args.AddArgument(ArgRegister(13, false, true)); + else // with PC + m_args.AddArgument(ArgRegister(15, false, true)); + m_args.AddArgument(ArgImmediate(Imm << 2)); + } + else + { + m_operator = "Unknown"; + } + } + +#undef Rb +#undef Ro +#undef Rs +#undef Rd +#undef Imm +#undef Off + +#undef HiRs +#undef HiRd + } +} diff --git a/libmeteor/source/dma.cpp b/libmeteor/source/dma.cpp new file mode 100644 index 0000000000..15a3a6d1ec --- /dev/null +++ b/libmeteor/source/dma.cpp @@ -0,0 +1,307 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/dma.hpp" +#include "ameteor/io.hpp" +#include "ameteor/memory.hpp" +#include "globals.hpp" +#include "ameteor.hpp" + +#include "debug.hpp" + +namespace AMeteor +{ + void Dma::Reset () + { + for (Channel* chan = m_chans; chan < m_chans+4; ++chan) + { + chan->src = 0; + chan->dest = 0; + chan->count = 0; + chan->control.w = 0; + } + m_graphic = 0; + } + + void Dma::UpdateCnt (uint8_t channum) + { + Channel& chan = m_chans[channum]; + + if (chan.control.w != + IO.DRead16(Io::DMA0CNT_H + channum * Io::DMA_CHANSIZE)) + { + if (!chan.control.b.enable && + (IO.DRead16(Io::DMA0CNT_H + channum * Io::DMA_CHANSIZE) + & (0x1 << 15))) + // if we changed enable from 0 to 1 + { + chan.dest = IO.DRead32(Io::DMA0DAD + channum * Io::DMA_CHANSIZE); + if (channum == 3) + chan.dest &= 0x0FFFFFFF; + else + chan.dest &= 0x07FFFFFF; + + chan.src = IO.DRead32(Io::DMA0SAD + channum * Io::DMA_CHANSIZE); + if (channum == 0) + chan.src &= 0x07FFFFFF; + else + chan.src &= 0x0FFFFFFF; + + chan.count = chan.reload; + if (channum != 3) + chan.count &= 0x3FFF; + + chan.control.w = + IO.DRead16(Io::DMA0CNT_H + channum * Io::DMA_CHANSIZE); + + Check(channum, Immediately); + } + else + chan.control.w = + IO.DRead16(Io::DMA0CNT_H + channum * Io::DMA_CHANSIZE); + + if (chan.control.b.start == Special) + { + switch (channum) + { + case 0: + met_abort("prohibited !"); + break; + case 1: + case 2: + // sound dma + if (chan.dest != 0x040000A0 && chan.dest != 0x040000A4) + met_abort("Special DMA 1 or 2 with unauthorized address : " + << IOS_ADD << chan.dest); + if (!chan.control.b.repeat) + met_abort("Special DMA 1 or 2 without repeat"); + + // 4 words of 32 bits + chan.count = 4; + chan.control.b.type = 1; + // no increment + chan.control.b.dest = 2; + break; + case 3: + met_abort("not implemented"); + break; + } + } + } + } + + void Dma::Check (uint8_t channum, uint8_t reason) + { + register Channel::Control cnt = m_chans[channum].control; + + if (cnt.b.enable && + cnt.b.start == reason) + Process(channum); + } + + void Dma::Process (uint8_t channum) + { + Channel& chan = m_chans[channum]; + + int8_t s_inc, d_inc; + s_inc = d_inc = 2; // increment or increment reload + + if (chan.control.b.src == 1) // decrement + s_inc = -2; + else if (chan.control.b.src == 2) // fixed + s_inc = 0; + else if (chan.control.b.src == 3) + met_abort("prohibited"); + + if (chan.control.b.dest == 1) + d_inc = -2; + else if (chan.control.b.dest == 2) + d_inc = 0; + //else if (chan.control.b.dest == 3) + // same as 0, but we do something at the end + + if (chan.control.b.type) // 32 bits transfer + { + s_inc <<= 1; + d_inc <<= 1; + } + + if (chan.count == 0) + { + if (channum != 3) + chan.count = 0x4000; + // 0x10000 doesn't fill in 16 bits, we treat this case in the Copy() call + } + + //printf("DMA%hhu from %08X to %08X of %hu %s\n", channum, chan.src, chan.dest, chan.count, chan.control.b.type ? "words":"halfwords"); + //if (i > debut) + debug ("DMA" << IOS_NOR << (int)channum << ", from " << IOS_ADD << chan.src + << " to " << IOS_ADD << chan.dest + << " of " << IOS_NOR << (chan.count ? chan.count : 0x10000) + << (chan.control.b.type ? " words" : " halfwords")); +#if 0 + if (channum == 3 && (chan.dest >> 24) == 0x0D || (chan.src >> 24) == 0x0D) + { + if (chan.control.b.type) + met_abort("Word copy for EEPROM DMA3"); + if (d_inc != 2 || s_inc != 2) + met_abort("Source or destination not incremeting in EEPROM DMA3"); + if ((chan.dest >> 24) == 0x0D) + MEM.WriteEepromDma(chan.src, chan.count ? chan.count : 0x10000); + else if ((chan.src >> 24) == 0x0D) + MEM.ReadEepromDma(chan.dest, chan.count ? chan.count : 0x10000); + chan.src += chan.count * 2; + chan.dest += chan.count * 2; + } + else +#endif + if (channum == 3 && (chan.dest >> 24) == 0x0D) + { + if (chan.control.b.type) + met_abort("Word copy for EEPROM DMA3"); + if (d_inc != 2 || s_inc != 2) + met_abort("Source or destination not incremeting in EEPROM DMA3"); + MEM.WriteEepromDma(chan.src, chan.count); + chan.src += chan.count * 2; + chan.dest += chan.count * 2; + } + else + Copy(chan.src, chan.dest, s_inc, d_inc, + chan.count ? chan.count : 0x10000, chan.control.b.type); + + if (chan.control.b.type) + { + CYCLES32NSeq(chan.src, chan.count); + CYCLES32NSeq(chan.dest, chan.count); + ICYCLES(2); + } + else + { + CYCLES16NSeq(chan.src, chan.count); + CYCLES16NSeq(chan.dest, chan.count); + ICYCLES(2); + } + uint32_t d = chan.dest >> 24; + uint32_t s = chan.src >> 24; + if (d >= 0x08 && d <= 0x0D && s >= 0x08 && s <= 0x0D) + // if both source and destination are in GamePak + ICYCLES(2); + + if (chan.control.b.irq) + CPU.SendInterrupt(0x1 << (8 + channum)); + + if (chan.control.b.dest == 3) + { + chan.dest = IO.DRead32(Io::DMA0DAD + channum * Io::DMA_CHANSIZE); + if (channum == 3) + chan.dest &= 0x0FFFFFFF; + else + chan.dest &= 0x07FFFFFF; + } + + // if repeat but not sound dma + //if (!((channum == 1 || channum == 2) && chan.control.b.start == Special) && chan.control.b.repeat) + if (((channum != 1 && channum != 2) || chan.control.b.start != Special) + && chan.control.b.repeat) + { + chan.count = chan.reload; + } + + if (!chan.control.b.repeat || chan.control.b.start == Immediately) + { + chan.control.b.enable = 0; + IO.GetRef16(Io::DMA0CNT_H + channum * Io::DMA_CHANSIZE) &= 0x7FFF; + } + } + + void Dma::Copy (uint32_t& src, uint32_t& dest, int8_t s_inc, int8_t d_inc, + uint32_t count, bool word) + { + uint32_t basedest = dest; + + if (word) + { + src &= 0xFFFFFFFC; + dest &= 0xFFFFFFFC; + } + else + { + src &= 0xFFFFFFFE; + dest &= 0xFFFFFFFE; + } + + // sound + if (dest == 0x040000A0) + { + SOUND.SendDigitalA((uint8_t*)MEM.GetRealAddress(src)); + src += 4*4; + if (d_inc != 0) + met_abort("dinc != 0 on dma sound, should not happen"); + return; + } + if (dest == 0x040000A4) + { + SOUND.SendDigitalB((uint8_t*)MEM.GetRealAddress(src)); + src += 4*4; + if (d_inc != 0) + met_abort("dinc != 0 on dma sound, should not happen"); + return; + } + + if ((dest >> 24) >= 0x05 && + (dest >> 24) <= 0x07) + m_graphic = true; + + if (word) + { + for (uint32_t cur = 0; cur < count; ++cur) + { + MEM.Write32(dest, MEM.Read32(src)); + src += s_inc; + dest += d_inc; + } + } + else + { + for (uint32_t cur = 0; cur < count; ++cur) + { + MEM.Write16(dest, MEM.Read16(src)); + src += s_inc; + dest += d_inc; + } + } + + m_graphic = false; + if ((basedest >> 24) == 0x07) + LCD.OamWrite(basedest, dest); + } + + bool Dma::SaveState (std::ostream& stream) + { + SS_WRITE_ARRAY(m_chans); + // no need to save or load m_graphic since we shouldn't save or load during + // a dma + + return true; + } + + bool Dma::LoadState (std::istream& stream) + { + SS_READ_ARRAY(m_chans); + + return true; + } +} diff --git a/libmeteor/source/eeprom.cpp b/libmeteor/source/eeprom.cpp new file mode 100644 index 0000000000..3dd99f07d3 --- /dev/null +++ b/libmeteor/source/eeprom.cpp @@ -0,0 +1,272 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/eeprom.hpp" +#include "globals.hpp" +#include "debug.hpp" + +#include + +namespace AMeteor +{ + Eeprom::Eeprom (bool big) : + CartMem(), + m_state(IDLE), + m_add(0), + m_pos(0) + { + if (big) + m_size = 0x2000; + else + m_size = 0x0200; + + *(uint32_t*)(m_data+MAX_SIZE) = m_size; + } + + void Eeprom::Reset () + { + std::memset(m_data, 0, m_size); + } + + bool Eeprom::Load (std::istream& f) + { + f.read((char*)m_data, m_size); + return f.good(); + } + + bool Eeprom::Save (std::ostream& f) + { + f.write((char*)m_data, m_size); + return f.good(); + } + + uint8_t Eeprom::Read (uint16_t MET_UNUSED(add)) + { + met_abort("8 bits write to EEPROM"); + return 0; + } + + uint16_t Eeprom::Read () + { + switch (m_state) + { + case READ_GARBAGE: + ++m_pos; + if (m_pos == 4) + { + m_pos = 0; + m_state = READ_DATA; + } + return 0; + case READ_DATA: + { + uint16_t ret = + (m_data[m_add + m_pos / 8] & (0x1 << (7 - (m_pos % 8)))) ? 1 : 0; + ++m_pos; + if (m_pos == 64) + m_state = IDLE; + return ret; + } + default: + return 1; + } + } + + bool Eeprom::Write (uint16_t MET_UNUSED(add), uint8_t MET_UNUSED(val)) + { + met_abort("8 bits write to EEPROM"); + return false; + } + //XXX +#if 0 + bool Eeprom::Write (uint16_t val) + { + switch (m_state) + { + case IDLE: + if (val & 0x1) + m_state = WAITING; + else + met_abort("First bit is not 1"); + return false; + case WAITING: + m_add = 0; + m_pos = 0; + if (val & 0x1) + m_state = READ_ADD; + else + m_state = WRITE_ADD; + return false; + + case READ_ADD: + m_add <<= 1; + m_add |= val & 0x1; + ++m_pos; + if (m_size == 0x0200 && m_pos == 6 || + m_size == 0x2000 && m_pos == 14) + { + m_state = READ_END; + if (m_size == 0x2000 && (m_add & 0x3C000)) + met_abort("In large EEPROM, 4 upper address bits are not 0"); + } + return false; + case READ_END: + if (val & 0x1) + met_abort("Last bit of EEPROM read request is not 0"); + m_pos = 0; + m_state = READ_GARBAGE; + return false; + + case WRITE_ADD: + m_add <<= 1; + m_add |= val & 0x1; + ++m_pos; + if (m_size == 0x0200 && m_pos == 6 || + m_size == 0x2000 && m_pos == 14) + { + m_state = WRITE_DATA; + m_pos = 0; + if (m_size == 0x2000 && (m_add & 0x3C000)) + met_abort("In large EEPROM, 4 upper address bits are not 0"); + } + return false; + case WRITE_DATA: + { + uint8_t& d = m_data[m_add + m_pos / 8]; + d <<= 1; + d |= val & 0x1; + } + ++m_pos; + if (m_pos == 64) + m_state = WRITE_END; + return true; + case WRITE_END: + if (val & 0x1) + met_abort("Last bit of EEPROM write request is not 0"); + return false; + } + } +#endif + + bool Eeprom::Write (uint16_t* pData, uint16_t size) + { + if (!(*pData & 0x1)) + met_abort("Bit 1 is not 1 in EEPROM DMA"); + ++pData; + + uint16_t add = 0; + if (*pData & 0x1) // read + { + if (size != 9 && size != 17) + met_abort("Invalid size for read"); + ++pData; + + // read address + for (uint8_t i = 0, end = (m_size == 0x0200) ? 6 : 14; i < end; + ++i, ++pData) + add = (add << 1) | (*pData & 0x1); + if (m_size == 0x2000 && (add & 0x3C00)) + met_abort("In large EEPROM, 4 upper address bits are not 0"); + + //XXX + /*if (*pData & 0x1) + met_abort("Last bit of EEPROM read request is not 0");*/ + + m_add = add*8; + m_state = READ_GARBAGE; + m_pos = 0; + + return false; + } + else // write + { + if (size != 73 && size != 81) + met_abort("Invalid size for write"); + ++pData; + + // read address + for (uint8_t i = 0, end = (m_size == 0x0200) ? 6 : 14; i < end; + ++i, ++pData) + add = (add << 1) | (*pData & 0x1); + if (m_size == 0x2000 && (add & 0x3C00)) + met_abort("In large EEPROM, 4 upper address bits are not 0"); + + // read data + uint8_t* pMem = m_data + add*8; + for (uint8_t i = 0; i < 8; ++i, ++pMem) + { + for (uint8_t j = 0; j < 8; ++j, ++pData) + { + *pMem <<= 1; + *pMem |= (*pData & 0x1); + } + } + + if (*pData & 0x1) + met_abort("Last bit of EEPROM write request is not 0"); + + m_state = IDLE; + + return true; + } + } + + //XXX +#if 0 + void Eeprom::Read (uint16_t* pOut) + { + if (m_state != READ) + met_abort("Read in invalid EEPROM state"); + + pOut += 4; // ignore these + uint8_t* pData = m_data + m_add; + uint8_t cur; + for (uint8_t i = 0; i < 8; ++i, ++pData) + { + cur = *pData; + pOut += 7; + for (uint8_t j = 0; j < 8; ++j, --pOut, cur >>= 1) + *pOut = cur & 0x1; + pOut += 9; + } + + m_state = NORMAL; + } +#endif + + bool Eeprom::SaveState (std::ostream& stream) + { + //XXX TODO + SS_WRITE_VAR(m_size); + SS_WRITE_VAR(m_state); + SS_WRITE_VAR(m_add); + + SS_WRITE_DATA(m_data, m_size); + + return true; + } + + bool Eeprom::LoadState (std::istream& stream) + { + SS_READ_VAR(m_size); + SS_READ_VAR(m_state); + SS_READ_VAR(m_add); + + SS_READ_DATA(m_data, m_size); + + return true; + } +} diff --git a/libmeteor/source/flash.cpp b/libmeteor/source/flash.cpp new file mode 100644 index 0000000000..1493cc0715 --- /dev/null +++ b/libmeteor/source/flash.cpp @@ -0,0 +1,178 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/flash.hpp" +#include "globals.hpp" +#include +#include + +namespace AMeteor +{ + Flash::Flash (bool big) : + CartMem(), + m_state(NORMAL) + { + if (big) + { + m_device_id = 0x13; + m_manufacturer_id = 0x62; + m_size = 128*1024; + } + else + { + m_device_id = 0x1B; + m_manufacturer_id = 0x32; + m_size = 64*1024; + } + + *(uint32_t*)(m_data+MAX_SIZE) = m_size; + } + + void Flash::Reset () + { + std::memset(m_data, 0, m_size); + } + + bool Flash::Load (std::istream& f) + { + f.read((char*)m_data, m_size); + return f.good(); + } + + bool Flash::Save (std::ostream& f) + { + f.write((char*)m_data, m_size); + return f.good(); + } + + uint8_t Flash::Read (uint16_t add) + { + switch (m_state) + { + case NORMAL: + return m_data[add]; + case ID: + switch (add) + { + case 0: return m_manufacturer_id; + case 1: return m_device_id; + default: return 0; + } + // FIXME vba returns 0xff after an erase regardless of the read address + default: + return 0; + } + } + + bool Flash::Write (uint16_t add, uint8_t val) + { + switch (m_state) + { + case ID: + case NORMAL: + if (add == 0x5555 && val == 0xAA) + m_state = CMD1; + else + m_state = NORMAL; // for case ID + break; + case CMD1: + if (add == 0x2AAA && val == 0x55) + m_state = CMD2; + else + m_state = NORMAL; + break; + case CMD2: + if (add == 0x5555) + switch (val) + { + case 0x80: // erase mode + m_state = ERASE1; + break; + case 0x90: // id mode + m_state = ID; + break; + case 0xA0: // write byte + m_state = WRITE; + break; + case 0xF0: + m_state = NORMAL; + break; + default: + m_state = NORMAL; + break; + } + else + m_state = NORMAL; + break; + case ERASE1: + if (add == 0x5555 && val == 0xAA) + m_state = ERASE2; + else + m_state = NORMAL; + break; + case ERASE2: + if (add == 0x2AAA && val == 0x55) + m_state = ERASE3; + else + m_state = NORMAL; + break; + case ERASE3: + // according to vba, after a whole erase command we can juste repeat the + // last byte of the command to execute another erase command + switch (val) + { + case 0x10: // erase entire chip + if (add == 0x5555) + memset(m_data, 0xFF, m_size); + m_state = NORMAL; + break; + case 0x30: // erase sector + if (!(add & 0x0FFF)) + memset(m_data+add, 0xFF, 0x1000); + m_state = NORMAL; + break; + default: + m_state = NORMAL; + break; + } + break; + case WRITE: + // I think this is how it works + m_data[add] &= val; + m_state = NORMAL; + return true; + } + return false; + } + + bool Flash::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_state); + + SS_WRITE_DATA(m_data, m_size); + + return true; + } + + bool Flash::LoadState (std::istream& stream) + { + SS_READ_VAR(m_state); + + SS_READ_DATA(m_data, m_size); + + return true; + } +} diff --git a/libmeteor/source/globals.hpp b/libmeteor/source/globals.hpp new file mode 100644 index 0000000000..393b597438 --- /dev/null +++ b/libmeteor/source/globals.hpp @@ -0,0 +1,140 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __GLOBALS_H__ +#define __GLOBALS_H__ + +#include +#include + +#define SPDUP_FRMSKIP 9 +#define SPDUP_SNDSKIP 3969 + +#define R(reg) CPU.Reg(reg) + +#define CPU _cpu +#define MEM _memory +#define IO _io +#define DMA _dma +#define LCD _lcd +#define SOUND _sound +#define KEYPAD _keypad +#define CLOCK _clock +#define TIMER0 _timer0 +#define TIMER1 _timer1 +#define TIMER2 _timer2 +#define TIMER3 _timer3 + +#define CYCLES16NSeq(add, count) \ + CLOCK.TimePass(MEM.GetCycles16NoSeq(add, count)) +#define CYCLES16Seq(add, count) \ + CLOCK.TimePass(MEM.GetCycles16Seq(add, count)) +#define CYCLES32NSeq(add, count) \ + CLOCK.TimePass(MEM.GetCycles32NoSeq(add, count)) +#define CYCLES32Seq(add, count) \ + CLOCK.TimePass(MEM.GetCycles32Seq(add, count)) +#define ICYCLES(i) \ + CLOCK.TimePass(i) +// we don't mind if the following code is slow since a MUL +// instruction is slow on a GBA too +#define MULICYCLES(reg) \ + { \ + uint32_t rs = R(reg) >> 8; \ + for (uint8_t i = 1; i <= 4; ++i, rs >>= 8) \ + { \ + if (!rs || rs == (0xFFFFFFFF >> (i*8))) \ + { \ + ICYCLES(i); \ + break; \ + } \ + } \ + } + +#define CPSR (CPU.Cpsr().dw) +#define ICPSR (CPU.ICpsr()) +#define SPSR (CPU.Spsr().dw) +#define FLAG_Z (CPU.ICpsr().f_zero) +#define FLAG_N (CPU.ICpsr().f_sign) +#define FLAG_C (CPU.ICpsr().f_carry) +#define FLAG_V (CPU.ICpsr().f_overflow) +#define FLAG_T (CPU.ICpsr().thumb) +#define SETF(flag, val) \ + FLAG_##flag = (val) ? 1 : 0 +#define SETFB(flag, val) \ + FLAG_##flag = (val) + +#define FZ(val) \ + SETF(Z, !(val)) +#define FN(val) \ + SETF(N, NEG(val)) + +#define POS(a) ((~a) >> 31) +#define NEG(a) ((a) >> 31) + +// inspired by VisualBoyAdvance +#define ADDCARRY(a, b, c) \ + ((NEG(a) & NEG(b)) | \ + (NEG(a) & POS(c)) | \ + (NEG(b) & POS(c))) +#define ADDOVERFLOW(a, b, c) \ + ((NEG(a) & NEG(b) & POS(c)) | \ + (POS(a) & POS(b) & NEG(c))) +#define SUBCARRY(a, b, c) \ + ((NEG(a) & POS(b)) | \ + (NEG(a) & POS(c)) | \ + (POS(b) & POS(c))) +#define SUBOVERFLOW(a, b, c) \ + ((NEG(a) & POS(b) & POS(c)) | \ + (POS(a) & NEG(b) & NEG(c))) + +// Save states macros +#define SS_WRITE_VAR(var) \ + if (!stream.write((char*)&var, sizeof(var))) \ + return false +#define SS_WRITE_ARRAY(var) \ + if (!stream.write((char*)var, sizeof(var))) \ + return false +#define SS_WRITE_DATA(var, size) \ + if (!stream.write((char*)var, size)) \ + return false +#define SS_READ_VAR(var) \ + if (!stream.read((char*)&var, sizeof(var))) \ + return false +#define SS_READ_ARRAY(var) \ + if (!stream.read((char*)var, sizeof(var))) \ + return false +#define SS_READ_DATA(var, size) \ + if (!stream.read((char*)var, size)) \ + return false + +// macro to avoid getting warnings about and unused parameter on GCC +#ifdef _MSC_VER +#define MET_UNUSED(v) +#else +#define MET_UNUSED(v) \ + __attribute__((unused)) MET_UNUSED_##v +#endif + +namespace AMeteor +{ + // ROtate Right + inline uint32_t ROR(uint32_t val, uint8_t shift) + { + return (val >> shift) | (val << (32 - shift)); + } +} + +#endif diff --git a/libmeteor/source/graphics/bglayer.cpp b/libmeteor/source/graphics/bglayer.cpp new file mode 100644 index 0000000000..72eea709c9 --- /dev/null +++ b/libmeteor/source/graphics/bglayer.cpp @@ -0,0 +1,451 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/graphics/bglayer.hpp" +#include "ameteor/graphics/screen.hpp" +#include "../debug.hpp" + +namespace AMeteor +{ + namespace Graphics + { + BgLayer::BgLayer (int8_t num, Memory& memory, Io& io, uint16_t* pPalette) : + m_memory(memory), + m_io(io), + m_num(num), + m_priority(0), + m_cnt(0), + m_xoff(0), + m_yoff(0), + m_tWidth(32), + m_tHeight(32), + m_rWidth(16), + m_rHeight(16), + m_mapAdd(0x06000000), + m_charAdd(0x06000000), + m_pPalette(pPalette) + { + } + + BgLayer::~BgLayer () + { + } + + void BgLayer::DrawLine0 (uint8_t line, uint16_t* ptr) + { + uint8_t mosH; + if (m_cnt & (0x1 << 6)) + { + mosH = m_io.DRead8(Io::MOSAIC); + uint8_t mosV = mosH >> 4; + mosH &= 0x0F; + + ++mosV; + if (mosH) // let it be 0 if it's 0 (=> no mosaic) + ++mosH; + + line /= mosV; + line *= mosV; + } + else + mosH = 0; + + uint16_t* pMap = (uint16_t*)m_memory.GetRealAddress(m_mapAdd), *pTile; + uint8_t* pChar = m_memory.GetRealAddress(m_charAdd), *tpChar; + uint8_t i = 0; + + // theses are the coordinates on the layer from which we will draw a line + uint16_t xoff = m_xoff % (m_tWidth * 8); + uint16_t yoff = (m_yoff + line) % (m_tHeight * 8); + + // theses are the coordinates on the tile of the pixels we are drawing + // NOTE : if the tile is horizontally flipped tileX = 8 - tileX, thus + // when we draw it is ALWAYS incremented no matter how the tile is + // flipped + // NOTE : tileY is NOT redefined if the tile is flipped vertically + int8_t tileX = xoff % 8, tileY; + bool flipH; + + // sets pTile to the tile we must draw first + // +---+---+ + // | 1 | 2 | + // +---+---+ + // | 3 | 4 | + // +---+---+ + if (yoff >= 256) + if (m_tWidth == 64) + if (xoff >= 256) + // zone 4 + pTile = pMap + 32*32 + 32*32 + 32*32 + + 32 * ((yoff-256)/8) + (xoff-256)/8; + else + // zone 3 (large map) + pTile = pMap + 32*32 + 32*32 + 32 * ((yoff-256)/8) + xoff/8; + else + // zone 3 (no large map) + pTile = pMap + 32*32 + 32 * ((yoff-256)/8) + xoff/8; + else + if (xoff >= 256) + // zone 2 + pTile = pMap + 32*32 + 32 * (yoff/8) + (xoff-256)/8; + else + // zone 1 + pTile = pMap + 32 * (yoff/8) + xoff/8; + + if (m_hicolor) + { + while (i < 240) + { + flipH = (bool)(*pTile & (0x1 << 10)); + + // if the tile is vertically fliped + if (*pTile & (0x1 << 11)) + tileY = 7 - (yoff % 8); + else + tileY = yoff % 8; + + if (flipH) + tpChar = pChar + (*pTile & 0x3FF)*8*8 + tileY*8 + 7 - tileX; + else + tpChar = pChar + (*pTile & 0x3FF)*8*8 + tileY*8 + tileX; + + while (tileX < 8) + { + if (mosH && i % mosH) + *ptr = ptr[-1]; + else + { + if (*tpChar) + *ptr = m_pPalette[*tpChar] | 0x8000; + else + *ptr = 0x0; + + if (flipH) + --tpChar; + else + ++tpChar; + } + + ++ptr; + ++tileX; + ++i; + + if (i == 240) + return; + } + tileX = 0; + ++pTile; + + // support for big maps and wrap around + if (!((pTile - pMap) % 32)) + if (m_tWidth == 32) + pTile -= 32; + else + if (yoff >= 256 && pTile - pMap > 32*32 * 3 + || yoff < 256 && pTile - pMap > 32*32) + pTile -= 32*33; + else + pTile += 32*31; + } + } + else + { + uint16_t* pPalette; + uint8_t colorInd; + + // for each pixel of the line we are drawing + while (i < 240) + { + pPalette = m_pPalette + ((*pTile >> 12) & 0xF) * 16; + flipH = (bool)(*pTile & (0x1 << 10)); + + // if the tile is vertically fliped + if (*pTile & (0x1 << 11)) + tileY = 7 - (yoff % 8); + else + tileY = yoff % 8; + + if (flipH) + tpChar = pChar + ((*pTile & 0x3FF)*8*8 + tileY*8 + 7 - tileX)/2; + else + tpChar = pChar + ((*pTile & 0x3FF)*8*8 + tileY*8 + tileX)/2; + + // we draw until the end of the tile or the line + while (tileX < 8) + { + if (mosH && i % mosH) + *ptr = ptr[-1]; + else + { + if (flipH) + if (tileX % 2) + { + colorInd = *tpChar & 0xF; + --tpChar; + } + else + colorInd = *tpChar >> 4; + else + if (tileX % 2) + { + colorInd = *tpChar >> 4; + ++tpChar; + } + else + colorInd = *tpChar & 0xF; + + if (colorInd) + *ptr = pPalette[colorInd] | 0x8000; + else + *ptr = 0x0; + } + + ++ptr; + ++tileX; + ++i; + + // if this was the last pixel of the line + if (i == 240) + return; + } + + // we have finished drawing a tile, so we go to the next tile + tileX = 0; + ++pTile; + + // support for big maps and wrap around + if (!((pTile - pMap) % 32)) + if (m_tWidth == 32) + pTile -= 32; + else + if (yoff >= 256 && pTile - pMap > 32*32 * 3 + || yoff < 256 && pTile - pMap > 32*32) + pTile -= 32*33; + else + pTile += 32*31; + } + } + } + + void BgLayer::DrawLine2 (uint16_t* ptr, + int32_t curX, int32_t curY, + int16_t dx, int16_t dy) + { + if (!m_hicolor) + { + //FIXME is this possible ?? + //this seems to be impossible, but we should not crash since some games + //do it + //puts("rotated layer with 16 colors"); + //abort(); + // XXX + //it seems now that we should not care about this flag, we draw in 256 + //colors, that's all + //return; + } + int32_t intX, intY; + + uint16_t colorInd; + + uint8_t* pMap = (uint8_t*)m_memory.GetRealAddress(m_mapAdd); + uint8_t* pChar = m_memory.GetRealAddress(m_charAdd); + + for (uint8_t x = 0; x < 240; ++x, ++ptr, curX += dx, curY += dy) + { + intX = curX >> 8; + intY = curY >> 8; + + // if we are off layer + if (intX < 0 || intX >= m_rWidth*8) + if (m_cnt & (0x1 << 13)) + { + // NOTE : in C++, the modulus can be negative, this is because in + // x86, the IDIV instruction gives a remainder of the same sign of + // the dividend + curX %= m_rWidth*8 << 8; + if (curX < 0) + curX += m_rWidth*8 << 8; + intX = curX >> 8; + } + else + continue; + if (intY < 0 || intY >= m_rHeight*8) + if (m_cnt & (0x1 << 13)) + { + curY %= m_rHeight*8 << 8; + if (curY < 0) + curY += m_rHeight*8 << 8; + intY = curY >> 8; + } + else + continue; + + colorInd = pChar[pMap[intY / 8 * m_rWidth + intX / 8] * 8 * 8 + + (intY % 8) * 8 + intX % 8]; + + if (colorInd) + *ptr = m_pPalette[colorInd] | 0x8000; + else + *ptr = 0x0; + } + } + + void BgLayer::DrawLine3 (uint16_t* ptr, + int32_t curX, int32_t curY, + int16_t dx, int16_t dy) + { + int32_t intX, intY; + + uint8_t* pChar = m_memory.GetRealAddress(0x06000000); + + for (uint8_t x = 0; x < 240; ++x, ++ptr, curX += dx, curY += dy) + { + intX = curX >> 8; + intY = curY >> 8; + + // if we are off layer + if (intX < 0 || intX >= 240) + if (m_cnt & (0x1 << 13)) + { + // NOTE : in C++, the modulus can be negative + intX %= 240; + if (intX < 0) + intX += 240; + } + else + continue; + if (intY < 0 || intY >= 160) + if (m_cnt & (0x1 << 13)) + { + intY %= 160; + if (intY < 0) + intY += 160; + } + else + continue; + + *ptr = pChar[intY * 240 * 2 + intX * 2] | 0x8000; + } + } + + void BgLayer::DrawLine4 (uint8_t line, uint16_t* ptr, + int32_t curX, int32_t curY, + int16_t dx, int16_t dmx, int16_t dy, int16_t dmy, bool frame1) + { + uint8_t mosH; + if (m_cnt & (0x1 << 6)) + { + // TODO horizontal mosaic not implemented + mosH = m_io.DRead8(Io::MOSAIC); + uint8_t mosV = mosH >> 4; + mosH &= 0x0F; + + ++mosV; + if (mosH) // let it be 0 if it's 0 (=> no mosaic) + ++mosH; + + uint8_t back = line % mosV; + + curX -= back*dmx; + curY -= back*dmy; + } + else + mosH = 0; + + int32_t intX, intY; + + uint16_t colorInd; + + uint8_t* pChar = + m_memory.GetRealAddress(frame1 ? 0x0600A000 : 0x06000000); + + for (uint8_t x = 0; x < 240; ++x, ++ptr, curX += dx, curY += dy) + { + if (mosH && x % mosH) + { + *ptr = ptr[-1]; + continue; + } + + intX = curX >> 8; + intY = curY >> 8; + + // if we are off layer + if (intX < 0 || intX >= 240) + if (m_cnt & (0x1 << 13)) + { + // NOTE : in C++, the modulus can be negative + intX %= 240; + if (intX < 0) + intX += 240; + } + else + continue; + if (intY < 0 || intY >= 160) + if (m_cnt & (0x1 << 13)) + { + intY %= 160; + if (intY < 0) + intY += 160; + } + else + continue; + + colorInd = pChar[intY * 240 + intX]; + + if (colorInd) + *ptr = m_pPalette[colorInd] | 0x8000; + else + *ptr = 0x0; + } + } + + void BgLayer::UpdateCnt (uint16_t cnt) + { + if (m_cnt == cnt) + return; + + switch (cnt >> 14) + { + case 0: + m_tWidth = m_tHeight = 32; + m_rWidth = m_rHeight = 16; + break; + case 1: + m_tWidth = 64; + m_tHeight = 32; + m_rWidth = m_rHeight = 32; + break; + case 2: + m_tWidth = 32; + m_tHeight = 64; + m_rWidth = m_rHeight = 64; + break; + case 3: + m_tWidth = m_tHeight = 64; + m_rWidth = m_rHeight = 128; + break; + } + + m_priority = (cnt & 0x3); + m_charAdd = 0x06000000 + ((cnt >> 2) & 0x3) * 0x4000; + m_hicolor = cnt & (0x1 << 7); + m_mapAdd = 0x06000000 + ((cnt >> 8) & 0x1F) * 0x0800; + + m_cnt = cnt; + } + } +} diff --git a/libmeteor/source/graphics/object.cpp b/libmeteor/source/graphics/object.cpp new file mode 100644 index 0000000000..bfcaeaa0c9 --- /dev/null +++ b/libmeteor/source/graphics/object.cpp @@ -0,0 +1,680 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/graphics/object.hpp" +#include "ameteor/graphics/screen.hpp" +#include "../debug.hpp" + +namespace AMeteor +{ + namespace Graphics + { + Object::Object (uint16_t* pPalette, uint8_t* pChar) : + m_attr0(0), + m_attr1(0), + m_attr2(0), + m_width(1), + m_height(1), + m_pPalette(pPalette), + m_pChar(pChar), + m_charBegin(0x06010000), + m_charEnd(0x06010020) + { + } + + Object::Object (const Object& obj) : + m_attr0(obj.m_attr0), + m_attr1(obj.m_attr1), + m_attr2(obj.m_attr2), + m_width(obj.m_width), + m_height(obj.m_height), + m_pPalette(obj.m_pPalette), + m_pChar(obj.m_pChar), + m_charBegin(obj.m_charBegin), + m_charEnd(obj.m_charEnd) + { + } + + void Object::DrawLine (uint8_t line, uint32_t* surface, bool oneDim, + uint8_t mosaic) + { + // if the object is disabled or it's an obj window + if (m_attr0 & (0x1 << 9) || ((m_attr0 >> 10) & 0x3) == 2) + return; + + int16_t yoff = (m_attr0 & 0xFF); + if (yoff > Screen::HEIGHT) + yoff -= 256; + + // if the object does not appear on the line + if (yoff > line || yoff + m_height*8 <= line) + return; + + uint8_t mosH; + if (m_attr0 & (0x1 << 12)) + { + uint8_t mosV = mosaic >> 4; + mosH = mosaic & 0xF; + + ++mosV; + if (mosH) // let it be 0 if it's 0 (=> no mosaic) + ++mosH; + + line /= mosV; + line *= mosV; + } + else + mosH = 0; + + int16_t xoff = (m_attr1 & 0x1FF); + if (xoff & (0x1 << 8)) + xoff |= 0xFE00; // extend sign bit + + uint32_t* ptr = surface + xoff; + + uint32_t prio = (((uint32_t)m_attr2) << 6) & (0x3 << 16); + uint32_t mask = prio; + // if semi transparent + if (((m_attr0 >> 10) & 0x3) == 0x1) + mask |= (0x1 << 18); + uint8_t* pChar = m_pChar + (m_attr2 & 0x3FF) * 32; + bool flipH = m_attr1 & (0x1 << 12); + if (m_attr0 & (0x1 << 13)) // if 256 colors mode + { + // if the tile is vertically flipped + if (m_attr1 & (0x1 << 13)) + { + // go to the tile + if (oneDim) + pChar += m_width * (8 * 8) * (m_height -1 -((line-yoff) / 8)); + else + pChar += (16 * 8 * 8) * (m_height -1 -((line-yoff) / 8)); + // advance to the right line + pChar += 8 * (7 - ((line-yoff) % 8)); + } + else + { + // go to the tile + if (oneDim) + pChar += m_width * (8 * 8) * ((line-yoff) / 8); + else + pChar += (16 * 8 * 8) * ((line-yoff) / 8); + // advance to the right line + pChar += 8 * ((line-yoff) % 8); + } + // go to the end of the line if the object is flipped + if (flipH) + pChar += (m_width-1) * (8*8) + 8 - 1; + + for (uint8_t j = 0; j < m_width*8; ++j, ++ptr) + { + // if we are on screen + if (ptr - surface < Screen::WIDTH && ptr >= surface) + { + if (mosH && (ptr - surface) % mosH) + *ptr = ptr[-1]; + else + { + // if there is something to draw + if (*pChar) + { + // if we have priority or there is nothing behind + if (prio < (*ptr & (0x3 << 16)) || !(*ptr & 0x8000)) + *ptr = m_pPalette[*pChar] | 0x8000 | mask; + } + // if we have nothing to draw + else + // if we have better priority + if (prio < (*ptr & (0x3 << 16))) + // we just modify priority and keep what was behind + *ptr = (*ptr & ~(0x3 << 16)) | (mask & (0x3 << 16)); + } + } + + if (flipH) + if ((j % 8) == 7) + // go to previous tile + pChar -= ((8*8) - (8) + 1); + else + --pChar; + else + if ((j % 8) == 7) + // go to next tile + pChar += ((8*8) - (8) + 1); + else + ++pChar; + } + } + else + { + // if the tile is vertically flipped + if (m_attr1 & (0x1 << 13)) + { + // go to the tile + if (oneDim) + pChar += m_width * (8 * 8 / 2) * (m_height -1 -((line-yoff) / 8)); + else + pChar += (32 * 8 * 8 / 2) * (m_height -1 -((line-yoff) / 8)); + // advance to the right line + pChar += (8/2) * (7 - ((line-yoff) % 8)); + } + else + { + // go to the tile + if (oneDim) + pChar += m_width * (8 * 8 / 2) * ((line-yoff) / 8); + else + pChar += (32 * 8 * 8 / 2) * ((line-yoff) / 8); + // advance to the right line + pChar += (8/2) * ((line-yoff) % 8); + } + // go to the end of the line if the object is flipped + if (flipH) + pChar += (m_width-1) * (8*8/2) + 8/2 - 1; + + uint16_t* pPalette = m_pPalette + 16 * (m_attr2 >> 12); + uint8_t colorInd; + for (uint8_t j = 0; j < m_width*8; ++j, ++ptr) + { + if (flipH) + if (j % 2) + { + colorInd = *pChar & 0xF; + + if ((j % 8) == 7) + // go to next tile + // it doesn't matter if we are in one or two dimensions mapping + // since we never go on the next line + pChar -= ((8*8/2) - (8/2) + 1); + else + --pChar; + } + else + colorInd = *pChar >> 4; + else + if (j % 2) + { + colorInd = *pChar >> 4; + + if ((j % 8) == 7) + // go to next tile + // it doesn't matter if we are in one or two dimensions mapping + // since we never go on the next line + pChar += ((8*8/2) - (8/2) + 1); + else + ++pChar; + } + else + colorInd = *pChar & 0xF; + + // if we are on screen + if (ptr - surface < Screen::WIDTH && ptr >= surface) + { + if (mosH && (ptr - surface) % mosH) + *ptr = ptr[-1]; + else + { + // if there is something to draw + if (colorInd) + { + // if we have priority or there is nothing behind + if (prio < (*ptr & (0x3 << 16)) || !(*ptr & 0x8000)) + *ptr = pPalette[colorInd] | 0x8000 | mask; + } + // if we have nothing to draw + else + // if we have better priority + if (prio < (*ptr & (0x3 << 16))) + // we just modify priority and keep what was behind + *ptr = (*ptr & ~(0x3 << 16)) | (mask & (0x3 << 16)); + } + } + } + } + } + + void Object::DrawLineRot (uint8_t line, uint32_t* surface, bool oneDim, + int16_t a, int16_t b, int16_t c, int16_t d, uint8_t mosaic) + { + // if it's an obj window + if (((m_attr0 >> 10) & 0x3) == 2) + return; + + int16_t yoff = (m_attr0 & 0xFF); + if (yoff > Screen::HEIGHT) + yoff -= 256; + + int16_t xoff = (m_attr1 & 0x1FF); + if (xoff & (0x1 << 8)) + xoff |= 0xFE00; // extend sign bit + + uint8_t fwidth = m_width*8, fheight = m_height*8; + if (m_attr0 & (0x1 << 9)) + { + fwidth *= 2; + fheight *= 2; + } + + // if the object does not appear on the line + if (yoff > line || yoff + fheight <= line) + return; + + uint8_t mosH; + if (m_attr0 & (0x1 << 12)) + { + uint8_t mosV = mosaic >> 4; + mosH = mosaic & 0xF; + + ++mosV; + if (mosH) // let it be 0 if it's 0 (=> no mosaic) + ++mosH; + + line /= mosV; + line *= mosV; + } + else + mosH = 0; + + int32_t curX = ((m_width*8) << 8)/2 + + (-fwidth/2) * a + (line-yoff-fheight/2) * b; + int32_t curY = ((m_height*8) << 8)/2 + + (-fwidth/2) * c + (line-yoff-fheight/2) * d; + int32_t intX, intY; + + uint32_t* ptr = surface + xoff; + + uint32_t prio = (((uint32_t)m_attr2) << 6) & (0x3 << 16); + uint32_t mask = prio; + // if semi transparent + if (((m_attr0 >> 10) & 0x3) == 0x1) + mask |= (0x1 << 18); + uint8_t* pChar = m_pChar + (m_attr2 & 0x3FF) * 32; + uint8_t colorInd; + if (m_attr0 & (0x1 << 13)) // if 256 colors mode + { + for (uint8_t i = 0; i < fwidth; ++i) + { + intX = curX >> 8; + intY = curY >> 8; + + // if we are on the object + if (intX >= 0 && intX < m_width*8 && + intY >= 0 && intY < m_height*8 && + // and we are on screen + ptr - surface < Screen::WIDTH && ptr >= surface) + { + if (mosH && (ptr - surface) % mosH) + *ptr = ptr[-1]; + else + { + colorInd = pChar[ + (intY/8) * (oneDim ? m_width : 16) * 8*8 + + (intX/8) * 8*8 + + (intY%8) * 8 + + (intX%8)]; + + // if there is something to draw + if (colorInd) + { + // if we have priority or there is nothing behind + if (prio < (*ptr & (0x3 << 16)) || !(*ptr & 0x8000)) + *ptr = m_pPalette[colorInd] | 0x8000 | mask; + } + // if we have nothing to draw + else + // if we have better priority + if (prio < (*ptr & (0x3 << 16))) + // we just modify priority and keep what was behind + *ptr = (*ptr & ~(0x3 << 16)) | (mask & (0x3 << 16)); + } + } + + ++ptr; + curX += a; + curY += c; + } + } + else + { + uint16_t* pPalette = m_pPalette + 16 * (m_attr2 >> 12); + for (uint8_t i = 0; i < fwidth; ++i) + { + intX = curX >> 8; + intY = curY >> 8; + + // if we are on the object + if (intX >= 0 && intX < m_width*8 && + intY >= 0 && intY < m_height*8 && + // and we are on screen + ptr - surface < Screen::WIDTH && ptr >= surface) + { + if (mosH && (ptr - surface) % mosH) + *ptr = ptr[-1]; + else + { + colorInd = pChar[( + (intY/8) * (oneDim ? m_width : 32) * 8*8 + + (intX/8) * 8*8 + + (intY%8) * 8 + + (intX%8) + ) / 2]; + + if (intX % 2) + colorInd >>= 4; + else + colorInd &= 0xF; + + // if there is something to draw + if (colorInd) + { + // if we have priority or there is nothing behind + if (prio < (*ptr & (0x3 << 16)) || !(*ptr & 0x8000)) + *ptr = pPalette[colorInd] | 0x8000 | mask; + } + // if we have nothing to draw + else + // if we have better priority + if (prio < (*ptr & (0x3 << 16))) + // we just modify priority and keep what was behind + *ptr = (*ptr & ~(0x3 << 16)) | (mask & (0x3 << 16)); + } + } + + ++ptr; + curX += a; + curY += c; + } + } + } + + void Object::DrawWindow (uint8_t line, uint8_t* surface, bool oneDim, + uint8_t mask) + { + // if the object is disabled or it's not an obj window + if (m_attr0 & (0x1 << 9) || ((m_attr0 >> 10) & 0x3) != 2) + return; + + int16_t yoff = (m_attr0 & 0xFF); + if (yoff > Screen::HEIGHT) + yoff -= 256; + + // if the object does not appear on the line + if (yoff > line || yoff + m_height*8 <= line) + return; + + int16_t xoff = (m_attr1 & 0x1FF); + if (xoff & (0x1 << 8)) + xoff |= 0xFE00; // extend sign bit + + bool flipH = m_attr1 & (0x1 << 12); + uint8_t* ptr = surface + xoff; + if (flipH) + ptr += m_width * 8 - 1; + uint8_t* pChar = m_pChar + (m_attr2 & 0x3FF) * 32; + if (m_attr0 & (0x1 << 13)) // if 256 colors mode + { + // TODO 2d map, vert flips + // we set pChar on the tile + if (oneDim) + pChar += m_width * (8 * 8) * ((line-yoff) / 8); + else + pChar += (16 * 8 * 8) * ((line-yoff) / 8); + // and we go to the first pixel we need to draw + pChar += 8 * ((line-yoff) % 8); + for (uint8_t j = 0; j < m_width*8; ++j) + { + if (ptr - surface < Screen::WIDTH && ptr >= surface && *pChar) + *ptr = mask; + + if (flipH) + --ptr; + else + ++ptr; + + if ((j % 8) == 7) + // go to next tile + pChar += ((8*8) - (8) + 1); + else + ++pChar; + } + } + else + { + // TODO verts flips + // we set pChar on the tile + if (oneDim) + pChar += m_width * (8 * 8 / 2) * ((line-yoff) / 8); + else + pChar += (32 * 8 * 8 / 2) * ((line-yoff) / 8); + // and we go to the first pixel we need to draw + pChar += (8/2) * ((line-yoff) % 8); + uint8_t colorInd; + for (uint8_t j = 0; j < m_width*8; ++j) + { + if (j % 2) + { + colorInd = *pChar >> 4; + + if ((j % 8) == 7) + // go to next tile + // it doesn't matter if we are in one or two dimensions mapping + // since we never go on the next line + pChar += ((8*8/2) - (8/2) + 1); + else + ++pChar; + } + else + colorInd = *pChar & 0xF; + + // if we are not offscreen and there is a color + if (ptr - surface < Screen::WIDTH && ptr >= surface && colorInd) + *ptr = mask; + + if (flipH) + --ptr; + else + ++ptr; + } + } + } + + void Object::DrawWindowRot (uint8_t line, uint8_t* surface, + bool oneDim, int16_t a, int16_t b, int16_t c, int16_t d, uint8_t mask) + { + // if it's not an obj window + if (((m_attr0 >> 10) & 0x3) != 2) + return; + + int16_t yoff = (m_attr0 & 0xFF); + if (yoff > Screen::HEIGHT) + yoff -= 256; + + int16_t xoff = (m_attr1 & 0x1FF); + if (xoff & (0x1 << 8)) + xoff |= 0xFE00; // extend sign bit + + uint8_t fwidth = m_width*8, fheight = m_height*8; + if (m_attr0 & (0x1 << 9)) + { + fwidth *= 2; + fheight *= 2; + } + + // if the object does not appear on the line + if (yoff > line || yoff + fheight <= line) + return; + + int32_t curX = ((m_width*8) << 8)/2 + + (-fwidth/2) * a + (line-yoff-fheight/2) * b; + int32_t curY = ((m_height*8) << 8)/2 + + (-fwidth/2) * c + (line-yoff-fheight/2) * d; + int32_t intX, intY; + + uint8_t* ptr = surface + xoff; + uint8_t* pChar = m_pChar + (m_attr2 & 0x3FF) * 32; + uint8_t colorInd; + if (m_attr0 & (0x1 << 13)) // if 256 colors mode + { + for (uint8_t i = 0; i < fwidth; ++i) + { + intX = curX >> 8; + intY = curY >> 8; + + if (intX >= 0 && intX < m_width*8 && + intY >= 0 && intY < m_height*8) + { + colorInd = pChar[ + (intY/8) * (oneDim ? m_width : 32) * 8*8 + + (intX/8) * 8*8 + + (intY%8) * 8 + + (intX%8)]; + + if (ptr - surface < Screen::WIDTH && ptr >= surface && colorInd) + *ptr = mask; + } + + ++ptr; + curX += a; + curY += c; + } + } + else + { + for (uint8_t i = 0; i < fwidth; ++i) + { + intX = curX >> 8; + intY = curY >> 8; + + if (intX >= 0 && intX < m_width*8 && + intY >= 0 && intY < m_height*8) + { + colorInd = pChar[( + (intY/8) * (oneDim ? m_width : 32) * 8*8 + + (intX/8) * 8*8 + + (intY%8) * 8 + + (intX%8) + ) / 2]; + + if (intX % 2) + colorInd >>= 4; + else + colorInd &= 0xF; + + if (ptr - surface < Screen::WIDTH && ptr >= surface && colorInd) + *ptr = mask; + } + + ++ptr; + curX += a; + curY += c; + } + } + } + + + void Object::UpdateAttrs (uint16_t attr0, uint16_t attr1, uint16_t attr2) + { + bool setsize = false, reload = false; + if ((m_attr0 & 0xFF00) != (attr0 & 0xFF00)) + { + reload = true; + setsize = true; + } + m_attr0 = attr0; + + if ((m_attr1 & 0xF000) != (attr1 & 0xF000)) + { + reload = true; + setsize = true; + } + m_attr1 = attr1; + + if ((m_attr2 & 0xF1FF) != (attr2 & 0xF1FF)) + reload = true; + m_attr2 = attr2; + + if (setsize) + { + SetSize(); + + if (reload) + { + m_charBegin = 0x06010000 + (m_attr2 & 0x3FF)*32; + m_charEnd = m_charBegin + m_width*m_height*8* + ((m_attr0 & (0x1 << 13)) ? 8 : 4); + } + } + } + + void Object::UpdateAttr0 (uint16_t attr0) + { + // FIXME : we can do a more restrictive condition + if ((m_attr0 & 0xFF00) != (attr0 & 0xFF00)) + { + m_attr0 = attr0; + SetSize(); + m_charEnd = m_charBegin + m_width*m_height*8* + ((m_attr0 & (0x1 << 13)) ? 8 : 4); + } + else + m_attr0 = attr0; + } + + void Object::UpdateAttr1 (uint16_t attr1) + { + // if the size has changed + if ((m_attr1 & 0xC000) != (attr1 & 0xC000)) + { + m_attr1 = attr1; + SetSize(); + m_charEnd = m_charBegin + m_width*m_height*8* + ((m_attr0 & (0x1 << 13)) ? 8 : 4); + } + else + m_attr1 = attr1; + } + + void Object::UpdateAttr2 (uint16_t attr2) + { + if ((m_attr2 & 0xF1FF) != (attr2 & 0xF1FF)) + { + m_attr2 = attr2; + m_charBegin = 0x06010000 + (m_attr2 & 0x3FF)*32; + m_charEnd = m_charBegin + m_width*m_height*8* + ((m_attr0 & (0x1 << 13)) ? 8 : 4); + } + else + m_attr2 = attr2; + } + + inline void Object::SetSize () + { + static const uint8_t Width[3][4] = { + {1, 2, 4, 8}, // Square + {2, 4, 4, 8}, // Horizontal + {1, 1, 2, 4} // Vertical + }; + static const uint8_t Height[3][4] = { + {1, 2, 4, 8}, // Square + {1, 1, 2, 4}, // Horizontal + {2, 4, 4, 8} // Vertical + }; + + m_width = Width[m_attr0 >> 14][m_attr1 >> 14]; + m_height = Height[m_attr0 >> 14][m_attr1 >> 14]; + } + } +} diff --git a/libmeteor/source/graphics/objects.cpp b/libmeteor/source/graphics/objects.cpp new file mode 100644 index 0000000000..6de598a3c7 --- /dev/null +++ b/libmeteor/source/graphics/objects.cpp @@ -0,0 +1,168 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/graphics/objects.hpp" +#include "../debug.hpp" +#include + +namespace AMeteor +{ + namespace Graphics + { + Objects::Objects (Memory& memory, Io& io, uint16_t* pPalette) : + m_io(io), + m_objs(128, Object(pPalette, + memory.GetRealAddress(0x06010000))), + m_pOam((uint16_t*)memory.GetRealAddress(0x07000000)) + { + } + + void Objects::DrawLine (uint8_t line, uint32_t* surface) + { + bool oneDim = m_io.DRead16(Io::DISPCNT) & (0x1 << 6); + uint8_t mosaic = m_io.DRead8(Io::MOSAIC+1); + int16_t rotSel; + for (Objs::iterator iter = m_objs.begin(); + iter != m_objs.end(); ++iter) + { + rotSel = iter->GetRotationParam(); + if (rotSel == -1) + iter->DrawLine (line, surface, oneDim, mosaic); + else + { + rotSel *= 16; + iter->DrawLineRot (line, surface, oneDim, m_pOam[rotSel + 3], + m_pOam[rotSel + 7], m_pOam[rotSel + 11], m_pOam[rotSel + 15], + mosaic); + } + } + } + + void Objects::DrawLineHighOnly (uint8_t line, uint32_t* surface) + { + bool oneDim = m_io.DRead16(Io::DISPCNT) & (0x1 << 6); + uint8_t mosaic = m_io.DRead8(Io::MOSAIC+1); + int16_t rotSel; + for (Objs::iterator iter = m_objs.begin(); + iter != m_objs.end(); ++iter) + if (iter->GetTileNum() >= 512) + { + rotSel = iter->GetRotationParam(); + if (rotSel == -1) + iter->DrawLine (line, surface, oneDim, mosaic); + else + { + rotSel *= 16; + iter->DrawLineRot (line, surface, oneDim, m_pOam[rotSel + 3], + m_pOam[rotSel + 7], m_pOam[rotSel + 11], m_pOam[rotSel + 15], + mosaic); + } + } + } + + void Objects::DrawWindow (uint8_t line, uint8_t* surface) + { + bool oneDim = m_io.DRead16(Io::DISPCNT) & (0x1 << 6); + int16_t rotSel; + uint8_t mask = (m_io.DRead16(Io::WINOUT) >> 8) & 0x3F; + for (Objs::iterator iter = m_objs.begin(); iter != m_objs.end(); ++iter) + if (iter->IsWindow()) + { + rotSel = iter->GetRotationParam(); + if (rotSel == -1) + iter->DrawWindow (line, surface, oneDim, mask); + else + { + rotSel *= 16; + iter->DrawWindowRot (line, surface, oneDim, m_pOam[rotSel + 3], + m_pOam[rotSel + 7], m_pOam[rotSel + 11], m_pOam[rotSel + 15], + mask); + } + } + } + + void Objects::OamWrite (uint32_t begin, uint32_t end) + { + uint32_t objnum; + // FIXME is this possible ? + if (begin & 0x3) + met_abort("OamWrite not 4 byte aligned"); + if (begin <= 0x07000000) + objnum = 0; + else + objnum = (begin - 0x07000000); + uint16_t* pOam = m_pOam + objnum/2; + objnum /= 8; + + Objs::iterator iterStart; + iterStart = m_objs.begin() + objnum; + Objs::iterator iterEnd = m_objs.begin() + (end - 0x07000000 + 7)/8; + if (iterEnd > m_objs.end()) + iterEnd = m_objs.end(); + + for (Objs::iterator iter = iterStart; iter != iterEnd; ++iter, ++objnum) + { + iter->UpdateAttrs(pOam[0], pOam[1], pOam[2]); + pOam += 4; + } + } + + void Objects::OamWrite16 (uint32_t add) + { + uint16_t objnum = (add - 0x07000000) / 8; + uint16_t* pOam = m_pOam + objnum*4; + Objs::iterator iter = m_objs.begin() + objnum; + switch ((add - 0x07000000) % 8) + { + case 0: + iter->UpdateAttr0(pOam[0]); + break; + case 2: + iter->UpdateAttr1(pOam[1]); + break; + case 4: + iter->UpdateAttr2(pOam[2]); + break; + case 6: + break; + default : + met_abort("Oam access not 16 bits aligned"); + break; + } + } + + void Objects::OamWrite32 (uint32_t add) + { + add -= 0x07000000; + uint16_t objnum = add / 8; + uint16_t* pOam = m_pOam + objnum * 4; + Objs::iterator iter = m_objs.begin() + objnum; + switch (add % 8) + { + case 0: + iter->UpdateAttr0(pOam[0]); + iter->UpdateAttr1(pOam[1]); + break; + case 4: + iter->UpdateAttr2(pOam[2]); + break; + default : + met_abort("Oam access not 32 bits aligned"); + break; + } + } + } +} diff --git a/libmeteor/source/graphics/renderer.cpp b/libmeteor/source/graphics/renderer.cpp new file mode 100644 index 0000000000..2d689aaf08 --- /dev/null +++ b/libmeteor/source/graphics/renderer.cpp @@ -0,0 +1,35 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/graphics/renderer.hpp" +#include "ameteor.hpp" + +namespace AMeteor +{ + namespace Graphics + { + Renderer::Renderer (const uint16_t* surface) : + m_base(surface) + { + } + + void Renderer::VBlank () + { + if (m_sig_frame) + m_sig_frame(m_base); + } + } +} diff --git a/libmeteor/source/graphics/screen.cpp b/libmeteor/source/graphics/screen.cpp new file mode 100644 index 0000000000..4c5628b33a --- /dev/null +++ b/libmeteor/source/graphics/screen.cpp @@ -0,0 +1,402 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/graphics/screen.hpp" +#include "../globals.hpp" +#include "../debug.hpp" +#include "ameteor.hpp" + +#include + +namespace AMeteor +{ + namespace Graphics + { + BgLayer Screen::* const Screen::BgLayers [4] = + { &Screen::m_bgLayer0, &Screen::m_bgLayer1, + &Screen::m_bgLayer2, &Screen::m_bgLayer3 }; + + // TODO there is no more need to pass theses references + Screen::Screen (Memory& memory, Io& io) : + m_io(io), + m_surface(new uint16_t[WIDTH*HEIGHT]), + m_renderer(m_surface), + m_frameskip(0), + m_curframe(0), + m_dispcnt(0), + m_refX2(0), m_refY2(0), m_refX3(0), m_refY3(0), + m_pPalette((uint16_t*)memory.GetRealAddress(0x05000000)), + m_bgLayer0(0, memory, io, m_pPalette), + m_bgLayer1(1, memory, io, m_pPalette), + m_bgLayer2(2, memory, io, m_pPalette), + m_bgLayer3(3, memory, io, m_pPalette), + m_objs(memory, io, m_pPalette + 256) + { + } + + Screen::~Screen () + { + delete [] m_surface; + } + + void Screen::DrawLine (uint8_t line) + { + if (m_curframe < m_frameskip) + { + // we skip this frame + // VBlank + if (line == 159) + // we don't update screen since we haven't drawn anything on it, it + // would show up a buffer with the previous image or maybe only + // garbage (we use double buffering) + m_curframe = (m_curframe + 1) % FRMSKIP_TOTAL; + return; + } + + uint16_t* lineBg = new uint16_t[4*WIDTH]; + // layers may draw transparent pixels, so we need to clear them + memset(lineBg, 0, 4*WIDTH*sizeof(uint16_t)); + uint32_t* lineObj = new uint32_t[WIDTH]; + for (uint32_t* p = lineObj + WIDTH - 1; p >= lineObj; --p) + *p = 0x00030000; + uint8_t prio[4]; + prio[0] = m_bgLayer0.GetPriority(); + prio[1] = m_bgLayer1.GetPriority(); + prio[2] = m_bgLayer2.GetPriority(); + prio[3] = m_bgLayer3.GetPriority(); + uint8_t layersOn = (m_dispcnt >> 8) & 0x1F; + + switch (m_dispcnt & 0x7) + { + case 0: // all in mode 0 + // if the bg is enabled draw it + if (layersOn & (0x1 )) m_bgLayer0.DrawLine0(line, lineBg); + if (layersOn & (0x1 << 1)) m_bgLayer1.DrawLine0(line, lineBg+WIDTH); + if (layersOn & (0x1 << 2)) m_bgLayer2.DrawLine0(line, lineBg+2*WIDTH); + if (layersOn & (0x1 << 3)) m_bgLayer3.DrawLine0(line, lineBg+3*WIDTH); + // if objects are enabled draw them + if (layersOn & (0x1 << 4)) m_objs.DrawLine(line, lineObj); + break; + case 1: // bg0 and bg1 in mode 0 and bg2 in mode 2, no bg3 + // disable layer 3 + layersOn &= 0xF7; + // if the bg is enabled draw it + if (layersOn & (0x1 )) m_bgLayer0.DrawLine0(line, lineBg); + if (layersOn & (0x1 << 1)) m_bgLayer1.DrawLine0(line, lineBg+WIDTH); + if (layersOn & (0x1 << 2)) + m_bgLayer2.DrawLine2(lineBg+2*WIDTH, + m_refX2, m_refY2, + m_io.DRead16(Io::BG2PA), + m_io.DRead16(Io::BG2PC)); + // if objects are enabled draw them + if (layersOn & (0x1 << 4)) m_objs.DrawLine(line, lineObj); + break; + case 2: // bg2 and bg3 in mode 2, no bg0 and bg1 + // disable layers 0 and 1 + layersOn &= 0xFC; + // if the bg is enabled draw it + if (layersOn & (0x1 << 2)) + m_bgLayer2.DrawLine2(lineBg+2*WIDTH, + m_refX2, m_refY2, + m_io.DRead16(Io::BG2PA), + m_io.DRead16(Io::BG2PC)); + if (layersOn & (0x1 << 3)) + m_bgLayer3.DrawLine2(lineBg+3*WIDTH, + m_refX3, m_refY3, + m_io.DRead16(Io::BG3PA), + m_io.DRead16(Io::BG3PC)); + // if objects are enabled draw them + if (layersOn & (0x1 << 4)) m_objs.DrawLine(line, lineObj); + break; + // TODO (remember, HIGH ONLY for objs, don't make shitty copy paste) + case 4: // bg2 only in mode 4 (bitmap 256) + layersOn &= 0xF4; + // if bg2 is enabled + if (layersOn & (0x1 << 2)) + // draw it + m_bgLayer2.DrawLine4( + line, + lineBg+2*WIDTH, + m_refX2, m_refY2, + m_io.DRead16(Io::BG2PA), + m_io.DRead16(Io::BG2PB), + m_io.DRead16(Io::BG2PC), + m_io.DRead16(Io::BG2PD), + m_dispcnt & (0x1 << 4)); + // if objs are enabled + if (layersOn & (0x1 << 4)) + // all objs with the current priority + m_objs.DrawLineHighOnly(line, lineObj); + break; + default : + met_abort("not supported : " << (m_dispcnt & 0x7)); + break; + } + + // windows + /* I got very little information for this, it may not be accurate. All + * the sources don't say the same thing */ + uint8_t* window = NULL; + if (m_dispcnt >> 13) + { + window = new uint8_t[WIDTH]; + // Outside window + memset(window, m_io.DRead16(Io::WINOUT) & 0x3F, WIDTH*sizeof(uint8_t)); + // OBJ window + if (m_dispcnt & (0x1 << 15)) + m_objs.DrawWindow(line, window); + // Window 1 + if (m_dispcnt & (0x1 << 14)) + DrawWindow(line, window, + m_io.DRead16(Io::WIN1V), m_io.DRead16(Io::WIN1H), + (m_io.DRead16(Io::WININ) >> 8) & 0x3F); + // Window 0 + if (m_dispcnt & (0x1 << 13)) + DrawWindow(line, window, + m_io.DRead16(Io::WIN0V), m_io.DRead16(Io::WIN0H), + m_io.DRead16(Io::WININ) & 0x3F); + } + + // color effects + uint16_t bldcnt = m_io.DRead16(Io::BLDCNT); + uint8_t colorEffect = (bldcnt >> 6) & 0x3; + uint8_t eva = std::min(m_io.DRead8(Io::BLDALPHA) & 0x1F, 16); + uint8_t evb = std::min(m_io.DRead8(Io::BLDALPHA+1) & 0x1F, 16); + uint8_t evy = std::min(m_io.DRead8(Io::BLDY) & 0x1F, 16); + + // blending + uint16_t* surface = m_surface + line*WIDTH; + uint16_t out, bout; + // top and back are formated as follow : + // 4 bits | 4 bits + // priority | layer + uint8_t top, back; + uint8_t curprio; + uint32_t* pObj = lineObj; + uint16_t* pBg = lineBg; + uint8_t* pWin = window; + uint8_t winmask; + // if window are disabled, we draw everything which is enabled by + // layersOn + if (!window) + winmask = 0xFF; + for (uint8_t x = 0; x < WIDTH; ++x, ++pBg, ++pObj, ++pWin) + { + if (window) + winmask = *pWin; + + // backdrop + bout = out = m_pPalette[0]; + back = top = 0xF5; + + // for each layer + for (uint8_t l = 0; l < 4; ++l) + // if layer is enabled and + if ((layersOn & (0x1 << l)) && + // pixel to draw is not transparent + (pBg[l*WIDTH] & 0x8000)) + { + curprio = ((prio[l] << 4) | l); + + if (curprio < back && curprio > top) + { + bout = pBg[l*WIDTH]; + back = curprio; + } + else if ( + // priority is lower than current top pixel and + curprio < top && + // this layer should be drawn in current window + (winmask & (0x1 << l))) + { + bout = out; + out = pBg[l*WIDTH]; + back = top; + top = curprio; + } + } + + // now objects + // if objects are enabled + if ((layersOn & (0x1 << 4)) && + // pixel to draw is not transparent + (*pObj & 0x8000)) + { + curprio = ((*pObj >> (16 - 4)) & (0x3 << 4)); + + if (curprio <= (back & 0xF0) && curprio > (top & 0xF0)) + { + bout = *pObj; + back = curprio | 4; + } + else if (// priority is lower than current top pixel and + // NOTE : objects are OVER bg with same priority + curprio <= (top & 0xF0) && + // objects should be drawn in current window + (winmask & (0x1 << 4))) + { + bout = out; + out = *pObj; + back = top; + top = curprio | 4; + } + } + + // if we have an object on top and it has semi transparency + if ((top & 0xF) == 4 && (*pObj & (0x1 << 18))) + { + // if second target is just behind + if (bldcnt & ((0x1 << 8) << (back & 0xF))) + // apply alpha blend + out = + std::min(((bout & 0x001F) * evb + + (out & 0x001F) * eva) / 16, 0x001F) | + std::min((((bout & 0x03E0) * evb + + (out & 0x03E0) * eva) / 16) & 0xFFE0, 0x03E0) | + // no need to take care of over flow since u16 & s32 = s32 + std::min((((bout & 0x7C00) * evb + + (out & 0x7C00) * eva) / 16) & 0xFC00, 0x7C00); + } + // else if no window or window and effects are enabled in window + // and we have a first target on top + else if ((!window || (*pWin & (0x1 << 5))) + && (bldcnt & (0x1 << (top & 0xF)))) + switch (colorEffect) + { + case 1: // alpha blend + // if second target is just behind + // TODO optimization : special cases for eva = 0 or evb = 0 + if (bldcnt & ((0x1 << 8) << (back & 0xF))) + // apply alpha blend + out = + std::min(((bout & 0x001F) * evb + + (out & 0x001F) * eva) / 16, 0x001F) | + std::min((((bout & 0x03E0) * evb + + (out & 0x03E0) * eva) / 16) & 0xFFE0, 0x03E0) | + // no need to take care of over flow since u16 & s32 = s32 + std::min((((bout & 0x7C00) * evb + + (out & 0x7C00) * eva) / 16) & 0xFC00, 0x7C00); + break; + case 2: // brightness increase + // we don't need saturation since the formula makes it so it never + // goes above 0x1F + out = + (((out & 0x001F) + + ((0x001F - (out & 0x001F)) * evy) / 16) & 0x001F) | + (((out & 0x03E0) + + ((0x03E0 - (out & 0x03E0)) * evy) / 16) & 0x03E0) | + (((out & 0x7C00) + + ((0x7C00 - (out & 0x7C00)) * evy) / 16) & 0x7C00); + break; + case 3: // brightness decrease + // we don't need saturation since the formula makes it so it never + // goes below 0 + out = + ((((out & 0x001F) * (16-evy)) / 16) & 0x001F) | + ((((out & 0x03E0) * (16-evy)) / 16) & 0x03E0) | + ((((out & 0x7C00) * (16-evy)) / 16) & 0x7C00); + break; + } + + *surface = out; + ++surface; + } + + m_refX2 += (int16_t)m_io.DRead16(Io::BG2PB); + m_refY2 += (int16_t)m_io.DRead16(Io::BG2PD); + m_refX3 += (int16_t)m_io.DRead16(Io::BG3PB); + m_refY3 += (int16_t)m_io.DRead16(Io::BG3PD); + + if (window) + delete [] window; + delete [] lineBg; + delete [] lineObj; + + // VBlank + if (line == 159) + { + m_curframe = (m_curframe + 1) % FRMSKIP_TOTAL; + m_renderer.VBlank(); + } + } + + void Screen::DrawWindow (uint8_t line, uint8_t* surface, + uint16_t win0v, uint16_t win0h, uint8_t mask) + { + // the variables are called win0, but this function works also for win1 + uint8_t win0t = win0v >> 8, win0b = win0v & 0xFF; + // VBA says that if t == b and they are greater than 228, we are in the + // window + // This is from Tonc documentation + if (win0t >= 227) + return; + else if (win0b > win0t && line >= win0t && line < win0b + // the above is the normal behaviour + || win0b < win0t && (line >= win0t || line < win0b) + // the above is the "inverted" behaviour + ) + { + uint8_t win0l, win0r; + uint8_t* ptr; + win0l = win0h >> 8; + win0r = win0h & 0xFF; + // this seems wrong + //if (win0l > 240) + // win0l = 240; + //if (win0r > 240) + // win0r = 240; + + // if this is the normal behaviour + if (win0l <= win0r) + { + ptr = surface + win0l; + for (uint8_t i = win0l; i < win0r && i < 240; ++i, ++ptr) + *ptr = mask; + } + // else, this is the inverted behaviour + else + { + ptr = surface; + for (uint8_t i = 0; i < win0r && i < 240; ++i, ++ptr) + *ptr = mask; + ptr = surface + win0l; + for (uint8_t i = win0l; i < 240; ++i, ++ptr) + *ptr = mask; + } + } + } + + bool Screen::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_refX2); + SS_WRITE_VAR(m_refY2); + SS_WRITE_VAR(m_refX3); + SS_WRITE_VAR(m_refY3); + + return true; + } + + bool Screen::LoadState (std::istream& stream) + { + SS_READ_VAR(m_refX2); + SS_READ_VAR(m_refY2); + SS_READ_VAR(m_refX3); + SS_READ_VAR(m_refY3); + + return true; + } + } +} diff --git a/libmeteor/source/interpreter.cpp b/libmeteor/source/interpreter.cpp new file mode 100644 index 0000000000..bd33e34267 --- /dev/null +++ b/libmeteor/source/interpreter.cpp @@ -0,0 +1,193 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/interpreter.hpp" +#include "ameteor/cpu.hpp" +#include "ameteor/memory.hpp" +#include "ameteor/bios.hpp" +#include "ameteor/disassembler/instruction.hpp" +#include "globals.hpp" +#include "cpu_globals.hpp" +#include "ameteor.hpp" + +#include "debug.hpp" + +namespace AMeteor +{ + Interpreter::Interpreter() : + m_run(false), + m_interrupt_(false), + m_haltcnt(IO.GetRef8(Io::HALTCNT)), + m_if(IO.GetRef16(Io::IF)), + m_ie(IO.GetRef16(Io::IE)) + { + } + + void Interpreter::SendInterrupt (uint16_t interrupt) + { + IO.GetRef16(Io::IF) |= interrupt; + if ((interrupt & IO.DRead16(Io::IE)) && + (IO.DRead16(Io::IME) & 0x1) && + !m_st.icpsr.irq_d) + // irq are enabled and theses irq are enabled... + m_interrupt = true; + } + + void Interpreter::CheckInterrupt () + { + m_interrupt = + (IO.DRead16(Io::IF) & IO.DRead16(Io::IE)) && + (IO.DRead16(Io::IME) & 0x1) && + !m_st.icpsr.irq_d; + } + + void Interpreter::Run (unsigned int cycles) + { + m_run = true; + CLOCK.ResetCounter(); + while(m_run && CLOCK.GetCounter() < cycles) + { + switch (m_haltcnt) + { + case 255: // normal mode + PrintRegs(); + if (FLAG_T) + { + if (R(15) & 0x1) + met_abort("PC not 16 bit aligned : " << IOS_ADD << R(15)); + + code = MEM.Read16(R(15)-2); + //std::cerr << IOS_ADD << R(15) << ' ' << Disassembler::Instruction(R(15), (uint16_t)code).ToString() << std::endl; + R(15) += 2; + t_Code(); + } + else + { + if (R(15) & 0x3) + met_abort("PC not 32 bit aligned : " << IOS_ADD << R(15)); + + if (R(15) < 0x01000000 && !MEM.HasBios()) + { + switch (R(15)) + { + case 0x004: + Bios::Bios000h(); + break; + case 0x00C: + Bios::Bios008h(); + break; + case 0x01C: + Bios::Bios018h(); + break; + case 0x134: + Bios::Bios130h(); + break; + case 0x33C: + Bios::Bios338h(); + break; + case 0x16C: + Bios::Bios168h(); + break; + default: + met_abort("Jump to " << IOS_ADD << R(15)); + } + } + else + { + code = MEM.Read32(R(15)-4); + //std::cerr << IOS_ADD << R(15) << ' ' << Disassembler::Instruction(R(15), (uint32_t)code).ToString() << std::endl; + R(15) += 4; + a_Code(); + } + } + if (R(15) < 0x01000000 && FLAG_T && !MEM.HasBios()) + met_abort("Jump to " << IOS_ADD << R(15)); + + CLOCK.Commit(); + + if (m_interrupt) + // irq are enabled and there are irq waiting... + { + // FIXME : do we really need this ?? + // if not, we can get rid of save and load state and reset + if (m_interrupt_) + { + m_interrupt_ = false; + // XXX we must be sure the cpu isn't halted when an interrupt + // occurs + // should be removed after since it takes no time to make a new + // iteration of the loop + m_haltcnt = 255; + CPU.Interrupt(); + } + else + { + // XXX + if (m_haltcnt != 255) + { + m_haltcnt = 255; + CPU.Interrupt(); + } + else + m_interrupt_ = true; + } + } + + break; + case 0: // halt mode + if (m_if & m_ie) // interrupt occured + { + m_haltcnt = 255; // return to normal mode + CPU.Interrupt(); + // XXX use an else + break; + } + + CLOCK.WaitForNext(); + + // XXX remove this block + if (m_if & m_ie) // interrupt occured + { + m_haltcnt = 255; // return to normal mode + CPU.Interrupt(); + } + + break; + case 1: // stop mode + met_abort("Stop mode not implemented"); + break; + default: + met_abort("Unknown HALTCNT value : " << (int)m_haltcnt); + break; + } + } + m_run = false; + } + + bool Interpreter::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_interrupt_); + + return Cpu::SaveState(stream); + } + + bool Interpreter::LoadState (std::istream& stream) + { + SS_READ_VAR(m_interrupt_); + + return Cpu::LoadState(stream); + } +} diff --git a/libmeteor/source/interpreter_arm.cpp b/libmeteor/source/interpreter_arm.cpp new file mode 100644 index 0000000000..731972848d --- /dev/null +++ b/libmeteor/source/interpreter_arm.cpp @@ -0,0 +1,1477 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __INTERPRETER_ARM_H__ +#define __INTERPRETER_ARM_H__ + +/* + +- From GBATEK - + +ARM Binary Opcode Format + +|..3 ..................2 ..................1 ..................0| +|1_0_9_8_7_6_5_4_3_2_1_0_9_8_7_6_5_4_3_2_1_0_9_8_7_6_5_4_3_2_1_0| +|_Cond__|0_0_0|___Op__|S|__Rn___|__Rd___|__Shift__|Typ|0|__Rm___| DataProc +|_Cond__|0_0_0|___Op__|S|__Rn___|__Rd___|__Rs___|0|Typ|1|__Rm___| DataProc +|_Cond__|0_0_1|___Op__|S|__Rn___|__Rd___|_Shift_|___Immediate___| DataProc +|_Cond__|0_0_1_1_0|P|1|0|_Field_|__Rd___|_Shift_|___Immediate___| PSR Imm +|_Cond__|0_0_0_1_0|P|L|0|_Field_|__Rd___|0_0_0_0|0_0_0_0|__Rm___| PSR Reg +|_Cond__|0_0_0_1_0_0_1_0_1_1_1_1_1_1_1_1_1_1_1_1|0_0|L|1|__Rn___| BX,BLX +|1_1_1_0|0_0_0_1_0_0_1_0|_____immediate_________|0_1_1_1|_immed_| BKPT ARM9 +|_Cond__|0_0_0_1_0_1_1_0_1_1_1_1|__Rd___|1_1_1_1|0_0_0_1|__Rm___| CLZ ARM9 +|_Cond__|0_0_0_1_0|Op_|0|__Rn___|__Rd___|0_0_0_0|0_1_0_1|__Rm___| QALU ARM9 +|_Cond__|0_0_0_0_0_0|A|S|__Rd___|__Rn___|__Rs___|1_0_0_1|__Rm___| Multiply +|_Cond__|0_0_0_0_1|U|A|S|_RdHi__|_RdLo__|__Rs___|1_0_0_1|__Rm___| MulLong +|_Cond__|0_0_0_1_0|Op_|0|Rd/RdHi|Rn/RdLo|__Rs___|1|y|x|0|__Rm___| MulHalf +|_Cond__|0_0_0_1_0|B|0_0|__Rn___|__Rd___|0_0_0_0|1_0_0_1|__Rm___| TransSwp12 +|_Cond__|0_0_0|P|U|0|W|L|__Rn___|__Rd___|0_0_0_0|1|S|H|1|__Rm___| TransReg10 +|_Cond__|0_0_0|P|U|1|W|L|__Rn___|__Rd___|OffsetH|1|S|H|1|OffsetL| TransImm10 +|_Cond__|0_1_0|P|U|B|W|L|__Rn___|__Rd___|_________Offset________| TransImm9 +|_Cond__|0_1_1|P|U|B|W|L|__Rn___|__Rd___|__Shift__|Typ|0|__Rm___| TransReg9 +|_Cond__|0_1_1|________________xxx____________________|1|__xxx__| Undefined +|_Cond__|1_0_0|P|U|S|W|L|__Rn___|__________Register_List________| BlockTrans +|_Cond__|1_0_1|L|___________________Offset______________________| B,BL,BLX +|_Cond__|1_1_0|P|U|N|W|L|__Rn___|__CRd__|__CP#__|____Offset_____| CoDataTrans +|_Cond__|1_1_0_0_0_1_0|L|__Rn___|__Rd___|__CP#__|_CPopc_|__CRm__| CoRR ARM9 +|_Cond__|1_1_1_0|_CPopc_|__CRn__|__CRd__|__CP#__|_CP__|0|__CRm__| CoDataOp +|_Cond__|1_1_1_0|CPopc|L|__CRn__|__Rd___|__CP#__|_CP__|1|__CRm__| CoRegTrans +|_Cond__|1_1_1_1|_____________Ignored_by_Processor______________| SWI + +*/ + +#include "ameteor/interpreter.hpp" +#include "globals.hpp" +#include "cpu_globals.hpp" +#include "ameteor.hpp" + +#include "debug.hpp" + +#define Rn ((code >> 16) & 0xF) +#define Rd ((code >> 12) & 0xF) +#define Rs ((code >> 8) & 0xF) +#define Rm (code & 0xF) + +// Load/Store immediate offset +#define LSOff (code & 0xFFF) + +#ifdef METDEBUG +# define NOT_PC(reg) \ + if (reg == 15) \ + met_abort("Register is PC") +# define NOT_PC_ALL() \ + NOT_PC(Rn); \ + NOT_PC(Rd); \ + NOT_PC(Rs); \ + NOT_PC(Rm) +# define NOT_SAME2(reg1, reg2) \ + if (reg1 == reg2) \ + met_abort("Two same registers") +# define NOT_SAME3(reg1, reg2, reg3) \ + NOT_SAME2(reg1, reg2); \ + NOT_SAME2(reg2, reg3); \ + NOT_SAME2(reg1, reg3) +#else +# define NOT_PC(reg) {} +# define NOT_PC_ALL() {} +# define NOT_SAME2(reg1, reg2) {} +# define NOT_SAME3(reg1, reg2, reg3) {} +#endif + +#define ARM(name) \ + inline void Interpreter::a##name () +#define NIARM(name) \ + void Interpreter::a##name () + +namespace AMeteor +{ + // Branch and Exchange + ARM(BXBLX) + { + if ((code & 0x0FFFFF00) != 0x012FFF00) + met_abort("Bits 8-27 must be 0001 00101111 11111111 for BX/BLX instructions"); + + if (Rm == 15) + met_abort("Branching on PC is undefined"); + + if (code & (0x1 << 5)) // BLX + { + R(14) = R(15); + met_abort("BLX not completly implemented"); + } + // else BX + + if (R(Rm) & 0x1) + { + FLAG_T = 1; + R(15) = R(Rm) + 1; + CYCLES32NSeq(R(15), 3); + } + else + { + if (R(Rm) & 0x3) + met_abort("BX with ARM on non 32 bit aligned address"); + R(15) = R(Rm)+4; + CYCLES32NSeq(R(15), 3); + } + } + + // Branch and Branch with Link + ARM(BBL) + { + if (((code >> 25) & 0x7) != 0x5) + met_abort("Bits 25-27 must be 101 for B/BL/BLX instructions"); + + if (((code >> 28) & 0xF) == 0xF) + met_abort("BLX not implemented"); + + int32_t off = code & 0x00FFFFFF; + // Extend the sign bit + off <<= 8; + off >>= 8; + // off is in steps of 4 + off *= 4; + + if (code & (0x1 << 24)) // BL + { + // R(15) points two instructions later, R(14) should point to next + // instruction + // LR = PC + 4 and R15 = PC + 8 + R(14) = R(15) - 4 ; + } + // else B + R(15) += off + 4; + CYCLES32NSeq(R(15), 3); + } + + // Data Processing + NIARM(_DataProcShiftImm) + { + if ((code >> 26) & 0x3) + met_abort("Bits 26-27 must be 00 for DataProc instructions"); + + uint8_t shift; + uint32_t op2 = 0; // to avoid a warning + bool shiftcarry = FLAG_C; + uint8_t rd = Rd; + + uint8_t rm = Rm; + if (rm == 15 && !(code & (0x1 << 20)) && (code & (0x1 << 4))) + met_abort("Rm = 15, not implemented"); + + shift = (code >> 7) & 0x1F; + switch ((code >> 5) & 0x3) + { + case 0: // Logical Shift Left + if (shift) + { + op2 = R(rm) << shift; + shiftcarry = (R(rm) >> (32-shift)) & 0x1; + } + else // LSL#0 + op2 = R(rm); + break; + case 1: // Logical Shift Right + if (shift) + { + op2 = R(rm) >> shift; + shiftcarry = (R(rm) >> (shift-1)) & 0x1; + } + else // LSR#32 + { + op2 = 0; + shiftcarry = R(rm) >> 31; + } + break; + case 2: // Arithmetic Shift Right + if (shift) + { + op2 = ((int32_t)R(rm)) >> shift; + shiftcarry = (((int32_t)R(rm)) >> (shift-1)) & 0x1; + } + else // ASR#32 + { + op2 = ((int32_t)R(rm)) >> 31; + shiftcarry = op2 & 0x1; + } + break; + case 3: // ROtate Right + if (shift) + { + op2 = ROR(R(rm), shift); + shiftcarry = op2 >> 31; + } + else // RRX#1 + { + shiftcarry = R(rm) & 0x1; + op2 = (FLAG_C << 31) | (R(rm) >> 1); + } + break; + } + + a_DataProcCore(rd, R(Rn), op2, shiftcarry); + } + + NIARM(_DataProcShiftReg) + { + if ((code >> 26) & 0x3) + met_abort("Bits 26-27 must be 00 for DataProc instructions"); + if (code & (0x1 << 7)) + met_abort("Bit 7 must be 0 for DataProc with shift by register instructions"); + + uint8_t shift; + uint32_t op1, op2 = 0; // to avoid a warning + bool shiftcarry = FLAG_C; + uint8_t rd = Rd; + + uint8_t rm = Rm; + if (rm == 15 && !(code & (0x1 << 20)) && (code & (0x1 << 4))) + met_abort("Rm = 15, not implemented"); + + op1 = R(Rn); + + NOT_PC(Rs); + ICYCLES(1); + if (Rn == 15) + op1 += 4; + + shift = R(Rs) & 0xFF; // only first byte used + if (shift) + switch ((code >> 5) & 0x3) + { + case 0: // Logical Shift Left + if (shift == 32) + { + op2 = 0; + shiftcarry = R(rm) & 0x1; + } + else if (shift < 32) + { + op2 = R(rm) << shift; + shiftcarry = (R(rm) >> (32-shift)) & 0x1; + } + else + { + op2 = 0; + shiftcarry = 0; + } + break; + case 1: // Logical Shift Right + if (shift == 32) + { + op2 = 0; + shiftcarry = R(rm) >> 31; + } + else if (shift < 32) + { + op2 = R(rm) >> shift; + shiftcarry = (R(rm) >> (shift-1)) & 0x1; + } + else + { + op2 = 0; + shiftcarry = 0; + } + break; + case 2: // Arithmetic Shift Right + if (shift >= 32) + { + op2 = ((int32_t)R(rm)) >> 31; + shiftcarry = op2 & 0x1; + } + else + { + op2 = ((int32_t)R(rm)) >> shift; + shiftcarry = (((int32_t)R(rm)) >> (shift-1)) & 0x1; + } + break; + case 3: // ROtate Right + op2 = ROR(R(rm), shift % 32); + shiftcarry = op2 >> 31; + break; + } + else + op2 = R(rm); + + a_DataProcCore(rd, op1, op2, shiftcarry); + } + + NIARM(_DataProcImm) + { + if ((code >> 26) & 0x3) + met_abort("Bits 26-27 must be 00 for DataProc instructions"); + + uint32_t op2; + bool shiftcarry = FLAG_C; + uint8_t rd = Rd; + + uint8_t shift = ((code >> 8) & 0xF); + if (shift) + { + op2 = ROR(code & 0xFF, shift * 2); + shiftcarry = op2 >> 31; + } + else + op2 = code & 0xFF; + + a_DataProcCore(rd, R(Rn), op2, shiftcarry); + } + + inline void Interpreter::a_DataProcCore(uint8_t rd, + uint32_t op1, uint32_t op2, bool shiftcarry) + { + uint8_t opcode = (code >> 21) & 0xF; + + if (opcode < 0x8 || opcode > 0xB) + { + } + else if (!((code >> 20) & 0x1) || (rd != 0x0 && rd != 0xF)) + met_abort("Set condition bit not set for test operation or Rd not acceptable for a test"); + if ((opcode == 0xD || opcode == 0xF) && Rn) + met_abort("Rn not null for MOV or MVN"); + +#ifndef X86_ASM + uint32_t res; +#endif + + if (code & (0x1 << 20)) // if set condition + { + switch (opcode) + { + case 0x0 : // AND +#ifdef X86_ASM + asm("andl %4, %3\n" + "setzb %1\n" + "setsb %2\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N) + :"0"(op1), "r"(op2)); +#else + res = R(rd) = op1 & op2; + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + FLAG_C = shiftcarry; + break; + case 0x1 : // EOR +#ifdef X86_ASM + asm("xorl %4, %3\n" + "setzb %1\n" + "setsb %2\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N) + :"0"(op1), "r"(op2)); +#else + res = R(rd) = op1 ^ op2; + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + FLAG_C = shiftcarry; + break; + case 0x2 : // SUB +#ifdef X86_ASM + asm("subl %6, %5\n" + "setzb %1\n" + "setsb %2\n" + "setncb %3\n" + "setob %4\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), + "=m"(FLAG_V) + :"0"(op1), "r"(op2)); +#else + res = R(rd) = op1 - op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = SUBCARRY(op1, op2, res); + FLAG_V = SUBOVERFLOW(op1, op2, res); +#endif + break; + case 0x3 : // RSB +#ifdef X86_ASM + asm("subl %6, %5\n" + "setzb %1\n" + "setsb %2\n" + "setncb %3\n" + "setob %4\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), + "=m"(FLAG_V) + :"0"(op2), "r"(op1)); +#else + res = R(rd) = op2 - op1; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = SUBCARRY(op1, op2, res); + FLAG_V = SUBOVERFLOW(op1, op2, res); +#endif + break; + case 0x4 : // ADD +#ifdef X86_ASM + asm("addl %6, %5\n" + "setzb %1\n" + "setsb %2\n" + "setcb %3\n" + "setob %4\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), + "=m"(FLAG_V) + :"0"(op1), "r"(op2)); +#else + res = R(rd) = op1 + op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = ADDCARRY(op1, op2, res); + FLAG_V = ADDOVERFLOW(op1, op2, res); +#endif + break; + case 0x5 : // ADC + // TODO test on hardware how overflow and carry work for this + // instruction +#ifdef X86_ASM + asm("addl %6, %5\n" + "setzb %1\n" + "setsb %2\n" + "setcb %3\n" + "setob %4\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), + "=m"(FLAG_V) + :"0"(op1+FLAG_C), "r"(op2)); +#else + res = R(rd) = op1 + op2 + FLAG_C; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = ADDCARRY(op1, op2, res); + FLAG_V = ADDOVERFLOW(op1, op2, res); +#endif + break; + case 0x6 : // SBC + // TODO test on hardware how overflow and carry work for this + // instruction +#ifdef X86_ASM + asm("subl %6, %5\n" + "setzb %1\n" + "setsb %2\n" + "setncb %3\n" + "setob %4\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), + "=m"(FLAG_V) + :"0"(op1+FLAG_C-1), "r"(op2)); +#else + res = R(rd) = op1 - op2 + FLAG_C - 1; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = SUBCARRY(op1, op2, res); + FLAG_V = SUBOVERFLOW(op1, op2, res); +#endif + break; + case 0x7 : // RSC +#ifdef X86_ASM + asm("subl %6, %5\n" + "setzb %1\n" + "setsb %2\n" + "setncb %3\n" + "setob %4\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), + "=m"(FLAG_V) + :"0"(op2+FLAG_C-1), "r"(op1)); +#else + res = R(rd) = op2 - op1 + FLAG_C - 1; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = SUBCARRY(op1, op2, res); + FLAG_V = SUBOVERFLOW(op1, op2, res); +#endif + break; + case 0x8 : // TST +#ifdef X86_ASM + asm("testl %3, %2\n" + "setzb %0\n" + "setsb %1\n" + :"=m"(FLAG_Z), "=m"(FLAG_N) + :"r"(op1), "r"(op2)); +#else + res = op1 & op2; + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + SETFB(C, shiftcarry); + break; + case 0x9 : // TEQ +#ifdef X86_ASM + asm("xorl %3, %2\n" + "setzb %0\n" + "setsb %1\n" + :"=m"(FLAG_Z), "=m"(FLAG_N) + :"r"(op1), "r"(op2) + :"2"); +#else + res = op1 ^ op2; + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + SETFB(C, shiftcarry); + break; + case 0xA : // CMP +#ifdef X86_ASM + asm("cmpl %5, %4\n" + "setzb %0\n" + "setsb %1\n" + "setncb %2\n" + "setob %3\n" + :"=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"r"(op1), "r"(op2)); +#else + res = op1 - op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = SUBCARRY(op1, op2, res); + FLAG_V = SUBOVERFLOW(op1, op2, res); +#endif + break; + case 0xB : // CMN +#ifdef X86_ASM + asm("addl %5, %4\n" + "setzb %0\n" + "setsb %1\n" + "setcb %2\n" + "setob %3\n" + :"=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"r"(op1), "r"(op2) + :"4"); +#else + res = op1 + op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = ADDCARRY(op1, op2, res); + FLAG_V = ADDOVERFLOW(op1, op2, res); +#endif + break; + case 0xC : // ORR +#ifdef X86_ASM + asm("orl %4, %3\n" + "setzb %1\n" + "setsb %2\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N) + :"0"(op1), "r"(op2)); +#else + res = R(rd) = op1 | op2; + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + FLAG_C = shiftcarry; + break; + case 0xD : // MOV +#ifdef X86_ASM + R(rd) = op2; + asm("testl %2, %2\n" + "setzb %0\n" + "setsb %1\n" + :"=m"(FLAG_Z), "=m"(FLAG_N) + :"r"(op2)); +#else + res = R(rd) = op2; + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + FLAG_C = shiftcarry; + break; + case 0xE : // BIC +#ifdef X86_ASM + asm("not %3\n" + "andl %4, %3\n" + "setzb %1\n" + "setsb %2\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N) + :"0"(op2), "r"(op1)); +#else + res = R(rd) = op1 & ~op2; + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + FLAG_C = shiftcarry; + break; + case 0xF : // MVN +#ifdef X86_ASM + asm("xorl $0xffffffff, %3\n" + "setzb %1\n" + "setsb %2\n" + :"=r"(R(rd)), "=m"(FLAG_Z), "=m"(FLAG_N) + :"0"(op2)); +#else + res = R(rd) = ~op2; + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + FLAG_C = shiftcarry; + break; + } + + if (rd == 15) + CPU.SwitchModeBack(); + } + else + switch (opcode) + { + case 0x0 : // AND + R(rd) = op1 & op2; + break; + case 0x1 : // EOR + R(rd) = op1 ^ op2; + break; + case 0x2 : // SUB + R(rd) = op1 - op2; + break; + case 0x3 : // RSB + R(rd) = op2 - op1; + break; + case 0x4 : // ADD + R(Rd) = op1 + op2; + break; + case 0x5 : // ADC + R(rd) = op1 + op2 + FLAG_C; + break; + case 0x6 : // SBC + R(rd) = op1 - op2 + FLAG_C - 1; + break; + case 0x7 : // RSC + R(rd) = op2 - op1 + FLAG_C - 1; + break; + case 0x8 : // TST + case 0x9 : // TEQ + case 0xA : // CMP + case 0xB : // CMN + met_abort("Comparison or test without set flags bit"); + break; + case 0xC : // ORR + R(rd) = op1 | op2; + break; + case 0xD : // MOV + R(rd) = op2; + break; + case 0xE : // BIC + R(rd) = op1 & ~op2; + break; + case 0xF : // MVN + R(rd) = ~op2; + break; + } + + if (rd == 15 && (opcode < 0x8 || opcode > 0xB)) + { + if (FLAG_T) + { + CYCLES16NSeq(R(15), 3); + R(15) += 2; + } + else + { + CYCLES32NSeq(R(15), 3); + R(15) += 4; + } + } + else + CYCLES32Seq(R(15), 1); + + if (opcode >= 0x8 && opcode <= 0xB && rd == 0xF) + met_abort("P test instruction (not implemented)"); + } + + // PSR Transfer (MRS, MSR) + ARM(PSR) + { + if ((code >> 26) & 0x3) + met_abort("Bits 26-27 must be 00 for PSR instructions"); + if (((code >> 23) & 0x3) != 0x2) + met_abort("Bits 23-24 must be 10 for PSR instructions"); + if (code & (0x1 << 20)) + met_abort("Bit 20 must be 0 for PSR instructions"); + + bool oncpsr = !(code & (0x1 << 22)); + if (oncpsr) + CPU.UpdateCpsr(); + uint32_t& psr = oncpsr ? CPSR : SPSR; + + if (code & (0x1 << 21)) // MSR + { + if (((code >> 12) & 0xF) != 0xF) + met_abort("Bits 12-15 must be 0xF for MSR instruction"); + + uint32_t val; + if (code & (0x1 << 25)) + { + //val = ROR(code & 0xF, ((code >> 8) & 0x4) * 2); + val = ROR(code & 0xF, (code >> 7) & 0x6); + } + else + { + if ((code >> 4) & 0xFF) + met_abort("Bits 4-11 must be 0 for MSR instruction"); + val = R(Rm); + } + if (!(code & (0x1 << 19))) + val = (val & 0x00FFFFFF) | (psr & 0xFF000000); + if (!(code & (0x1 << 18))) + val = (val & 0xFF00FFFF) | (psr & 0x00FF0000); + if (!(code & (0x1 << 17))) + val = (val & 0xFFFF00FF) | (psr & 0x0000FF00); + if (!(code & (0x1 << 16))) + val = (val & 0xFFFFFF00) | (psr & 0x000000FF); + else if (oncpsr && + (psr & 0x1F) != (val & 0x1F)) // have we changed mode ? + CPU.SwitchToMode(val & 0x1F); + psr = val; + if (oncpsr) + { + CPU.UpdateICpsr(); + CPU.CheckInterrupt(); + } + } + else // MRS + { + if ((code >> 25) & 0x1) + met_abort("Bit 25 must be 0 for MRS instruction"); + if (((code >> 16) & 0xF) != 0xF) + met_abort("Bits 16-19 must be 0xF for MRS instruction"); + if (code & 0xFFF) + met_abort("Bits 0-11 must be 0 for MRS instruction"); + R(Rd) = psr; + } + + CYCLES32Seq(R(15), 1); + } + + // Multiply and Multiply-Accumulate (MUL,MLA) + ARM(_Multiply) + { + // NOTE : In this instruction Rn and Rd are inverted + if ((code >> 25) & 0x7) + met_abort("Bits 25-27 must be 000 for Multiply instructions"); + if (code & (0x1 << 24)) + { + if (!(code & (0x1 << 7))) + met_abort("Bit 7 must be 1 for halfword multiply"); + if (code & (0x1 << 4)) + met_abort("Bit 7 must be 0 for halfword multiply"); + } + else + { + if (((code >> 4) & 0xF) != 0x9) + met_abort("Bits 4-7 must be 1001 for non halfword multiplies"); + } + NOT_PC_ALL(); + + switch ((code >> 21) & 0xF) + { + case 0x0 : // MUL + if (Rd != 0) + met_abort("Rd must be 0 for MUL instructions"); + NOT_SAME2(Rn, Rm); + R(Rn) = R(Rm)*R(Rs); + if (code & (0x1 << 20)) + { + FZ(R(Rn)); + FN(R(Rn)); + } + MULICYCLES(Rs); + CYCLES32Seq(R(15), 1); + break; + case 0x1 : // MLA + NOT_SAME2(Rn, Rm); + R(Rn) = R(Rm)*R(Rs)+R(Rd); + if (code & (0x1 << 20)) + { + FZ(R(Rn)); + FN(R(Rn)); + } + MULICYCLES(Rs); + ICYCLES(1); + CYCLES32Seq(R(15), 1); + break; + case 0x4 : // UMULL + { + NOT_SAME3(Rn, Rd, Rm); + uint64_t out = ((uint64_t)R(Rm))*((uint64_t)R(Rs)); + R(Rn) = out >> 32; + R(Rd) = out & 0xFFFFFFFF; + if (code & (0x1 << 20)) + { + FZ(out); + FN(R(Rn)); + } + } + MULICYCLES(Rs); + ICYCLES(1); + CYCLES32Seq(R(15), 1); + break; + case 0x5 : // UMLAL + { + NOT_SAME3(Rn, Rd, Rm); + uint64_t out = + ((uint64_t)R(Rm)) * ((uint64_t)R(Rs)) + + ((((uint64_t)R(Rn)) << 32) | ((uint64_t)R(Rd))); + R(Rn) = out >> 32; + R(Rd) = out & 0xFFFFFFFF; + if (code & (0x1 << 20)) + { + FZ(out); + FN(R(Rn)); + } + } + MULICYCLES(Rs); + ICYCLES(2); + CYCLES32Seq(R(15), 1); + break; + case 0x6 : // SMULL + { + NOT_SAME3(Rn, Rd, Rm); + int64_t out = ((int64_t)(int32_t)R(Rm)) * ((int64_t)(int32_t)R(Rs)); + R(Rn) = out >> 32; + R(Rd) = out & 0xFFFFFFFF; + if (code & (0x1 << 20)) + { + FZ(out); + FN(R(Rn)); + } + } + MULICYCLES(Rs); + ICYCLES(1); + CYCLES32Seq(R(15), 1); + break; + case 0x7 : // SMLAL + { + NOT_SAME3(Rn, Rd, Rm); + int64_t out = ((int64_t)(int32_t)R(Rm)) * ((int64_t)(int32_t)R(Rs)) + + ((((int64_t)R(Rn)) << 32) | ((int64_t)R(Rd))); + R(Rn) = out >> 32; + R(Rd) = out & 0xFFFFFFFF; + if (code & (0x1 << 20)) + { + FZ(out); + FN(R(Rn)); + } + } + MULICYCLES(Rs); + ICYCLES(2); + CYCLES32Seq(R(15), 1); + break; + case 0x8 : // SMLAxy + case 0x9 : // SMLAW/SMULW + case 0xA : // SMLALxy + case 0xB : // SMULxy + default : + met_abort("Not implemented multiply instruction or unknown"); + } + } + + // Single Data Transfer (LDR, STR, PLD) + + // Load and store + // FIXME : should this support Prepare Cache for Load instructions ? + ARM(LDRSTR) + { + if (((code >> 28) & 0xF) == 0xF) + met_abort("PLD instructions not implemented"); + if (((code >> 26) & 0x3) != 0x1) + met_abort("Bits 26-27 must be 01 for LDR/STR instructions"); + + uint32_t offset; + + if (code & (0x1 << 25)) // register offset + { + if (code & (0x1 << 4)) + met_abort("Bit 4 must be 0 for LDR or STR instruction with register offset"); + offset = (code >> 7) & 0x1F; + switch ((code >> 5) & 0x3) + { + case 0: // Logical Shift Left + if (offset) + offset = R(Rm) << offset; + else + offset = R(Rm); + break; + case 1: // Logical Shift Right + if (offset) + offset = R(Rm) >> offset; + else + offset = 0; + break; + case 2: // Arithmetic Shift Right + if (offset) + offset = ((int32_t)R(Rm)) >> offset; + else + { + if (R(Rm) >> 31) + offset = 0xFFFFFFFF; + else + offset = 0; + } + break; + case 3: // ROtate Right + if (offset) + offset = ROR(R(Rm), offset); + else + offset = (FLAG_C << 31) | (R(Rm) >> 1); + break; + } + } + else // immediate offset + { + offset = code & 0xFFF; + } + + // bit 24 : 0 = add offset after and write-back, 1 = add offset before + uint32_t add = R(Rn); + if (code & (0x1 << 24)) + { + if (code & (0x1 << 23)) + add += offset; + else + add -= offset; + } + + /* bit 22 : 0 = write word, 1 = write byte + * bit 20 : 0 = store, 1 = load */ + if (code & (0x1 << 22)) + { + if (code & (0x1 << 20)) // LDRB + { + R(Rd) = MEM.Read8(add); + if (Rd == 15) + met_abort("LDRB to R15 !"); + CYCLES16NSeq(add, 1); + ICYCLES(1); + CYCLES32Seq(R(15), 1); + } + else // STRB + { + MEM.Write8(add, R(Rd)); + CYCLES16NSeq(add, 1); + CYCLES32NSeq(R(15), 1); + } + } + else + { + if (code & (0x1 << 20)) // LDR + { + R(Rd) = MEM.Read32(add); + CYCLES32NSeq(add, 1); + ICYCLES(1); + if (Rd == 15) + { + CYCLES32NSeq(R(15), 3); + R(Rd) += 4; + } + else + CYCLES32Seq(R(15), 1); + } + else // STR + { + MEM.Write32(add, R(Rd)); + CYCLES32NSeq(add, 1); + CYCLES32NSeq(R(15), 1); + } + } + + // bit 21 if write before : 0 = nothing, 1 = write-back + if (!(code & (0x1 << 24))) // in post, writeback is always enabled + { + if (code & (0x1 << 23)) + R(Rn) = add + offset; + else + R(Rn) = add - offset; + } + else if (code & (0x1 << 21)) + R(Rn) = add; + } + + // Halfword, Doubleword, and Signed Data Transfer + ARM(STRLDR_HD) + { + if ((code >> 25) & 0x7) + met_abort("Bits 25-27 must be 000 for halfword transfer instructions"); + if (!(code & (0x1 << 7)) || !(code & (0x1 << 4))) + met_abort("Bits 4 and 7 must be 1 for halfword transfer instructions"); + if (Rd == 15) + met_abort("operation on r15, not implemented"); + + uint8_t rd = Rd; + + uint32_t off; + if (code & (0x1 << 22)) // immediate offset + off = ((code >> 4) & 0xF0) | (code & 0xF); + else // register offset + { + if ((code >> 8) & 0xF) + met_abort("Bits 8-11 must be 0 for halfword transfer with register offset instructions"); + NOT_PC(Rm); + off = R(Rm); + } + + uint32_t add = R(Rn); + if (code & (0x1 << 24)) + { + if (code & (0x1 << 23)) + add += off; + else + add -= off; + } + else if (code & (0x1 << 21)) + met_abort("Bit 21 must be 0 for post indexed halfword transfers instructions"); + + switch (((code >> 18) & 0x4) | ((code >> 5) & 0x3)) + { + case 0x0: + met_abort("Reserved for SWP instruction !"); + break; + case 0x1: // STRH + MEM.Write16(add, rd == 15 ? R(15) + 4 : R(rd)); + CYCLES16NSeq(add, 1); + CYCLES32NSeq(R(15), 1); + break; + case 0x2: // LDRD + if (rd % 2) + met_abort("Register number not even for double word transfer"); + if (add % 8) + met_abort("Address not double word aligned"); + if (rd == 15) + met_abort("Rd is 15 for double word transfer !"); + R(rd) = MEM.Read32(add); + R(rd+1) = MEM.Read32(add+4); + CYCLES32NSeq(add, 2); + ICYCLES(1); + CYCLES32Seq(R(15), 1); + break; + case 0x3: // STRD + if (rd % 2) + met_abort("Register number not even for double word transfer"); + if (add % 8) + met_abort("Address not double word aligned"); + if (rd == 15) + met_abort("Rd is 15 for double word transfer !"); + MEM.Write32(add, R(rd)); + MEM.Write32(add + 4, rd == 14 ? R(15) + 4 : R(rd+1)); + CYCLES32NSeq(add, 2); + CYCLES32NSeq(R(15), 1); + break; + case 0x4: + met_abort("Reserved !"); + break; + case 0x5: // LDRH + R(rd) = MEM.Read16(add); + CYCLES16NSeq(add, 1); + ICYCLES(1); + CYCLES32Seq(R(15), 1); + break; + case 0x6: // LDRSB + R(rd) = MEM.Read8(add); + // sign-extend + R(rd) <<= 24; + R(rd) = ((int32_t)R(rd)) >> 24; + CYCLES16NSeq(add, 1); + ICYCLES(1); + CYCLES32Seq(R(15), 1); + break; + case 0x7: // LDRSH + R(rd) = MEM.Read16(add); + // sign-extend + R(rd) <<= 16; + R(rd) = ((int32_t)R(rd)) >> 16; + CYCLES16NSeq(add, 1); + ICYCLES(1); + CYCLES32Seq(R(15), 1); + break; + } + + if (!(code & (0x1 << 24))) // in post, writeback is always enabled + { + if (code & (0x1 << 23)) + R(Rn) = add + off; + else + R(Rn) = add - off; + } + else if (code & (0x1 << 21)) + R(Rn) = add; + } + + // Block Data Transfer (LDM,STM) + ARM(LDMSTM) + { + if (((code >> 25) & 0x7) != 0x4) + met_abort("Bits 25-27 must be 100 for LDM/STM instructions"); + if (code & (0x1 << 22)) + met_abort("not implemented"); + + static const uint8_t NumBits[] = + {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + + // Works like LDR and STR, look at above comments + uint8_t numregs = + NumBits[(code >> 12) & 0xF] + + NumBits[(code >> 8) & 0xF] + + NumBits[(code >> 4) & 0xF] + + NumBits[ code & 0xF]; + uint16_t regs = code & 0xFFFF; + + uint32_t add, baseadd; + baseadd = add = R(Rn); + if (code & (0x1 << 24)) + if (code & (0x1 << 23)) + add += 4; // increment before + else + add -= numregs * 4; // decrement before + else + if (!(code & (0x1 << 23))) + add -= (numregs-1) * 4; // decrement after + add &= 0xFFFFFFFC; + + if (code & (0x1 << 20)) // LDM + { + CYCLES32NSeq(add, numregs); + ICYCLES(1); + for (register uint8_t n = 0; n < 16; ++n) + if (regs & (0x1 << n)) + { + R(n) = MEM.Read32 (add); + if (n == 15) + R(15) += 4; + add += 4; + } + if (regs & (0x1 << 15)) + CYCLES32NSeq(R(15), 3); + else + CYCLES32Seq(R(15), 1); + } + else // STM + { + CYCLES32NSeq(add, numregs); + CYCLES32NSeq(R(15), 1); + + for (register uint8_t n = 0; n < 16; ++n) + if (regs & (0x1 << n)) + { + if (n == 15) + MEM.Write32 (add, R(15) + 4); + else + MEM.Write32 (add, R(n)); + add += 4; + } + } + + if (code & (0x1 << 21)) + if (code & (0x1 << 23)) + R(Rn) = baseadd + numregs * 4; + else + R(Rn) = baseadd - numregs * 4; + } + + // Single Data Swap (SWP) + ARM(SWP) + { + if (((code >> 23) & 0x1F) != 0x02) + met_abort("Bits 23-27 must be 00010 for SWP instructions"); + if ((code >> 20) & 0x3) + met_abort("Bits 20-21 must be 00 for SWP instructions"); + if (((code >> 4) & 0xFF) != 0x09) + met_abort("Bits 4-11 must be 00001001 for SWP instructions"); + + if (code & (0x1 << 22)) // SWPB + { + R(Rd) = MEM.Read8(R(Rn)); + MEM.Write8(R(Rn), R(Rm)); + } + else // SWP + { + R(Rd) = MEM.Read32(R(Rn)); + MEM.Write32(R(Rn), R(Rm)); + } + CYCLES32NSeq(R(Rn), 1); + CYCLES32NSeq(R(Rn), 1); + ICYCLES(1); + CYCLES32Seq(R(15), 1); + } + + // Software Interrupt (SWI,BKPT) + ARM(SWI) + { + if (((code >> 24) & 0xF) != 0xF) + met_abort("Bits 24-27 must be 1111 for SWI instructions"); + + CPU.SoftwareInterrupt((code >> 16) & 0xFF); + + // FIXME seems wrong ! + CYCLES32NSeq(0, 3); + } + + inline bool Interpreter::a_CheckCondition (uint8_t cond) + { + if (cond == 0xE) + return true; + + switch (cond) + { + case 0x0 : // EQ + if (!FLAG_Z) + return false; + break; + case 0x1 : // NE + if (FLAG_Z) + return false; + break; + case 0x2 : // CS + if (!FLAG_C) + return false; + break; + case 0x3 : // CC + if (FLAG_C) + return false; + break; + case 0x4 : // MI + if (!FLAG_N) + return false; + break; + case 0x5 : // PL + if (FLAG_N) + return false; + break; + case 0x6 : // VS + if (!FLAG_V) + return false; + break; + case 0x7 : // VC + if (FLAG_V) + return false; + break; + case 0x8 : // HI + if (!FLAG_C || FLAG_Z) + return false; + break; + case 0x9 : // LS + if (FLAG_C && !FLAG_Z) + return false; + break; + case 0xA : // GE + if (FLAG_N != FLAG_V) + return false; + break; + case 0xB : // LT + if (FLAG_N == FLAG_V) + return false; + break; + case 0xC : // GT + if (FLAG_Z || FLAG_N != FLAG_V) + return false; + break; + case 0xD : // LE + if (!FLAG_Z && FLAG_N == FLAG_V) + return false; + break; + case 0xE : // AL + break; + case 0xF : // reserved + break; + } + + return true; + } + + NIARM(_Code) + { + if (!a_CheckCondition(code >> 28)) // condition failed + CYCLES32Seq(R(15), 1); + else + switch ((code >> 25) & 0x7) + { + case 0x0: + switch ((code >> 18) & 0x60 | (code >> 16) & 0x10 | + (code >> 4) & 0x0F) + { + case 0x40: + aPSR(); + break; + case 0x00: + case 0x02: + case 0x04: + case 0x06: + case 0x08: + case 0x0A: + case 0x0C: + case 0x0E: + case 0x10: + case 0x12: + case 0x14: + case 0x16: + case 0x18: + case 0x1A: + case 0x1C: + case 0x1E: + case 0x20: + case 0x22: + case 0x24: + case 0x26: + case 0x28: + case 0x2A: + case 0x2C: + case 0x2E: + case 0x30: + case 0x32: + case 0x34: + case 0x36: + case 0x38: + case 0x3A: + case 0x3C: + case 0x3E: + case 0x50: + case 0x52: + case 0x54: + case 0x56: + case 0x58: + case 0x5A: + case 0x5C: + case 0x5E: + case 0x60: + case 0x62: + case 0x64: + case 0x66: + case 0x68: + case 0x6A: + case 0x6C: + case 0x6E: + case 0x70: + case 0x72: + case 0x74: + case 0x76: + case 0x78: + case 0x7A: + case 0x7C: + case 0x7E: + a_DataProcShiftImm(); + break; + case 0x01: + case 0x03: + case 0x05: + case 0x07: + case 0x11: + case 0x13: + case 0x15: + case 0x17: + case 0x21: + case 0x23: + case 0x25: + case 0x27: + case 0x31: + case 0x33: + case 0x35: + case 0x37: + case 0x51: + case 0x53: + case 0x55: + case 0x57: + case 0x61: + case 0x63: + case 0x65: + case 0x67: + case 0x71: + case 0x73: + case 0x75: + case 0x77: + a_DataProcShiftReg(); + break; + case 0x09: + case 0x19: + case 0x29: + case 0x39: + case 0x48: + case 0x4A: + case 0x4C: + case 0x4E: + a_Multiply(); + break; + case 0x0B: + case 0x0D: + case 0x0F: + case 0x1B: + case 0x1D: + case 0x1F: + case 0x2B: + case 0x2D: + case 0x2F: + case 0x3B: + case 0x3D: + case 0x3F: + case 0x4B: + case 0x4D: + case 0x4F: + case 0x5B: + case 0x5D: + case 0x5F: + case 0x6B: + case 0x6D: + case 0x6F: + case 0x7B: + case 0x7D: + case 0x7F: + aSTRLDR_HD(); + break; + case 0x49: + aSWP(); + break; + case 0x41: + case 0x43: + aBXBLX(); + break; + default: + met_abort("unknown"); + break; + } + break; + case 0x1: + // TODO PSR + a_DataProcImm(); + break; + case 0x2: + case 0x3: + aLDRSTR(); + break; + case 0x4: + aLDMSTM(); + break; + case 0x5: + aBBL(); + break; + case 0x7: + if (code & (0x1 << 24)) + aSWI(); + else + met_abort("unknown"); + break; + default: + { std::cerr << IOS_ADD << R(15)-8 << " : " << IOS_ADD << code << " : "; debug_bits(code); met_abort("not implemented"); } + break; + } + } +} + +#undef Rn +#undef Rd +#undef Rs +#undef Rm + +#undef LSOff + +#undef NOT_PC +#undef NOT_PC_ALL +#undef NOT_SAME2 +#undef NOT_SAME3 + +#undef ARM + +#endif diff --git a/libmeteor/source/interpreter_thumb.cpp b/libmeteor/source/interpreter_thumb.cpp new file mode 100644 index 0000000000..0beb374086 --- /dev/null +++ b/libmeteor/source/interpreter_thumb.cpp @@ -0,0 +1,1222 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#ifndef __INTERPRETER_THUMB_H__ +#define __INTERPRETER_THUMB_H__ + +/* + +- From GBATEK - + +THUMB Binary Opcode Format + +Fo|_15|_14|_13|_12|_11|_10|_9_|_8_|_7_|_6_|_5_|_4_|_3_|_2_|_1_|_0_| +_1|_0___0___0_|__Op___|_______Offset______|____Rs_____|____Rd_____|Shifted +_2|_0___0___0___1___1_|_I,_Op_|___Rn/nn___|____Rs_____|____Rd_____|ADD/SUB +_3|_0___0___1_|__Op___|____Rd_____|_____________Offset____________|Immedi. +_4|_0___1___0___0___0___0_|______Op_______|____Rs_____|____Rd_____|AluOp +_5|_0___1___0___0___0___1_|__Op___|Hd_|Hs_|____Rs_____|____Rd_____|HiReg/BX +_6|_0___1___0___0___1_|____Rd_____|_____________Word______________|LDR PC +_7|_0___1___0___1_|__Op___|_0_|___Ro______|____Rb_____|____Rd_____|LDR/STR +_8|_0___1___0___1_|__Op___|_1_|___Ro______|____Rb_____|____Rd_____|""H/SB/SH +_9|_0___1___1_|__Op___|_______Offset______|____Rb_____|____Rd_____|""{B} +10|_1___0___0___0_|Op_|_______Offset______|____Rb_____|____Rd_____|""H +11|_1___0___0___1_|Op_|____Rd_____|_____________Word______________|"" SP +12|_1___0___1___0_|Op_|____Rd_____|_____________Word______________|ADD PC/SP +13|_1___0___1___1___0___0___0___0_|_S_|___________Word____________|ADD SP,nn +14|_1___0___1___1_|Op_|_1___0_|_R_|____________Rlist______________|PUSH/POP +17|_1___0___1___1___1___1___1___0_|___________User_Data___________|BKPT ARM9 +15|_1___1___0___0_|Op_|____Rb_____|____________Rlist______________|STM/LDM +16|_1___1___0___1_|_____Cond______|_________Signed_Offset_________|B{cond} +U_|_1___1___0___1___1___1___1___0_|_____________var_______________|UNDEF ARM9 +17|_1___1___0___1___1___1___1___1_|___________User_Data___________|SWI +18|_1___1___1___0___0_|________________Offset_____________________|B +19|_1___1___1___0___1_|_________________________var___________|_0_|BLXsuf ARM9 +U_|_1___1___1___0___1_|_________________________var___________|_1_|UNDEF ARM9 +19|_1___1___1___1_|_H_|______________Offset_Low/High______________|BL (BLX ARM9) + +*/ + +#include "ameteor/interpreter.hpp" +#include "globals.hpp" +#include "cpu_globals.hpp" +#include "ameteor/memory.hpp" +#include "ameteor.hpp" + +#include "debug.hpp" + +#define Rb ((code >> 8) & 0x7) +#define Ro ((code >> 6) & 0x7) +#define Rs ((code >> 3) & 0x7) +#define Rd ((code ) & 0x7) +#define Imm (code & 0xFF) +#define Off ((code >> 6) & 0x1F) + +//#define HiRs (((code & (0x1 << 6)) >> 3) | Rs) +#define HiRs ((code >> 3) & 0xF) +#define HiRd (((code & (0x1 << 7)) >> 4) | Rd) + +#ifdef METDEBUG +# define NOT_PC(reg) \ + if (reg == 15) \ + met_abort("Register is PC") +#else +# define NOT_PC(reg) {} +#endif + +#define THUMB(name) \ + inline void Interpreter::t##name () +#define NITHUMB(name) \ + void Interpreter::t##name () + +namespace AMeteor +{ + // move shifted register + THUMB(_Shift) + { + if ((code >> 13) & 0x7) + { + met_abort("Bits 13-15 must be 000 for shift instructions"); + } + + uint8_t off = Off; + switch ((code >> 11) & 0x3) + { + case 0x0: // LSL + if (off) + { + SETF(C, R(Rs) & (0x1 << (32-off))); + R(Rd) = R(Rs) << off; + FZ(R(Rd)); + FN(R(Rd)); + } + else + { + R(Rd) = R(Rs); + FZ(R(Rd)); + FN(R(Rd)); + } + break; + case 0x1: // LSR + if (off) + { + SETF(C, R(Rs) & (0x1 << (off-1))); + R(Rd) = R(Rs) >> off; + FZ(R(Rd)); + FN(R(Rd)); + } + else + { + SETF(C, R(Rs) & (0x1 << 31)); + R(Rd) = 0; + FLAG_Z = 1; + FLAG_N = 0; + } + break; + case 0x2: // ASR + if (off) + { + SETF(C, (((int32_t)R(Rs)) >> (off - 1)) & 0x1); + R(Rd) = ((int32_t)R(Rs)) >> off; + FZ(R(Rd)); + FN(R(Rd)); + } + else + { + if (R(Rd) & (0x1 << 31)) + { + R(Rd) = 0xFFFFFFFF; + FLAG_Z = 0; + FLAG_N = 1; + FLAG_C = 1; + } + else + { + R(Rd) = 0; + FLAG_Z = 1; + FLAG_N = 0; + FLAG_C = 0; + } + } + break; + case 0x3: // reserved + met_abort("Bits 11-12 must not be 11 for shift instructions"); + break; + } + + CYCLES16Seq(R(15), 1); + } + + // add/substract + THUMB(ADDSUB) + { + if (((code >> 11) & 0x1F) != 0x3) + { + met_abort("Bits 11-15 should be 00011 for add/substract"); + } + + uint32_t op2; + if ((code >> 10) & 0x1) // imm + op2 = Ro; + else // reg + op2 = R(Ro); + + if ((code >> 9) & 0x1) // SUB + { +#ifdef X86_ASM + asm("subl %6, %5\n" + "setzb %1\n" + "setsb %2\n" + "setncb %3\n" + "setob %4\n" + :"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"0"(R(Rs)), "r"(op2)); +#else + uint32_t op1 = R(Rs), res = R(Rd) = op1 - op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = SUBCARRY(op1, op2, res); + FLAG_V = SUBOVERFLOW(op1, op2, res); +#endif + } + else // ADD + { +#ifdef X86_ASM + asm("addl %6, %5\n" + "setzb %1\n" + "setsb %2\n" + "setcb %3\n" + "setob %4\n" + :"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"0"(R(Rs)), "r"(op2)); +#else + uint32_t op1 = R(Rs), res = R(Rd) = op1 + op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = ADDCARRY(op1, op2, res); + FLAG_V = ADDOVERFLOW(op1, op2, res); +#endif + } + + CYCLES16Seq(R(15), 1); + } + + // move/compare/add/substact immediate + THUMB(_Imm) + { + if (((code >> 13) & 0x7) != 0x1) + { + met_abort("Bits 13-15 must be 001 for immediate instructions"); + } + + switch ((code >> 11) & 0x3) + { + case 0x0: // MOV + R(Rb) = Imm; + FLAG_Z = !Imm; + FLAG_N = 0; + break; + case 0x1: // CMP +#ifdef X86_ASM + asm("cmpl %5, %4\n" + "setzb %0\n" + "setsb %1\n" + "setncb %2\n" + "setob %3\n" + :"=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"r"(R(Rb)), "r"(Imm)); +#else + { + uint32_t op1 = R(Rb), op2 = Imm, res = op1 - op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = SUBCARRY(op1, op2, res); + FLAG_V = SUBOVERFLOW(op1, op2, res); + } +#endif + break; + case 0x2: // ADD +#ifdef X86_ASM + asm("addl %6, %5\n" + "setzb %1\n" + "setsb %2\n" + "setcb %3\n" + "setob %4\n" + :"=r"(R(Rb)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"0"(R(Rb)), "r"(Imm)); +#else + { + uint32_t op1 = R(Rb), op2 = Imm, res = R(Rb) = op1 + op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = ADDCARRY(op1, op2, res); + FLAG_V = ADDOVERFLOW(op1, op2, res); + } +#endif + break; + case 0x3: // SUB +#ifdef X86_ASM + asm("subl %6, %5\n" + "setzb %1\n" + "setsb %2\n" + "setncb %3\n" + "setob %4\n" + :"=r"(R(Rb)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"0"(R(Rb)), "r"(Imm)); +#else + { + uint32_t op1 = R(Rb), op2 = Imm, res = R(Rb) = op1 - op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = SUBCARRY(op1, op2, res); + FLAG_V = SUBOVERFLOW(op1, op2, res); + } +#endif + break; + } + + CYCLES16Seq(R(15), 1); + } + + // ALU operations + THUMB(_ALU) + { + if (((code >> 10) & 0x3F) != 0x10) + { + met_abort("Bits 10-15 must be 010000 for ALU instructions"); + } + + uint8_t opcode = (code >> 6) & 0xF; + // TODO put a ifndef X86_ASM here around this declaration + uint32_t res = 0; // to avoid a warning + uint8_t shift; + + switch (opcode) + { + case 0x0: // AND +#ifdef X86_ASM + asm("andl %4, %3\n" + "setzb %1\n" + "setsb %2\n" + :"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N) + :"0"(R(Rd)), "r"(R(Rs))); +#else + res = R(Rd) &= R(Rs); + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + break; + case 0x1: // EOR +#ifdef X86_ASM + asm("xorl %4, %3\n" + "setzb %1\n" + "setsb %2\n" + :"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N) + :"0"(R(Rd)), "r"(R(Rs))); +#else + res = R(Rd) ^= R(Rs); + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + break; + case 0x2: // LSL + ICYCLES(1); + shift = R(Rs) & 0xFF; + if (shift) + { + if (shift == 32) + { + SETFB(C, R(Rd) & 0x1); + R(Rd) = 0; + FLAG_Z = FLAG_N = 0; + } + else if (shift < 32) + { + SETFB(C, (R(Rd) >> (32-shift)) & 0x1); + res = R(Rd) <<= shift; + FLAG_Z = !res; + FLAG_N = res >> 31; + } + else + { + R(Rd) = 0; + FLAG_C = FLAG_Z = FLAG_N = 0; + } + } + else + { + res = R(Rd); + FLAG_Z = !res; + FLAG_N = res >> 31; + } + break; + case 0x3: // LSR + ICYCLES(1); + shift = R(Rs) & 0xFF; + if (shift) + { + if (shift == 32) + { + SETFB(C, R(Rd) >> 31); + R(Rd) = 0; + FLAG_Z = FLAG_N = 0; + } + else if (shift < 32) + { + SETFB(C, (R(Rd) >> (shift-1)) & 0x1); + res = R(Rd) >>= shift; + FLAG_Z = !res; + FLAG_N = res >> 31; + } + else + { + R(Rd) = 0; + FLAG_C = FLAG_Z = FLAG_N = 0; + } + } + else + { + res = R(Rd); + FLAG_Z = !res; + FLAG_N = res >> 31; + } + break; + case 0x4: // ASR + ICYCLES(1); + shift = R(Rs) & 0xFF; + if (shift) + { + if (shift >= 32) + { + R(Rd) = ((int32_t)R(Rs)) >> 31; + FLAG_C = FLAG_Z = FLAG_N = R(Rd) & 0x1; + } + else + { + SETFB(C, (((int32_t)R(Rd)) >> (shift-1)) & 0x1); + res = R(Rd) = ((int32_t)R(Rd)) >> shift; + FLAG_Z = !res; + FLAG_N = res >> 31; + } + } + else + { + res = R(Rd); + FLAG_Z = !res; + FLAG_N = res >> 31; + } + break; + case 0x5: // ADC + res = R(Rd) + R(Rs) + FLAG_C; + FLAG_C = ADDCARRY(R(Rd), R(Rs), res); + FLAG_V = ADDOVERFLOW(R(Rd), R(Rs), res); + R(Rd) = res; + break; + case 0x6: // SBC + res = R(Rd) - R(Rs) + FLAG_C - 1; + FLAG_C = SUBCARRY(R(Rd), R(Rs), res); + FLAG_V = SUBOVERFLOW(R(Rd), R(Rs), res); + R(Rd) = res; + break; + case 0x7: // ROR + ICYCLES(1); + shift = R(Rs) & 0xFF; + if (shift) + { + shift %= 32; + res = R(Rd); + SETFB(C, (res >> (shift - 1)) & 0x1); + res = R(Rd) = ROR(res, shift); + FLAG_Z = !res; + FLAG_N = res >> 31; + } + else + { + res = R(Rd); + FLAG_Z = !res; + FLAG_N = res >> 31; + } + break; + case 0x8: // TST +#ifdef X86_ASM + asm("testl %3, %2\n" + "setzb %0\n" + "setsb %1\n" + :"=m"(FLAG_Z), "=m"(FLAG_N) + :"r"(R(Rd)), "r"(R(Rs))); +#else + res = R(Rd) & R(Rs); + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + break; + case 0x9: // NEG +#ifdef X86_ASM + asm("negl %5\n" + "setzb %1\n" + "setsb %2\n" + "setncb %3\n" + "setob %4\n" + :"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"0"(R(Rs))); +#else + { + uint32_t op2; + op2 = R(Rs); + res = R(Rd) = -op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + // we overflow only if op2 == INT_MIN + FLAG_V = SUBOVERFLOW(0, op2, res); + // we have a carry only if op2 == 0 + FLAG_C = SUBCARRY(0, op2, res); + } +#endif + break; + case 0xA: // CMP +#ifdef X86_ASM + asm("cmpl %5, %4\n" + "setzb %0\n" + "setsb %1\n" + "setncb %2\n" + "setob %3\n" + :"=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"r"(R(Rd)), "r"(R(Rs))); +#else + { + uint32_t op1, op2; + op1 = R(Rd); + op2 = R(Rs); + res = op1 - op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = SUBCARRY(op1, op2, res); + FLAG_V = SUBOVERFLOW(op1, op2, res); + } +#endif + break; + case 0xB: // CMN +#ifdef X86_ASM + asm("addl %5, %4\n" + "setzb %0\n" + "setsb %1\n" + "setcb %2\n" + "setob %3\n" + :"=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"r"(R(Rd)), "r"(R(Rs)) + :"4"); +#else + { + uint32_t op1, op2; + op1 = R(Rd); + op2 = R(Rs); + res = op1 + op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = ADDCARRY(op1, op2, res); + FLAG_V = ADDOVERFLOW(op1, op2, res); + } +#endif + break; + case 0xC: // ORR +#ifdef X86_ASM + asm("orl %4, %3\n" + "setzb %1\n" + "setsb %2\n" + :"=r"(R(Rd)), "=m"(FLAG_Z), "=m"(FLAG_N) + :"0"(R(Rd)), "r"(R(Rs))); +#else + res = R(Rd) |= R(Rs); + FLAG_Z = !res; + FLAG_N = res >> 31; +#endif + break; + case 0xD: // MUL + MULICYCLES(Rs); + res = R(Rd) *= R(Rs); + FLAG_Z = !res; + FLAG_N = res >> 31; + break; + case 0xE: // BIC + res = R(Rd) &= ~R(Rs); + FLAG_Z = !res; + FLAG_N = res >> 31; + break; + case 0xF: // MVN + res = R(Rd) = ~R(Rs); + FLAG_Z = !res; + FLAG_N = res >> 31; + break; + } + + CYCLES16Seq(R(15), 1); + } + + // Hi register operations/branch exchange + THUMB(_HiRegOp) + { + if (((code >> 10) & 0x3F) != 0x11) + { + met_abort("Bits 10-15 must be 010001 for HiReg instructions"); + } + + uint8_t rd = HiRd, rs = HiRs; + switch ((code >> 8) & 0x3) + { + case 0x0: // ADD + R(rd) += R(rs); + if (rd == 15) + { + R(15) &= 0xFFFFFFFE; + CYCLES16NSeq(R(15), 3); + R(15) += 2; + } + else + CYCLES16Seq(R(15), 1); + break; + case 0x1: // CMP +#ifdef X86_ASM + asm("cmpl %5, %4\n" + "setzb %0\n" + "setsb %1\n" + "setncb %2\n" + "setob %3\n" + :"=m"(FLAG_Z), "=m"(FLAG_N), "=m"(FLAG_C), "=m"(FLAG_V) + :"r"(R(rd)), "r"(R(rs))); +#else + { + uint32_t op1 = R(rd), op2 = R(rs), res = op1 - op2; + FLAG_Z = !res; + FLAG_N = res >> 31; + FLAG_C = SUBCARRY(op1, op2, res); + FLAG_V = SUBOVERFLOW(op1, op2, res); + } +#endif + CYCLES16Seq(R(15), 1); + break; + case 0x2: + if (rd != 8 || rs != 8) // MOV + { + R(rd) = R(rs); + if (rd == 15) + { + R(15) &= 0xFFFFFFFE; + CYCLES16NSeq(R(15), 3); + R(15) += 2; + } + else + CYCLES16Seq(R(15), 1); + } + else // NOP + CYCLES16Seq(R(15), 1); + break; + case 0x3: + if (Rd) + met_abort("Rd must be 0 for BX/BLX instructions"); + if (code & (0x1 << 7)) // BLX + { + NOT_PC(rs); + met_abort("BLX not implemented"); + } + else // BX + { + if (R(rs) & 0x1) + { + R(15) = (R(rs) & 0xFFFFFFFE) + 2; + CYCLES16NSeq(R(15), 3); + } + else + { + // switch to arm + FLAG_T = 0; + R(15) = (R(rs) & 0xFFFFFFFC) + 4; + CYCLES32NSeq(R(15), 3); + } + } + break; + } + } + + // load PC-relative + THUMB(LDRimm) + { + if (((code >> 11) & 0x1F) != 0x9) + { + met_abort("Bits 11-15 must be 01001 for LDR with imm instructions"); + } + + uint32_t add = (R(15) & 0xFFFFFFFC) + (Imm << 2); + R(Rb) = MEM.Read32(add); + + CYCLES32NSeq(add, 1); + ICYCLES(1); + CYCLES16Seq(R(15), 1); + } + + // load/store with register offset + // and load/store sign-extended byte/halfword + THUMB(STRLDRreg) + { + if (((code >> 12) & 0xF) != 0x5) + { + met_abort("Bits 12-15 must be 0101 for LDR/STR with reg instructions"); + } + + uint32_t add = R(Ro) + R(Rs); + switch ((code >> 9) & 0x7) + { + // load/store with register offset + case 0x0: // STR + MEM.Write32(add, R(Rd)); + CYCLES32NSeq(add, 1); + CYCLES16NSeq(R(15), 1); + break; + case 0x2: // STRB + MEM.Write8(add, R(Rd)); + CYCLES16NSeq(add, 1); + CYCLES16NSeq(R(15), 1); + break; + case 0x4: // LDR + R(Rd) = MEM.Read32(add); + CYCLES32NSeq(add, 1); + ICYCLES(1); + CYCLES16Seq(R(15), 1); + break; + case 0x6: // LDRB + R(Rd) = MEM.Read8(add); + CYCLES16NSeq(add, 1); + ICYCLES(1); + CYCLES16Seq(R(15), 1); + break; + + // load/store sign-extended byte/halfword + case 0x1: // STRH + MEM.Write16(add, R(Rd)); + CYCLES16NSeq(add, 1); + CYCLES16NSeq(R(15), 1); + break; + case 0x3: // LDSB + R(Rd) = (int8_t)MEM.Read8(add); + CYCLES16NSeq(add, 1); + ICYCLES(1); + CYCLES16Seq(R(15), 1); + break; + case 0x5: // LDRH + R(Rd) = MEM.Read16(add); + CYCLES16NSeq(add, 1); + ICYCLES(1); + CYCLES16Seq(R(15), 1); + break; + case 0x7: // LDSH + R(Rd) = (int16_t)MEM.Read16(add); + CYCLES16NSeq(add, 1); + ICYCLES(1); + CYCLES16Seq(R(15), 1); + break; + } + } + + // load/store with immediate offset + THUMB(STRLDRoff) + { + if (((code >> 13) & 0x7) != 0x3) + { + met_abort("Bits 13-15 must be 011 for LDR/STR with off instructions"); + } + + uint32_t add; + switch ((code >> 11) & 0x3) + { + case 0x0: // STR + add = R(Rs) + (Off << 2); + MEM.Write32(add, R(Rd)); + CYCLES32NSeq(add, 1); + CYCLES16NSeq(R(15), 1); + break; + case 0x1: // LDR + add = R(Rs) + (Off << 2); + R(Rd) = MEM.Read32(add); + CYCLES32NSeq(add, 1); + ICYCLES(1); + CYCLES16Seq(R(15), 1); + break; + case 0x2: // STRB + add = R(Rs) + Off; + MEM.Write8(add, R(Rd)); + CYCLES16NSeq(add, 1); + CYCLES16NSeq(R(15), 1); + break; + case 0x3: // LDRB + add = R(Rs) + Off; + R(Rd) = MEM.Read8(add); + CYCLES16NSeq(add, 1); + ICYCLES(1); + CYCLES16Seq(R(15), 1); + break; + } + } + + // load/store halfword + THUMB(LDRHSTRHoff) + { + if (((code >> 12) & 0xF) != 0x8) + { + met_abort("Bits 12-15 must be 1000 for LDRH/STRH with off instructions"); + } + + uint32_t add = R(Rs) + (Off * 2); + if (code & (0x1 << 11)) // LDRH + { + R(Rd) = MEM.Read16(add); + CYCLES16NSeq(add, 1); + ICYCLES(1); + CYCLES16Seq(R(15), 1); + } + else // STRH + { + MEM.Write16(add, R(Rd)); + CYCLES16NSeq(add, 1); + CYCLES16NSeq(R(15), 1); + } + } + + // load/store SP-relative + THUMB(STRLDRsp) + { + if (((code >> 12) & 0xF) != 0x9) + { + met_abort("Bits 12-15 must be 1001 for LDR/STR SP-relative instructions"); + } + + uint32_t add = R(13) + (Imm << 2); + if (code & (0x1 << 11)) // LDR + { + R(Rb) = MEM.Read32(add); + CYCLES32NSeq(add, 1); + ICYCLES(1); + CYCLES16Seq(R(15), 1); + } + else // STR + { + MEM.Write32(add, R(Rb)); + CYCLES32NSeq(add, 1); + CYCLES16NSeq(R(15), 1); + } + } + + // get relative address + THUMB(ADDpcsp) + { + if (((code >> 12) & 0xF) != 0xA) + { + met_abort("Bits 12-15 must be 1010 for ADD relative instructions"); + } + + if (code & (0x1 << 11)) // with SP + { + R(Rb) = R(13) + (Imm << 2); + } + else // with PC + { + R(Rb) = (R(15) & 0xFFFFFFFC) + (Imm << 2); + } + + CYCLES16Seq(R(15), 1); + } + + // add offset to stack pointer + THUMB(ADDsp) + { + if (((code >> 8) & 0xFF) != 0xB0) + { + met_abort("Bits 8-15 must be 10110000 for ADD to SP instructions"); + } + + if (code & (0x1 << 7)) // substract + { + R(13) -= ((code & 0x7F) << 2); + } + else // add + { + R(13) += ((code & 0x7F) << 2); + } + + CYCLES16Seq(R(15), 1); + } + + // push/pop registers + THUMB(PUSHPOP) + { + if (((code >> 12) & 0xF) != 0xB) + { + met_abort("Bits 12-15 must be 1011 for PUSH/POP instructions"); + } + if (((code >> 9) & 0x3) != 0x2) + { + met_abort("Bits 9-10 must be 10 for PUSH/POP instructions"); + } + + static const uint8_t NumBits[] = + {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + + uint8_t numregs = + NumBits[(code >> 4) & 0xF] + + NumBits[ code & 0xF]; + if (code & (0x1 << 8)) + ++numregs; + + uint32_t add, newsp; + if (code & (0x1 << 11)) // POP + { + newsp = R(13) + 4 * numregs; + add = R(13) & 0xFFFFFFFC; + + CYCLES32NSeq(add, numregs); + ICYCLES(1); + + for (register uint8_t n = 0; n < 8; ++n) + if (code & (0x1 << n)) + { + R(n) = MEM.Read32 (add); + add += 4; + } + if (code & (0x1 << 8)) // POP PC + { + R(15) = (MEM.Read32(add) + 2) & 0xFFFFFFFE; + CYCLES16NSeq(R(15), 3); + } + else + CYCLES16Seq(R(15), 1); + } + else // PUSH + { + newsp = R(13) - 4 * numregs; + add = newsp & 0xFFFFFFFC; + + CYCLES32NSeq(add, numregs); + CYCLES16NSeq(R(15), 1); + + for (register uint8_t n = 0; n < 8; ++n) + if (code & (0x1 << n)) + { + MEM.Write32 (add, R(n)); + add += 4; + } + if (code & (0x1 << 8)) // PUSH LR + { + MEM.Write32 (add, R(14)); + } + } + + R(13) = newsp; + } + + // multiple load/store + THUMB(STMLDM) + { + if (((code >> 12) & 0xF) != 0xC) + { + met_abort("Bits 12-15 must be 1100 for STM/LDM instructions"); + } + + static const uint8_t NumBits[] = + {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4}; + + uint8_t numregs = + NumBits[(code >> 4) & 0xF] + + NumBits[ code & 0xF]; + + uint32_t add = R(Rb) & 0xFFFFFFFC; + + if (code & (0x1 << 11)) // LDMIA + { + CYCLES32NSeq(add, numregs); + ICYCLES(1); + + for (register uint8_t n = 0; n < 8; ++n) + if (code & (0x1 << n)) + { + R(n) = MEM.Read32 (add); + add += 4; + } + + CYCLES16Seq(R(15), 1); + } + else // STMIA + { + CYCLES32NSeq(add, numregs); + CYCLES16NSeq(R(15), 1); + + for (register uint8_t n = 0; n < 8; ++n) + if (code & (0x1 << n)) + { + MEM.Write32 (add, R(n)); + add += 4; + } + } + + if (!(code & (0x1 << Rb))) + R(Rb) = R(Rb) + 4 * numregs; + } + + // conditional branch + THUMB(_CondBranch) + { + if (((code >> 12) & 0xF) != 0xD) + { + met_abort("Bits 12-15 must be 1101 for branch on condition instructions"); + } + + bool branch = false; + switch ((code >> 8) & 0xF) + { + case 0x0: // BEQ + if (FLAG_Z) + branch = true; + break; + case 0x1: // BNE + if (!FLAG_Z) + branch = true; + break; + case 0x2: // BCS + if (FLAG_C) + branch = true; + break; + case 0x3: // BCC + if (!FLAG_C) + branch = true; + break; + case 0x4: // BMI + if (FLAG_N) + branch = true; + break; + case 0x5: // BPL + if (!FLAG_N) + branch = true; + break; + case 0x6: // BVS + if (FLAG_V) + branch = true; + break; + case 0x7: // BVC + if (!FLAG_V) + branch = true; + break; + case 0x8: // BHI + if (FLAG_C && !FLAG_Z) + branch = true; + break; + case 0x9: // BLS + if (!FLAG_C || FLAG_Z) + branch = true; + break; + case 0xA: // BGE + if (FLAG_N == FLAG_V) + branch = true; + break; + case 0xB: // BLT + if (FLAG_N != FLAG_V) + branch = true; + break; + case 0xC: // BGT + if (!FLAG_Z && FLAG_N == FLAG_V) + branch = true; + break; + case 0xD: // BLE + if (FLAG_Z || FLAG_N != FLAG_V) + branch = true; + break; + case 0xE: // undefined + met_abort("Undefined branch on condition"); + break; + case 0xF: // reserved for SWI instruction + met_abort("Bits 8-11 must not be 1111 for branch instruction"); + break; + } + + if (branch) + { + R(15) += (((int32_t)(int8_t)Imm) << 1) + 2; + + CYCLES16NSeq(R(15), 3); + } + else + CYCLES16Seq(R(15), 1); + } + + // software interrupt and breakpoint + THUMB(SWI) + { + CPU.SoftwareInterrupt(code & 0xFF); + + // FIXME seems wrong ! + CYCLES32NSeq(0, 3); + } + + // unconditional branch + THUMB(B) + { + if (((code >> 11) & 0x1F) != 0x1C) + { + met_abort("Bits 11-15 must be 11100 for branch instructions"); + } + + int32_t off = (code & 0x7FF) << 1; + if (off & 0x800) + off |= 0xFFFFF000; // extends sign bit + R(15) += off + 2; + + CYCLES16NSeq(R(15), 3); + } + + // long branch with link + THUMB(_BL1) + { + if (((code >> 11) & 0x1F) != 0x1E) + { + met_abort("Bits 11-15 must be 11110 for long branch instructions"); + } + + if (code & (0x1 << 10)) // negative offset + R(14) = R(15) + (((int32_t)((code & 0x7FF) << 12)) | 0xFF800000); + else + R(14) = R(15) + ((code & 0x7FF) << 12); + + CYCLES16Seq(R(15), 1); + } + + THUMB(_BL2) + { + // XXX we dont do check here to be sure this instruction is called after + // BL1 + uint32_t add = R(14) + ((code & 0x7FF) << 1); + + if (code & (0x1 << 11)) // BL + { + R(14) = (R(15)-2) | 0x1; + R(15) = (add & 0xFFFFFFFE) + 2; + } + else // BLX + { + met_abort("BLX not implemented"); + if (code & 0x1) + met_abort("BLX with odd address"); + FLAG_T = 0; + } + + CYCLES16NSeq(R(15), 3); + } + + NITHUMB(_Code) + { + switch (code >> 13) + { + case 0: // 000 + if ((code & 0x1800) == 0x1800) // 00011 + tADDSUB(); + else // 000 + t_Shift(); + break; + case 1: // 001 + t_Imm(); + break; + case 2: // 010 + switch ((code >> 10) & 0x7) + { + case 0: // 010000 + t_ALU(); + break; + case 1: // 010001 + t_HiRegOp(); + break; + case 2: + case 3: // 01001x + tLDRimm(); + break; + default: + tSTRLDRreg(); + break; + } + break; + case 3: // 011 + tSTRLDRoff(); + break; + case 4: // 100 + if (code & (0x1 << 12)) // 1001 + tSTRLDRsp(); + else // 100 + tLDRHSTRHoff(); + break; + case 5: // 101 + if (code & (0x1 << 12)) // 1011 + { + switch (code & 0x0600) + { + case 0x0000: // 1011x00 + tADDsp(); + break; + case 0x0400: // 1011x10 + tPUSHPOP(); + break; + default: + met_abort("not implemented or unknown"); + break; + } + } + else // 101 + tADDpcsp(); + break; + case 6: // 110 + if (code & (0x1 << 12)) // 1101 + { + if ((code & 0x0F00) == 0x0F00) // 11011111 + tSWI(); + else // 1101xxxx + t_CondBranch(); + } + else // 1100 + tSTMLDM(); + break; + case 7: // 111 + switch ((code >> 11) & 0x3) + { + case 0: // 11100 + tB(); + break; + case 2: // 11110 + t_BL1(); + break; + case 3: // 11111 + t_BL2(); + break; + default: + met_abort("not implemented or unknown"); + break; + } + break; + } + } +} + +#undef Rb +#undef Ro +#undef Rs +#undef Rd +#undef Imm + +#undef THUMB + +#endif diff --git a/libmeteor/source/io.cpp b/libmeteor/source/io.cpp new file mode 100644 index 0000000000..c0cd244ab1 --- /dev/null +++ b/libmeteor/source/io.cpp @@ -0,0 +1,493 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/io.hpp" +#include "ameteor/dma.hpp" +#include "globals.hpp" +#include "ameteor.hpp" +#include + +#include "debug.hpp" + +#define W8(add, val) \ + m_iomem[(add) & 0xFFF] = (val) +#define W16(add, val) \ + *(uint16_t*)(m_iomem + ((add) & 0xFFF)) = (val) +#define W32(add, val) \ + *(uint32_t*)(m_iomem + ((add) & 0xFFF)) = (val) + +namespace AMeteor +{ + Io::Io () + { + m_iomem = new uint8_t[IO_SIZE]; + Reset (); + } + + Io::~Io () + { + delete [] m_iomem; + } + + void Io::Reset () + { + std::memset(m_iomem, 0, IO_SIZE); + + // TODO use clears intead + + // TODO DISPCNT should be 0x80 by default + // TODO do it also for clears + // TODO lcd should draw white lines when hblank forced + // TODO when passing disabling hblank forced, lcd should start drawing from + // first line + W16(SOUNDBIAS, 0x0200); // default value + W16(KEYINPUT, 0x03FF); // all keys released + W8(HALTCNT, 0xFF); // normal mode (internal) + W16(DISPSTAT, 0x0004); // vcount match (since default vcount is 0) + W16(BG2PA, 0x0100); + W16(BG2PD, 0x0100); + W16(BG3PA, 0x0100); + W16(BG3PD, 0x0100); + W16(RCNT, 0x8000); // we start in general purpose mode + } + + void Io::ClearSio () + { + // TODO + W16(RCNT, 0x8000); + } + + void Io::ClearSound () + { + // TODO + } + + void Io::ClearOthers () + { + // FIXME !! shouldn't we call Write*() ? + // lcd + for (uint8_t i = 0x0; i < 0x56; i += 2) + Write16(i, 0x0000); + // dma + for (uint8_t i = 0xB0; i < 0xE0; i += 4) + Write32(i, 0x0000); + // FIXME : should timers be set to 0 too ? (vba does not) + W8(HALTCNT, 0xFF); // normal mode (internal) + W16(IE, 0x0000); + W16(IF, 0x0000); + W16(IME, 0x0000); + Write16(WAITCNT, 0x0000); + W16(BG2PA, 0x0100); + W16(BG2PD, 0x0100); + W16(BG3PA, 0x0100); + W16(BG3PD, 0x0100); + } + + // TODO implement unreadable or write-only io + uint8_t Io::Read8 (uint32_t add) + { + //debug ("IO Read8 at " << IOS_ADD << add << " of " << IOS_ADD << (int)*(uint8_t*)(m_iomem + (add & 0xFFF))); + if ((add & 0xFF0) == 0x100) + switch (add & 0xF) + { + case 0x0: + case 0x4: + case 0x8: + case 0xC: + met_abort("Misaligned reading of timers"); + } + return m_iomem[add & 0xFFF]; + } + + uint16_t Io::Read16 (uint32_t add) + { + //debug ("IO Read16 at " << IOS_ADD << add << " of " << IOS_ADD << *(uint16_t*)(m_iomem + (add & 0xFFF))); + // special case, reading timers + if ((add & 0xFF0) == 0x100) + switch (add & 0xF) + { + case 0x0: return TIMER0.GetCount(); + case 0x4: return TIMER1.GetCount(); + case 0x8: return TIMER2.GetCount(); + case 0xC: return TIMER3.GetCount(); + } + return *(uint16_t*)(m_iomem + (add & 0xFFF)); + } + + uint32_t Io::Read32 (uint32_t add) + { + //debug ("IO Read32 at " << IOS_ADD << add << " of " << IOS_ADD << *(uint32_t*)(m_iomem + (add & 0xFFF))); + // special case, reading timers + if ((add & 0xFF0) == 0x100) + switch (add & 0xF) + { + case 0x0: return TIMER0.GetCount(); + case 0x4: return TIMER1.GetCount(); + case 0x8: return TIMER2.GetCount(); + case 0xC: return TIMER3.GetCount(); + } + return *(uint32_t*)(m_iomem + (add & 0xFFF)); + } + + void Io::Write8 (uint32_t add, uint8_t val) + { + //debug ("IO Write8 at " << IOS_ADD << add << " of " << IOS_ADD << (int)val); + switch (add & 0xFFF) + { + case NR10+1: + case NR52+1: + case NR52+2: + case NR52+3: + break; + case NR10: + case NR11: + case NR13: + case NR21: + case NR23: + case NR41: + case NR43: + case NR50: + case NR51: + case SOUNDCNT_H: + W8(add, val); + break; + case NR12: + W8(add, val); + if (!(val & (0xF << 4))) + SOUND.ResetSound1Envelope(); + break; + case NR14: + W8(add, val & 0xC7); + if (val & (0x1 << 7)) + SOUND.ResetSound1(); + break; + case NR22: + W8(add, val); + if (!(val & (0xF << 4))) + SOUND.ResetSound2Envelope(); + break; + case NR24: + W8(add, val & 0xC7); + if (val & (0x1 << 7)) + SOUND.ResetSound2(); + break; + case NR42: + W8(add, val); + if (!(val & (0xF << 4))) + SOUND.ResetSound4Envelope(); + break; + case NR44: + W8(add, val & 0xC7); + if (val & (0x1 << 7)) + SOUND.ResetSound4(); + break; + case SOUNDCNT_H+1: + W8(add, val); + SOUND.UpdateCntH1(val); + break; + case NR52: + // this will also reset the sound on flags + W8(add, val & 0x80); + break; + case POSTFLG: + // FIXME is this right, i have no idea about why i should do that + if (val) + val &= 0xFE; + W8(add, val); + break; + case HALTCNT: + W8(add, val); + break; + default: + //W8(add, val); + // TODO make a function which will apply masks to IO memory and trigger + // the update functions, this function will be called by write8 and + // write16 +#if 1 + add &= 0xFFF; + if (add % 2) + Write16(add & ~0x1, (val << 8) | m_iomem[add & ~0x1]); + else + Write16(add, (m_iomem[add | 0x1] << 8) | val); +#endif + break; + } + } + + void Io::Write16 (uint32_t add, uint16_t val) + { + //debug ("IO Write16 at " << IOS_ADD << add << " of " << IOS_ADD << val); + //*(uint16_t*)(m_iomem + (add & 0xFFF)) = val; + + switch (add & 0xFFF) + { + case KEYINPUT: + case VCOUNT: + break; + case DMA0CNT_L: + case DMA1CNT_L: + case DMA2CNT_L: + case DMA3CNT_L: + //W16(add, val); + DMA.SetReload(((add & 0xFFF) - DMA0CNT_L) / DMA_CHANSIZE, val); + break; + case KEYCNT: + W16(add, val & 0xC3FF); + break; + case IME: + W16(add, val & 0x0001); + CPU.CheckInterrupt(); + break; + case IE: + W16(add, val & 0x3FFF); + CPU.CheckInterrupt(); + break; + case IF: + *((uint16_t*)(m_iomem+IF)) ^= (val & (*((uint16_t*)(m_iomem+IF)))); + CPU.CheckInterrupt(); + break; + case BG0CNT: + W16(add, val & 0xFFCF); + LCD.UpdateBg0Cnt(val & 0xFFCF); + break; + case BG1CNT: + W16(add, val & 0xFFCF); + LCD.UpdateBg1Cnt(val & 0xFFCF); + break; + case BG2CNT: + W16(add, val & 0xFFCF); + LCD.UpdateBg2Cnt(val & 0xFFCF); + break; + case BG3CNT: + W16(add, val & 0xFFCF); + LCD.UpdateBg3Cnt(val & 0xFFCF); + break; + case DISPSTAT: + // the first 3 bits are read only and they are used by the lcd + // NOTE : we are in LITTLE ENDIAN + // FIXME : if vcount setting has changed to current vcount, we should + // update the vcounter flag and eventually trigger an interrupt + W16(add, (val & 0xFFF8) | (m_iomem[add & 0xFFF] & 0x07)); + break; + // The BG*OFS are write-only, we don't need to W16() + case BG0HOFS: + LCD.UpdateBg0XOff(val & 0x1FF); + break; + case BG0VOFS: + LCD.UpdateBg0YOff(val & 0x1FF); + break; + case BG1HOFS: + LCD.UpdateBg1XOff(val & 0x1FF); + break; + case BG1VOFS: + LCD.UpdateBg1YOff(val & 0x1FF); + break; + case BG2HOFS: + LCD.UpdateBg2XOff(val & 0x1FF); + break; + case BG2VOFS: + LCD.UpdateBg2YOff(val & 0x1FF); + break; + case BG3HOFS: + LCD.UpdateBg3XOff(val & 0x1FF); + break; + case BG3VOFS: + LCD.UpdateBg3YOff(val & 0x1FF); + break; + case BG2X_H: + val &= 0x0FFF; + case BG2X_L: + W16(add, val); + LCD.UpdateBg2RefX(IO.DRead32(Io::BG2X_L)); + break; + case BG2Y_H: + val &= 0x0FFF; + case BG2Y_L: + W16(add, val); + LCD.UpdateBg2RefY(IO.DRead32(Io::BG2Y_L)); + break; + case BG3X_H: + val &= 0x0FFF; + case BG3X_L: + W16(add, val); + LCD.UpdateBg3RefX(IO.DRead32(Io::BG3X_L)); + break; + case BG3Y_H: + val &= 0x0FFF; + case BG3Y_L: + W16(add, val); + LCD.UpdateBg3RefY(IO.DRead32(Io::BG3Y_L)); + break; + case WIN0H: + case WIN1H: + case WIN0V: + case WIN1V: + case WININ: + case WINOUT: + W16(add, val); + break; + case BLDCNT: + W16(add, val); + break; + case MOSAIC: + W16(add, val); + break; + case DISPCNT: + W16(add, val); + LCD.UpdateDispCnt(val); + break; + case DMA0CNT_H: + case DMA1CNT_H: + case DMA2CNT_H: + case DMA3CNT_H: + W16(add, val & 0xFFE0); + DMA.UpdateCnt(((add & 0xFFF) - DMA0CNT_H) / DMA_CHANSIZE); + break; + case WAITCNT: + W16(add, val & 0xDFFF); + MEM.UpdateWaitStates (val & 0xDFFF); + break; + case SOUND1CNT_L: + case SOUND1CNT_H: + case SOUND1CNT_X: + case SOUND2CNT_L: + case SOUND2CNT_H: + case SOUND4CNT_L: + case SOUND4CNT_H: + case SOUNDCNT_L: + case SOUNDCNT_H: + case SOUNDCNT_X: + case POSTFLG: + Write8(add, val & 0xFF); + Write8(add + 1, val >> 8); + break; + case TM0CNT_L: + TIMER0.SetReload(val); + break; + case TM1CNT_L: + TIMER1.SetReload(val); + break; + case TM2CNT_L: + TIMER2.SetReload(val); + break; + case TM3CNT_L: + TIMER3.SetReload(val); + break; + case TM0CNT_H: + W16(add, val & 0x00C7); + TIMER0.Reload(); + break; + case TM1CNT_H: + W16(add, val & 0x00C7); + TIMER1.Reload(); + break; + case TM2CNT_H: + W16(add, val & 0x00C7); + TIMER2.Reload(); + break; + case TM3CNT_H: + W16(add, val & 0x00C7); + TIMER3.Reload(); + break; + default: + //met_abort("Unknown IO at " << IOS_ADD << add); + W16(add, val); + break; + } + } + + void Io::Write32 (uint32_t add, uint32_t val) + { + //debug ("IO Write32 at " << IOS_ADD << add << " of " << IOS_ADD << val); + switch (add & 0xFF) + { + case DMA1DAD: + case DMA0SAD: + case DMA1SAD: + case DMA2SAD: + case DMA3SAD: + case DMA0DAD: + case DMA2DAD: + case DMA3DAD: + W32(add, val); + break; + case BG0HOFS: + case BG1HOFS: + case BG2HOFS: + case BG3HOFS: + Write16(add, val & 0xFFFF); + Write16(add+2, val >> 16); + break; + case BG2X_L: + W32(add, val & 0x0FFFFFFF); + LCD.UpdateBg2RefX(IO.DRead32(Io::BG2X_L)); + break; + case BG2Y_L: + W32(add, val & 0x0FFFFFFF); + LCD.UpdateBg2RefY(IO.DRead32(Io::BG2Y_L)); + break; + case BG3X_L: + W32(add, val & 0x0FFFFFFF); + LCD.UpdateBg3RefX(IO.DRead32(Io::BG3X_L)); + break; + case BG3Y_L: + W32(add, val & 0x0FFFFFFF); + LCD.UpdateBg3RefY(IO.DRead32(Io::BG3Y_L)); + break; + case BG2PA: + case BG2PC: + case BG3PA: + case BG3PC: + case WIN0H: + case WIN0V: + case WININ: + Write16(add, val & 0xFFFF); + Write16(add+2, val >> 16); + break; + case DMA0CNT_L: + case DMA1CNT_L: + case DMA2CNT_L: + case DMA3CNT_L: + Write16(add, val & 0xFFFF); + Write16(add+2, val >> 16); + break; + case FIFO_A: + case FIFO_B: + // TODO + break; + default: + //met_abort("Unknown IO at " << IOS_ADD << add); + //*(uint32_t*)(m_iomem + (add & 0xFFF)) = val; + Write16(add, val & 0xFFFF); + Write16(add+2, val >> 16); + break; + } + } + + bool Io::SaveState (std::ostream& stream) + { + SS_WRITE_DATA(m_iomem, IO_SIZE); + + return true; + } + + bool Io::LoadState (std::istream& stream) + { + SS_READ_DATA(m_iomem, IO_SIZE); + + return true; + } +} diff --git a/libmeteor/source/keypad.cpp b/libmeteor/source/keypad.cpp new file mode 100644 index 0000000000..6e53a4f155 --- /dev/null +++ b/libmeteor/source/keypad.cpp @@ -0,0 +1,96 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/keypad.hpp" +#include "globals.hpp" +#include "ameteor.hpp" + +namespace AMeteor +{ + Keypad::Keypad () : + m_keyinput(IO.GetRef16(Io::KEYINPUT)), + m_keycnt(IO.GetRef16(Io::KEYCNT)) + { + } + + void Keypad::KeyPressed(int code) + { + if (m_keys.count(code)) + m_keyinput &= ~m_keys[code]; + } + + void Keypad::KeyReleased(int code) + { + if (m_keys.count(code)) + m_keyinput |= m_keys[code]; + } + + void Keypad::JoyButtonPressed (uint16_t joyid, uint16_t button) + { + uint32_t id = ((int)joyid) << 16 | button; + if (m_joys.count(id)) + m_keyinput &= ~m_joys[id]; + } + + void Keypad::JoyButtonReleased (uint16_t joyid, uint16_t button) + { + uint32_t id = ((int)joyid) << 16 | button; + if (m_joys.count(id)) + m_keyinput |= m_joys[id]; + } + + void Keypad::JoyMoved (uint16_t joyid, uint16_t axis, float pos) + { + uint32_t id = (((int)joyid) << 16) | ((pos < 0) << 15) | (axis & 0x7FFF); + // if pos is 0, we disable the positive and negative targets + if (pos == 0) + { + if (m_axis.count(id)) + m_keyinput |= m_axis[id]; + if (m_axis.count(id | (1 << 15))) + m_keyinput |= m_axis[id | (1 << 15)]; + } + else + { + // we enable the corresponding button + if (m_axis.count(id)) + m_keyinput &= ~((uint16_t)m_axis[id]); + // we disable the opposite button (we may have skipped 0) + if (m_axis.count(id ^ 0x8000)) + m_keyinput |= m_axis[id ^ 0x8000]; + } + } + + void Keypad::VBlank () + { + // if keypad IRQ are enabled + if (m_keycnt & (0x1 << 14)) + // if irq condition is and + if (m_keycnt & (0x1 << 15)) + { + // if condition is satisfied + if ((~m_keyinput & m_keycnt & 0x3FF) == (m_keycnt & 0x3FF)) + CPU.SendInterrupt(0x1000); + } + // if irq condition is or + else + { + // if condition is satisfied + if (~m_keyinput & m_keycnt & 0x3FF) + CPU.SendInterrupt(0x1000); + } + } +} diff --git a/libmeteor/source/lcd.cpp b/libmeteor/source/lcd.cpp new file mode 100644 index 0000000000..d54375a5f8 --- /dev/null +++ b/libmeteor/source/lcd.cpp @@ -0,0 +1,148 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/lcd.hpp" +#include "ameteor/io.hpp" +#include "globals.hpp" +#include "ameteor.hpp" + +#include "debug.hpp" + +namespace AMeteor +{ + Lcd::Lcd () : + m_screen(MEM, IO) + { + Reset(); + } + + void Lcd::Reset () + { + CLOCK.AddLcd(960); // call me at first H-Blank + } + + void Lcd::TimeEvent () + { + uint16_t& dispstat = IO.GetRef16(Io::DISPSTAT); + uint16_t& vcount = IO.GetRef16(Io::VCOUNT); + + if (dispstat & 0x2) // if we were H-Blanking + { + debug("hblank end"); + // we are not anymore, we're on next line + dispstat ^= 0x2; + // call me when we are H-Blanking again + CLOCK.AddLcd(960); + + // we have finished drawing a line, do our stuff... + if (vcount == 227) // this was the last line + { + vcount = 0; // we're now at first + // we reload the reference points + m_screen.UpdateBg2RefX(IO.DRead32(Io::BG2X_L)); + m_screen.UpdateBg2RefY(IO.DRead32(Io::BG2Y_L)); + m_screen.UpdateBg3RefX(IO.DRead32(Io::BG3X_L)); + m_screen.UpdateBg3RefY(IO.DRead32(Io::BG3Y_L)); + // and we draw the line 0 + m_screen.DrawLine(0); + // FIXME see below, vblank finished + dispstat ^= 0x1; + } + else + { + ++vcount; // we're on next line + if (vcount < 160) // we draw normally + m_screen.DrawLine(vcount); + else if (vcount == 160) // We enter V-Blank + { + dispstat |= 0x1; + if (dispstat & (0x1 << 3)) // if V-Blank irq is enabled + CPU.SendInterrupt(0x1); + DMA.CheckAll(Dma::VBlank); + + KEYPAD.VBlank(); + + // we send the vblank signal + //sig_vblank.emit(); + } + // NOTE : v-blank finishes on line 227, not 0 + // FIXME on vba, it finishes on 0 + //if (vcount == 227) // V-Blank finished + //dispstat ^= 0x1; + } + + // check for vcount match + if (vcount == ((dispstat >> 8) & 0xFF)) // vcount match + { + dispstat |= 0x4; // enable vcount match bit + if (dispstat & (0x1 << 5)) // if V-Counter irq is enabled + CPU.SendInterrupt(0x4); + } + else // no vcount match + dispstat &= ~(uint16_t)0x4; + } + else // if we were not H-Blanking + { + debug("hblank vcount : " << std::dec << vcount); + // now, we are + dispstat |= 0x2; + // call me when we are not H-Blanking anymore + CLOCK.AddLcd(272); + + // NOTE : H-Blank interrupts are not generated during V-Blank + // FIXME vba generates hblank interrupts even during vblank + // if H-Blank irq is enabled //and we're not in V-Blank + if ((dispstat & 0x10) == 0x10) + CPU.SendInterrupt(0x2); + // NOTE : hblank DMAs are not triggered during vblank + // (seen on vba) + if (!(dispstat & 0x1)) + // if we're not vblanking + DMA.CheckAll(Dma::HBlank); + } + } + + bool Lcd::SaveState (std::ostream& stream) + { + if (!m_screen.SaveState(stream)) + return false; + + return true; + } + + bool Lcd::LoadState (std::istream& stream) + { + if (!m_screen.LoadState(stream)) + return false; + + UpdateDispCnt (IO.DRead16(Io::DISPCNT)); + UpdateBg0Cnt (IO.DRead16(Io::BG0CNT)); + UpdateBg1Cnt (IO.DRead16(Io::BG1CNT)); + UpdateBg2Cnt (IO.DRead16(Io::BG2CNT)); + UpdateBg3Cnt (IO.DRead16(Io::BG3CNT)); + UpdateBg0XOff (IO.DRead16(Io::BG0HOFS)); + UpdateBg0YOff (IO.DRead16(Io::BG0VOFS)); + UpdateBg1XOff (IO.DRead16(Io::BG1HOFS)); + UpdateBg1YOff (IO.DRead16(Io::BG1VOFS)); + UpdateBg2XOff (IO.DRead16(Io::BG2HOFS)); + UpdateBg2YOff (IO.DRead16(Io::BG2VOFS)); + UpdateBg3XOff (IO.DRead16(Io::BG3HOFS)); + UpdateBg3YOff (IO.DRead16(Io::BG3VOFS)); + OamWrite (0x07000000, 0x07000400); + + return true; + } +} diff --git a/libmeteor/source/memory.cpp b/libmeteor/source/memory.cpp new file mode 100644 index 0000000000..3bcab840ff --- /dev/null +++ b/libmeteor/source/memory.cpp @@ -0,0 +1,823 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/memory.hpp" +#include "ameteor/io.hpp" +#include "ameteor/eeprom.hpp" +#include "ameteor/flash.hpp" +#include "ameteor/sram.hpp" +#include "globals.hpp" +#include "ameteor.hpp" + +#include "debug.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#define RET_ADD(mem, low, high, size) \ + if (add >= low && (add+size) <= high) \ + { \ + return mem + (add - low); \ + } + +#define debugm debug + +namespace AMeteor +{ + Memory::Memory () : + m_brom(NULL), + m_carttype(CTYPE_UNKNOWN), + m_cart(NULL) + { + m_wbram = new uint8_t[0x00040000]; + m_wcram = new uint8_t[0x00008000]; + m_pram = new uint8_t[0x00000400]; + m_vram = new uint8_t[0x00018000]; + m_oram = new uint8_t[0x00000400]; + m_rom = new uint8_t[0x02000000]; + + Reset(); + } + + Memory::~Memory () + { + if (m_brom) + delete [] m_brom; + delete [] m_wbram; + delete [] m_wcram; + delete [] m_pram; + delete [] m_vram; + delete [] m_oram; + delete [] m_rom; + if (m_cart) + delete m_cart; + } + + void Memory::SetCartTypeFromSize (uint32_t size) + { + switch (size) + { + case 0x0200: + SetCartType(CTYPE_EEPROM512); + break; + case 0x2000: + SetCartType(CTYPE_EEPROM8192); + break; + case 0x8000: + SetCartType(CTYPE_SRAM); + break; + case 0x10000: + SetCartType(CTYPE_FLASH64); + break; + case 0x20000: + SetCartType(CTYPE_FLASH128); + break; + default: + met_abort("Unknown cartridge memory size"); + break; + } + } + + void Memory::SetCartType (uint8_t type) + { + if (m_cart) + delete m_cart; + switch (type) + { + case CTYPE_UNKNOWN: + m_cart = NULL; + break; + case CTYPE_FLASH64: + m_cart = new Flash(false); + break; + case CTYPE_FLASH128: + m_cart = new Flash(true); + break; + case CTYPE_EEPROM512: + m_cart = new Eeprom(false); + break; + case CTYPE_EEPROM8192: + m_cart = new Eeprom(true); + break; + case CTYPE_SRAM: + m_cart = new Sram(); + break; + default: + met_abort("Unknown cartridge memory type"); + break; + } + m_carttype = type; + } + + void Memory::Reset (uint32_t params) + { + static const uint8_t InitMemoryTime[0xF] = { + 1, // 00 - BIOS + 0, // 01 - Not used + 3, // 02 - Work RAM 256K + 1, // 03 - Work RAM 32K + 1, // 04 - I/O Registers + 1, // 05 - Palette RAM + 1, // 06 - VRAM + 1, // 07 - OAM + 5, // 08 - ROM, Wait0 + 5, // 09 - ROM, Wait0 + 5, // 0A - ROM, Wait1 + 5, // 0B - ROM, Wait1 + 5, // 0C - ROM, Wait2 + 5, // 0D - ROM, Wait2 + 5 // 0E - SRAM + }; + + static const uint8_t InitMemoryTimeSeq[0x3] = { + 3, // 08-09 - ROM, Wait0 + 5, // 0A-0B - ROM, Wait1 + 9 // 0C-0D - ROM, Wait2 + }; + + if (m_brom && (params & UNIT_MEMORY_BIOS)) + { + delete [] m_brom; + m_brom = NULL; + } + std::memcpy(m_memtime, InitMemoryTime, sizeof(m_memtime)); + std::memcpy(m_memtimeseq, InitMemoryTimeSeq, sizeof(m_memtimeseq)); + std::memset(m_wbram, 0, 0x00040000); + std::memset(m_wcram, 0, 0x00008000); + std::memset(m_pram , 0, 0x00000400); + std::memset(m_vram , 0, 0x00018000); + std::memset(m_oram , 0, 0x00000400); + if (params & UNIT_MEMORY_ROM) + std::memset(m_rom , 0, 0x02000000); + SetCartType(CTYPE_UNKNOWN); + m_cartfile.clear(); + } + + void Memory::ClearWbram () + { + std::memset(m_wbram, 0, 0x00040000); + } + + void Memory::ClearWcram () + { + std::memset(m_wcram, 0, 0x00008000); + } + + void Memory::ClearPalette () + { + std::memset(m_pram , 0, 0x00000400); + } + + void Memory::ClearVram () + { + std::memset(m_vram , 0, 0x00018000); + } + + void Memory::ClearOam () + { + std::memset(m_oram , 0, 0x00000400); + LCD.OamWrite(0x07000000, 0x07000400); + } + + void Memory::SoftReset () + { + std::memset(m_wcram+0x7E00, 0, 0x200); + } + + void Memory::TimeEvent () + { + if (!m_cartfile.empty()) + { + // FIXME, this may fail, we should do something to inform user + std::ofstream f(m_cartfile.c_str()); + m_cart->Save(f); + } + CLOCK.DisableBattery(); + } + + bool Memory::LoadBios (const char* filename) + { + std::ifstream file(filename); + if (!m_brom) + m_brom = new uint8_t[0x00004000]; + memset(m_brom, 0, 0x00004000); + file.read((char*)m_brom, 0x00004000); + if (file.fail()) + return false; + return true; + } + + void Memory::LoadBios (const uint8_t* data, uint32_t size) + { + if (!m_brom) + m_brom = new uint8_t[0x00004000]; + uint32_t until = std::min(size, 0x00004000u); + std::memcpy(m_brom, data, until); + std::memset(m_brom+until, 0, 0x00004000-until); + } + + bool Memory::LoadRom (const char* filename) + { + std::ifstream file(filename); + std::memset(m_rom, 0, 0x02000000); + file.read((char*)m_rom, 0x02000000); + if (file.bad()) + return false; + return true; + } + + void Memory::LoadRom (const uint8_t* data, uint32_t size) + { + uint32_t until = std::min(size, 0x02000000u); + std::memcpy(m_rom, data, until); + std::memset(m_rom+until, 0, 0x02000000-until); + } + + Memory::CartError Memory::LoadCart () + { + struct stat buf; + if (stat(m_cartfile.c_str(), &buf) == -1) + return errno == ENOENT ? CERR_NOT_FOUND : CERR_FAIL; + SetCartTypeFromSize(buf.st_size); + std::ifstream f(m_cartfile.c_str()); + if (!m_cart->Load(f)) + return CERR_FAIL; + return CERR_NO_ERROR; + } + +#ifdef __LIBRETRO__ + bool Memory::LoadCartInferred () + { + uint32_t size = *(uint32_t*)(CartMemData+CartMem::MAX_SIZE); + if (!size) + return false; + SetCartTypeFromSize(size); + std::istringstream ss; + ss.str(std::string((char*)CartMemData, CartMem::MAX_SIZE)); + if (!m_cart->Load(ss)) + return false; + return true; + } +#endif + +#if 0 + uint8_t* Memory::GetRealAddress (uint32_t add, uint8_t size) + { + RET_ADD(m_brom , 0x00000000u, 0x00004000u, size); + RET_ADD(m_wbram, 0x02000000u, 0x02040000u, size); + RET_ADD(m_wbram, 0x02040000u, 0x02080000u, size); // mirror + RET_ADD(m_wcram, 0x03000000u, 0x03008000u, size); + RET_ADD(m_wcram, 0x03008000u, 0x03010000u, size); // mirror + RET_ADD(m_wcram, 0x03010000u, 0x03018000u, size); // mirror + RET_ADD(m_wcram, 0x03FF8000u, 0x04000000u, size); // mirror + RET_ADD(m_pram , 0x05000000u, 0x05000400u, size); + RET_ADD(m_vram , 0x06000000u, 0x06018000u, size); + RET_ADD(m_oram , 0x07000000u, 0x07000400u, size); + RET_ADD(m_oram , 0x07000400u, 0x07000800u, size); + RET_ADD(m_rom , 0x08000000u, 0x0A000000u, size); + RET_ADD(m_rom , 0x0A000000u, 0x0C000000u, size); + RET_ADD(m_rom , 0x0C000000u, 0x0E000000u, size); + RET_ADD(m_sram , 0x0E000000u, 0x0E010000u, size); + + return NULL; + } +#else + uint8_t* Memory::GetRealAddress (uint32_t add, uint8_t MET_UNUSED(size)) + { + uint32_t loadd = add & 0x00FFFFFF; + switch (add >> 24) + { + case 0x0: + if (m_brom) + return m_brom+(loadd & 0x3FFF); + else + return NULL; + case 0x2: + return m_wbram+(loadd & 0x3FFFF); + case 0x3: + return m_wcram+(loadd & 0x7FFF); + case 0x5: + return m_pram+(loadd & 0x3FF); + case 0x6: + // we have 64K+32K+(32K mirror) and this whole thing is mirrored + // TODO : can't we simplify this with an AND ? + if ((loadd % 0x20000) > 0x18000) + loadd -= 0x8000; + return m_vram+(loadd & 0x1FFFF); + case 0x7: + return m_oram+(loadd & 0x3FF); + case 0x8: + return m_rom+loadd; + case 0x9: + return m_rom+0x01000000+loadd; + case 0xA: + return m_rom+loadd; + case 0xB: + return m_rom+0x01000000+loadd; + case 0xC: + return m_rom+loadd; + case 0xD: + return m_rom+0x01000000+loadd; + default: + return NULL; + } + } +#endif + + void Memory::UpdateWaitStates (uint16_t waitcnt) + { + static const uint8_t GamePakTimeFirstAccess[] = { 5, 4, 3, 9 }; + + // SRAM + m_memtime[0xE] = GamePakTimeFirstAccess[waitcnt & 0x3]; + + // Second access + if (waitcnt & (0x1 << 4)) + m_memtimeseq[0] = 2; + else + m_memtimeseq[0] = 3; + if (waitcnt & (0x1 << 7)) + m_memtimeseq[1] = 2; + else + m_memtimeseq[1] = 5; + if (waitcnt & (0x1 << 10)) + m_memtimeseq[2] = 2; + else + m_memtimeseq[2] = 9; + + // First access + m_memtime[0x8] = m_memtime[0x9] = + GamePakTimeFirstAccess[(waitcnt >> 2) & 0x3]; + m_memtime[0xA] = m_memtime[0xB] = + GamePakTimeFirstAccess[(waitcnt >> 5) & 0x3]; + m_memtime[0xC] = m_memtime[0xD] = + GamePakTimeFirstAccess[(waitcnt >> 8) & 0x3]; + } + + uint8_t Memory::GetCycles16NoSeq (uint32_t add, uint32_t count) + { + add >>= 24; + switch (add) + { + case 0x00 : + case 0x03 : + case 0x04 : + case 0x07 : + // 32 bits bus + return m_memtime[add] * count; + case 0x08 : + case 0x09 : + case 0x0A : + case 0x0B : + case 0x0C : + case 0x0D : + // 16 bits bus + // sequencial and non sequencial reads don't take the same time + return m_memtime[add] + m_memtimeseq[(add-0x08) & 0xFE] * (count-1); + default : + // 16 bits bus (and 8 for SRAM, but only 8 bits accesses are + // authorized in this area, so we don't care about 16 and 32 bits + // accesses) + return m_memtime[add] * count; + } + } + + uint8_t Memory::GetCycles16Seq (uint32_t add, uint32_t count) + { + add >>= 24; + switch (add) + { + case 0x00 : + case 0x03 : + case 0x04 : + case 0x07 : + return m_memtime[add] * count; + case 0x08 : + case 0x09 : + case 0x0A : + case 0x0B : + case 0x0C : + case 0x0D : + return m_memtimeseq[(add-0x08) & 0xFE] * count; + default : + return m_memtime[add] * count; + } + } + + uint8_t Memory::GetCycles32NoSeq (uint32_t add, uint32_t count) + { + add >>= 24; + switch (add) + { + case 0x00 : + case 0x03 : + case 0x04 : + case 0x07 : + return m_memtime[add] * count; + case 0x08 : + case 0x09 : + case 0x0A : + case 0x0B : + case 0x0C : + case 0x0D : + return m_memtime[add] + m_memtimeseq[(add-0x08) & 0xFE] * (count*2-1); + default : + return m_memtime[add] * count * 2; + } + } + + uint8_t Memory::GetCycles32Seq (uint32_t add, uint32_t count) + { + add >>= 24; + switch (add) + { + case 0x00 : + case 0x03 : + case 0x04 : + case 0x07 : + return m_memtime[add] * count; + case 0x08 : + case 0x09 : + case 0x0A : + case 0x0B : + case 0x0C : + case 0x0D : + return m_memtimeseq[(add-0x08) & 0xFE] * count * 2; + default : + return m_memtime[add] * count * 2; + } + } + + bool Memory::SaveState (std::ostream& stream) + { + SS_WRITE_ARRAY(m_memtime); + SS_WRITE_ARRAY(m_memtimeseq); + // write if we have a custom bios and write it too + bool b = m_brom; + SS_WRITE_VAR(b); + if (b) + SS_WRITE_DATA(m_brom, 0x00004000); + SS_WRITE_DATA(m_wbram, 0x00040000); + SS_WRITE_DATA(m_wcram, 0x00008000); + SS_WRITE_DATA(m_pram , 0x00000400); + SS_WRITE_DATA(m_vram , 0x00018000); + SS_WRITE_DATA(m_oram , 0x00000400); + + SS_WRITE_VAR(m_carttype); + + if (m_cart) + if (!m_cart->SaveState(stream)) + return false; + + return true; + } + + bool Memory::LoadState (std::istream& stream) + { + Reset(0); + + SS_READ_ARRAY(m_memtime); + SS_READ_ARRAY(m_memtimeseq); + // read if we have a custom bios and write it too + bool b; + SS_READ_VAR(b); + if (b) + SS_READ_DATA(m_brom , 0x00004000); + else + UnloadBios(); + SS_READ_DATA(m_wbram, 0x00040000); + SS_READ_DATA(m_wcram, 0x00008000); + SS_READ_DATA(m_pram , 0x00000400); + SS_READ_DATA(m_vram , 0x00018000); + SS_READ_DATA(m_oram , 0x00000400); + + SS_READ_VAR(m_carttype); + + this->SetCartType(m_carttype); + if (m_cart) + if (!m_cart->LoadState(stream)) + return false; + + return true; + } + +//////////////////////////////////////////////////////////////////////////////// +// Read +//////////////////////////////////////////////////////////////////////////////// + + uint8_t Memory::Read8 (uint32_t add) + { + switch (add >> 24) + { + case 0x00: + if (R(15) < 0x01000000) + return m_brom[add & 0x3FFF]; + else + return 0x0E; + case 0x04: + return IO.Read8(add); + case 0x0E: + return ReadCart(add); + default: + uint8_t *r = (uint8_t*)GetRealAddress(add, 1); + if (!r) + { + debugm("Unknown address for Read8 : " << IOS_ADD << add); + // FIXME : in arm state, vba returns read8(r15 + (add & 3)) + // and in thumb read8(r15 + (add & 1)) + return Read8(R(15)); + } + return *r; + } + } + + uint16_t Memory::Read16 (uint32_t add) + { + switch (add >> 24) + { + case 0x00: + if (R(15) < 0x01000000) + return *(uint16_t*)(m_brom+(add & 0x3FFE)); + else + return 0xF00E; + case 0x04: + return IO.Read16(add); + case 0x0D: + if (m_carttype == CTYPE_EEPROM512 || m_carttype == CTYPE_EEPROM8192) + return static_cast(m_cart)->Read(); + default: + uint16_t *r = (uint16_t*)GetRealAddress(add, 2); + if (!r) + { + debugm("Unknown address for Read16 : " << IOS_ADD << add); + if (R(15) == add) + met_abort("Illegal PC"); + // FIXME : in arm state, vba returns read16(r15 + (add & 2)) + return Read16(R(15)); + } + return *r; + } + } + + uint32_t Memory::Read32 (uint32_t add) + { + switch (add >> 24) + { + case 0x00: + if (R(15) < 0x01000000) + return *(uint32_t*)(m_brom+(add & 0x3FFC)); + else + // TODO a better bios protection than this one (read8 and read16 too) + // this value corresponds to MOVS r15, r14 + return 0xE1B0F00E; + case 0x04: + return IO.Read32(add); + default: + uint32_t *r = (uint32_t*)GetRealAddress(add, 4); + if (!r) + { + debugm("Unknown address for Read32 : " << IOS_ADD << add); + if (R(15) == add) + met_abort("Illegal PC"); + if (FLAG_T) + { + uint16_t o = Read16(R(15)); + return o | o << 16; + } + else + return Read32(R(15)); + } + return *r; + } + } + + uint8_t Memory::ReadCart (uint16_t add) + { + if (m_cart) + return m_cart->Read(add); + else + return 0; + } + +//////////////////////////////////////////////////////////////////////////////// +// Write +//////////////////////////////////////////////////////////////////////////////// + + void Memory::Write8 (uint32_t add, uint8_t val) + { + uint8_t baseadd = add >> 24; + switch (baseadd) + { + case 0x04: + IO.Write8(add, val); + break; + case 0x00: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + debugm("Writing on read only memory"); + break; + case 0x0E: + WriteCart(add & 0xFFFF, val); + break; + default: + uint8_t *r = (uint8_t*)GetRealAddress(add, 1); + if (!r) + { + debugm("Unknown address for Write8 : " << IOS_ADD << add); + } + else + { + *r = val; + if (baseadd == 5 || baseadd == 6) + r[1] = val; + else if (baseadd == 7) + met_abort("not implemented"); + } + break; + } + } + + void Memory::Write16 (uint32_t add, uint16_t val) + { + add &= 0xFFFFFFFE; + uint8_t baseadd = add >> 24; + switch (baseadd) + { + case 0x04: + IO.Write16(add, val); + break; + case 0x00: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + debugm("Writing on read only memory"); + break; + case 0x0E: + met_abort("Writing 16 bytes in SRAM/Flash"); + break; + default: + uint16_t *r = (uint16_t*)GetRealAddress(add, 2); + if (!r) + { + debugm("Unknown address for Write16 : " << IOS_ADD << add); + } + else + { + *r = val; + if (!DMA.GraphicDma() && baseadd == 7) + LCD.OamWrite16((add & 0x3FF) | 0x07000000); + } + break; + } + } + + void Memory::Write32 (uint32_t add, uint32_t val) + { + add &= 0xFFFFFFFC; + uint8_t baseadd = add >> 24; + switch (baseadd) + { + case 0x04: + IO.Write32(add, val); + break; + case 0x00: + case 0x08: + case 0x09: + case 0x0A: + case 0x0B: + case 0x0C: + case 0x0D: + debugm("Writing on read only memory"); + break; + case 0x0E: + met_abort("Writing 32 bytes in SRAM/Flash"); + break; + default: + uint32_t *r = (uint32_t*)GetRealAddress(add, 4); + if (!r) + { + debugm("Unknown address for Write32 : " << IOS_ADD << add); + } + else + { + *r = val; + if (!DMA.GraphicDma() && baseadd == 7) + LCD.OamWrite32((add & 0x3FF) | 0x07000000); + } + break; + } + } + + void Memory::WriteEepromDma (uint32_t src, uint16_t size) + { + if (m_carttype == CTYPE_UNKNOWN) + { + if (size == 17 || size == 81) + SetCartType(CTYPE_EEPROM8192); + else if (size == 9 || size == 73) + SetCartType(CTYPE_EEPROM512); + else + met_abort("Unknown DMA3 size for EEPROM"); + } + else if (m_carttype != CTYPE_EEPROM512 && m_carttype != CTYPE_EEPROM8192) + met_abort("EEPROM DMA3 on non EEPROM cartridge"); + + Eeprom* eeprom = static_cast(m_cart); + if (size == 17 || size == 81) + { + if (eeprom->GetSize() != 0x2000) + met_abort("DMA3 size not corresponding to EEPROM size"); + } + else if (size == 9 || size == 73) + { + if (eeprom->GetSize() != 0x0200) + met_abort("DMA3 size not corresponding to EEPROM size"); + } + else + met_abort("Unknown size for EEPROM DMA"); + + if (eeprom->Write((uint16_t*)GetRealAddress(src), size)) + CLOCK.SetBattery(CART_SAVE_TIME); + } + +#if 0 + void Memory::ReadEepromDma (uint32_t dest, uint16_t size) + { + if (m_carttype != CTYPE_EEPROM) + met_abort("EEPROM DMA3 on non EEPROM or unknown cartridge"); + if (size != 68) + met_abort("EEPROM DMA3 read with invalid size"); + Eeprom* eeprom = static_cast(m_cart); + eeprom->Read((uint16_t*)GetRealAddress(dest)); + } +#endif + +#define NO_MEMMEM +#ifdef NO_MEMMEM // memmem() is a GNU extension, and does not exist in at least MinGW. +#define memmem memmem_compat + // Implementation from Git. + static void *memmem_compat(const void *haystack, size_t haystack_len, + const void *needle, size_t needle_len) + { + const char *begin = (const char*)haystack; + const char *last_possible = begin + haystack_len - needle_len; + + if (needle_len == 0) + return (void *)begin; + + if (haystack_len < needle_len) + return NULL; + + for (; begin <= last_possible; begin++) + { + if (!memcmp(begin, needle, needle_len)) + return (void *)begin; + } + + return NULL; + } +#endif + + void Memory::WriteCart (uint16_t add, uint8_t val) + { + if (m_carttype == CTYPE_EEPROM512 || m_carttype == CTYPE_EEPROM8192) + met_abort("Writing in SRAM/FLASH while using EEPROM"); + if (!m_cart) + if (add == 0x5555) + { + if (memmem((char*)m_rom, 0x02000000, "FLASH1M_V", 9)) + SetCartType(CTYPE_FLASH128); + else + SetCartType(CTYPE_FLASH64); + } + else + SetCartType(CTYPE_SRAM); + if (m_cart->Write(add, val)) + CLOCK.SetBattery(CART_SAVE_TIME); + } +} diff --git a/libmeteor/source/sound.cpp b/libmeteor/source/sound.cpp new file mode 100644 index 0000000000..d5d9e38ebd --- /dev/null +++ b/libmeteor/source/sound.cpp @@ -0,0 +1,111 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/sound.hpp" +#include "globals.hpp" +#include "ameteor.hpp" + +#include "debug.hpp" + +namespace AMeteor +{ + Sound::Sound () : + m_speaker(IO.GetRef16(Io::SOUND1CNT_L), IO.GetRef16(Io::SOUND1CNT_H), + IO.GetRef16(Io::SOUND1CNT_X), + IO.GetRef16(Io::SOUND2CNT_L), IO.GetRef16(Io::SOUND2CNT_H), + IO.GetRef16(Io::SOUND4CNT_L), IO.GetRef16(Io::SOUND4CNT_H), + IO.GetRef16(Io::SOUNDCNT_L), IO.GetRef16(Io::SOUNDCNT_H), + IO.GetRef16(Io::SOUNDCNT_X), IO.GetRef16(Io::SOUNDBIAS)), + m_fATimer(0), + m_fBTimer(0) + { + } + + void Sound::Reset () + { + m_fATimer = m_fBTimer = 0; + m_speaker.Reset(); + } + + void Sound::UpdateCntH1 (uint8_t val) + { + m_fATimer = (val & (0x1 << 10)) ? 1 : 0; + m_fBTimer = (val & (0x1 << 14)) ? 1 : 0; + if (val & (0x1 << 3)) + m_speaker.ResetFifoA(); + if (val & (0x1 << 7)) + m_speaker.ResetFifoB(); + } + + void Sound::TimerOverflow (uint8_t timernum) + { + // both fifo may be triggered by the same timer + if (m_fATimer == timernum) + TimerOverflowA(); + if (m_fBTimer == timernum) + TimerOverflowB(); + } + + inline void Sound::TimerOverflowA () + { + if (m_speaker.GetSizeA() <= 16) + { + DMA.Check(1, Dma::Special); + if (m_speaker.GetSizeA() <= 16) + { + int8_t buf[16] = {0}; + m_speaker.FillFifoA(buf); + } + } + m_speaker.NextSampleA (); + } + + inline void Sound::TimerOverflowB () + { + if (m_speaker.GetSizeB() <= 16) + { + DMA.Check(2, Dma::Special); + if (m_speaker.GetSizeB() <= 16) + { + int8_t buf[16] = {0}; + m_speaker.FillFifoB(buf); + } + } + m_speaker.NextSampleB (); + } + + bool Sound::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_fATimer); + SS_WRITE_VAR(m_fBTimer); + + if (!m_speaker.SaveState(stream)) + return false; + + return true; + } + + bool Sound::LoadState (std::istream& stream) + { + SS_READ_VAR(m_fATimer); + SS_READ_VAR(m_fBTimer); + + if (!m_speaker.LoadState(stream)) + return false; + + return true; + } +} diff --git a/libmeteor/source/sram.cpp b/libmeteor/source/sram.cpp new file mode 100644 index 0000000000..d98edfedc4 --- /dev/null +++ b/libmeteor/source/sram.cpp @@ -0,0 +1,62 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/sram.hpp" +#include +#include +#include "globals.hpp" + +namespace AMeteor +{ + Sram::Sram () : + CartMem() + { + m_size = SIZE; + + *(uint32_t*)(m_data+MAX_SIZE) = m_size; + } + + void Sram::Reset () + { + std::memset(m_data, 0, SIZE); + } + + bool Sram::Load (std::istream& f) + { + f.read((char*)m_data, SIZE); + return f.good(); + } + + bool Sram::Save (std::ostream& f) + { + f.write((char*)m_data, SIZE); + return f.good(); + } + + bool Sram::SaveState (std::ostream& stream) + { + SS_WRITE_DATA(m_data, SIZE); + + return true; + } + + bool Sram::LoadState (std::istream& stream) + { + SS_READ_DATA(m_data, SIZE); + + return true; + } +} diff --git a/libmeteor/source/timer.cpp b/libmeteor/source/timer.cpp new file mode 100644 index 0000000000..b287f79649 --- /dev/null +++ b/libmeteor/source/timer.cpp @@ -0,0 +1,146 @@ +// Meteor - A Nintendo Gameboy Advance emulator +// Copyright (C) 2009-2011 Philippe Daouadi +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +#include "ameteor/timer.hpp" +#include "globals.hpp" +#include "ameteor.hpp" + +#include "debug.hpp" + +namespace AMeteor +{ + static const uint16_t Prescalers[] = {1, 64, 256, 1024}; + + void Timer::Reset () + { + m_reload = 0; + m_count = 0; + m_control.w = 0; + } + + void Timer::Reload () + { + // FIXME to test on hardware, vba update the prescaler even if the timer + // didn't restart + // if the timer is at 33, 63 cycles have passed since the last timer + // increment and prescaler is 1/64, setting TM0CNT even to the same value + // it had will reset the prescaler part, so the timer would be at 33 and we + // will have to wait another 64 cycles before having it go to 34 + // + // the current behaviour for the above example is that it will reach 34 in + // 1 cycle instead of 64 + if (!m_control.b.start && + (IO.DRead16(Io::TM0CNT_H + m_num * Io::TIMER_SIZE) + & (0x1 << 7))) + // if start has changed from 0 to 1 + { + m_control.w = IO.DRead16(Io::TM0CNT_H + m_num * Io::TIMER_SIZE); + m_count = 65536 - m_reload; + if (!m_control.b.countup) + { + m_count *= Prescalers[m_control.b.prescaler]; + + // here, the str instruction which have triggered this function + // will be taken in account by this timer + // in other words, if the str instruction takes 3 cycles, the + // timer will be incremented by 3 cycles just after its start + CLOCK.SetTimer(m_num, m_count); + } + } + else + { + uint16_t cnt = IO.DRead16(Io::TM0CNT_H + m_num * Io::TIMER_SIZE); + if (m_control.b.start && (cnt & (0x1 << 7)) + && m_control.b.prescaler != (cnt & 0x3)) + met_abort("Prescaler changed while timer " << (int)m_num << " was up"); + + m_control.w = IO.DRead16(Io::TM0CNT_H + m_num * Io::TIMER_SIZE); + + if (!m_control.b.start) + CLOCK.DisableTimer(m_num); + } + + if (m_num == 0 && m_control.b.countup) + met_abort("Count-up on first timer !"); + } + + uint16_t Timer::GetCount () const + { + if (m_control.b.countup) + return 65536 - m_count; + else + return 65536 - + CLOCK.GetTimer(m_num) / Prescalers[m_control.b.prescaler]; + } + + void Timer::TimeEvent () + { + debug("Timer" << (int)m_num << " overflow"); + SOUND.TimerOverflow(m_num); + + m_count = 65536 - m_reload; + if (!m_control.b.countup) + { + m_count *= Prescalers[m_control.b.prescaler]; + + // GetTimer should be zero or less since this function was called + if (m_count >= (unsigned short)-CLOCK.GetTimer(m_num)) + { + m_count += CLOCK.GetTimer(m_num); + + CLOCK.SetTimer(m_num, m_count); + } + else + { + CLOCK.AddTimer(m_num, m_count); + } + } + + if (m_control.b.irq) + CPU.SendInterrupt(0x1 << (3 + m_num)); + + if (m_num != 3) + m_next->Countup(); + } + + void Timer::Countup () + { + if (m_control.b.countup) + { + --m_count; + if (m_count == 0) + TimeEvent(); + } + } + + bool Timer::SaveState (std::ostream& stream) + { + SS_WRITE_VAR(m_reload); + SS_WRITE_VAR(m_count); + SS_WRITE_VAR(m_control); + + return true; + } + + bool Timer::LoadState (std::istream& stream) + { + SS_READ_VAR(m_reload); + SS_READ_VAR(m_count); + SS_READ_VAR(m_control); + + return true; + } +}