From e5ded9b139fd5a8cb785cdab8ca8c4efec78033c Mon Sep 17 00:00:00 2001 From: alyosha-tas Date: Mon, 15 Jan 2018 07:45:15 -0500 Subject: [PATCH] Gambatte: Merge improvements from gifvex(mcmaeve) Restores Bios support and loading GB games into GBC Accuracy imrpovements --- .../Consoles/Nintendo/Gameboy/Gambatte.cs | 19 + .../Consoles/Nintendo/Gameboy/LibGambatte.cs | 18 + libgambatte/include/gambatte.h | 11 +- libgambatte/libgambatte.vcxproj | 451 +- libgambatte/src/cinterface.cpp | 428 +- libgambatte/src/cinterface.h | 16 +- libgambatte/src/cpu.cpp | 5811 +++++++++-------- libgambatte/src/cpu.h | 283 +- libgambatte/src/gambatte.cpp | 34 +- libgambatte/src/initstate.cpp | 135 +- libgambatte/src/initstate.h | 2 +- libgambatte/src/insertion_sort.h | 102 +- libgambatte/src/interruptrequester.cpp | 16 +- libgambatte/src/interruptrequester.h | 5 +- libgambatte/src/mem/cartridge.cpp | 1503 +++-- libgambatte/src/mem/cartridge.h | 225 +- libgambatte/src/mem/memptrs.cpp | 449 +- libgambatte/src/mem/memptrs.h | 189 +- libgambatte/src/mem/rtc.cpp | 354 +- libgambatte/src/mem/rtc.h | 196 +- libgambatte/src/memory.cpp | 2279 +++---- libgambatte/src/memory.h | 634 +- libgambatte/src/minkeeper.h | 14 +- libgambatte/src/newstate.cpp | 138 +- libgambatte/src/newstate.h | 204 +- libgambatte/src/savestate.h | 8 +- libgambatte/src/sound.cpp | 22 +- libgambatte/src/sound.h | 4 +- libgambatte/src/sound/channel1.cpp | 58 +- libgambatte/src/sound/channel1.h | 194 +- libgambatte/src/sound/channel2.cpp | 38 +- libgambatte/src/sound/channel2.h | 150 +- libgambatte/src/sound/channel3.cpp | 44 +- libgambatte/src/sound/channel3.h | 210 +- libgambatte/src/sound/channel4.cpp | 58 +- libgambatte/src/sound/channel4.h | 6 +- libgambatte/src/sound/duty_unit.cpp | 20 +- libgambatte/src/sound/duty_unit.h | 4 +- libgambatte/src/sound/envelope_unit.cpp | 12 +- libgambatte/src/sound/envelope_unit.h | 4 +- libgambatte/src/sound/length_counter.cpp | 12 +- libgambatte/src/sound/length_counter.h | 4 +- libgambatte/src/tima.cpp | 16 +- libgambatte/src/tima.h | 2 +- libgambatte/src/video.cpp | 81 +- libgambatte/src/video.h | 32 +- libgambatte/src/video/ly_counter.cpp | 16 +- libgambatte/src/video/ly_counter.h | 4 +- libgambatte/src/video/lyc_irq.cpp | 18 +- libgambatte/src/video/lyc_irq.h | 4 +- libgambatte/src/video/next_m0_time.cpp | 12 +- libgambatte/src/video/next_m0_time.h | 4 +- libgambatte/src/video/ppu.cpp | 133 +- libgambatte/src/video/ppu.h | 8 +- libgambatte/src/video/sprite_mapper.cpp | 36 +- libgambatte/src/video/sprite_mapper.h | 6 +- output/dll/libgambatte.dll | Bin 186880 -> 185856 bytes 57 files changed, 7492 insertions(+), 7244 deletions(-) diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs index 74412c7ff4..2e04142fdf 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/Gambatte.cs @@ -93,6 +93,25 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy throw new InvalidOperationException("gambatte_load() returned non-zero (is this not a gb or gbc rom?)"); } + if ((flags & LibGambatte.LoadFlags.FORCE_DMG) == LibGambatte.LoadFlags.FORCE_DMG) + { + byte[] Bios = comm.CoreFileProvider.GetFirmware("GB", "World", false, "BIOS Not Found, Cannot Load"); + + if (LibGambatte.gambatte_loaddmgbios(GambatteState, Bios) != 0) + { + throw new InvalidOperationException("gambatte_loaddmgbios() returned non-zero (bios error)"); + } + } + else + { + byte[] Bios = comm.CoreFileProvider.GetFirmware("GBC", "World", false, "BIOS Not Found, Cannot Load"); + + if (LibGambatte.gambatte_loadgbcbios(GambatteState, Bios) != 0) + { + throw new InvalidOperationException("gambatte_loadgbcbios() returned non-zero (bios error)"); + } + } + // set real default colors (before anyone mucks with them at all) PutSettings((GambatteSettings)settings ?? new GambatteSettings()); diff --git a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs index 3624bc9512..621f8c8998 100644 --- a/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs +++ b/BizHawk.Emulation.Cores/Consoles/Nintendo/Gameboy/LibGambatte.cs @@ -59,6 +59,24 @@ namespace BizHawk.Emulation.Cores.Nintendo.Gameboy [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] public static extern int gambatte_load(IntPtr core, byte[] romdata, uint length, long now, LoadFlags flags); + /// + /// Load GB BIOS image. + /// + /// opaque state pointer + /// the bios data, can be disposed of once this function returns + /// 0 on success, negative value on failure. + [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern int gambatte_loaddmgbios(IntPtr core, byte[] biosdata); + + /// + /// Load GBC BIOS image. + /// + /// opaque state pointer + /// the bios data, can be disposed of once this function returns + /// 0 on success, negative value on failure. + [DllImport("libgambatte.dll", CallingConvention = CallingConvention.Cdecl)] + public static extern int gambatte_loadgbcbios(IntPtr core, byte[] biosdata); + /// /// Emulates until at least 'samples' stereo sound samples are produced in the supplied buffer, /// or until a video frame has been drawn. diff --git a/libgambatte/include/gambatte.h b/libgambatte/include/gambatte.h index 5f04191fe6..872bf73263 100644 --- a/libgambatte/include/gambatte.h +++ b/libgambatte/include/gambatte.h @@ -51,7 +51,8 @@ public: enum LoadFlag { FORCE_DMG = 1, /**< Treat the ROM as not having CGB support regardless of what its header advertises. */ GBA_CGB = 2, /**< Use GBA intial CPU register values when in CGB mode. */ - MULTICART_COMPAT = 4 /**< Use heuristics to detect and support some multicart MBCs disguised as MBC1. */ + MULTICART_COMPAT = 4, /**< Use heuristics to detect and support some multicart MBCs disguised as MBC1. */ + TRUE_COLOR = 8 /**< Use GBP color conversion instead of GBC-screen approximation */ }; /** Load ROM image. @@ -62,6 +63,9 @@ public: */ int load(const char *romfiledata, unsigned romfilelength, std::uint32_t now, unsigned flags = 0); + int loadGBCBios(const char* biosfiledata); + int loadDMGBios(const char* biosfiledata); + /** Emulates until at least 'samples' stereo sound samples are produced in the supplied buffer, * or until a video frame has been drawn. * @@ -109,7 +113,7 @@ public: void setTraceCallback(void (*callback)(void *)); void setScanlineCallback(void (*callback)(), int sl); void setRTCCallback(std::uint32_t (*callback)()); - void setLinkCallback(void (*callback)()); + void setLinkCallback(void(*callback)()); /** Returns true if the currently loaded ROM image is treated as having CGB support. */ bool isCgb() const; @@ -135,6 +139,9 @@ public: void GetRegs(int *dest); + void SetInterruptAddresses(int *addrs, int numAddrs); + int GetHitInterruptAddress(); + templatevoid SyncState(NewState *ns); private: diff --git a/libgambatte/libgambatte.vcxproj b/libgambatte/libgambatte.vcxproj index 4d34da2828..0af47e31bb 100644 --- a/libgambatte/libgambatte.vcxproj +++ b/libgambatte/libgambatte.vcxproj @@ -1,231 +1,222 @@ - - - - - Debug - Win32 - - - Debug - x64 - - - Release - Win32 - - - Release - x64 - - - - {5D630682-7BDA-474D-B387-0EB420DDC199} - Win32Proj - libgambatte - - - - DynamicLibrary - true - Unicode - v140 - - - DynamicLibrary - true - Unicode - v140 - - - DynamicLibrary - false - true - Unicode - v140 - - - DynamicLibrary - false - true - Unicode - v140 - - - - - - - - - - - - - - - - - - - true - - - true - - - false - - - false - - - - - - Level3 - Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) - include;src;src\common - 4244;4373;4800;4804 - - - Windows - true - - - copy /y $(TargetDir)$(TargetFileName) $(ProjectDir)..\output\dll\$(TargetFileName) - - - - - - - Level3 - Disabled - WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) - include;src;src\common - 4244;4373;4800;4804 - - - Windows - true - - - copy /y $(TargetDir)$(TargetFileName) $(ProjectDir)..\output\dll\$(TargetFileName) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) - include;src;src\common - 4244;4373;4800;4804 - - - Windows - true - true - true - - - copy /y $(TargetDir)$(TargetFileName) $(ProjectDir)..\output\dll\$(TargetFileName) - - - - - Level3 - - - MaxSpeed - true - true - WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) - include;src;src\common - 4244;4373;4800;4804 - - - Windows - true - true - true - - - copy /y $(TargetDir)$(TargetFileName) $(ProjectDir)..\output\dll\$(TargetFileName) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + {5D630682-7BDA-474D-B387-0EB420DDC199} + Win32Proj + libgambatte + 8.1 + + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + DynamicLibrary + true + v140 + Unicode + + + DynamicLibrary + false + v140 + true + Unicode + + + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) + include;src;src\common + 4244;4373;4800;4804 + + + Windows + true + + + + + + + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) + include;src;src\common + 4244;4373;4800;4804 + + + Windows + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) + include;src;src\common + 4244;4373;4800;4804 + + + Windows + true + true + true + + + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;LIBGAMBATTE_EXPORTS;%(PreprocessorDefinitions) + include;src;src\common + 4244;4373;4800;4804 + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/libgambatte/src/cinterface.cpp b/libgambatte/src/cinterface.cpp index abb750b12b..f50658c23a 100644 --- a/libgambatte/src/cinterface.cpp +++ b/libgambatte/src/cinterface.cpp @@ -1,203 +1,225 @@ -#include "cinterface.h" -#include "gambatte.h" -#include -#include -#include "newstate.h" - -using namespace gambatte; - -// new is actually called in a few different places, so replace all of them for determinism guarantees -void *operator new(std::size_t n) -{ - void *p = std::malloc(n); - std::memset(p, 0, n); - return p; -} - -void operator delete(void *p) -{ - std::free(p); -} - -GBEXPORT GB *gambatte_create() -{ - return new GB(); -} - -GBEXPORT void gambatte_destroy(GB *g) -{ - delete g; -} - -GBEXPORT int gambatte_load(GB *g, const char *romfiledata, unsigned romfilelength, long long now, unsigned flags) -{ - int ret = g->load(romfiledata, romfilelength, now, flags); - return ret; -} - -GBEXPORT int gambatte_runfor(GB *g, short *soundbuf, unsigned *samples) -{ - unsigned sampv = *samples; - int ret = g->runFor((unsigned int *) soundbuf, sampv); - *samples = sampv; - return ret; -} - -GBEXPORT void gambatte_blitto(GB *g, unsigned int *videobuf, int pitch) -{ - g->blitTo((unsigned int *)videobuf, pitch); -} - -GBEXPORT void gambatte_setlayers(GB *g, unsigned mask) -{ - g->setLayers(mask); -} - -GBEXPORT void gambatte_reset(GB *g, long long now) -{ - g->reset(now); -} - -GBEXPORT void gambatte_setdmgpalettecolor(GB *g, unsigned palnum, unsigned colornum, unsigned rgb32) -{ - g->setDmgPaletteColor(palnum, colornum, rgb32); -} - -GBEXPORT void gambatte_setcgbpalette(GB *g, unsigned *lut) -{ - g->setCgbPalette(lut); -} - -GBEXPORT void gambatte_setinputgetter(GB *g, unsigned (*getinput)(void)) -{ - g->setInputGetter(getinput); -} - -GBEXPORT void gambatte_setreadcallback(GB *g, void (*callback)(unsigned)) -{ - g->setReadCallback(callback); -} - -GBEXPORT void gambatte_setwritecallback(GB *g, void (*callback)(unsigned)) -{ - g->setWriteCallback(callback); -} - -GBEXPORT void gambatte_setexeccallback(GB *g, void (*callback)(unsigned)) -{ - g->setExecCallback(callback); -} - -GBEXPORT void gambatte_setcdcallback(GB *g, CDCallback cdc) -{ - g->setCDCallback(cdc); -} - - -GBEXPORT void gambatte_settracecallback(GB *g, void (*callback)(void *)) -{ - g->setTraceCallback(callback); -} - -GBEXPORT void gambatte_setscanlinecallback(GB *g, void (*callback)(), int sl) -{ - g->setScanlineCallback(callback, sl); -} - -GBEXPORT void gambatte_setrtccallback(GB *g, unsigned int (*callback)()) -{ - g->setRTCCallback(callback); -} - -GBEXPORT void gambatte_setlinkcallback(GB *g, void (*callback)()) -{ - g->setLinkCallback(callback); -} - -GBEXPORT int gambatte_iscgb(GB *g) -{ - return g->isCgb(); -} - -GBEXPORT int gambatte_isloaded(GB *g) -{ - return g->isLoaded(); -} - -GBEXPORT void gambatte_savesavedata(GB *g, char *dest) -{ - g->saveSavedata(dest); -} - -GBEXPORT void gambatte_loadsavedata(GB *g, const char *data) -{ - g->loadSavedata(data); -} - -GBEXPORT int gambatte_savesavedatalength(GB *g) -{ - return g->saveSavedataLength(); -} - -GBEXPORT int gambatte_newstatelen(GB *g) -{ - NewStateDummy dummy; - g->SyncState(&dummy); - return dummy.GetLength(); -} - -GBEXPORT int gambatte_newstatesave(GB *g, char *data, int len) -{ - NewStateExternalBuffer saver(data, len); - g->SyncState(&saver); - return !saver.Overflow() && saver.GetLength() == len; -} - -GBEXPORT int gambatte_newstateload(GB *g, const char *data, int len) -{ - NewStateExternalBuffer loader((char *)data, len); - g->SyncState(&loader); - return !loader.Overflow() && loader.GetLength() == len; -} - -GBEXPORT void gambatte_newstatesave_ex(GB *g, FPtrs *ff) -{ - NewStateExternalFunctions saver(ff); - g->SyncState(&saver); -} - -GBEXPORT void gambatte_newstateload_ex(GB *g, FPtrs *ff) -{ - NewStateExternalFunctions loader(ff); - g->SyncState(&loader); -} - -GBEXPORT void gambatte_romtitle(GB *g, char *dest) -{ - std::strcpy(dest, g->romTitle().c_str()); -} - -GBEXPORT int gambatte_getmemoryarea(GB *g, int which, unsigned char **data, int *length) -{ - return g->getMemoryArea(which, data, length); -} - -GBEXPORT unsigned char gambatte_cpuread(GB *g, unsigned short addr) -{ - return g->ExternalRead(addr); -} - -GBEXPORT void gambatte_cpuwrite(GB *g, unsigned short addr, unsigned char val) -{ - g->ExternalWrite(addr, val); -} - -GBEXPORT int gambatte_linkstatus(GB *g, int which) -{ - return g->LinkStatus(which); -} - -GBEXPORT void gambatte_getregs(GB *g, int *dest) -{ - g->GetRegs(dest); -} +#include "cinterface.h" +#include "gambatte.h" +#include +#include +#include "newstate.h" + +using namespace gambatte; + +// new is actually called in a few different places, so replace all of them for determinism guarantees +void *operator new(std::size_t n) +{ + void *p = std::malloc(n); + std::memset(p, 0, n); + return p; +} + +void operator delete(void *p) +{ + std::free(p); +} + +GBEXPORT GB *gambatte_create() +{ + return new GB(); +} + +GBEXPORT void gambatte_destroy(GB *g) +{ + delete g; +} + +GBEXPORT int gambatte_load(GB *g, const char *romfiledata, unsigned romfilelength, long long now, unsigned flags) +{ + int ret = g->load(romfiledata, romfilelength, now, flags); + return ret; +} + +GBEXPORT int gambatte_loadgbcbios(GB* g, const char* biosfiledata) +{ + int ret = g->loadGBCBios(biosfiledata); + return ret; +} + +GBEXPORT int gambatte_loaddmgbios(GB* g, const char* biosfiledata) +{ + int ret = g->loadDMGBios(biosfiledata); + return ret; +} + +GBEXPORT int gambatte_runfor(GB *g, short *soundbuf, unsigned *samples) +{ + unsigned sampv = *samples; + int ret = g->runFor((unsigned int *) soundbuf, sampv); + *samples = sampv; + return ret; +} + +GBEXPORT void gambatte_blitto(GB *g, unsigned int *videobuf, int pitch) +{ + g->blitTo((unsigned int *)videobuf, pitch); +} + +GBEXPORT void gambatte_setlayers(GB *g, unsigned mask) +{ + g->setLayers(mask); +} + +GBEXPORT void gambatte_reset(GB *g, long long now) +{ + g->reset(now); +} + +GBEXPORT void gambatte_setdmgpalettecolor(GB *g, unsigned palnum, unsigned colornum, unsigned rgb32) +{ + //g->setDmgPaletteColor(palnum, colornum, rgb32); +} + +GBEXPORT void gambatte_setcgbpalette(GB *g, unsigned *lut) +{ + g->setCgbPalette(lut); +} + +GBEXPORT void gambatte_setinputgetter(GB *g, unsigned (*getinput)(void)) +{ + g->setInputGetter(getinput); +} + +GBEXPORT void gambatte_setreadcallback(GB *g, void (*callback)(unsigned)) +{ + g->setReadCallback(callback); +} + +GBEXPORT void gambatte_setwritecallback(GB *g, void (*callback)(unsigned)) +{ + g->setWriteCallback(callback); +} + +GBEXPORT void gambatte_setexeccallback(GB *g, void (*callback)(unsigned)) +{ + g->setExecCallback(callback); +} + +GBEXPORT void gambatte_setcdcallback(GB *g, CDCallback cdc) +{ + g->setCDCallback(cdc); +} + + +GBEXPORT void gambatte_settracecallback(GB *g, void (*callback)(void *)) +{ + g->setTraceCallback(callback); +} + +GBEXPORT void gambatte_setscanlinecallback(GB *g, void (*callback)(), int sl) +{ + g->setScanlineCallback(callback, sl); +} + +GBEXPORT void gambatte_setrtccallback(GB *g, unsigned int (*callback)()) +{ + g->setRTCCallback(callback); +} + +GBEXPORT void gambatte_setlinkcallback(GB *g, void(*callback)()) +{ + g->setLinkCallback(callback); +} + +GBEXPORT int gambatte_iscgb(GB *g) +{ + return g->isCgb(); +} + +GBEXPORT int gambatte_isloaded(GB *g) +{ + return g->isLoaded(); +} + +GBEXPORT void gambatte_savesavedata(GB *g, char *dest) +{ + g->saveSavedata(dest); +} + +GBEXPORT void gambatte_loadsavedata(GB *g, const char *data) +{ + g->loadSavedata(data); +} + +GBEXPORT int gambatte_savesavedatalength(GB *g) +{ + return g->saveSavedataLength(); +} + +GBEXPORT int gambatte_newstatelen(GB *g) +{ + NewStateDummy dummy; + g->SyncState(&dummy); + return dummy.GetLength(); +} + +GBEXPORT int gambatte_newstatesave(GB *g, char *data, int len) +{ + NewStateExternalBuffer saver(data, len); + g->SyncState(&saver); + return !saver.Overflow() && saver.GetLength() == len; +} + +GBEXPORT int gambatte_newstateload(GB *g, const char *data, int len) +{ + NewStateExternalBuffer loader((char *)data, len); + g->SyncState(&loader); + return !loader.Overflow() && loader.GetLength() == len; +} + +GBEXPORT void gambatte_newstatesave_ex(GB *g, FPtrs *ff) +{ + NewStateExternalFunctions saver(ff); + g->SyncState(&saver); +} + +GBEXPORT void gambatte_newstateload_ex(GB *g, FPtrs *ff) +{ + NewStateExternalFunctions loader(ff); + g->SyncState(&loader); +} + +GBEXPORT void gambatte_romtitle(GB *g, char *dest) +{ + std::strcpy(dest, g->romTitle().c_str()); +} + +GBEXPORT int gambatte_getmemoryarea(GB *g, int which, unsigned char **data, int *length) +{ + return g->getMemoryArea(which, data, length); +} + +GBEXPORT unsigned char gambatte_cpuread(GB *g, unsigned short addr) +{ + return g->ExternalRead(addr); +} + +GBEXPORT void gambatte_cpuwrite(GB *g, unsigned short addr, unsigned char val) +{ + g->ExternalWrite(addr, val); +} + +GBEXPORT int gambatte_linkstatus(GB *g, int which) +{ + return g->LinkStatus(which); +} + +GBEXPORT void gambatte_getregs(GB *g, int *dest) +{ + g->GetRegs(dest); +} + +GBEXPORT void gambatte_setinterruptaddresses(GB *g, int *addrs, int numAddrs) +{ + g->SetInterruptAddresses(addrs, numAddrs); +} + +GBEXPORT int gambatte_gethitinterruptaddress(GB *g) +{ + return g->GetHitInterruptAddress(); +} diff --git a/libgambatte/src/cinterface.h b/libgambatte/src/cinterface.h index 353991ab12..5e8a0087ec 100644 --- a/libgambatte/src/cinterface.h +++ b/libgambatte/src/cinterface.h @@ -1,8 +1,8 @@ -#ifndef CINTERFACE_H -#define CINTERFACE_H - -// these are all documented on the C# side - -#define GBEXPORT extern "C" __declspec(dllexport) - -#endif +#ifndef CINTERFACE_H +#define CINTERFACE_H + +// these are all documented on the C# side + +#define GBEXPORT extern "C" __declspec(dllexport) + +#endif diff --git a/libgambatte/src/cpu.cpp b/libgambatte/src/cpu.cpp index 88665f1989..0fcebe0ca6 100644 --- a/libgambatte/src/cpu.cpp +++ b/libgambatte/src/cpu.cpp @@ -1,2893 +1,2918 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#include "cpu.h" -#include "memory.h" -#include "savestate.h" - -namespace gambatte { - -CPU::CPU() -: memory(Interrupter(SP, PC)), - cycleCounter_(0), - PC(0x100), - SP(0xFFFE), - HF1(0xF), - HF2(0xF), - ZF(0), - CF(0x100), - A(0x01), - B(0x00), - C(0x13), - D(0x00), - E(0xD8), - H(0x01), - L(0x4D), - skip(false), - tracecallback(0) -{ -} - -long CPU::runFor(const unsigned long cycles) { - process(cycles/* << memory.isDoubleSpeed()*/); - - const long csb = memory.cyclesSinceBlit(cycleCounter_); - - if (cycleCounter_ & 0x80000000) - cycleCounter_ = memory.resetCounters(cycleCounter_); - - return csb; -} - -// (HF2 & 0x200) == true means HF is set. -// (HF2 & 0x400) marks the subtract flag. -// (HF2 & 0x800) is set for inc/dec. -// (HF2 & 0x100) is set if there's a carry to add. -static void calcHF(const unsigned HF1, unsigned& HF2) { - unsigned arg1 = HF1 & 0xF; - unsigned arg2 = (HF2 & 0xF) + (HF2 >> 8 & 1); - - if (HF2 & 0x800) { - arg1 = arg2; - arg2 = 1; - } - - if (HF2 & 0x400) - arg1 -= arg2; - else - arg1 = (arg1 + arg2) << 5; - - HF2 |= arg1 & 0x200; -} - -#define F() (((HF2 & 0x600) | (CF & 0x100)) >> 4 | ((ZF & 0xFF) ? 0 : 0x80)) - -#define FROM_F(f_in) do { \ - unsigned from_f_var = f_in; \ -\ - ZF = ~from_f_var & 0x80; \ - HF2 = from_f_var << 4 & 0x600; \ - CF = from_f_var << 4 & 0x100; \ -} while (0) - -void CPU::setStatePtrs(SaveState &state) { - memory.setStatePtrs(state); -} - -void CPU::loadState(const SaveState &state) { - memory.loadState(state/*, cycleCounter_*/); - - cycleCounter_ = state.cpu.cycleCounter; - PC = state.cpu.PC & 0xFFFF; - SP = state.cpu.SP & 0xFFFF; - A = state.cpu.A & 0xFF; - B = state.cpu.B & 0xFF; - C = state.cpu.C & 0xFF; - D = state.cpu.D & 0xFF; - E = state.cpu.E & 0xFF; - FROM_F(state.cpu.F); - H = state.cpu.H & 0xFF; - L = state.cpu.L & 0xFF; - skip = state.cpu.skip; -} - -#define BC() ( B << 8 | C ) -#define DE() ( D << 8 | E ) -#define HL() ( H << 8 | L ) - -#define READ(dest, addr) do { (dest) = memory.read(addr, cycleCounter); cycleCounter += 4; } while (0) -// #define PC_READ(dest, addr) do { (dest) = memory.pc_read(addr, cycleCounter); cycleCounter += 4; } while (0) -#define PC_READ(dest) do { (dest) = memory.read_excb(PC, cycleCounter, false); PC = (PC + 1) & 0xFFFF; cycleCounter += 4; } while (0) -#define PC_READ_FIRST(dest) do { (dest) = memory.read_excb(PC, cycleCounter, true); PC = (PC + 1) & 0xFFFF; cycleCounter += 4; } while (0) -#define FF_READ(dest, addr) do { (dest) = memory.ff_read(addr, cycleCounter); cycleCounter += 4; } while (0) - -#define WRITE(addr, data) do { memory.write(addr, data, cycleCounter); cycleCounter += 4; } while (0) -#define FF_WRITE(addr, data) do { memory.ff_write(addr, data, cycleCounter); cycleCounter += 4; } while (0) - -#define PC_MOD(data) do { PC = data; cycleCounter += 4; } while (0) - -#define PUSH(r1, r2) do { \ - SP = (SP - 1) & 0xFFFF; \ - WRITE(SP, (r1)); \ - SP = (SP - 1) & 0xFFFF; \ - WRITE(SP, (r2)); \ -} while (0) - -//CB OPCODES (Shifts, rotates and bits): -//swap r (8 cycles): -//Swap upper and lower nibbles of 8-bit register, reset flags, check zero flag: -#define swap_r(r) do { \ - CF = HF2 = 0; \ - ZF = (r); \ - (r) = (ZF << 4 | ZF >> 4) & 0xFF; \ -} while (0) - -//rlc r (8 cycles): -//Rotate 8-bit register left, store old bit7 in CF. Reset SF and HCF, Check ZF: -#define rlc_r(r) do { \ - CF = (r) << 1; \ - ZF = CF | CF >> 8; \ - (r) = ZF & 0xFF; \ - HF2 = 0; \ -} while (0) - -//rl r (8 cycles): -//Rotate 8-bit register left through CF, store old bit7 in CF, old CF value becomes bit0. Reset SF and HCF, Check ZF: -#define rl_r(r) do { \ - const unsigned rl_r_var_oldcf = CF >> 8 & 1; \ - CF = (r) << 1; \ - ZF = CF | rl_r_var_oldcf; \ - (r) = ZF & 0xFF; \ - HF2 = 0; \ -} while (0) - -//rrc r (8 cycles): -//Rotate 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: -#define rrc_r(r) do { \ - ZF = (r); \ - CF = ZF << 8; \ - (r) = (ZF | CF) >> 1 & 0xFF; \ - HF2 = 0; \ -} while (0) - -//rr r (8 cycles): -//Rotate 8-bit register right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF and HCF, Check ZF: -#define rr_r(r) do { \ - const unsigned rr_r_var_oldcf = CF & 0x100; \ - CF = (r) << 8; \ - (r) = ZF = ((r) | rr_r_var_oldcf) >> 1; \ - HF2 = 0; \ -} while (0) - -//sla r (8 cycles): -//Shift 8-bit register left, store old bit7 in CF. Reset SF and HCF, Check ZF: -#define sla_r(r) do { \ - ZF = CF = (r) << 1; \ - (r) = ZF & 0xFF; \ - HF2 = 0; \ -} while (0) - -//sra r (8 cycles): -//Shift 8-bit register right, store old bit0 in CF. bit7=old bit7. Reset SF and HCF, Check ZF: -#define sra_r(r) do { \ - CF = (r) << 8; \ - ZF = (r) >> 1; \ - (r) = ZF | ((r) & 0x80); \ - HF2 = 0; \ -} while (0) - -//srl r (8 cycles): -//Shift 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: -#define srl_r(r) do { \ - ZF = (r); \ - CF = (r) << 8; \ - ZF >>= 1; \ - (r) = ZF; \ - HF2 = 0; \ -} while (0) - -//bit n,r (8 cycles): -//bit n,(hl) (12 cycles): -//Test bitn in 8-bit value, check ZF, unset SF, set HCF: -#define bitn_u8(bitmask, u8) do { \ - ZF = (u8) & (bitmask); \ - HF2 = 0x200; \ -} while (0) - -#define bit0_u8(u8) bitn_u8(1, (u8)) -#define bit1_u8(u8) bitn_u8(2, (u8)) -#define bit2_u8(u8) bitn_u8(4, (u8)) -#define bit3_u8(u8) bitn_u8(8, (u8)) -#define bit4_u8(u8) bitn_u8(0x10, (u8)) -#define bit5_u8(u8) bitn_u8(0x20, (u8)) -#define bit6_u8(u8) bitn_u8(0x40, (u8)) -#define bit7_u8(u8) bitn_u8(0x80, (u8)) - -//set n,r (8 cycles): -//Set bitn of 8-bit register: -#define set0_r(r) ( (r) |= 0x1 ) -#define set1_r(r) ( (r) |= 0x2 ) -#define set2_r(r) ( (r) |= 0x4 ) -#define set3_r(r) ( (r) |= 0x8 ) -#define set4_r(r) ( (r) |= 0x10 ) -#define set5_r(r) ( (r) |= 0x20 ) -#define set6_r(r) ( (r) |= 0x40 ) -#define set7_r(r) ( (r) |= 0x80 ) - -//set n,(hl) (16 cycles): -//Set bitn of value at address stored in HL: -#define setn_mem_hl(n) do { \ - const unsigned setn_mem_hl_var_addr = HL(); \ - unsigned setn_mem_hl_var_tmp; \ -\ - READ(setn_mem_hl_var_tmp, setn_mem_hl_var_addr); \ - setn_mem_hl_var_tmp |= 1 << (n); \ -\ - WRITE(setn_mem_hl_var_addr, setn_mem_hl_var_tmp); \ -} while (0) - -//res n,r (8 cycles): -//Unset bitn of 8-bit register: -#define res0_r(r) ( (r) &= 0xFE ) -#define res1_r(r) ( (r) &= 0xFD ) -#define res2_r(r) ( (r) &= 0xFB ) -#define res3_r(r) ( (r) &= 0xF7 ) -#define res4_r(r) ( (r) &= 0xEF ) -#define res5_r(r) ( (r) &= 0xDF ) -#define res6_r(r) ( (r) &= 0xBF ) -#define res7_r(r) ( (r) &= 0x7F ) - -//res n,(hl) (16 cycles): -//Unset bitn of value at address stored in HL: -#define resn_mem_hl(n) do { \ - const unsigned resn_mem_hl_var_addr = HL(); \ - unsigned resn_mem_hl_var_tmp; \ -\ - READ(resn_mem_hl_var_tmp, resn_mem_hl_var_addr); \ - resn_mem_hl_var_tmp &= ~(1 << (n)); \ -\ - WRITE(resn_mem_hl_var_addr, resn_mem_hl_var_tmp); \ -} while (0) - - -//16-BIT LOADS: -//ld rr,nn (12 cycles) -//set rr to 16-bit value of next 2 bytes in memory -#define ld_rr_nn(r1, r2) do { \ - PC_READ(r2); \ - PC_READ(r1); \ -} while (0) - -//push rr (16 cycles): -//Push value of register pair onto stack: -#define push_rr(r1, r2) do { \ - PUSH(r1, r2); \ - cycleCounter += 4; \ -} while (0) - -//pop rr (12 cycles): -//Pop two bytes off stack into register pair: -#define pop_rr(r1, r2) do { \ - READ(r2, SP); \ - SP = (SP + 1) & 0xFFFF; \ - READ(r1, SP); \ - SP = (SP + 1) & 0xFFFF; \ -} while (0) - -//8-BIT ALU: -//add a,r (4 cycles): -//add a,(addr) (8 cycles): -//Add 8-bit value to A, check flags: -#define add_a_u8(u8) do { \ - HF1 = A; \ - HF2 = u8; \ - ZF = CF = A + HF2; \ - A = ZF & 0xFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//adc a,r (4 cycles): -//adc a,(addr) (8 cycles): -//Add 8-bit value+CF to A, check flags: -#define adc_a_u8(u8) do { \ - HF1 = A; \ - HF2 = (CF & 0x100) | (u8); \ - ZF = CF = (CF >> 8 & 1) + (u8) + A; \ - A = ZF & 0xFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//sub a,r (4 cycles): -//sub a,(addr) (8 cycles): -//Subtract 8-bit value from A, check flags: -#define sub_a_u8(u8) do { \ - HF1 = A; \ - HF2 = u8; \ - ZF = CF = A - HF2; \ - A = ZF & 0xFF; \ - HF2 |= 0x400; \ - calcHF(HF1, HF2); \ -} while (0) - -//sbc a,r (4 cycles): -//sbc a,(addr) (8 cycles): -//Subtract CF and 8-bit value from A, check flags: -#define sbc_a_u8(u8) do { \ - HF1 = A; \ - HF2 = 0x400 | (CF & 0x100) | (u8); \ - ZF = CF = A - ((CF >> 8) & 1) - (u8); \ - A = ZF & 0xFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//and a,r (4 cycles): -//and a,(addr) (8 cycles): -//bitwise and 8-bit value into A, check flags: -#define and_a_u8(u8) do { \ - HF2 = 0x200; \ - CF = 0; \ - A &= (u8); \ - ZF = A; \ -} while (0) - -//or a,r (4 cycles): -//or a,(hl) (8 cycles): -//bitwise or 8-bit value into A, check flags: -#define or_a_u8(u8) do { \ - CF = HF2 = 0; \ - A |= (u8); \ - ZF = A; \ -} while (0) - -//xor a,r (4 cycles): -//xor a,(hl) (8 cycles): -//bitwise xor 8-bit value into A, check flags: -#define xor_a_u8(u8) do { \ - CF = HF2 = 0; \ - A ^= (u8); \ - ZF = A; \ -} while (0) - -//cp a,r (4 cycles): -//cp a,(addr) (8 cycles): -//Compare (subtract without storing result) 8-bit value to A, check flags: -#define cp_a_u8(u8) do { \ - HF1 = A; \ - HF2 = u8; \ - ZF = CF = A - HF2; \ - HF2 |= 0x400; \ - calcHF(HF1, HF2); \ -} while (0) - -//inc r (4 cycles): -//Increment value of 8-bit register, check flags except CF: -#define inc_r(r) do { \ - HF2 = (r) | 0x800; \ - ZF = (r) + 1; \ - (r) = ZF & 0xFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//dec r (4 cycles): -//Decrement value of 8-bit register, check flags except CF: -#define dec_r(r) do { \ - HF2 = (r) | 0xC00; \ - ZF = (r) - 1; \ - (r) = ZF & 0xFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//16-BIT ARITHMETIC -//add hl,rr (8 cycles): -//add 16-bit register to HL, check flags except ZF: -/*#define add_hl_rr(rh, rl) do { \ - L = HF1 = L + (rl); \ - HF1 >>= 8; \ - HF1 += H; \ - HF2 = (rh); \ - H = CF = HF1 + (rh); \ - cycleCounter += 4; \ -} while (0)*/ - -#define add_hl_rr(rh, rl) do { \ - CF = L + (rl); \ - L = CF & 0xFF; \ - HF1 = H; \ - HF2 = (CF & 0x100) | (rh); \ - CF = H + (CF >> 8) + (rh); \ - H = CF & 0xFF; \ - cycleCounter += 4; \ - calcHF(HF1, HF2); \ -} while (0) - -//inc rr (8 cycles): -//Increment 16-bit register: -#define inc_rr(rh, rl) do { \ - const unsigned inc_rr_var_tmp = (rl) + 1; \ - (rl) = inc_rr_var_tmp & 0xFF; \ - (rh) = ((rh) + (inc_rr_var_tmp >> 8)) & 0xFF; \ - cycleCounter += 4; \ -} while (0) - -//dec rr (8 cycles): -//Decrement 16-bit register: -#define dec_rr(rh, rl) do { \ - const unsigned dec_rr_var_tmp = (rl) - 1; \ - (rl) = dec_rr_var_tmp & 0xFF; \ - (rh) = ((rh) - (dec_rr_var_tmp >> 8 & 1)) & 0xFF; \ - cycleCounter += 4; \ -} while (0) - -#define sp_plus_n(sumout) do { \ - unsigned sp_plus_n_var_n; \ - PC_READ(sp_plus_n_var_n); \ - sp_plus_n_var_n = (sp_plus_n_var_n ^ 0x80) - 0x80; \ - \ - const unsigned sp_plus_n_var_sum = SP + sp_plus_n_var_n; \ - CF = SP ^ sp_plus_n_var_n ^ sp_plus_n_var_sum; \ - HF2 = CF << 5 & 0x200; \ - ZF = 1; \ - cycleCounter += 4; \ - (sumout) = sp_plus_n_var_sum & 0xFFFF; \ - calcHF(HF1, HF2); \ -} while (0) - -//JUMPS: -//jp nn (16 cycles): -//Jump to address stored in the next two bytes in memory: -#define jp_nn() do { \ - unsigned jp_nn_var_l, jp_nn_var_h; \ -\ - PC_READ(jp_nn_var_l); \ - PC_READ(jp_nn_var_h); \ -\ - PC_MOD(jp_nn_var_h << 8 | jp_nn_var_l); \ -} while (0) - -//jr disp (12 cycles): -//Jump to value of next (signed) byte in memory+current address: -#define jr_disp() do { \ - unsigned jr_disp_var_tmp; \ -\ - PC_READ(jr_disp_var_tmp); \ - jr_disp_var_tmp = (jr_disp_var_tmp ^ 0x80) - 0x80; \ -\ - PC_MOD((PC + jr_disp_var_tmp) & 0xFFFF); \ -} while (0) - -// CALLS, RESTARTS AND RETURNS: -// call nn (24 cycles): -// Jump to 16-bit immediate operand and push return address onto stack: -#define call_nn() do { \ - unsigned const npc = (PC + 2) & 0xFFFF; \ - jp_nn(); \ - PUSH(npc >> 8, npc & 0xFF); \ -} while (0) - -//rst n (16 Cycles): -//Push present address onto stack, jump to address n (one of 00h,08h,10h,18h,20h,28h,30h,38h): -#define rst_n(n) do { \ - PUSH(PC >> 8, PC & 0xFF); \ - PC_MOD(n); \ -} while (0) - -//ret (16 cycles): -//Pop two bytes from the stack and jump to that address: -#define ret() do { \ - unsigned ret_var_l, ret_var_h; \ -\ - pop_rr(ret_var_h, ret_var_l); \ -\ - PC_MOD(ret_var_h << 8 | ret_var_l); \ -} while (0) - -void CPU::process(const unsigned long cycles) { - memory.setEndtime(cycleCounter_, cycles); - memory.updateInput(); - - //unsigned char A = A_; - unsigned long cycleCounter = cycleCounter_; - - while (memory.isActive()) { - //unsigned short PC = PC_; - - if (memory.halted()) { - if (cycleCounter < memory.nextEventTime()) { - const unsigned long cycles = memory.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } - } else while (cycleCounter < memory.nextEventTime()) { - unsigned char opcode; - - if (tracecallback) { - int result[14]; - result[0] = cycleCounter; - result[1] = PC; - result[2] = SP; - result[3] = A; - result[4] = B; - result[5] = C; - result[6] = D; - result[7] = E; - result[8] = F(); - result[9] = H; - result[10] = L; - result[11] = skip; - PC_READ_FIRST(opcode); - result[12] = opcode; - result[13] = memory.debugGetLY(); - tracecallback((void *)result); - } - else { - PC_READ_FIRST(opcode); - } - - if (skip) { - PC = (PC - 1) & 0xFFFF; - skip = false; - } - - switch (opcode) { - //nop (4 cycles): - //Do nothing for 4 cycles: - case 0x00: - break; - case 0x01: - ld_rr_nn(B, C); - break; - case 0x02: - WRITE(BC(), A); - break; - case 0x03: - inc_rr(B, C); - break; - case 0x04: - inc_r(B); - break; - case 0x05: - dec_r(B); - break; - case 0x06: - PC_READ(B); - break; - - //rlca (4 cycles): - //Rotate 8-bit register A left, store old bit7 in CF. Reset SF, HCF, ZF: - case 0x07: - CF = A << 1; - A = (CF | CF >> 8) & 0xFF; - HF2 = 0; - ZF = 1; - break; - - //ld (nn),SP (20 cycles): - //Put value of SP into address given by next 2 bytes in memory: - case 0x08: - { - unsigned l, h; - - PC_READ(l); - PC_READ(h); - - const unsigned addr = h << 8 | l; - - WRITE(addr, SP & 0xFF); - WRITE((addr + 1) & 0xFFFF, SP >> 8); - } - break; - - case 0x09: - add_hl_rr(B, C); - break; - case 0x0A: - READ(A, BC()); - break; - case 0x0B: - dec_rr(B, C); - break; - case 0x0C: - inc_r(C); - break; - case 0x0D: - dec_r(C); - break; - case 0x0E: - PC_READ(C); - break; - - //rrca (4 cycles): - //Rotate 8-bit register A right, store old bit0 in CF. Reset SF, HCF, ZF: - case 0x0F: - CF = A << 8 | A; - A = CF >> 1 & 0xFF; - HF2 = 0; - ZF = 1; - break; - - //stop (4 cycles): - //Halt CPU and LCD display until button pressed: - case 0x10: - PC = (PC + 1) & 0xFFFF; - - cycleCounter = memory.stop(cycleCounter); - - if (cycleCounter < memory.nextEventTime()) { - const unsigned long cycles = memory.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } - - break; - case 0x11: - ld_rr_nn(D, E); - break; - case 0x12: - WRITE(DE(), A); - break; - case 0x13: - inc_rr(D, E); - break; - case 0x14: - inc_r(D); - break; - case 0x15: - dec_r(D); - break; - case 0x16: - PC_READ(D); - break; - - //rla (4 cycles): - //Rotate 8-bit register A left through CF, store old bit7 in CF, old CF value becomes bit0. Reset SF, HCF, ZF: - case 0x17: - { - const unsigned oldcf = CF >> 8 & 1; - CF = A << 1; - A = (CF | oldcf) & 0xFF; - } - - HF2 = 0; - ZF = 1; - break; - - case 0x18: - jr_disp(); - break; - case 0x19: - add_hl_rr(D, E); - break; - case 0x1A: - READ(A, DE()); - break; - case 0x1B: - dec_rr(D, E); - break; - case 0x1C: - inc_r(E); - break; - case 0x1D: - dec_r(E); - break; - case 0x1E: - PC_READ(E); - break; - - //rra (4 cycles): - //Rotate 8-bit register A right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF, HCF, ZF: - case 0x1F: - { - const unsigned oldcf = CF & 0x100; - CF = A << 8; - A = (A | oldcf) >> 1; - } - - HF2 = 0; - ZF = 1; - break; - - //jr nz,disp (12;8 cycles): - //Jump to value of next (signed) byte in memory+current address if ZF is unset: - case 0x20: - if (ZF & 0xFF) { - jr_disp(); - } else { - PC_MOD((PC + 1) & 0xFFFF); - } - break; - - case 0x21: - ld_rr_nn(H, L); - break; - - //ldi (hl),a (8 cycles): - //Put A into memory address in hl. Increment HL: - case 0x22: - { - unsigned addr = HL(); - - WRITE(addr, A); - - addr = (addr + 1) & 0xFFFF; - L = addr; - H = addr >> 8; - } - break; - - case 0x23: - inc_rr(H, L); - break; - case 0x24: - inc_r(H); - break; - case 0x25: - dec_r(H); - break; - case 0x26: - PC_READ(H); - break; - - - //daa (4 cycles): - //Adjust register A to correctly represent a BCD. Check ZF, HF and CF: - case 0x27: - /*{ - unsigned correction = ((A > 0x99) || (CF & 0x100)) ? 0x60 : 0x00; - - calcHF(HF1, HF2); - - if ((A & 0x0F) > 0x09 || (HF2 & 0x200)) - correction |= 0x06; - - HF1 = A; - HF2 = (HF2 & 0x400) | correction; - CF = (correction & 0x40) << 2; - A = (HF2 & 0x400) ? A - correction : (A + correction); - ZF = A; - }*/ - - calcHF(HF1, HF2); - - { - unsigned correction = (CF & 0x100) ? 0x60 : 0x00; - - if (HF2 & 0x200) - correction |= 0x06; - - if (!(HF2 &= 0x400)) { - if ((A & 0x0F) > 0x09) - correction |= 0x06; - - if (A > 0x99) - correction |= 0x60; - - A += correction; - } else - A -= correction; - - CF = correction << 2 & 0x100; - ZF = A; - A &= 0xFF; - } - break; - - //jr z,disp (12;8 cycles): - //Jump to value of next (signed) byte in memory+current address if ZF is set: - case 0x28: - if (ZF & 0xFF) { - PC_MOD((PC + 1) & 0xFFFF); - } else { - jr_disp(); - } - break; - - //add hl,hl (8 cycles): - //add 16-bit register HL to HL, check flags except ZF: - case 0x29: - add_hl_rr(H, L); - break; - - //ldi a,(hl) (8 cycles): - //Put value at address in hl into A. Increment HL: - case 0x2A: - { - unsigned addr = HL(); - - READ(A, addr); - - addr = (addr + 1) & 0xFFFF; - L = addr; - H = addr >> 8; - } - break; - - case 0x2B: - dec_rr(H, L); - break; - case 0x2C: - inc_r(L); - break; - case 0x2D: - dec_r(L); - break; - case 0x2E: - PC_READ(L); - break; - - //cpl (4 cycles): - //Complement register A. (Flip all bits), set SF and HCF: - case 0x2F: /*setSubtractFlag(); setHalfCarryFlag();*/ - HF2 = 0x600; - A ^= 0xFF; - break; - - //jr nc,disp (12;8 cycles): - //Jump to value of next (signed) byte in memory+current address if CF is unset: - case 0x30: - if (CF & 0x100) { - PC_MOD((PC + 1) & 0xFFFF); - } else { - jr_disp(); - } - break; - - //ld sp,nn (12 cycles) - //set sp to 16-bit value of next 2 bytes in memory - case 0x31: - { - unsigned l, h; - - PC_READ(l); - PC_READ(h); - - SP = h << 8 | l; - } - break; - - //ldd (hl),a (8 cycles): - //Put A into memory address in hl. Decrement HL: - case 0x32: - { - unsigned addr = HL(); - - WRITE(addr, A); - - addr = (addr - 1) & 0xFFFF; - L = addr; - H = addr >> 8; - } - break; - - case 0x33: - SP = (SP + 1) & 0xFFFF; - cycleCounter += 4; - break; - - //inc (hl) (12 cycles): - //Increment value at address in hl, check flags except CF: - case 0x34: - { - const unsigned addr = HL(); - - READ(HF2, addr); - ZF = HF2 + 1; - WRITE(addr, ZF & 0xFF); - HF2 |= 0x800; - calcHF(HF1, HF2); - } - break; - - //dec (hl) (12 cycles): - //Decrement value at address in hl, check flags except CF: - case 0x35: - { - const unsigned addr = HL(); - - READ(HF2, addr); - ZF = HF2 - 1; - WRITE(addr, ZF & 0xFF); - HF2 |= 0xC00; - calcHF(HF1, HF2); - } - break; - - //ld (hl),n (12 cycles): - //set memory at address in hl to value of next byte in memory: - case 0x36: - { - unsigned tmp; - - PC_READ(tmp); - WRITE(HL(), tmp); - } - break; - - //scf (4 cycles): - //Set CF. Unset SF and HCF: - case 0x37: /*setCarryFlag(); unsetSubtractFlag(); unsetHalfCarryFlag();*/ - CF = 0x100; - HF2 = 0; - break; - - //jr c,disp (12;8 cycles): - //Jump to value of next (signed) byte in memory+current address if CF is set: - case 0x38: //PC+=(((int8_t)memory.read(PC++))*CarryFlag()); Cycles(8); break; - if (CF & 0x100) { - jr_disp(); - } else { - PC_MOD((PC + 1) & 0xFFFF); - } - break; - - //add hl,sp (8 cycles): - //add SP to HL, check flags except ZF: - case 0x39: /*add_hl_rr(SP>>8, SP); break;*/ - CF = L + SP; - L = CF & 0xFF; - HF1 = H; - HF2 = ((CF ^ SP) & 0x100) | SP >> 8; - CF >>= 8; - CF += H; - H = CF & 0xFF; - cycleCounter += 4; - calcHF(HF1, HF2); - break; - - //ldd a,(hl) (8 cycles): - //Put value at address in hl into A. Decrement HL: - case 0x3A: - { - unsigned addr = HL(); - - A = memory.read(addr, cycleCounter); - cycleCounter += 4; - - addr = (addr - 1) & 0xFFFF; - L = addr; - H = addr >> 8; - } - break; - - case 0x3B: - SP = (SP - 1) & 0xFFFF; - cycleCounter += 4; - break; - case 0x3C: - inc_r(A); - break; - case 0x3D: - dec_r(A); - break; - case 0x3E: - PC_READ(A); - break; - - //ccf (4 cycles): - //Complement CF (unset if set vv.) Unset SF and HCF. - case 0x3F: /*complementCarryFlag(); unsetSubtractFlag(); unsetHalfCarryFlag();*/ - CF ^= 0x100; - HF2 = 0; - break; - - //ld r,r (4 cycles):next_irqEventTime - //ld r,(r) (8 cycles): - case 0x40: - B = B; - break; - case 0x41: - B = C; - break; - case 0x42: - B = D; - break; - case 0x43: - B = E; - break; - case 0x44: - B = H; - break; - case 0x45: - B = L; - break; - case 0x46: - READ(B, HL()); - break; - case 0x47: - B = A; - break; - case 0x48: - C = B; - break; - case 0x49: - C = C; - break; - case 0x4A: - C = D; - break; - case 0x4B: - C = E; - break; - case 0x4C: - C = H; - break; - case 0x4D: - C = L; - break; - case 0x4E: - READ(C, HL()); - break; - case 0x4F: - C = A; - break; - case 0x50: - D = B; - break; - case 0x51: - D = C; - break; - case 0x52: - D = D; - break; - case 0x53: - D = E; - break; - case 0x54: - D = H; - break; - case 0x55: - D = L; - break; - case 0x56: - READ(D, HL()); - break; - case 0x57: - D = A; - break; - case 0x58: - E = B; - break; - case 0x59: - E = C; - break; - case 0x5A: - E = D; - break; - case 0x5B: - E = E; - break; - case 0x5C: - E = H; - break; - case 0x5D: - E = L; - break; - case 0x5E: - READ(E, HL()); - break; - case 0x5F: - E = A; - break; - case 0x60: - H = B; - break; - case 0x61: - H = C; - break; - case 0x62: - H = D; - break; - case 0x63: - H = E; - break; - case 0x64: - H = H; - break; - case 0x65: - H = L; - break; - case 0x66: - READ(H, HL()); - break; - case 0x67: - H = A; - break; - case 0x68: - L = B; - break; - case 0x69: - L = C; - break; - case 0x6A: - L = D; - break; - case 0x6B: - L = E; - break; - case 0x6C: - L = H; - break; - case 0x6D: - L = L; - break; - case 0x6E: - READ(L, HL()); - break; - case 0x6F: - L = A; - break; - case 0x70: - WRITE(HL(), B); - break; - case 0x71: - WRITE(HL(), C); - break; - case 0x72: - WRITE(HL(), D); - break; - case 0x73: - WRITE(HL(), E); - break; - case 0x74: - WRITE(HL(), H); - break; - case 0x75: - WRITE(HL(), L); - break; - - //halt (4 cycles): - case 0x76: - if (!memory.ime() && (memory.ff_read(0xFF0F, cycleCounter) & memory.ff_read(0xFFFF, cycleCounter) & 0x1F)) { - if (memory.isCgb()) - cycleCounter += 4; - else - skip = true; - } else { - memory.halt(); - - if (cycleCounter < memory.nextEventTime()) { - const unsigned long cycles = memory.nextEventTime() - cycleCounter; - cycleCounter += cycles + (-cycles & 3); - } - } - - break; - case 0x77: - WRITE(HL(), A); - break; - case 0x78: - A = B; - break; - case 0x79: - A = C; - break; - case 0x7A: - A = D; - break; - case 0x7B: - A = E; - break; - case 0x7C: - A = H; - break; - case 0x7D: - A = L; - break; - case 0x7E: - READ(A, HL()); - break; - case 0x7F: - // A = A; - break; - case 0x80: - add_a_u8(B); - break; - case 0x81: - add_a_u8(C); - break; - case 0x82: - add_a_u8(D); - break; - case 0x83: - add_a_u8(E); - break; - case 0x84: - add_a_u8(H); - break; - case 0x85: - add_a_u8(L); - break; - case 0x86: - { - unsigned data; - - READ(data, HL()); - - add_a_u8(data); - } - break; - case 0x87: - add_a_u8(A); - break; - case 0x88: - adc_a_u8(B); - break; - case 0x89: - adc_a_u8(C); - break; - case 0x8A: - adc_a_u8(D); - break; - case 0x8B: - adc_a_u8(E); - break; - case 0x8C: - adc_a_u8(H); - break; - case 0x8D: - adc_a_u8(L); - break; - case 0x8E: - { - unsigned data; - - READ(data, HL()); - - adc_a_u8(data); - } - break; - case 0x8F: - adc_a_u8(A); - break; - case 0x90: - sub_a_u8(B); - break; - case 0x91: - sub_a_u8(C); - break; - case 0x92: - sub_a_u8(D); - break; - case 0x93: - sub_a_u8(E); - break; - case 0x94: - sub_a_u8(H); - break; - case 0x95: - sub_a_u8(L); - break; - case 0x96: - { - unsigned data; - - READ(data, HL()); - - sub_a_u8(data); - } - break; - //A-A is always 0: - case 0x97: - HF2 = 0x400; - CF = ZF = A = 0; - break; - case 0x98: - sbc_a_u8(B); - break; - case 0x99: - sbc_a_u8(C); - break; - case 0x9A: - sbc_a_u8(D); - break; - case 0x9B: - sbc_a_u8(E); - break; - case 0x9C: - sbc_a_u8(H); - break; - case 0x9D: - sbc_a_u8(L); - break; - case 0x9E: - { - unsigned data; - - READ(data, HL()); - - sbc_a_u8(data); - } - break; - case 0x9F: - sbc_a_u8(A); - break; - case 0xA0: - and_a_u8(B); - break; - case 0xA1: - and_a_u8(C); - break; - case 0xA2: - and_a_u8(D); - break; - case 0xA3: - and_a_u8(E); - break; - case 0xA4: - and_a_u8(H); - break; - case 0xA5: - and_a_u8(L); - break; - case 0xA6: - { - unsigned data; - - READ(data, HL()); - - and_a_u8(data); - } - break; - //A&A will always be A: - case 0xA7: - ZF = A; - CF = 0; - HF2 = 0x200; - break; - case 0xA8: - xor_a_u8(B); - break; - case 0xA9: - xor_a_u8(C); - break; - case 0xAA: - xor_a_u8(D); - break; - case 0xAB: - xor_a_u8(E); - break; - case 0xAC: - xor_a_u8(H); - break; - case 0xAD: - xor_a_u8(L); - break; - case 0xAE: - { - unsigned data; - - READ(data, HL()); - - xor_a_u8(data); - } - break; - //A^A will always be 0: - case 0xAF: - CF = HF2 = ZF = A = 0; - break; - case 0xB0: - or_a_u8(B); - break; - case 0xB1: - or_a_u8(C); - break; - case 0xB2: - or_a_u8(D); - break; - case 0xB3: - or_a_u8(E); - break; - case 0xB4: - or_a_u8(H); - break; - case 0xB5: - or_a_u8(L); - break; - case 0xB6: - { - unsigned data; - - READ(data, HL()); - - or_a_u8(data); - } - break; - //A|A will always be A: - case 0xB7: - ZF = A; - HF2 = CF = 0; - break; - case 0xB8: - cp_a_u8(B); - break; - case 0xB9: - cp_a_u8(C); - break; - case 0xBA: - cp_a_u8(D); - break; - case 0xBB: - cp_a_u8(E); - break; - case 0xBC: - cp_a_u8(H); - break; - case 0xBD: - cp_a_u8(L); - break; - case 0xBE: - { - unsigned data; - - READ(data, HL()); - - cp_a_u8(data); - } - break; - //A always equals A: - case 0xBF: - CF = ZF = 0; - HF2 = 0x400; - calcHF(HF1, HF2); \ - break; - - //ret nz (20;8 cycles): - //Pop two bytes from the stack and jump to that address, if ZF is unset: - case 0xC0: - cycleCounter += 4; - - if (ZF & 0xFF) { - ret(); - } - break; - - case 0xC1: - pop_rr(B, C); - break; - - //jp nz,nn (16;12 cycles): - //Jump to address stored in next two bytes in memory if ZF is unset: - case 0xC2: - if (ZF & 0xFF) { - jp_nn(); - } else { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } - break; - - case 0xC3: - jp_nn(); - break; - - //call nz,nn (24;12 cycles): - //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if ZF is unset: - case 0xC4: - if (ZF & 0xFF) { - call_nn(); - } else { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } - break; - - case 0xC5: - push_rr(B, C); - break; - case 0xC6: - { - unsigned data; - - PC_READ(data); - - add_a_u8(data); - } - break; - case 0xC7: - rst_n(0x00); - break; - - //ret z (20;8 cycles): - //Pop two bytes from the stack and jump to that address, if ZF is set: - case 0xC8: - cycleCounter += 4; - - if (!(ZF & 0xFF)) { - ret(); - } - - break; - - //ret (16 cycles): - //Pop two bytes from the stack and jump to that address: - case 0xC9: - ret(); - break; - - //jp z,nn (16;12 cycles): - //Jump to address stored in next two bytes in memory if ZF is set: - case 0xCA: - if (ZF & 0xFF) { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } else { - jp_nn(); - } - break; - - - //CB OPCODES (Shifts, rotates and bits): - case 0xCB: - PC_READ(opcode); - - switch (opcode) { - case 0x00: - rlc_r(B); - break; - case 0x01: - rlc_r(C); - break; - case 0x02: - rlc_r(D); - break; - case 0x03: - rlc_r(E); - break; - case 0x04: - rlc_r(H); - break; - case 0x05: - rlc_r(L); - break; - //rlc (hl) (16 cycles): - //Rotate 8-bit value stored at address in HL left, store old bit7 in CF. Reset SF and HCF. Check ZF: - case 0x06: - { - const unsigned addr = HL(); - - READ(CF, addr); - CF <<= 1; - - ZF = CF | (CF >> 8); - - WRITE(addr, ZF & 0xFF); - - HF2 = 0; - } - break; - case 0x07: - rlc_r(A); - break; - case 0x08: - rrc_r(B); - break; - case 0x09: - rrc_r(C); - break; - case 0x0A: - rrc_r(D); - break; - case 0x0B: - rrc_r(E); - break; - case 0x0C: - rrc_r(H); - break; - case 0x0D: - rrc_r(L); - break; - //rrc (hl) (16 cycles): - //Rotate 8-bit value stored at address in HL right, store old bit0 in CF. Reset SF and HCF. Check ZF: - case 0x0E: - { - const unsigned addr = HL(); - - READ(ZF, addr); - - CF = ZF << 8; - - WRITE(addr, (ZF | CF) >> 1 & 0xFF); - - HF2 = 0; - } - break; - case 0x0F: - rrc_r(A); - break; - case 0x10: - rl_r(B); - break; - case 0x11: - rl_r(C); - break; - case 0x12: - rl_r(D); - break; - case 0x13: - rl_r(E); - break; - case 0x14: - rl_r(H); - break; - case 0x15: - rl_r(L); - break; - //rl (hl) (16 cycles): - //Rotate 8-bit value stored at address in HL left thorugh CF, store old bit7 in CF, old CF value becoms bit0. Reset SF and HCF. Check ZF: - case 0x16: - { - const unsigned addr = HL(); - const unsigned oldcf = CF >> 8 & 1; - - READ(CF, addr); - CF <<= 1; - - ZF = CF | oldcf; - - WRITE(addr, ZF & 0xFF); - - HF2 = 0; - } - break; - case 0x17: - rl_r(A); - break; - case 0x18: - rr_r(B); - break; - case 0x19: - rr_r(C); - break; - case 0x1A: - rr_r(D); - break; - case 0x1B: - rr_r(E); - break; - case 0x1C: - rr_r(H); - break; - case 0x1D: - rr_r(L); - break; - //rr (hl) (16 cycles): - //Rotate 8-bit value stored at address in HL right thorugh CF, store old bit0 in CF, old CF value becoms bit7. Reset SF and HCF. Check ZF: - case 0x1E: - { - const unsigned addr = HL(); - - READ(ZF, addr); - - const unsigned oldcf = CF & 0x100; - CF = ZF << 8; - ZF = (ZF | oldcf) >> 1; - - WRITE(addr, ZF); - - HF2 = 0; - } - break; - case 0x1F: - rr_r(A); - break; - case 0x20: - sla_r(B); - break; - case 0x21: - sla_r(C); - break; - case 0x22: - sla_r(D); - break; - case 0x23: - sla_r(E); - break; - case 0x24: - sla_r(H); - break; - case 0x25: - sla_r(L); - break; - //sla (hl) (16 cycles): - //Shift 8-bit value stored at address in HL left, store old bit7 in CF. Reset SF and HCF. Check ZF: - case 0x26: - { - const unsigned addr = HL(); - - READ(CF, addr); - CF <<= 1; - - ZF = CF; - - WRITE(addr, ZF & 0xFF); - - HF2 = 0; - } - break; - case 0x27: - sla_r(A); - break; - case 0x28: - sra_r(B); - break; - case 0x29: - sra_r(C); - break; - case 0x2A: - sra_r(D); - break; - case 0x2B: - sra_r(E); - break; - case 0x2C: - sra_r(H); - break; - case 0x2D: - sra_r(L); - break; - //sra (hl) (16 cycles): - //Shift 8-bit value stored at address in HL right, store old bit0 in CF, bit7=old bit7. Reset SF and HCF. Check ZF: - case 0x2E: - { - const unsigned addr = HL(); - - READ(CF, addr); - - ZF = CF >> 1; - - WRITE(addr, ZF | (CF & 0x80)); - - CF <<= 8; - HF2 = 0; - } - break; - case 0x2F: - sra_r(A); - break; - case 0x30: - swap_r(B); - break; - case 0x31: - swap_r(C); - break; - case 0x32: - swap_r(D); - break; - case 0x33: - swap_r(E); - break; - case 0x34: - swap_r(H); - break; - case 0x35: - swap_r(L); - break; - //swap (hl) (16 cycles): - //Swap upper and lower nibbles of 8-bit value stored at address in HL, reset flags, check zero flag: - case 0x36: - { - const unsigned addr = HL(); - - READ(ZF, addr); - - WRITE(addr, (ZF << 4 | ZF >> 4) & 0xFF); - - CF = HF2 = 0; - } - break; - case 0x37: - swap_r(A); - break; - case 0x38: - srl_r(B); - break; - case 0x39: - srl_r(C); - break; - case 0x3A: - srl_r(D); - break; - case 0x3B: - srl_r(E); - break; - case 0x3C: - srl_r(H); - break; - case 0x3D: - srl_r(L); - break; - //srl (hl) (16 cycles): - //Shift 8-bit value stored at address in HL right, store old bit0 in CF. Reset SF and HCF. Check ZF: - case 0x3E: - { - const unsigned addr = HL(); - - READ(CF, addr); - - ZF = CF >> 1; - - WRITE(addr, ZF); - - CF <<= 8; - HF2 = 0; - } - break; - case 0x3F: - srl_r(A); - break; - case 0x40: - bit0_u8(B); - break; - case 0x41: - bit0_u8(C); - break; - case 0x42: - bit0_u8(D); - break; - case 0x43: - bit0_u8(E); - break; - case 0x44: - bit0_u8(H); - break; - case 0x45: - bit0_u8(L); - break; - case 0x46: - { - unsigned data; - - READ(data, HL()); - - bit0_u8(data); - } - break; - case 0x47: - bit0_u8(A); - break; - case 0x48: - bit1_u8(B); - break; - case 0x49: - bit1_u8(C); - break; - case 0x4A: - bit1_u8(D); - break; - case 0x4B: - bit1_u8(E); - break; - case 0x4C: - bit1_u8(H); - break; - case 0x4D: - bit1_u8(L); - break; - case 0x4E: - { - unsigned data; - - READ(data, HL()); - - bit1_u8(data); - } - break; - case 0x4F: - bit1_u8(A); - break; - case 0x50: - bit2_u8(B); - break; - case 0x51: - bit2_u8(C); - break; - case 0x52: - bit2_u8(D); - break; - case 0x53: - bit2_u8(E); - break; - case 0x54: - bit2_u8(H); - break; - case 0x55: - bit2_u8(L); - break; - case 0x56: - { - unsigned data; - - READ(data, HL()); - - bit2_u8(data); - } - break; - case 0x57: - bit2_u8(A); - break; - case 0x58: - bit3_u8(B); - break; - case 0x59: - bit3_u8(C); - break; - case 0x5A: - bit3_u8(D); - break; - case 0x5B: - bit3_u8(E); - break; - case 0x5C: - bit3_u8(H); - break; - case 0x5D: - bit3_u8(L); - break; - case 0x5E: - { - unsigned data; - - READ(data, HL()); - - bit3_u8(data); - } - break; - case 0x5F: - bit3_u8(A); - break; - case 0x60: - bit4_u8(B); - break; - case 0x61: - bit4_u8(C); - break; - case 0x62: - bit4_u8(D); - break; - case 0x63: - bit4_u8(E); - break; - case 0x64: - bit4_u8(H); - break; - case 0x65: - bit4_u8(L); - break; - case 0x66: - { - unsigned data; - - READ(data, HL()); - - bit4_u8(data); - } - break; - case 0x67: - bit4_u8(A); - break; - case 0x68: - bit5_u8(B); - break; - case 0x69: - bit5_u8(C); - break; - case 0x6A: - bit5_u8(D); - break; - case 0x6B: - bit5_u8(E); - break; - case 0x6C: - bit5_u8(H); - break; - case 0x6D: - bit5_u8(L); - break; - case 0x6E: - { - unsigned data; - - READ(data, HL()); - - bit5_u8(data); - } - break; - case 0x6F: - bit5_u8(A); - break; - case 0x70: - bit6_u8(B); - break; - case 0x71: - bit6_u8(C); - break; - case 0x72: - bit6_u8(D); - break; - case 0x73: - bit6_u8(E); - break; - case 0x74: - bit6_u8(H); - break; - case 0x75: - bit6_u8(L); - break; - case 0x76: - { - unsigned data; - - READ(data, HL()); - - bit6_u8(data); - } - break; - case 0x77: - bit6_u8(A); - break; - case 0x78: - bit7_u8(B); - break; - case 0x79: - bit7_u8(C); - break; - case 0x7A: - bit7_u8(D); - break; - case 0x7B: - bit7_u8(E); - break; - case 0x7C: - bit7_u8(H); - break; - case 0x7D: - bit7_u8(L); - break; - case 0x7E: - { - unsigned data; - - READ(data, HL()); - - bit7_u8(data); - } - break; - case 0x7F: - bit7_u8(A); - break; - case 0x80: - res0_r(B); - break; - case 0x81: - res0_r(C); - break; - case 0x82: - res0_r(D); - break; - case 0x83: - res0_r(E); - break; - case 0x84: - res0_r(H); - break; - case 0x85: - res0_r(L); - break; - case 0x86: - resn_mem_hl(0); - break; - case 0x87: - res0_r(A); - break; - case 0x88: - res1_r(B); - break; - case 0x89: - res1_r(C); - break; - case 0x8A: - res1_r(D); - break; - case 0x8B: - res1_r(E); - break; - case 0x8C: - res1_r(H); - break; - case 0x8D: - res1_r(L); - break; - case 0x8E: - resn_mem_hl(1); - break; - case 0x8F: - res1_r(A); - break; - case 0x90: - res2_r(B); - break; - case 0x91: - res2_r(C); - break; - case 0x92: - res2_r(D); - break; - case 0x93: - res2_r(E); - break; - case 0x94: - res2_r(H); - break; - case 0x95: - res2_r(L); - break; - case 0x96: - resn_mem_hl(2); - break; - case 0x97: - res2_r(A); - break; - case 0x98: - res3_r(B); - break; - case 0x99: - res3_r(C); - break; - case 0x9A: - res3_r(D); - break; - case 0x9B: - res3_r(E); - break; - case 0x9C: - res3_r(H); - break; - case 0x9D: - res3_r(L); - break; - case 0x9E: - resn_mem_hl(3); - break; - case 0x9F: - res3_r(A); - break; - case 0xA0: - res4_r(B); - break; - case 0xA1: - res4_r(C); - break; - case 0xA2: - res4_r(D); - break; - case 0xA3: - res4_r(E); - break; - case 0xA4: - res4_r(H); - break; - case 0xA5: - res4_r(L); - break; - case 0xA6: - resn_mem_hl(4); - break; - case 0xA7: - res4_r(A); - break; - case 0xA8: - res5_r(B); - break; - case 0xA9: - res5_r(C); - break; - case 0xAA: - res5_r(D); - break; - case 0xAB: - res5_r(E); - break; - case 0xAC: - res5_r(H); - break; - case 0xAD: - res5_r(L); - break; - case 0xAE: - resn_mem_hl(5); - break; - case 0xAF: - res5_r(A); - break; - case 0xB0: - res6_r(B); - break; - case 0xB1: - res6_r(C); - break; - case 0xB2: - res6_r(D); - break; - case 0xB3: - res6_r(E); - break; - case 0xB4: - res6_r(H); - break; - case 0xB5: - res6_r(L); - break; - case 0xB6: - resn_mem_hl(6); - break; - case 0xB7: - res6_r(A); - break; - case 0xB8: - res7_r(B); - break; - case 0xB9: - res7_r(C); - break; - case 0xBA: - res7_r(D); - break; - case 0xBB: - res7_r(E); - break; - case 0xBC: - res7_r(H); - break; - case 0xBD: - res7_r(L); - break; - case 0xBE: - resn_mem_hl(7); - break; - case 0xBF: - res7_r(A); - break; - case 0xC0: - set0_r(B); - break; - case 0xC1: - set0_r(C); - break; - case 0xC2: - set0_r(D); - break; - case 0xC3: - set0_r(E); - break; - case 0xC4: - set0_r(H); - break; - case 0xC5: - set0_r(L); - break; - case 0xC6: - setn_mem_hl(0); - break; - case 0xC7: - set0_r(A); - break; - case 0xC8: - set1_r(B); - break; - case 0xC9: - set1_r(C); - break; - case 0xCA: - set1_r(D); - break; - case 0xCB: - set1_r(E); - break; - case 0xCC: - set1_r(H); - break; - case 0xCD: - set1_r(L); - break; - case 0xCE: - setn_mem_hl(1); - break; - case 0xCF: - set1_r(A); - break; - case 0xD0: - set2_r(B); - break; - case 0xD1: - set2_r(C); - break; - case 0xD2: - set2_r(D); - break; - case 0xD3: - set2_r(E); - break; - case 0xD4: - set2_r(H); - break; - case 0xD5: - set2_r(L); - break; - case 0xD6: - setn_mem_hl(2); - break; - case 0xD7: - set2_r(A); - break; - case 0xD8: - set3_r(B); - break; - case 0xD9: - set3_r(C); - break; - case 0xDA: - set3_r(D); - break; - case 0xDB: - set3_r(E); - break; - case 0xDC: - set3_r(H); - break; - case 0xDD: - set3_r(L); - break; - case 0xDE: - setn_mem_hl(3); - break; - case 0xDF: - set3_r(A); - break; - case 0xE0: - set4_r(B); - break; - case 0xE1: - set4_r(C); - break; - case 0xE2: - set4_r(D); - break; - case 0xE3: - set4_r(E); - break; - case 0xE4: - set4_r(H); - break; - case 0xE5: - set4_r(L); - break; - case 0xE6: - setn_mem_hl(4); - break; - case 0xE7: - set4_r(A); - break; - case 0xE8: - set5_r(B); - break; - case 0xE9: - set5_r(C); - break; - case 0xEA: - set5_r(D); - break; - case 0xEB: - set5_r(E); - break; - case 0xEC: - set5_r(H); - break; - case 0xED: - set5_r(L); - break; - case 0xEE: - setn_mem_hl(5); - break; - case 0xEF: - set5_r(A); - break; - case 0xF0: - set6_r(B); - break; - case 0xF1: - set6_r(C); - break; - case 0xF2: - set6_r(D); - break; - case 0xF3: - set6_r(E); - break; - case 0xF4: - set6_r(H); - break; - case 0xF5: - set6_r(L); - break; - case 0xF6: - setn_mem_hl(6); - break; - case 0xF7: - set6_r(A); - break; - case 0xF8: - set7_r(B); - break; - case 0xF9: - set7_r(C); - break; - case 0xFA: - set7_r(D); - break; - case 0xFB: - set7_r(E); - break; - case 0xFC: - set7_r(H); - break; - case 0xFD: - set7_r(L); - break; - case 0xFE: - setn_mem_hl(7); - break; - case 0xFF: - set7_r(A); - break; -// default: break; - } - break; - - - //call z,nn (24;12 cycles): - //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if ZF is set: - case 0xCC: - if (ZF & 0xFF) { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } else { - call_nn(); - } - break; - - case 0xCD: - call_nn(); - break; - case 0xCE: - { - unsigned data; - - PC_READ(data); - - adc_a_u8(data); - } - break; - case 0xCF: - rst_n(0x08); - break; - - //ret nc (20;8 cycles): - //Pop two bytes from the stack and jump to that address, if CF is unset: - case 0xD0: - cycleCounter += 4; - - if (!(CF & 0x100)) { - ret(); - } - - break; - - case 0xD1: - pop_rr(D, E); - break; - - //jp nc,nn (16;12 cycles): - //Jump to address stored in next two bytes in memory if CF is unset: - case 0xD2: - if (CF & 0x100) { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } else { - jp_nn(); - } - break; - - case 0xD3: /*doesn't exist*/ - skip = true; - memory.di(); - break; - - //call nc,nn (24;12 cycles): - //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if CF is unset: - case 0xD4: - if (CF & 0x100) { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } else { - call_nn(); - } - break; - - case 0xD5: - push_rr(D, E); - break; - case 0xD6: - { - unsigned data; - - PC_READ(data); - - sub_a_u8(data); - } - break; - case 0xD7: - rst_n(0x10); - break; - - //ret c (20;8 cycles): - //Pop two bytes from the stack and jump to that address, if CF is set: - case 0xD8: - cycleCounter += 4; - - if (CF & 0x100) { - ret(); - } - - break; - - //reti (16 cycles): - //Pop two bytes from the stack and jump to that address, then enable interrupts: - case 0xD9: - { - unsigned l, h; - - pop_rr(h, l); - - memory.ei(cycleCounter); - - PC_MOD(h << 8 | l); - } - break; - - //jp c,nn (16;12 cycles): - //Jump to address stored in next two bytes in memory if CF is set: - case 0xDA: //PC=( ((PC+2)*(1-CarryFlag())) + (((memory.read(PC+1)<<8)+memory.read(PC))*CarryFlag()) ); Cycles(12); break; - if (CF & 0x100) { - jp_nn(); - } else { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } - break; - - case 0xDB: /*doesn't exist*/ - skip = true; - memory.di(); - break; - - //call z,nn (24;12 cycles): - //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if CF is set: - case 0xDC: - if (CF & 0x100) { - call_nn(); - } else { - PC_MOD((PC + 2) & 0xFFFF); - cycleCounter += 4; - } - break; - - case 0xDD: /*doesn't exist*/ - skip = true; - memory.di(); - break; - - case 0xDE: - { - unsigned data; - - PC_READ(data); - - sbc_a_u8(data); - } - break; - case 0xDF: - rst_n(0x18); - break; - - //ld ($FF00+n),a (12 cycles): - //Put value in A into address (0xFF00 + next byte in memory): - case 0xE0: - { - unsigned tmp; - - PC_READ(tmp); - - FF_WRITE(0xFF00 | tmp, A); - } - break; - - case 0xE1: - pop_rr(H, L); - break; - - //ld ($FF00+C),a (8 ycles): - //Put A into address (0xFF00 + register C): - case 0xE2: - FF_WRITE(0xFF00 | C, A); - break; - case 0xE3: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xE4: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xE5: - push_rr(H, L); - break; - case 0xE6: - { - unsigned data; - - PC_READ(data); - - and_a_u8(data); - } - break; - case 0xE7: - rst_n(0x20); - break; - - //add sp,n (16 cycles): - //Add next (signed) byte in memory to SP, reset ZF and SF, check HCF and CF: - case 0xE8: - /*{ - int8_t tmp = int8_t(memory.pc_read(PC++, cycleCounter)); - HF2 = (((SP & 0xFFF) + tmp) >> 3) & 0x200; - CF = SP + tmp; - SP = CF; - CF >>= 8; - ZF = 1; - cycleCounter += 12; - }*/ - sp_plus_n(SP); - cycleCounter += 4; - break; - - //jp hl (4 cycles): - //Jump to address in hl: - case 0xE9: - PC = HL(); - break; - - //ld (nn),a (16 cycles): - //set memory at address given by the next 2 bytes to value in A: - //Incrementing PC before call, because of possible interrupt. - case 0xEA: - { - unsigned l, h; - - PC_READ(l); - PC_READ(h); - - WRITE(h << 8 | l, A); - } - break; - - case 0xEB: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xEC: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xED: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xEE: - { - unsigned data; - - PC_READ(data); - - xor_a_u8(data); - } - break; - case 0xEF: - rst_n(0x28); - break; - - //ld a,($FF00+n) (12 cycles): - //Put value at address (0xFF00 + next byte in memory) into A: - case 0xF0: - { - unsigned tmp; - - PC_READ(tmp); - - FF_READ(A, 0xFF00 | tmp); - } - break; - - case 0xF1: /*pop_rr(A, F); Cycles(12); break;*/ - { - unsigned F; - - pop_rr(A, F); - - FROM_F(F); - } - break; - - //ld a,($FF00+C) (8 cycles): - //Put value at address (0xFF00 + register C) into A: - case 0xF2: - FF_READ(A, 0xFF00 | C); - break; - - //di (4 cycles): - case 0xF3: - memory.di(); - break; - - case 0xF4: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xF5: /*push_rr(A, F); Cycles(16); break;*/ - calcHF(HF1, HF2); - - { - unsigned F = F(); - - push_rr(A, F); - } - break; - - case 0xF6: - { - unsigned data; - - PC_READ(data); - - or_a_u8(data); - } - break; - case 0xF7: - rst_n(0x30); - break; - - //ldhl sp,n (12 cycles): - //Put (sp+next (signed) byte in memory) into hl (unsets ZF and SF, may enable HF and CF): - case 0xF8: - /*{ - int8_t tmp = int8_t(memory.pc_read(PC++, cycleCounter)); - HF2 = (((SP & 0xFFF) + tmp) >> 3) & 0x200; - CF = SP + tmp; - L = CF; - CF >>= 8; - H = CF; - ZF = 1; - cycleCounter += 8; - }*/ - { - unsigned sum; - sp_plus_n(sum); - L = sum & 0xFF; - H = sum >> 8; - } - break; - - //ld sp,hl (8 cycles): - //Put value in HL into SP - case 0xF9: - SP = HL(); - cycleCounter += 4; - break; - - //ld a,(nn) (16 cycles): - //set A to value in memory at address given by the 2 next bytes. - case 0xFA: - { - unsigned l, h; - - PC_READ(l); - PC_READ(h); - - READ(A, h << 8 | l); - } - break; - - //ei (4 cycles): - //Enable Interrupts after next instruction: - case 0xFB: - memory.ei(cycleCounter); - break; - - case 0xFC: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xFD: /*doesn't exist*/ - skip = true; - memory.di(); - break; - case 0xFE: - { - unsigned data; - - PC_READ(data); - - cp_a_u8(data); - } - break; - case 0xFF: - rst_n(0x38); - break; -// default: break; - } - } - - //PC_ = PC; - cycleCounter = memory.event(cycleCounter); - } - - //A_ = A; - cycleCounter_ = cycleCounter; -} - -void CPU::GetRegs(int *dest) -{ - dest[0] = PC; - dest[1] = SP; - dest[2] = A; - dest[3] = B; - dest[4] = C; - dest[5] = D; - dest[6] = E; - dest[7] = F(); - dest[8] = H; - dest[9] = L; -} - -SYNCFUNC(CPU) -{ - SSS(memory); - NSS(cycleCounter_); - NSS(PC); - NSS(SP); - NSS(HF1); - NSS(HF2); - NSS(ZF); - NSS(CF); - NSS(A); - NSS(B); - NSS(C); - NSS(D); - NSS(E); - NSS(H); - NSS(L); - NSS(skip); -} - -} +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "cpu.h" +#include "memory.h" +#include "savestate.h" + +namespace gambatte { + +CPU::CPU() +: memory(Interrupter(SP, PC), SP, PC), + cycleCounter_(0), + PC(0x100), + SP(0xFFFE), + HF1(0xF), + HF2(0xF), + ZF(0), + CF(0x100), + A(0x01), + B(0x00), + C(0x13), + D(0x00), + E(0xD8), + H(0x01), + L(0x4D), + skip(false), + numInterruptAddresses(), + tracecallback(0) +{ +} + +long CPU::runFor(const unsigned long cycles) { + process(cycles/* << memory.isDoubleSpeed()*/); + + const long csb = memory.cyclesSinceBlit(cycleCounter_); + + if (cycleCounter_ & 0x80000000) + cycleCounter_ = memory.resetCounters(cycleCounter_); + + return csb; +} + +// (HF2 & 0x200) == true means HF is set. +// (HF2 & 0x400) marks the subtract flag. +// (HF2 & 0x800) is set for inc/dec. +// (HF2 & 0x100) is set if there's a carry to add. +static void calcHF(const unsigned HF1, unsigned& HF2) { + unsigned arg1 = HF1 & 0xF; + unsigned arg2 = (HF2 & 0xF) + (HF2 >> 8 & 1); + + if (HF2 & 0x800) { + arg1 = arg2; + arg2 = 1; + } + + if (HF2 & 0x400) + arg1 -= arg2; + else + arg1 = (arg1 + arg2) << 5; + + HF2 |= arg1 & 0x200; +} + +#define F() (((HF2 & 0x600) | (CF & 0x100)) >> 4 | ((ZF & 0xFF) ? 0 : 0x80)) + +#define FROM_F(f_in) do { \ + unsigned from_f_var = f_in; \ +\ + ZF = ~from_f_var & 0x80; \ + HF2 = from_f_var << 4 & 0x600; \ + CF = from_f_var << 4 & 0x100; \ +} while (0) + +void CPU::setStatePtrs(SaveState &state) { + memory.setStatePtrs(state); +} + +void CPU::loadState(const SaveState &state) { + memory.loadState(state/*, cycleCounter_*/); + + cycleCounter_ = state.cpu.cycleCounter; + PC = state.cpu.PC & 0xFFFF; + SP = state.cpu.SP & 0xFFFF; + A = state.cpu.A & 0xFF; + B = state.cpu.B & 0xFF; + C = state.cpu.C & 0xFF; + D = state.cpu.D & 0xFF; + E = state.cpu.E & 0xFF; + FROM_F(state.cpu.F); + H = state.cpu.H & 0xFF; + L = state.cpu.L & 0xFF; + skip = state.cpu.skip; +} + +#define BC() ( B << 8 | C ) +#define DE() ( D << 8 | E ) +#define HL() ( H << 8 | L ) + +#define READ(dest, addr) do { (dest) = memory.read(addr, cycleCounter); cycleCounter += 4; } while (0) +#define PEEK(dest, addr) do { (dest) = memory.read(addr, cycleCounter); } while(0) +// #define PC_READ(dest, addr) do { (dest) = memory.pc_read(addr, cycleCounter); cycleCounter += 4; } while (0) +#define PC_READ(dest) do { (dest) = memory.read_excb(PC, cycleCounter, false); PC = (PC + 1) & 0xFFFF; cycleCounter += 4; } while (0) +#define PC_READ_FIRST(dest) do { (dest) = memory.read_excb(PC, cycleCounter, true); PC = (PC + 1) & 0xFFFF; cycleCounter += 4; } while (0) +#define FF_READ(dest, addr) do { (dest) = memory.ff_read(addr, cycleCounter); cycleCounter += 4; } while (0) + +#define WRITE(addr, data) do { memory.write(addr, data, cycleCounter); cycleCounter += 4; } while (0) +#define FF_WRITE(addr, data) do { memory.ff_write(addr, data, cycleCounter); cycleCounter += 4; } while (0) + +#define PC_MOD(data) do { PC = data; cycleCounter += 4; } while (0) + +#define PUSH(r1, r2) do { \ + SP = (SP - 1) & 0xFFFF; \ + WRITE(SP, (r1)); \ + SP = (SP - 1) & 0xFFFF; \ + WRITE(SP, (r2)); \ +} while (0) + +//CB OPCODES (Shifts, rotates and bits): +//swap r (8 cycles): +//Swap upper and lower nibbles of 8-bit register, reset flags, check zero flag: +#define swap_r(r) do { \ + CF = HF2 = 0; \ + ZF = (r); \ + (r) = (ZF << 4 | ZF >> 4) & 0xFF; \ +} while (0) + +//rlc r (8 cycles): +//Rotate 8-bit register left, store old bit7 in CF. Reset SF and HCF, Check ZF: +#define rlc_r(r) do { \ + CF = (r) << 1; \ + ZF = CF | CF >> 8; \ + (r) = ZF & 0xFF; \ + HF2 = 0; \ +} while (0) + +//rl r (8 cycles): +//Rotate 8-bit register left through CF, store old bit7 in CF, old CF value becomes bit0. Reset SF and HCF, Check ZF: +#define rl_r(r) do { \ + const unsigned rl_r_var_oldcf = CF >> 8 & 1; \ + CF = (r) << 1; \ + ZF = CF | rl_r_var_oldcf; \ + (r) = ZF & 0xFF; \ + HF2 = 0; \ +} while (0) + +//rrc r (8 cycles): +//Rotate 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: +#define rrc_r(r) do { \ + ZF = (r); \ + CF = ZF << 8; \ + (r) = (ZF | CF) >> 1 & 0xFF; \ + HF2 = 0; \ +} while (0) + +//rr r (8 cycles): +//Rotate 8-bit register right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF and HCF, Check ZF: +#define rr_r(r) do { \ + const unsigned rr_r_var_oldcf = CF & 0x100; \ + CF = (r) << 8; \ + (r) = ZF = ((r) | rr_r_var_oldcf) >> 1; \ + HF2 = 0; \ +} while (0) + +//sla r (8 cycles): +//Shift 8-bit register left, store old bit7 in CF. Reset SF and HCF, Check ZF: +#define sla_r(r) do { \ + ZF = CF = (r) << 1; \ + (r) = ZF & 0xFF; \ + HF2 = 0; \ +} while (0) + +//sra r (8 cycles): +//Shift 8-bit register right, store old bit0 in CF. bit7=old bit7. Reset SF and HCF, Check ZF: +#define sra_r(r) do { \ + CF = (r) << 8; \ + ZF = (r) >> 1; \ + (r) = ZF | ((r) & 0x80); \ + HF2 = 0; \ +} while (0) + +//srl r (8 cycles): +//Shift 8-bit register right, store old bit0 in CF. Reset SF and HCF, Check ZF: +#define srl_r(r) do { \ + ZF = (r); \ + CF = (r) << 8; \ + ZF >>= 1; \ + (r) = ZF; \ + HF2 = 0; \ +} while (0) + +//bit n,r (8 cycles): +//bit n,(hl) (12 cycles): +//Test bitn in 8-bit value, check ZF, unset SF, set HCF: +#define bitn_u8(bitmask, u8) do { \ + ZF = (u8) & (bitmask); \ + HF2 = 0x200; \ +} while (0) + +#define bit0_u8(u8) bitn_u8(1, (u8)) +#define bit1_u8(u8) bitn_u8(2, (u8)) +#define bit2_u8(u8) bitn_u8(4, (u8)) +#define bit3_u8(u8) bitn_u8(8, (u8)) +#define bit4_u8(u8) bitn_u8(0x10, (u8)) +#define bit5_u8(u8) bitn_u8(0x20, (u8)) +#define bit6_u8(u8) bitn_u8(0x40, (u8)) +#define bit7_u8(u8) bitn_u8(0x80, (u8)) + +//set n,r (8 cycles): +//Set bitn of 8-bit register: +#define set0_r(r) ( (r) |= 0x1 ) +#define set1_r(r) ( (r) |= 0x2 ) +#define set2_r(r) ( (r) |= 0x4 ) +#define set3_r(r) ( (r) |= 0x8 ) +#define set4_r(r) ( (r) |= 0x10 ) +#define set5_r(r) ( (r) |= 0x20 ) +#define set6_r(r) ( (r) |= 0x40 ) +#define set7_r(r) ( (r) |= 0x80 ) + +//set n,(hl) (16 cycles): +//Set bitn of value at address stored in HL: +#define setn_mem_hl(n) do { \ + const unsigned setn_mem_hl_var_addr = HL(); \ + unsigned setn_mem_hl_var_tmp; \ +\ + READ(setn_mem_hl_var_tmp, setn_mem_hl_var_addr); \ + setn_mem_hl_var_tmp |= 1 << (n); \ +\ + WRITE(setn_mem_hl_var_addr, setn_mem_hl_var_tmp); \ +} while (0) + +//res n,r (8 cycles): +//Unset bitn of 8-bit register: +#define res0_r(r) ( (r) &= 0xFE ) +#define res1_r(r) ( (r) &= 0xFD ) +#define res2_r(r) ( (r) &= 0xFB ) +#define res3_r(r) ( (r) &= 0xF7 ) +#define res4_r(r) ( (r) &= 0xEF ) +#define res5_r(r) ( (r) &= 0xDF ) +#define res6_r(r) ( (r) &= 0xBF ) +#define res7_r(r) ( (r) &= 0x7F ) + +//res n,(hl) (16 cycles): +//Unset bitn of value at address stored in HL: +#define resn_mem_hl(n) do { \ + const unsigned resn_mem_hl_var_addr = HL(); \ + unsigned resn_mem_hl_var_tmp; \ +\ + READ(resn_mem_hl_var_tmp, resn_mem_hl_var_addr); \ + resn_mem_hl_var_tmp &= ~(1 << (n)); \ +\ + WRITE(resn_mem_hl_var_addr, resn_mem_hl_var_tmp); \ +} while (0) + + +//16-BIT LOADS: +//ld rr,nn (12 cycles) +//set rr to 16-bit value of next 2 bytes in memory +#define ld_rr_nn(r1, r2) do { \ + PC_READ(r2); \ + PC_READ(r1); \ +} while (0) + +//push rr (16 cycles): +//Push value of register pair onto stack: +#define push_rr(r1, r2) do { \ + PUSH(r1, r2); \ + cycleCounter += 4; \ +} while (0) + +//pop rr (12 cycles): +//Pop two bytes off stack into register pair: +#define pop_rr(r1, r2) do { \ + READ(r2, SP); \ + SP = (SP + 1) & 0xFFFF; \ + READ(r1, SP); \ + SP = (SP + 1) & 0xFFFF; \ +} while (0) + +//8-BIT ALU: +//add a,r (4 cycles): +//add a,(addr) (8 cycles): +//Add 8-bit value to A, check flags: +#define add_a_u8(u8) do { \ + HF1 = A; \ + HF2 = u8; \ + ZF = CF = A + HF2; \ + A = ZF & 0xFF; \ +} while (0) + +//adc a,r (4 cycles): +//adc a,(addr) (8 cycles): +//Add 8-bit value+CF to A, check flags: +#define adc_a_u8(u8) do { \ + HF1 = A; \ + HF2 = (CF & 0x100) | (u8); \ + ZF = CF = (CF >> 8 & 1) + (u8) + A; \ + A = ZF & 0xFF; \ +} while (0) + +//sub a,r (4 cycles): +//sub a,(addr) (8 cycles): +//Subtract 8-bit value from A, check flags: +#define sub_a_u8(u8) do { \ + HF1 = A; \ + HF2 = u8; \ + ZF = CF = A - HF2; \ + A = ZF & 0xFF; \ + HF2 |= 0x400; \ +} while (0) + +//sbc a,r (4 cycles): +//sbc a,(addr) (8 cycles): +//Subtract CF and 8-bit value from A, check flags: +#define sbc_a_u8(u8) do { \ + HF1 = A; \ + HF2 = 0x400 | (CF & 0x100) | (u8); \ + ZF = CF = A - ((CF >> 8) & 1) - (u8); \ + A = ZF & 0xFF; \ +} while (0) + +//and a,r (4 cycles): +//and a,(addr) (8 cycles): +//bitwise and 8-bit value into A, check flags: +#define and_a_u8(u8) do { \ + HF2 = 0x200; \ + CF = 0; \ + A &= (u8); \ + ZF = A; \ +} while (0) + +//or a,r (4 cycles): +//or a,(hl) (8 cycles): +//bitwise or 8-bit value into A, check flags: +#define or_a_u8(u8) do { \ + CF = HF2 = 0; \ + A |= (u8); \ + ZF = A; \ +} while (0) + +//xor a,r (4 cycles): +//xor a,(hl) (8 cycles): +//bitwise xor 8-bit value into A, check flags: +#define xor_a_u8(u8) do { \ + CF = HF2 = 0; \ + A ^= (u8); \ + ZF = A; \ +} while (0) + +//cp a,r (4 cycles): +//cp a,(addr) (8 cycles): +//Compare (subtract without storing result) 8-bit value to A, check flags: +#define cp_a_u8(u8) do { \ + HF1 = A; \ + HF2 = u8; \ + ZF = CF = A - HF2; \ + HF2 |= 0x400; \ +} while (0) + +//inc r (4 cycles): +//Increment value of 8-bit register, check flags except CF: +#define inc_r(r) do { \ + HF2 = (r) | 0x800; \ + ZF = (r) + 1; \ + (r) = ZF & 0xFF; \ +} while (0) + +//dec r (4 cycles): +//Decrement value of 8-bit register, check flags except CF: +#define dec_r(r) do { \ + HF2 = (r) | 0xC00; \ + ZF = (r) - 1; \ + (r) = ZF & 0xFF; \ +} while (0) + +//16-BIT ARITHMETIC +//add hl,rr (8 cycles): +//add 16-bit register to HL, check flags except ZF: +/*#define add_hl_rr(rh, rl) do { \ + L = HF1 = L + (rl); \ + HF1 >>= 8; \ + HF1 += H; \ + HF2 = (rh); \ + H = CF = HF1 + (rh); \ + cycleCounter += 4; \ +} while (0)*/ + +#define add_hl_rr(rh, rl) do { \ + CF = L + (rl); \ + L = CF & 0xFF; \ + HF1 = H; \ + HF2 = (CF & 0x100) | (rh); \ + CF = H + (CF >> 8) + (rh); \ + H = CF & 0xFF; \ + cycleCounter += 4; \ +} while (0) + +//inc rr (8 cycles): +//Increment 16-bit register: +#define inc_rr(rh, rl) do { \ + const unsigned inc_rr_var_tmp = (rl) + 1; \ + (rl) = inc_rr_var_tmp & 0xFF; \ + (rh) = ((rh) + (inc_rr_var_tmp >> 8)) & 0xFF; \ + cycleCounter += 4; \ +} while (0) + +//dec rr (8 cycles): +//Decrement 16-bit register: +#define dec_rr(rh, rl) do { \ + const unsigned dec_rr_var_tmp = (rl) - 1; \ + (rl) = dec_rr_var_tmp & 0xFF; \ + (rh) = ((rh) - (dec_rr_var_tmp >> 8 & 1)) & 0xFF; \ + cycleCounter += 4; \ +} while (0) + +#define sp_plus_n(sumout) do { \ + unsigned sp_plus_n_var_n; \ + PC_READ(sp_plus_n_var_n); \ + sp_plus_n_var_n = (sp_plus_n_var_n ^ 0x80) - 0x80; \ + \ + const unsigned sp_plus_n_var_sum = SP + sp_plus_n_var_n; \ + CF = SP ^ sp_plus_n_var_n ^ sp_plus_n_var_sum; \ + HF2 = CF << 5 & 0x200; \ + ZF = 1; \ + cycleCounter += 4; \ + (sumout) = sp_plus_n_var_sum & 0xFFFF; \ +} while (0) + +//JUMPS: +//jp nn (16 cycles): +//Jump to address stored in the next two bytes in memory: +#define jp_nn() do { \ + unsigned jp_nn_var_l, jp_nn_var_h; \ +\ + PC_READ(jp_nn_var_l); \ + PC_READ(jp_nn_var_h); \ +\ + PC_MOD(jp_nn_var_h << 8 | jp_nn_var_l); \ +} while (0) + +//jr disp (12 cycles): +//Jump to value of next (signed) byte in memory+current address: +#define jr_disp() do { \ + unsigned jr_disp_var_tmp; \ +\ + PC_READ(jr_disp_var_tmp); \ + jr_disp_var_tmp = (jr_disp_var_tmp ^ 0x80) - 0x80; \ +\ + PC_MOD((PC + jr_disp_var_tmp) & 0xFFFF); \ +} while (0) + +// CALLS, RESTARTS AND RETURNS: +// call nn (24 cycles): +// Jump to 16-bit immediate operand and push return address onto stack: +#define call_nn() do { \ + unsigned const npc = (PC + 2) & 0xFFFF; \ + jp_nn(); \ + PUSH(npc >> 8, npc & 0xFF); \ +} while (0) + +//rst n (16 Cycles): +//Push present address onto stack, jump to address n (one of 00h,08h,10h,18h,20h,28h,30h,38h): +#define rst_n(n) do { \ + PUSH(PC >> 8, PC & 0xFF); \ + PC_MOD(n); \ +} while (0) + +//ret (16 cycles): +//Pop two bytes from the stack and jump to that address: +#define ret() do { \ + unsigned ret_var_l, ret_var_h; \ +\ + pop_rr(ret_var_h, ret_var_l); \ +\ + PC_MOD(ret_var_h << 8 | ret_var_l); \ +} while (0) + +void CPU::process(const unsigned long cycles) { + memory.setEndtime(cycleCounter_, cycles); + hitInterruptAddress = 0; + memory.updateInput(); + + //unsigned char A = A_; + unsigned long cycleCounter = cycleCounter_; + + while (memory.isActive()) { + //unsigned short PC = PC_; + + if (memory.halted()) { + if (cycleCounter < memory.nextEventTime()) { + const unsigned long cycles = memory.nextEventTime() - cycleCounter; + cycleCounter += cycles + (-cycles & 3); + } + } else while (cycleCounter < memory.nextEventTime()) { + unsigned char opcode = 0x00; + + int FullPC = PC; + + if (PC >= 0x4000 && PC <= 0x7FFF) + FullPC |= memory.curRomBank() << 16; + + for (int i = 0; i < numInterruptAddresses; i++) { + if (FullPC == interruptAddresses[i]) { + hitInterruptAddress = interruptAddresses[i]; + memory.setEndtime(cycleCounter, 0); + break; + } + } + + if (!hitInterruptAddress) + { + if (tracecallback) { + int result[14]; + result[0] = cycleCounter; + result[1] = PC; + result[2] = SP; + result[3] = A; + result[4] = B; + result[5] = C; + result[6] = D; + result[7] = E; + result[8] = F(); + result[9] = H; + result[10] = L; + result[11] = skip; + PC_READ_FIRST(opcode); + result[12] = opcode; + result[13] = memory.debugGetLY(); + tracecallback((void *)result); + } + else { + PC_READ_FIRST(opcode); + } + + if (skip) { + PC = (PC - 1) & 0xFFFF; + skip = false; + } + } + + switch (opcode) { + //nop (4 cycles): + //Do nothing for 4 cycles: + case 0x00: + break; + case 0x01: + ld_rr_nn(B, C); + break; + case 0x02: + WRITE(BC(), A); + break; + case 0x03: + inc_rr(B, C); + break; + case 0x04: + inc_r(B); + break; + case 0x05: + dec_r(B); + break; + case 0x06: + PC_READ(B); + break; + + //rlca (4 cycles): + //Rotate 8-bit register A left, store old bit7 in CF. Reset SF, HCF, ZF: + case 0x07: + CF = A << 1; + A = (CF | CF >> 8) & 0xFF; + HF2 = 0; + ZF = 1; + break; + + //ld (nn),SP (20 cycles): + //Put value of SP into address given by next 2 bytes in memory: + case 0x08: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + const unsigned addr = h << 8 | l; + + WRITE(addr, SP & 0xFF); + WRITE((addr + 1) & 0xFFFF, SP >> 8); + } + break; + + case 0x09: + add_hl_rr(B, C); + break; + case 0x0A: + READ(A, BC()); + break; + case 0x0B: + dec_rr(B, C); + break; + case 0x0C: + inc_r(C); + break; + case 0x0D: + dec_r(C); + break; + case 0x0E: + PC_READ(C); + break; + + //rrca (4 cycles): + //Rotate 8-bit register A right, store old bit0 in CF. Reset SF, HCF, ZF: + case 0x0F: + CF = A << 8 | A; + A = CF >> 1 & 0xFF; + HF2 = 0; + ZF = 1; + break; + + //stop (4 cycles): + //Halt CPU and LCD display until button pressed: + case 0x10: + { + unsigned char followingByte; + PEEK(followingByte, PC); + PC = (PC + 1) & 0xFFFF; + + //if (followingByte != 0x00) { + //memory.di(); + //memory.blackScreen(); + //} + + cycleCounter = memory.stop(cycleCounter); + + if (cycleCounter < memory.nextEventTime()) { + const unsigned long cycles = memory.nextEventTime() - cycleCounter; + cycleCounter += cycles + (-cycles & 3); + } + } + break; + case 0x11: + ld_rr_nn(D, E); + break; + case 0x12: + WRITE(DE(), A); + break; + case 0x13: + inc_rr(D, E); + break; + case 0x14: + inc_r(D); + break; + case 0x15: + dec_r(D); + break; + case 0x16: + PC_READ(D); + break; + + //rla (4 cycles): + //Rotate 8-bit register A left through CF, store old bit7 in CF, old CF value becomes bit0. Reset SF, HCF, ZF: + case 0x17: + { + const unsigned oldcf = CF >> 8 & 1; + CF = A << 1; + A = (CF | oldcf) & 0xFF; + } + + HF2 = 0; + ZF = 1; + break; + + case 0x18: + jr_disp(); + break; + case 0x19: + add_hl_rr(D, E); + break; + case 0x1A: + READ(A, DE()); + break; + case 0x1B: + dec_rr(D, E); + break; + case 0x1C: + inc_r(E); + break; + case 0x1D: + dec_r(E); + break; + case 0x1E: + PC_READ(E); + break; + + //rra (4 cycles): + //Rotate 8-bit register A right through CF, store old bit0 in CF, old CF value becomes bit7. Reset SF, HCF, ZF: + case 0x1F: + { + const unsigned oldcf = CF & 0x100; + CF = A << 8; + A = (A | oldcf) >> 1; + } + + HF2 = 0; + ZF = 1; + break; + + //jr nz,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if ZF is unset: + case 0x20: + if (ZF & 0xFF) { + jr_disp(); + } else { + PC_MOD((PC + 1) & 0xFFFF); + } + break; + + case 0x21: + ld_rr_nn(H, L); + break; + + //ldi (hl),a (8 cycles): + //Put A into memory address in hl. Increment HL: + case 0x22: + { + unsigned addr = HL(); + + WRITE(addr, A); + + addr = (addr + 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x23: + inc_rr(H, L); + break; + case 0x24: + inc_r(H); + break; + case 0x25: + dec_r(H); + break; + case 0x26: + PC_READ(H); + break; + + + //daa (4 cycles): + //Adjust register A to correctly represent a BCD. Check ZF, HF and CF: + case 0x27: + /*{ + unsigned correction = ((A > 0x99) || (CF & 0x100)) ? 0x60 : 0x00; + + calcHF(HF1, HF2); + + if ((A & 0x0F) > 0x09 || (HF2 & 0x200)) + correction |= 0x06; + + HF1 = A; + HF2 = (HF2 & 0x400) | correction; + CF = (correction & 0x40) << 2; + A = (HF2 & 0x400) ? A - correction : (A + correction); + ZF = A; + }*/ + + calcHF(HF1, HF2); + + { + unsigned correction = (CF & 0x100) ? 0x60 : 0x00; + + if (HF2 & 0x200) + correction |= 0x06; + + if (!(HF2 &= 0x400)) { + if ((A & 0x0F) > 0x09) + correction |= 0x06; + + if (A > 0x99) + correction |= 0x60; + + A += correction; + } else + A -= correction; + + CF = correction << 2 & 0x100; + ZF = A; + A &= 0xFF; + } + break; + + //jr z,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if ZF is set: + case 0x28: + if (ZF & 0xFF) { + PC_MOD((PC + 1) & 0xFFFF); + } else { + jr_disp(); + } + break; + + //add hl,hl (8 cycles): + //add 16-bit register HL to HL, check flags except ZF: + case 0x29: + add_hl_rr(H, L); + break; + + //ldi a,(hl) (8 cycles): + //Put value at address in hl into A. Increment HL: + case 0x2A: + { + unsigned addr = HL(); + + READ(A, addr); + + addr = (addr + 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x2B: + dec_rr(H, L); + break; + case 0x2C: + inc_r(L); + break; + case 0x2D: + dec_r(L); + break; + case 0x2E: + PC_READ(L); + break; + + //cpl (4 cycles): + //Complement register A. (Flip all bits), set SF and HCF: + case 0x2F: /*setSubtractFlag(); setHalfCarryFlag();*/ + HF2 = 0x600; + A ^= 0xFF; + break; + + //jr nc,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if CF is unset: + case 0x30: + if (CF & 0x100) { + PC_MOD((PC + 1) & 0xFFFF); + } else { + jr_disp(); + } + break; + + //ld sp,nn (12 cycles) + //set sp to 16-bit value of next 2 bytes in memory + case 0x31: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + SP = h << 8 | l; + } + break; + + //ldd (hl),a (8 cycles): + //Put A into memory address in hl. Decrement HL: + case 0x32: + { + unsigned addr = HL(); + + WRITE(addr, A); + + addr = (addr - 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x33: + SP = (SP + 1) & 0xFFFF; + cycleCounter += 4; + break; + + //inc (hl) (12 cycles): + //Increment value at address in hl, check flags except CF: + case 0x34: + { + const unsigned addr = HL(); + + READ(HF2, addr); + ZF = HF2 + 1; + WRITE(addr, ZF & 0xFF); + HF2 |= 0x800; + } + break; + + //dec (hl) (12 cycles): + //Decrement value at address in hl, check flags except CF: + case 0x35: + { + const unsigned addr = HL(); + + READ(HF2, addr); + ZF = HF2 - 1; + WRITE(addr, ZF & 0xFF); + HF2 |= 0xC00; + } + break; + + //ld (hl),n (12 cycles): + //set memory at address in hl to value of next byte in memory: + case 0x36: + { + unsigned tmp; + + PC_READ(tmp); + WRITE(HL(), tmp); + } + break; + + //scf (4 cycles): + //Set CF. Unset SF and HCF: + case 0x37: /*setCarryFlag(); unsetSubtractFlag(); unsetHalfCarryFlag();*/ + CF = 0x100; + HF2 = 0; + break; + + //jr c,disp (12;8 cycles): + //Jump to value of next (signed) byte in memory+current address if CF is set: + case 0x38: //PC+=(((int8_t)memory.read(PC++))*CarryFlag()); Cycles(8); break; + if (CF & 0x100) { + jr_disp(); + } else { + PC_MOD((PC + 1) & 0xFFFF); + } + break; + + //add hl,sp (8 cycles): + //add SP to HL, check flags except ZF: + case 0x39: /*add_hl_rr(SP>>8, SP); break;*/ + CF = L + SP; + L = CF & 0xFF; + HF1 = H; + HF2 = ((CF ^ SP) & 0x100) | SP >> 8; + CF >>= 8; + CF += H; + H = CF & 0xFF; + cycleCounter += 4; + break; + + //ldd a,(hl) (8 cycles): + //Put value at address in hl into A. Decrement HL: + case 0x3A: + { + unsigned addr = HL(); + + A = memory.read(addr, cycleCounter); + cycleCounter += 4; + + addr = (addr - 1) & 0xFFFF; + L = addr; + H = addr >> 8; + } + break; + + case 0x3B: + SP = (SP - 1) & 0xFFFF; + cycleCounter += 4; + break; + case 0x3C: + inc_r(A); + break; + case 0x3D: + dec_r(A); + break; + case 0x3E: + PC_READ(A); + break; + + //ccf (4 cycles): + //Complement CF (unset if set vv.) Unset SF and HCF. + case 0x3F: /*complementCarryFlag(); unsetSubtractFlag(); unsetHalfCarryFlag();*/ + CF ^= 0x100; + HF2 = 0; + break; + + //ld r,r (4 cycles):next_irqEventTime + //ld r,(r) (8 cycles): + case 0x40: + B = B; + break; + case 0x41: + B = C; + break; + case 0x42: + B = D; + break; + case 0x43: + B = E; + break; + case 0x44: + B = H; + break; + case 0x45: + B = L; + break; + case 0x46: + READ(B, HL()); + break; + case 0x47: + B = A; + break; + case 0x48: + C = B; + break; + case 0x49: + C = C; + break; + case 0x4A: + C = D; + break; + case 0x4B: + C = E; + break; + case 0x4C: + C = H; + break; + case 0x4D: + C = L; + break; + case 0x4E: + READ(C, HL()); + break; + case 0x4F: + C = A; + break; + case 0x50: + D = B; + break; + case 0x51: + D = C; + break; + case 0x52: + D = D; + break; + case 0x53: + D = E; + break; + case 0x54: + D = H; + break; + case 0x55: + D = L; + break; + case 0x56: + READ(D, HL()); + break; + case 0x57: + D = A; + break; + case 0x58: + E = B; + break; + case 0x59: + E = C; + break; + case 0x5A: + E = D; + break; + case 0x5B: + E = E; + break; + case 0x5C: + E = H; + break; + case 0x5D: + E = L; + break; + case 0x5E: + READ(E, HL()); + break; + case 0x5F: + E = A; + break; + case 0x60: + H = B; + break; + case 0x61: + H = C; + break; + case 0x62: + H = D; + break; + case 0x63: + H = E; + break; + case 0x64: + H = H; + break; + case 0x65: + H = L; + break; + case 0x66: + READ(H, HL()); + break; + case 0x67: + H = A; + break; + case 0x68: + L = B; + break; + case 0x69: + L = C; + break; + case 0x6A: + L = D; + break; + case 0x6B: + L = E; + break; + case 0x6C: + L = H; + break; + case 0x6D: + L = L; + break; + case 0x6E: + READ(L, HL()); + break; + case 0x6F: + L = A; + break; + case 0x70: + WRITE(HL(), B); + break; + case 0x71: + WRITE(HL(), C); + break; + case 0x72: + WRITE(HL(), D); + break; + case 0x73: + WRITE(HL(), E); + break; + case 0x74: + WRITE(HL(), H); + break; + case 0x75: + WRITE(HL(), L); + break; + + //halt (4 cycles): + case 0x76: + if (!memory.ime() && (memory.ff_read(0xFF0F, cycleCounter) & memory.ff_read(0xFFFF, cycleCounter) & 0x1F)) { + if (memory.isCgb()) + cycleCounter += 4; + else + skip = true; + } else { + memory.halt(); + + if (cycleCounter < memory.nextEventTime()) { + const unsigned long cycles = memory.nextEventTime() - cycleCounter; + cycleCounter += cycles + (-cycles & 3); + } + } + + break; + case 0x77: + WRITE(HL(), A); + break; + case 0x78: + A = B; + break; + case 0x79: + A = C; + break; + case 0x7A: + A = D; + break; + case 0x7B: + A = E; + break; + case 0x7C: + A = H; + break; + case 0x7D: + A = L; + break; + case 0x7E: + READ(A, HL()); + break; + case 0x7F: + // A = A; + break; + case 0x80: + add_a_u8(B); + break; + case 0x81: + add_a_u8(C); + break; + case 0x82: + add_a_u8(D); + break; + case 0x83: + add_a_u8(E); + break; + case 0x84: + add_a_u8(H); + break; + case 0x85: + add_a_u8(L); + break; + case 0x86: + { + unsigned data; + + READ(data, HL()); + + add_a_u8(data); + } + break; + case 0x87: + add_a_u8(A); + break; + case 0x88: + adc_a_u8(B); + break; + case 0x89: + adc_a_u8(C); + break; + case 0x8A: + adc_a_u8(D); + break; + case 0x8B: + adc_a_u8(E); + break; + case 0x8C: + adc_a_u8(H); + break; + case 0x8D: + adc_a_u8(L); + break; + case 0x8E: + { + unsigned data; + + READ(data, HL()); + + adc_a_u8(data); + } + break; + case 0x8F: + adc_a_u8(A); + break; + case 0x90: + sub_a_u8(B); + break; + case 0x91: + sub_a_u8(C); + break; + case 0x92: + sub_a_u8(D); + break; + case 0x93: + sub_a_u8(E); + break; + case 0x94: + sub_a_u8(H); + break; + case 0x95: + sub_a_u8(L); + break; + case 0x96: + { + unsigned data; + + READ(data, HL()); + + sub_a_u8(data); + } + break; + //A-A is always 0: + case 0x97: + HF2 = 0x400; + CF = ZF = A = 0; + break; + case 0x98: + sbc_a_u8(B); + break; + case 0x99: + sbc_a_u8(C); + break; + case 0x9A: + sbc_a_u8(D); + break; + case 0x9B: + sbc_a_u8(E); + break; + case 0x9C: + sbc_a_u8(H); + break; + case 0x9D: + sbc_a_u8(L); + break; + case 0x9E: + { + unsigned data; + + READ(data, HL()); + + sbc_a_u8(data); + } + break; + case 0x9F: + sbc_a_u8(A); + break; + case 0xA0: + and_a_u8(B); + break; + case 0xA1: + and_a_u8(C); + break; + case 0xA2: + and_a_u8(D); + break; + case 0xA3: + and_a_u8(E); + break; + case 0xA4: + and_a_u8(H); + break; + case 0xA5: + and_a_u8(L); + break; + case 0xA6: + { + unsigned data; + + READ(data, HL()); + + and_a_u8(data); + } + break; + //A&A will always be A: + case 0xA7: + ZF = A; + CF = 0; + HF2 = 0x200; + break; + case 0xA8: + xor_a_u8(B); + break; + case 0xA9: + xor_a_u8(C); + break; + case 0xAA: + xor_a_u8(D); + break; + case 0xAB: + xor_a_u8(E); + break; + case 0xAC: + xor_a_u8(H); + break; + case 0xAD: + xor_a_u8(L); + break; + case 0xAE: + { + unsigned data; + + READ(data, HL()); + + xor_a_u8(data); + } + break; + //A^A will always be 0: + case 0xAF: + CF = HF2 = ZF = A = 0; + break; + case 0xB0: + or_a_u8(B); + break; + case 0xB1: + or_a_u8(C); + break; + case 0xB2: + or_a_u8(D); + break; + case 0xB3: + or_a_u8(E); + break; + case 0xB4: + or_a_u8(H); + break; + case 0xB5: + or_a_u8(L); + break; + case 0xB6: + { + unsigned data; + + READ(data, HL()); + + or_a_u8(data); + } + break; + //A|A will always be A: + case 0xB7: + ZF = A; + HF2 = CF = 0; + break; + case 0xB8: + cp_a_u8(B); + break; + case 0xB9: + cp_a_u8(C); + break; + case 0xBA: + cp_a_u8(D); + break; + case 0xBB: + cp_a_u8(E); + break; + case 0xBC: + cp_a_u8(H); + break; + case 0xBD: + cp_a_u8(L); + break; + case 0xBE: + { + unsigned data; + + READ(data, HL()); + + cp_a_u8(data); + } + break; + //A always equals A: + case 0xBF: + CF = ZF = 0; + HF2 = 0x400; + break; + + //ret nz (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if ZF is unset: + case 0xC0: + cycleCounter += 4; + + if (ZF & 0xFF) { + ret(); + } + break; + + case 0xC1: + pop_rr(B, C); + break; + + //jp nz,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if ZF is unset: + case 0xC2: + if (ZF & 0xFF) { + jp_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xC3: + jp_nn(); + break; + + //call nz,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if ZF is unset: + case 0xC4: + if (ZF & 0xFF) { + call_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xC5: + push_rr(B, C); + break; + case 0xC6: + { + unsigned data; + + PC_READ(data); + + add_a_u8(data); + } + break; + case 0xC7: + rst_n(0x00); + break; + + //ret z (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if ZF is set: + case 0xC8: + cycleCounter += 4; + + if (!(ZF & 0xFF)) { + ret(); + } + + break; + + //ret (16 cycles): + //Pop two bytes from the stack and jump to that address: + case 0xC9: + ret(); + break; + + //jp z,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if ZF is set: + case 0xCA: + if (ZF & 0xFF) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + jp_nn(); + } + break; + + + //CB OPCODES (Shifts, rotates and bits): + case 0xCB: + PC_READ(opcode); + + switch (opcode) { + case 0x00: + rlc_r(B); + break; + case 0x01: + rlc_r(C); + break; + case 0x02: + rlc_r(D); + break; + case 0x03: + rlc_r(E); + break; + case 0x04: + rlc_r(H); + break; + case 0x05: + rlc_r(L); + break; + //rlc (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL left, store old bit7 in CF. Reset SF and HCF. Check ZF: + case 0x06: + { + const unsigned addr = HL(); + + READ(CF, addr); + CF <<= 1; + + ZF = CF | (CF >> 8); + + WRITE(addr, ZF & 0xFF); + + HF2 = 0; + } + break; + case 0x07: + rlc_r(A); + break; + case 0x08: + rrc_r(B); + break; + case 0x09: + rrc_r(C); + break; + case 0x0A: + rrc_r(D); + break; + case 0x0B: + rrc_r(E); + break; + case 0x0C: + rrc_r(H); + break; + case 0x0D: + rrc_r(L); + break; + //rrc (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL right, store old bit0 in CF. Reset SF and HCF. Check ZF: + case 0x0E: + { + const unsigned addr = HL(); + + READ(ZF, addr); + + CF = ZF << 8; + + WRITE(addr, (ZF | CF) >> 1 & 0xFF); + + HF2 = 0; + } + break; + case 0x0F: + rrc_r(A); + break; + case 0x10: + rl_r(B); + break; + case 0x11: + rl_r(C); + break; + case 0x12: + rl_r(D); + break; + case 0x13: + rl_r(E); + break; + case 0x14: + rl_r(H); + break; + case 0x15: + rl_r(L); + break; + //rl (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL left thorugh CF, store old bit7 in CF, old CF value becoms bit0. Reset SF and HCF. Check ZF: + case 0x16: + { + const unsigned addr = HL(); + const unsigned oldcf = CF >> 8 & 1; + + READ(CF, addr); + CF <<= 1; + + ZF = CF | oldcf; + + WRITE(addr, ZF & 0xFF); + + HF2 = 0; + } + break; + case 0x17: + rl_r(A); + break; + case 0x18: + rr_r(B); + break; + case 0x19: + rr_r(C); + break; + case 0x1A: + rr_r(D); + break; + case 0x1B: + rr_r(E); + break; + case 0x1C: + rr_r(H); + break; + case 0x1D: + rr_r(L); + break; + //rr (hl) (16 cycles): + //Rotate 8-bit value stored at address in HL right thorugh CF, store old bit0 in CF, old CF value becoms bit7. Reset SF and HCF. Check ZF: + case 0x1E: + { + const unsigned addr = HL(); + + READ(ZF, addr); + + const unsigned oldcf = CF & 0x100; + CF = ZF << 8; + ZF = (ZF | oldcf) >> 1; + + WRITE(addr, ZF); + + HF2 = 0; + } + break; + case 0x1F: + rr_r(A); + break; + case 0x20: + sla_r(B); + break; + case 0x21: + sla_r(C); + break; + case 0x22: + sla_r(D); + break; + case 0x23: + sla_r(E); + break; + case 0x24: + sla_r(H); + break; + case 0x25: + sla_r(L); + break; + //sla (hl) (16 cycles): + //Shift 8-bit value stored at address in HL left, store old bit7 in CF. Reset SF and HCF. Check ZF: + case 0x26: + { + const unsigned addr = HL(); + + READ(CF, addr); + CF <<= 1; + + ZF = CF; + + WRITE(addr, ZF & 0xFF); + + HF2 = 0; + } + break; + case 0x27: + sla_r(A); + break; + case 0x28: + sra_r(B); + break; + case 0x29: + sra_r(C); + break; + case 0x2A: + sra_r(D); + break; + case 0x2B: + sra_r(E); + break; + case 0x2C: + sra_r(H); + break; + case 0x2D: + sra_r(L); + break; + //sra (hl) (16 cycles): + //Shift 8-bit value stored at address in HL right, store old bit0 in CF, bit7=old bit7. Reset SF and HCF. Check ZF: + case 0x2E: + { + const unsigned addr = HL(); + + READ(CF, addr); + + ZF = CF >> 1; + + WRITE(addr, ZF | (CF & 0x80)); + + CF <<= 8; + HF2 = 0; + } + break; + case 0x2F: + sra_r(A); + break; + case 0x30: + swap_r(B); + break; + case 0x31: + swap_r(C); + break; + case 0x32: + swap_r(D); + break; + case 0x33: + swap_r(E); + break; + case 0x34: + swap_r(H); + break; + case 0x35: + swap_r(L); + break; + //swap (hl) (16 cycles): + //Swap upper and lower nibbles of 8-bit value stored at address in HL, reset flags, check zero flag: + case 0x36: + { + const unsigned addr = HL(); + + READ(ZF, addr); + + WRITE(addr, (ZF << 4 | ZF >> 4) & 0xFF); + + CF = HF2 = 0; + } + break; + case 0x37: + swap_r(A); + break; + case 0x38: + srl_r(B); + break; + case 0x39: + srl_r(C); + break; + case 0x3A: + srl_r(D); + break; + case 0x3B: + srl_r(E); + break; + case 0x3C: + srl_r(H); + break; + case 0x3D: + srl_r(L); + break; + //srl (hl) (16 cycles): + //Shift 8-bit value stored at address in HL right, store old bit0 in CF. Reset SF and HCF. Check ZF: + case 0x3E: + { + const unsigned addr = HL(); + + READ(CF, addr); + + ZF = CF >> 1; + + WRITE(addr, ZF); + + CF <<= 8; + HF2 = 0; + } + break; + case 0x3F: + srl_r(A); + break; + case 0x40: + bit0_u8(B); + break; + case 0x41: + bit0_u8(C); + break; + case 0x42: + bit0_u8(D); + break; + case 0x43: + bit0_u8(E); + break; + case 0x44: + bit0_u8(H); + break; + case 0x45: + bit0_u8(L); + break; + case 0x46: + { + unsigned data; + + READ(data, HL()); + + bit0_u8(data); + } + break; + case 0x47: + bit0_u8(A); + break; + case 0x48: + bit1_u8(B); + break; + case 0x49: + bit1_u8(C); + break; + case 0x4A: + bit1_u8(D); + break; + case 0x4B: + bit1_u8(E); + break; + case 0x4C: + bit1_u8(H); + break; + case 0x4D: + bit1_u8(L); + break; + case 0x4E: + { + unsigned data; + + READ(data, HL()); + + bit1_u8(data); + } + break; + case 0x4F: + bit1_u8(A); + break; + case 0x50: + bit2_u8(B); + break; + case 0x51: + bit2_u8(C); + break; + case 0x52: + bit2_u8(D); + break; + case 0x53: + bit2_u8(E); + break; + case 0x54: + bit2_u8(H); + break; + case 0x55: + bit2_u8(L); + break; + case 0x56: + { + unsigned data; + + READ(data, HL()); + + bit2_u8(data); + } + break; + case 0x57: + bit2_u8(A); + break; + case 0x58: + bit3_u8(B); + break; + case 0x59: + bit3_u8(C); + break; + case 0x5A: + bit3_u8(D); + break; + case 0x5B: + bit3_u8(E); + break; + case 0x5C: + bit3_u8(H); + break; + case 0x5D: + bit3_u8(L); + break; + case 0x5E: + { + unsigned data; + + READ(data, HL()); + + bit3_u8(data); + } + break; + case 0x5F: + bit3_u8(A); + break; + case 0x60: + bit4_u8(B); + break; + case 0x61: + bit4_u8(C); + break; + case 0x62: + bit4_u8(D); + break; + case 0x63: + bit4_u8(E); + break; + case 0x64: + bit4_u8(H); + break; + case 0x65: + bit4_u8(L); + break; + case 0x66: + { + unsigned data; + + READ(data, HL()); + + bit4_u8(data); + } + break; + case 0x67: + bit4_u8(A); + break; + case 0x68: + bit5_u8(B); + break; + case 0x69: + bit5_u8(C); + break; + case 0x6A: + bit5_u8(D); + break; + case 0x6B: + bit5_u8(E); + break; + case 0x6C: + bit5_u8(H); + break; + case 0x6D: + bit5_u8(L); + break; + case 0x6E: + { + unsigned data; + + READ(data, HL()); + + bit5_u8(data); + } + break; + case 0x6F: + bit5_u8(A); + break; + case 0x70: + bit6_u8(B); + break; + case 0x71: + bit6_u8(C); + break; + case 0x72: + bit6_u8(D); + break; + case 0x73: + bit6_u8(E); + break; + case 0x74: + bit6_u8(H); + break; + case 0x75: + bit6_u8(L); + break; + case 0x76: + { + unsigned data; + + READ(data, HL()); + + bit6_u8(data); + } + break; + case 0x77: + bit6_u8(A); + break; + case 0x78: + bit7_u8(B); + break; + case 0x79: + bit7_u8(C); + break; + case 0x7A: + bit7_u8(D); + break; + case 0x7B: + bit7_u8(E); + break; + case 0x7C: + bit7_u8(H); + break; + case 0x7D: + bit7_u8(L); + break; + case 0x7E: + { + unsigned data; + + READ(data, HL()); + + bit7_u8(data); + } + break; + case 0x7F: + bit7_u8(A); + break; + case 0x80: + res0_r(B); + break; + case 0x81: + res0_r(C); + break; + case 0x82: + res0_r(D); + break; + case 0x83: + res0_r(E); + break; + case 0x84: + res0_r(H); + break; + case 0x85: + res0_r(L); + break; + case 0x86: + resn_mem_hl(0); + break; + case 0x87: + res0_r(A); + break; + case 0x88: + res1_r(B); + break; + case 0x89: + res1_r(C); + break; + case 0x8A: + res1_r(D); + break; + case 0x8B: + res1_r(E); + break; + case 0x8C: + res1_r(H); + break; + case 0x8D: + res1_r(L); + break; + case 0x8E: + resn_mem_hl(1); + break; + case 0x8F: + res1_r(A); + break; + case 0x90: + res2_r(B); + break; + case 0x91: + res2_r(C); + break; + case 0x92: + res2_r(D); + break; + case 0x93: + res2_r(E); + break; + case 0x94: + res2_r(H); + break; + case 0x95: + res2_r(L); + break; + case 0x96: + resn_mem_hl(2); + break; + case 0x97: + res2_r(A); + break; + case 0x98: + res3_r(B); + break; + case 0x99: + res3_r(C); + break; + case 0x9A: + res3_r(D); + break; + case 0x9B: + res3_r(E); + break; + case 0x9C: + res3_r(H); + break; + case 0x9D: + res3_r(L); + break; + case 0x9E: + resn_mem_hl(3); + break; + case 0x9F: + res3_r(A); + break; + case 0xA0: + res4_r(B); + break; + case 0xA1: + res4_r(C); + break; + case 0xA2: + res4_r(D); + break; + case 0xA3: + res4_r(E); + break; + case 0xA4: + res4_r(H); + break; + case 0xA5: + res4_r(L); + break; + case 0xA6: + resn_mem_hl(4); + break; + case 0xA7: + res4_r(A); + break; + case 0xA8: + res5_r(B); + break; + case 0xA9: + res5_r(C); + break; + case 0xAA: + res5_r(D); + break; + case 0xAB: + res5_r(E); + break; + case 0xAC: + res5_r(H); + break; + case 0xAD: + res5_r(L); + break; + case 0xAE: + resn_mem_hl(5); + break; + case 0xAF: + res5_r(A); + break; + case 0xB0: + res6_r(B); + break; + case 0xB1: + res6_r(C); + break; + case 0xB2: + res6_r(D); + break; + case 0xB3: + res6_r(E); + break; + case 0xB4: + res6_r(H); + break; + case 0xB5: + res6_r(L); + break; + case 0xB6: + resn_mem_hl(6); + break; + case 0xB7: + res6_r(A); + break; + case 0xB8: + res7_r(B); + break; + case 0xB9: + res7_r(C); + break; + case 0xBA: + res7_r(D); + break; + case 0xBB: + res7_r(E); + break; + case 0xBC: + res7_r(H); + break; + case 0xBD: + res7_r(L); + break; + case 0xBE: + resn_mem_hl(7); + break; + case 0xBF: + res7_r(A); + break; + case 0xC0: + set0_r(B); + break; + case 0xC1: + set0_r(C); + break; + case 0xC2: + set0_r(D); + break; + case 0xC3: + set0_r(E); + break; + case 0xC4: + set0_r(H); + break; + case 0xC5: + set0_r(L); + break; + case 0xC6: + setn_mem_hl(0); + break; + case 0xC7: + set0_r(A); + break; + case 0xC8: + set1_r(B); + break; + case 0xC9: + set1_r(C); + break; + case 0xCA: + set1_r(D); + break; + case 0xCB: + set1_r(E); + break; + case 0xCC: + set1_r(H); + break; + case 0xCD: + set1_r(L); + break; + case 0xCE: + setn_mem_hl(1); + break; + case 0xCF: + set1_r(A); + break; + case 0xD0: + set2_r(B); + break; + case 0xD1: + set2_r(C); + break; + case 0xD2: + set2_r(D); + break; + case 0xD3: + set2_r(E); + break; + case 0xD4: + set2_r(H); + break; + case 0xD5: + set2_r(L); + break; + case 0xD6: + setn_mem_hl(2); + break; + case 0xD7: + set2_r(A); + break; + case 0xD8: + set3_r(B); + break; + case 0xD9: + set3_r(C); + break; + case 0xDA: + set3_r(D); + break; + case 0xDB: + set3_r(E); + break; + case 0xDC: + set3_r(H); + break; + case 0xDD: + set3_r(L); + break; + case 0xDE: + setn_mem_hl(3); + break; + case 0xDF: + set3_r(A); + break; + case 0xE0: + set4_r(B); + break; + case 0xE1: + set4_r(C); + break; + case 0xE2: + set4_r(D); + break; + case 0xE3: + set4_r(E); + break; + case 0xE4: + set4_r(H); + break; + case 0xE5: + set4_r(L); + break; + case 0xE6: + setn_mem_hl(4); + break; + case 0xE7: + set4_r(A); + break; + case 0xE8: + set5_r(B); + break; + case 0xE9: + set5_r(C); + break; + case 0xEA: + set5_r(D); + break; + case 0xEB: + set5_r(E); + break; + case 0xEC: + set5_r(H); + break; + case 0xED: + set5_r(L); + break; + case 0xEE: + setn_mem_hl(5); + break; + case 0xEF: + set5_r(A); + break; + case 0xF0: + set6_r(B); + break; + case 0xF1: + set6_r(C); + break; + case 0xF2: + set6_r(D); + break; + case 0xF3: + set6_r(E); + break; + case 0xF4: + set6_r(H); + break; + case 0xF5: + set6_r(L); + break; + case 0xF6: + setn_mem_hl(6); + break; + case 0xF7: + set6_r(A); + break; + case 0xF8: + set7_r(B); + break; + case 0xF9: + set7_r(C); + break; + case 0xFA: + set7_r(D); + break; + case 0xFB: + set7_r(E); + break; + case 0xFC: + set7_r(H); + break; + case 0xFD: + set7_r(L); + break; + case 0xFE: + setn_mem_hl(7); + break; + case 0xFF: + set7_r(A); + break; +// default: break; + } + break; + + + //call z,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if ZF is set: + case 0xCC: + if (ZF & 0xFF) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + call_nn(); + } + break; + + case 0xCD: + call_nn(); + break; + case 0xCE: + { + unsigned data; + + PC_READ(data); + + adc_a_u8(data); + } + break; + case 0xCF: + rst_n(0x08); + break; + + //ret nc (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if CF is unset: + case 0xD0: + cycleCounter += 4; + + if (!(CF & 0x100)) { + ret(); + } + + break; + + case 0xD1: + pop_rr(D, E); + break; + + //jp nc,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if CF is unset: + case 0xD2: + if (CF & 0x100) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + jp_nn(); + } + break; + + case 0xD3: /*doesn't exist*/ + skip = true; + memory.di(); + break; + + //call nc,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if CF is unset: + case 0xD4: + if (CF & 0x100) { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } else { + call_nn(); + } + break; + + case 0xD5: + push_rr(D, E); + break; + case 0xD6: + { + unsigned data; + + PC_READ(data); + + sub_a_u8(data); + } + break; + case 0xD7: + rst_n(0x10); + break; + + //ret c (20;8 cycles): + //Pop two bytes from the stack and jump to that address, if CF is set: + case 0xD8: + cycleCounter += 4; + + if (CF & 0x100) { + ret(); + } + + break; + + //reti (16 cycles): + //Pop two bytes from the stack and jump to that address, then enable interrupts: + case 0xD9: + { + unsigned l, h; + + pop_rr(h, l); + + memory.ei(cycleCounter); + + PC_MOD(h << 8 | l); + } + break; + + //jp c,nn (16;12 cycles): + //Jump to address stored in next two bytes in memory if CF is set: + case 0xDA: //PC=( ((PC+2)*(1-CarryFlag())) + (((memory.read(PC+1)<<8)+memory.read(PC))*CarryFlag()) ); Cycles(12); break; + if (CF & 0x100) { + jp_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xDB: /*doesn't exist*/ + skip = true; + memory.di(); + break; + + //call z,nn (24;12 cycles): + //Push address of next instruction onto stack and then jump to address stored in next two bytes in memory, if CF is set: + case 0xDC: + if (CF & 0x100) { + call_nn(); + } else { + PC_MOD((PC + 2) & 0xFFFF); + cycleCounter += 4; + } + break; + + case 0xDD: /*doesn't exist*/ + skip = true; + memory.di(); + break; + + case 0xDE: + { + unsigned data; + + PC_READ(data); + + sbc_a_u8(data); + } + break; + case 0xDF: + rst_n(0x18); + break; + + //ld ($FF00+n),a (12 cycles): + //Put value in A into address (0xFF00 + next byte in memory): + case 0xE0: + { + unsigned tmp; + + PC_READ(tmp); + + FF_WRITE(0xFF00 | tmp, A); + } + break; + + case 0xE1: + pop_rr(H, L); + break; + + //ld ($FF00+C),a (8 ycles): + //Put A into address (0xFF00 + register C): + case 0xE2: + FF_WRITE(0xFF00 | C, A); + break; + case 0xE3: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xE4: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xE5: + push_rr(H, L); + break; + case 0xE6: + { + unsigned data; + + PC_READ(data); + + and_a_u8(data); + } + break; + case 0xE7: + rst_n(0x20); + break; + + //add sp,n (16 cycles): + //Add next (signed) byte in memory to SP, reset ZF and SF, check HCF and CF: + case 0xE8: + /*{ + int8_t tmp = int8_t(memory.pc_read(PC++, cycleCounter)); + HF2 = (((SP & 0xFFF) + tmp) >> 3) & 0x200; + CF = SP + tmp; + SP = CF; + CF >>= 8; + ZF = 1; + cycleCounter += 12; + }*/ + sp_plus_n(SP); + cycleCounter += 4; + break; + + //jp hl (4 cycles): + //Jump to address in hl: + case 0xE9: + PC = HL(); + break; + + //ld (nn),a (16 cycles): + //set memory at address given by the next 2 bytes to value in A: + //Incrementing PC before call, because of possible interrupt. + case 0xEA: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + WRITE(h << 8 | l, A); + } + break; + + case 0xEB: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xEC: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xED: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xEE: + { + unsigned data; + + PC_READ(data); + + xor_a_u8(data); + } + break; + case 0xEF: + rst_n(0x28); + break; + + //ld a,($FF00+n) (12 cycles): + //Put value at address (0xFF00 + next byte in memory) into A: + case 0xF0: + { + unsigned tmp; + + PC_READ(tmp); + + FF_READ(A, 0xFF00 | tmp); + } + break; + + case 0xF1: /*pop_rr(A, F); Cycles(12); break;*/ + { + unsigned F; + + pop_rr(A, F); + + FROM_F(F); + } + break; + + //ld a,($FF00+C) (8 cycles): + //Put value at address (0xFF00 + register C) into A: + case 0xF2: + FF_READ(A, 0xFF00 | C); + break; + + //di (4 cycles): + case 0xF3: + memory.di(); + break; + + case 0xF4: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xF5: /*push_rr(A, F); Cycles(16); break;*/ + calcHF(HF1, HF2); + + { + unsigned F = F(); + + push_rr(A, F); + } + break; + + case 0xF6: + { + unsigned data; + + PC_READ(data); + + or_a_u8(data); + } + break; + case 0xF7: + rst_n(0x30); + break; + + //ldhl sp,n (12 cycles): + //Put (sp+next (signed) byte in memory) into hl (unsets ZF and SF, may enable HF and CF): + case 0xF8: + /*{ + int8_t tmp = int8_t(memory.pc_read(PC++, cycleCounter)); + HF2 = (((SP & 0xFFF) + tmp) >> 3) & 0x200; + CF = SP + tmp; + L = CF; + CF >>= 8; + H = CF; + ZF = 1; + cycleCounter += 8; + }*/ + { + unsigned sum; + sp_plus_n(sum); + L = sum & 0xFF; + H = sum >> 8; + } + break; + + //ld sp,hl (8 cycles): + //Put value in HL into SP + case 0xF9: + SP = HL(); + cycleCounter += 4; + break; + + //ld a,(nn) (16 cycles): + //set A to value in memory at address given by the 2 next bytes. + case 0xFA: + { + unsigned l, h; + + PC_READ(l); + PC_READ(h); + + READ(A, h << 8 | l); + } + break; + + //ei (4 cycles): + //Enable Interrupts after next instruction: + case 0xFB: + memory.ei(cycleCounter); + break; + + case 0xFC: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xFD: /*doesn't exist*/ + skip = true; + memory.di(); + break; + case 0xFE: + { + unsigned data; + + PC_READ(data); + + cp_a_u8(data); + } + break; + case 0xFF: + rst_n(0x38); + break; +// default: break; + } + } + + //PC_ = PC; + cycleCounter = memory.event(cycleCounter); + } + + //A_ = A; + cycleCounter_ = cycleCounter; +} + +void CPU::GetRegs(int *dest) +{ + dest[0] = PC; + dest[1] = SP; + dest[2] = A; + dest[3] = B; + dest[4] = C; + dest[5] = D; + dest[6] = E; + dest[7] = F(); + dest[8] = H; + dest[9] = L; +} + +void CPU::SetInterruptAddresses(int *addrs, int numAddrs) +{ + interruptAddresses = addrs; + numInterruptAddresses = numAddrs; +} + +int CPU::GetHitInterruptAddress() +{ + return hitInterruptAddress; +} + +SYNCFUNC(CPU) +{ + SSS(memory); + NSS(cycleCounter_); + NSS(PC); + NSS(SP); + NSS(HF1); + NSS(HF2); + NSS(ZF); + NSS(CF); + NSS(A); + NSS(B); + NSS(C); + NSS(D); + NSS(E); + NSS(H); + NSS(L); + NSS(skip); +} + +} diff --git a/libgambatte/src/cpu.h b/libgambatte/src/cpu.h index b36112f7ea..46dc592015 100644 --- a/libgambatte/src/cpu.h +++ b/libgambatte/src/cpu.h @@ -1,136 +1,147 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef CPU_H -#define CPU_H - -#include "memory.h" -#include "newstate.h" - -namespace gambatte { - -class CPU { - Memory memory; - - unsigned long cycleCounter_; - - unsigned short PC; - unsigned short SP; - - unsigned HF1, HF2, ZF, CF; - - unsigned char A, B, C, D, E, /*F,*/ H, L; - - bool skip; - - void process(unsigned long cycles); - - void (*tracecallback)(void *); - -public: - - CPU(); -// void halt(); - -// unsigned interrupt(unsigned address, unsigned cycleCounter); - - long runFor(unsigned long cycles); - void setStatePtrs(SaveState &state); - void loadState(const SaveState &state); - void setLayers(unsigned mask) { memory.setLayers(mask); } - - void loadSavedata(const char *data) { memory.loadSavedata(data); } - int saveSavedataLength() {return memory.saveSavedataLength(); } - void saveSavedata(char *dest) { memory.saveSavedata(dest); } - - bool getMemoryArea(int which, unsigned char **data, int *length) { return memory.getMemoryArea(which, data, length); } - - void setVideoBuffer(uint_least32_t *const videoBuf, const int pitch) { - memory.setVideoBuffer(videoBuf, pitch); - } - - void setInputGetter(unsigned (*getInput)()) { - memory.setInputGetter(getInput); - } - - void setReadCallback(void (*callback)(unsigned)) { - memory.setReadCallback(callback); - } - - void setWriteCallback(void (*callback)(unsigned)) { - memory.setWriteCallback(callback); - } - - void setExecCallback(void (*callback)(unsigned)) { - memory.setExecCallback(callback); - } - - void setCDCallback(CDCallback cdc) { - memory.setCDCallback(cdc); - } - - void setTraceCallback(void (*callback)(void *)) { - tracecallback = callback; - } - - void setScanlineCallback(void (*callback)(), int sl) { - memory.setScanlineCallback(callback, sl); - } - - void setRTCCallback(std::uint32_t (*callback)()) { - memory.setRTCCallback(callback); - } - - void setLinkCallback(void (*callback)()) { - memory.setLinkCallback(callback); - } - - int load(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat) { - return memory.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat); - } - - bool loaded() const { return memory.loaded(); } - const char * romTitle() const { return memory.romTitle(); } - - void setSoundBuffer(uint_least32_t *const buf) { memory.setSoundBuffer(buf); } - unsigned fillSoundBuffer() { return memory.fillSoundBuffer(cycleCounter_); } - - bool isCgb() const { return memory.isCgb(); } - - void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32) { - memory.setDmgPaletteColor(palNum, colorNum, rgb32); - } - - void setCgbPalette(unsigned *lut) { - memory.setCgbPalette(lut); - } - - //unsigned char ExternalRead(unsigned short addr) { return memory.read(addr, cycleCounter_); } - unsigned char ExternalRead(unsigned short addr) { return memory.peek(addr); } - void ExternalWrite(unsigned short addr, unsigned char val) { memory.write_nocb(addr, val, cycleCounter_); } - - int LinkStatus(int which) { return memory.LinkStatus(which); } - - void GetRegs(int *dest); - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CPU_H +#define CPU_H + +#include "memory.h" +#include "newstate.h" + +namespace gambatte { + +class CPU { + Memory memory; + + unsigned long cycleCounter_; + + unsigned short PC; + unsigned short SP; + + unsigned HF1, HF2, ZF, CF; + + unsigned char A, B, C, D, E, /*F,*/ H, L; + + bool skip; + + int *interruptAddresses; + int numInterruptAddresses; + int hitInterruptAddress; + + void process(unsigned long cycles); + + void (*tracecallback)(void *); + +public: + + CPU(); +// void halt(); + +// unsigned interrupt(unsigned address, unsigned cycleCounter); + + long runFor(unsigned long cycles); + void setStatePtrs(SaveState &state); + void loadState(const SaveState &state); + void setLayers(unsigned mask) { memory.setLayers(mask); } + + void loadSavedata(const char *data) { memory.loadSavedata(data); } + int saveSavedataLength() {return memory.saveSavedataLength(); } + void saveSavedata(char *dest) { memory.saveSavedata(dest); } + + bool getMemoryArea(int which, unsigned char **data, int *length) { return memory.getMemoryArea(which, data, length); } + + void setVideoBuffer(uint_least32_t *const videoBuf, const int pitch) { + memory.setVideoBuffer(videoBuf, pitch); + } + + void setInputGetter(unsigned (*getInput)()) { + memory.setInputGetter(getInput); + } + + void setReadCallback(void (*callback)(unsigned)) { + memory.setReadCallback(callback); + } + + void setWriteCallback(void (*callback)(unsigned)) { + memory.setWriteCallback(callback); + } + + void setExecCallback(void (*callback)(unsigned)) { + memory.setExecCallback(callback); + } + + void setCDCallback(CDCallback cdc) { + memory.setCDCallback(cdc); + } + + void setTraceCallback(void (*callback)(void *)) { + tracecallback = callback; + } + + void setScanlineCallback(void (*callback)(), int sl) { + memory.setScanlineCallback(callback, sl); + } + + void setRTCCallback(std::uint32_t (*callback)()) { + memory.setRTCCallback(callback); + } + + void setLinkCallback(void(*callback)()) { + memory.setLinkCallback(callback); + } + + int load(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat) { + return memory.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat); + } + + bool loaded() const { return memory.loaded(); } + const char * romTitle() const { return memory.romTitle(); } + + void setSoundBuffer(uint_least32_t *const buf) { memory.setSoundBuffer(buf); } + unsigned fillSoundBuffer() { return memory.fillSoundBuffer(cycleCounter_); } + + bool isCgb() const { return memory.isCgb(); } + + void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned rgb32) { + memory.setDmgPaletteColor(palNum, colorNum, rgb32); + } + + void setCgbPalette(unsigned *lut) { + memory.setCgbPalette(lut); + } + + unsigned char* cgbBiosBuffer() { return memory.cgbBiosBuffer(); } + unsigned char* dmgBiosBuffer() { return memory.dmgBiosBuffer(); } + bool gbIsCgb() { return memory.gbIsCgb(); } + + //unsigned char ExternalRead(unsigned short addr) { return memory.read(addr, cycleCounter_); } + unsigned char ExternalRead(unsigned short addr) { return memory.peek(addr); } + void ExternalWrite(unsigned short addr, unsigned char val) { memory.write_nocb(addr, val, cycleCounter_); } + + int LinkStatus(int which) { return memory.LinkStatus(which); } + + void GetRegs(int *dest); + + void SetInterruptAddresses(int *addrs, int numAddrs); + int GetHitInterruptAddress(); + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/gambatte.cpp b/libgambatte/src/gambatte.cpp index 5e9c59e63f..511024947a 100644 --- a/libgambatte/src/gambatte.cpp +++ b/libgambatte/src/gambatte.cpp @@ -26,12 +26,12 @@ namespace gambatte { struct GB::Priv { CPU cpu; - bool gbaCgbMode; + unsigned loadflags; unsigned layersMask; uint_least32_t vbuff[160*144]; - Priv() : gbaCgbMode(false), layersMask(LAYER_MASK_BG | LAYER_MASK_OBJ) + Priv() : loadflags(0), layersMask(LAYER_MASK_BG | LAYER_MASK_OBJ) { } @@ -94,8 +94,7 @@ void GB::reset(const std::uint32_t now) { SaveState state; p_->cpu.setStatePtrs(state); - - setInitState(state, p_->cpu.isCgb(), p_->gbaCgbMode, now); + setInitState(state, !(p_->loadflags & FORCE_DMG), p_->loadflags & GBA_CGB, p_->loadflags & TRUE_COLOR, now); p_->cpu.loadState(state); if (length > 0) { @@ -146,11 +145,12 @@ int GB::load(const char *romfiledata, unsigned romfilelength, const std::uint32_ // p_->cpu.saveSavedata(); const int failed = p_->cpu.load(romfiledata, romfilelength, flags & FORCE_DMG, flags & MULTICART_COMPAT); - + if (!failed) { SaveState state; p_->cpu.setStatePtrs(state); - setInitState(state, p_->cpu.isCgb(), p_->gbaCgbMode = flags & GBA_CGB, now); + p_->loadflags = flags; + setInitState(state, !(flags & FORCE_DMG), flags & GBA_CGB, flags & TRUE_COLOR, now); p_->cpu.loadState(state); //p_->cpu.loadSavedata(); } @@ -158,6 +158,16 @@ int GB::load(const char *romfiledata, unsigned romfilelength, const std::uint32_ return failed; } +int GB::loadGBCBios(const char* biosfiledata) { + memcpy(p_->cpu.cgbBiosBuffer(), biosfiledata, 0x900); + return 0; +} + +int GB::loadDMGBios(const char* biosfiledata) { + memcpy(p_->cpu.dmgBiosBuffer(), biosfiledata, 0x100); + return 0; +} + bool GB::isCgb() const { return p_->cpu.isCgb(); } @@ -228,10 +238,20 @@ void GB::GetRegs(int *dest) { p_->cpu.GetRegs(dest); } +void GB::SetInterruptAddresses(int *addrs, int numAddrs) +{ + p_->cpu.SetInterruptAddresses(addrs, numAddrs); +} + +int GB::GetHitInterruptAddress() +{ + return p_->cpu.GetHitInterruptAddress(); +} + SYNCFUNC(GB) { SSS(p_->cpu); - NSS(p_->gbaCgbMode); + NSS(p_->loadflags); NSS(p_->vbuff); } diff --git a/libgambatte/src/initstate.cpp b/libgambatte/src/initstate.cpp index 5f11104d08..c791a140de 100644 --- a/libgambatte/src/initstate.cpp +++ b/libgambatte/src/initstate.cpp @@ -1041,11 +1041,11 @@ static void setInitialCgbIoamhram(unsigned char *const ioamhram) { }; static const unsigned char ffxxDump[0x100] = { - 0xCF, 0x00, 0x7C, 0xFF, 0x44, 0x00, 0x00, 0xF8, + 0xCF, 0x00, 0x7C, 0xFF, 0x00, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE1, - 0x80, 0xBF, 0xF3, 0xFF, 0xBF, 0xFF, 0x3F, 0x00, + 0x80, 0x3F, 0x00, 0xFF, 0xBF, 0xFF, 0x3F, 0x00, 0xFF, 0xBF, 0x7F, 0xFF, 0x9F, 0xFF, 0xBF, 0xFF, - 0xFF, 0x00, 0x00, 0xBF, 0x77, 0xF3, 0xF1, 0xFF, + 0xFF, 0x00, 0x00, 0xBF, 0x00, 0x00, 0x70, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, @@ -1146,61 +1146,58 @@ static void setInitialDmgIoamhram(unsigned char *const ioamhram) { } // anon namespace -void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbMode, const std::uint32_t now) { +void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbMode, const bool trueColors, const std::uint32_t now) { static const unsigned char cgbObjpDump[0x40] = { - 0x00, 0x00, 0xF2, 0xAB, - 0x61, 0xC2, 0xD9, 0xBA, - 0x88, 0x6E, 0xDD, 0x63, - 0x28, 0x27, 0xFB, 0x9F, - 0x35, 0x42, 0xD6, 0xD4, - 0x50, 0x48, 0x57, 0x5E, - 0x23, 0x3E, 0x3D, 0xCA, - 0x71, 0x21, 0x37, 0xC0, - 0xC6, 0xB3, 0xFB, 0xF9, - 0x08, 0x00, 0x8D, 0x29, - 0xA3, 0x20, 0xDB, 0x87, - 0x62, 0x05, 0x5D, 0xD4, - 0x0E, 0x08, 0xFE, 0xAF, - 0x20, 0x02, 0xD7, 0xFF, - 0x07, 0x6A, 0x55, 0xEC, + 0x00, 0x00, 0xF2, 0xAB, + 0x61, 0xC2, 0xD9, 0xBA, + 0x88, 0x6E, 0xDD, 0x63, + 0x28, 0x27, 0xFB, 0x9F, + 0x35, 0x42, 0xD6, 0xD4, + 0x50, 0x48, 0x57, 0x5E, + 0x23, 0x3E, 0x3D, 0xCA, + 0x71, 0x21, 0x37, 0xC0, + 0xC6, 0xB3, 0xFB, 0xF9, + 0x08, 0x00, 0x8D, 0x29, + 0xA3, 0x20, 0xDB, 0x87, + 0x62, 0x05, 0x5D, 0xD4, + 0x0E, 0x08, 0xFE, 0xAF, + 0x20, 0x02, 0xD7, 0xFF, + 0x07, 0x6A, 0x55, 0xEC, 0x83, 0x40, 0x0B, 0x77 }; - - state.cpu.PC = 0x100; - state.cpu.SP = 0xFFFE; - state.cpu.A = cgb * 0x10 | 0x01; - state.cpu.B = cgb & gbaCgbMode; - state.cpu.C = 0x13; - state.cpu.D = 0x00; - state.cpu.E = 0xD8; - state.cpu.F = 0xB0; - state.cpu.H = 0x01; - state.cpu.L = 0x4D; - state.cpu.skip = false; - setInitialVram(state.mem.vram.ptr, cgb); - state.cpu.cycleCounter = cgb ? 0x102A0 : 0x102A0 + 0x8D2C; + state.cpu.cycleCounter = 8; + state.cpu.PC = 0; + state.cpu.SP = 0; + state.cpu.A = 0; + state.cpu.B = 0; + state.cpu.C = 0; + state.cpu.D = 0; + state.cpu.E = 0; + state.cpu.F = 0; + state.cpu.H = 0; + state.cpu.L = 0; + state.cpu.skip = false; + state.mem.biosMode = true; + state.mem.cgbSwitching = false; + state.mem.agbMode = gbaCgbMode; std::memset(state.mem.sram.ptr, 0xFF, state.mem.sram.getSz()); + + setInitialVram(state.mem.vram.ptr, cgb); if (cgb) { setInitialCgbWram(state.mem.wram.ptr); - } - else { - setInitialDmgWram(state.mem.wram.ptr); - } - - if (cgb) { setInitialCgbIoamhram(state.mem.ioamhram.ptr); - } - else { + } else { + setInitialDmgWram(state.mem.wram.ptr); setInitialDmgIoamhram(state.mem.ioamhram.ptr); } - - state.mem.ioamhram.ptr[0x140] = 0x91; - state.mem.ioamhram.ptr[0x104] = 0x1C; + + state.mem.ioamhram.ptr[0x104] = 0; + state.mem.ioamhram.ptr[0x140] = 0; state.mem.ioamhram.ptr[0x144] = 0x00; - + state.mem.divLastUpdate = 0; state.mem.timaLastUpdate = 0; state.mem.tmatime = DISABLED_TIME; @@ -1218,29 +1215,30 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.mem.enableRam = false; state.mem.rambankMode = false; state.mem.hdmaTransfer = false; + state.mem.gbIsCgb = cgb; - + for (unsigned i = 0x00; i < 0x40; i += 0x02) { - state.ppu.bgpData.ptr[i] = 0xFF; + state.ppu.bgpData.ptr[i ] = 0xFF; state.ppu.bgpData.ptr[i + 1] = 0x7F; } - + std::memcpy(state.ppu.objpData.ptr, cgbObjpDump, sizeof(cgbObjpDump)); - + if (!cgb) { state.ppu.bgpData.ptr[0] = state.mem.ioamhram.get()[0x147]; state.ppu.objpData.ptr[0] = state.mem.ioamhram.get()[0x148]; state.ppu.objpData.ptr[1] = state.mem.ioamhram.get()[0x149]; } - + for (unsigned pos = 0; pos < 80; ++pos) state.ppu.oamReaderBuf.ptr[pos] = state.mem.ioamhram.ptr[(pos * 2 & ~3) | (pos & 1)]; - + std::fill_n(state.ppu.oamReaderSzbuf.ptr, 40, false); std::memset(state.ppu.spAttribList, 0, sizeof(state.ppu.spAttribList)); std::memset(state.ppu.spByte0List, 0, sizeof(state.ppu.spByte0List)); std::memset(state.ppu.spByte1List, 0, sizeof(state.ppu.spByte1List)); - state.ppu.videoCycles = cgb ? 144 * 456ul + 164 : 153 * 456ul + 396; + state.ppu.videoCycles = 0; state.ppu.enableDisplayM0Time = state.cpu.cycleCounter; state.ppu.winYPos = 0xFF; state.ppu.xpos = 0; @@ -1254,7 +1252,7 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.ppu.state = 0; state.ppu.nextSprite = 0; state.ppu.currentSprite = 0; - state.ppu.lyc = state.mem.ioamhram.get()[0x145]; + state.ppu.lyc = state.mem.ioamhram.get()[0x145]; state.ppu.m0lyc = state.mem.ioamhram.get()[0x145]; state.ppu.weMaster = false; state.ppu.winDrawState = 0; @@ -1263,36 +1261,39 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.ppu.nextM0Irq = 0; state.ppu.oldWy = state.mem.ioamhram.get()[0x14A]; state.ppu.pendingLcdstatIrq = false; + state.ppu.isCgb = cgb; + state.ppu.trueColors = !trueColors; - state.spu.cycleCounter = 0x1000 | (state.cpu.cycleCounter >> 1 & 0xFFF); // spu.cycleCounter >> 12 & 7 represents the frame sequencer position. - + + state.spu.cycleCounter = 0; // spu.cycleCounter >> 12 & 7 represents the frame sequencer position. + state.spu.ch1.sweep.counter = SoundUnit::COUNTER_DISABLED; state.spu.ch1.sweep.shadow = 0; state.spu.ch1.sweep.nr0 = 0; state.spu.ch1.sweep.negging = false; - state.spu.ch1.duty.nextPosUpdate = (state.spu.cycleCounter & ~1) + 2048 * 2; + state.spu.ch1.duty.nextPosUpdate = (state.spu.cycleCounter & ~1ul) + 37 * 2; state.spu.ch1.duty.nr3 = 0; state.spu.ch1.duty.pos = 0; state.spu.ch1.env.counter = SoundUnit::COUNTER_DISABLED; state.spu.ch1.env.volume = 0; state.spu.ch1.lcounter.counter = SoundUnit::COUNTER_DISABLED; - state.spu.ch1.lcounter.lengthCounter = 0x40; + state.spu.ch1.lcounter.lengthCounter = 0; state.spu.ch1.nr4 = 0; state.spu.ch1.master = true; - - state.spu.ch2.duty.nextPosUpdate = (state.spu.cycleCounter & ~1) + 2048 * 2; + + state.spu.ch2.duty.nextPosUpdate = SoundUnit::COUNTER_DISABLED; state.spu.ch2.duty.nr3 = 0; state.spu.ch2.duty.pos = 0; - state.spu.ch2.env.counter = state.spu.cycleCounter - ((state.spu.cycleCounter - 0x1000) & 0x7FFF) + 8ul * 0x8000; + state.spu.ch2.env.counter = SoundUnit::COUNTER_DISABLED; state.spu.ch2.env.volume = 0; state.spu.ch2.lcounter.counter = SoundUnit::COUNTER_DISABLED; - state.spu.ch2.lcounter.lengthCounter = 0x40; + state.spu.ch2.lcounter.lengthCounter = 0; state.spu.ch2.nr4 = 0; state.spu.ch2.master = false; - + for (unsigned i = 0; i < 0x10; ++i) state.spu.ch3.waveRam.ptr[i] = state.mem.ioamhram.get()[0x130 + i]; - + state.spu.ch3.lcounter.counter = SoundUnit::COUNTER_DISABLED; state.spu.ch3.lcounter.lengthCounter = 0x100; state.spu.ch3.waveCounter = SoundUnit::COUNTER_DISABLED; @@ -1302,16 +1303,16 @@ void gambatte::setInitState(SaveState &state, const bool cgb, const bool gbaCgbM state.spu.ch3.wavePos = 0; state.spu.ch3.sampleBuf = 0; state.spu.ch3.master = false; - + state.spu.ch4.lfsr.counter = state.spu.cycleCounter + 4; state.spu.ch4.lfsr.reg = 0xFF; - state.spu.ch4.env.counter = state.spu.cycleCounter - ((state.spu.cycleCounter - 0x1000) & 0x7FFF) + 8ul * 0x8000; + state.spu.ch4.env.counter = SoundUnit::COUNTER_DISABLED; state.spu.ch4.env.volume = 0; state.spu.ch4.lcounter.counter = SoundUnit::COUNTER_DISABLED; - state.spu.ch4.lcounter.lengthCounter = 0x40; + state.spu.ch4.lcounter.lengthCounter = 0; state.spu.ch4.nr4 = 0; state.spu.ch4.master = false; - + state.rtc.baseTime = now; state.rtc.haltTime = state.rtc.baseTime; state.rtc.dataDh = 0; diff --git a/libgambatte/src/initstate.h b/libgambatte/src/initstate.h index 8d8ed5aaf6..36587e8512 100644 --- a/libgambatte/src/initstate.h +++ b/libgambatte/src/initstate.h @@ -22,7 +22,7 @@ #include namespace gambatte { -void setInitState(struct SaveState &state, bool cgb, bool gbaCgbMode, std::uint32_t now); +void setInitState(struct SaveState &state, bool cgb, bool gbaCgbMode, bool trueColors, std::uint32_t now); } #endif diff --git a/libgambatte/src/insertion_sort.h b/libgambatte/src/insertion_sort.h index 939ba07442..f6a3c991a4 100644 --- a/libgambatte/src/insertion_sort.h +++ b/libgambatte/src/insertion_sort.h @@ -1,51 +1,51 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre Aamås * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ - -#ifndef INSERTION_SORT_H -#define INSERTION_SORT_H - -#include - -template -void insertionSort(T *const start, T *const end, Less less) { - if (start >= end) - return; - - T *a = start; - - while (++a < end) { - const T e = *a; - - T *b = a; - - while (b != start && less(e, *(b - 1))) { - *b = *(b - 1); - b = b - 1; - } - - *b = e; - } -} - -template -inline void insertionSort(T *const start, T *const end) { - insertionSort(start, end, std::less()); -} - -#endif /*INSERTION_SORT_H*/ +/*************************************************************************** + * Copyright (C) 2007 by Sindre Aamås * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef INSERTION_SORT_H +#define INSERTION_SORT_H + +#include + +template +void insertionSort(T *const start, T *const end, Less less) { + if (start >= end) + return; + + T *a = start; + + while (++a < end) { + const T e = *a; + + T *b = a; + + while (b != start && less(e, *(b - 1))) { + *b = *(b - 1); + b = b - 1; + } + + *b = e; + } +} + +template +inline void insertionSort(T *const start, T *const end) { + insertionSort(start, end, std::less()); +} + +#endif /*INSERTION_SORT_H*/ diff --git a/libgambatte/src/interruptrequester.cpp b/libgambatte/src/interruptrequester.cpp index 5ed7869c39..c656ad2368 100644 --- a/libgambatte/src/interruptrequester.cpp +++ b/libgambatte/src/interruptrequester.cpp @@ -94,13 +94,13 @@ void InterruptRequester::setIfreg(const unsigned ifreg) { eventTimes.setValue(pendingIrqs() ? minIntTime : static_cast(DISABLED_TIME)); } -SYNCFUNC(InterruptRequester) -{ - SSS(eventTimes); - NSS(minIntTime); - NSS(ifreg_); - NSS(iereg_); - NSS(intFlags.flags_); -} +SYNCFUNC(InterruptRequester) +{ + SSS(eventTimes); + NSS(minIntTime); + NSS(ifreg_); + NSS(iereg_); + NSS(intFlags.flags_); +} } diff --git a/libgambatte/src/interruptrequester.h b/libgambatte/src/interruptrequester.h index c99efc73f6..8d22e35afb 100644 --- a/libgambatte/src/interruptrequester.h +++ b/libgambatte/src/interruptrequester.h @@ -21,7 +21,7 @@ #include "counterdef.h" #include "minkeeper.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { struct SaveState; @@ -62,6 +62,7 @@ public: void resetCc(unsigned long oldCc, unsigned long newCc); unsigned ifreg() const { return ifreg_; } + unsigned iereg() const { return iereg_; } unsigned pendingIrqs() const { return ifreg_ & iereg_; } bool ime() const { return intFlags.ime(); } bool halted() const { return intFlags.halted(); } @@ -81,7 +82,7 @@ public: void setEventTime(const MemEventId id, unsigned long value) { eventTimes.setValue(id, value); } unsigned long eventTime(MemEventId id) const { return eventTimes.value(id); } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; inline void flagHdmaReq(InterruptRequester *const intreq) { intreq->setEventTime(0); } diff --git a/libgambatte/src/mem/cartridge.cpp b/libgambatte/src/mem/cartridge.cpp index 548508ba3d..e062fb0c96 100644 --- a/libgambatte/src/mem/cartridge.cpp +++ b/libgambatte/src/mem/cartridge.cpp @@ -1,753 +1,750 @@ -/*************************************************************************** - * Copyright (C) 2007-2010 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#include "cartridge.h" -#include "../savestate.h" -#include -#include -#include -#include - -namespace gambatte { - -namespace { - -static unsigned toMulti64Rombank(const unsigned rombank) { - return (rombank >> 1 & 0x30) | (rombank & 0xF); -} - -class DefaultMbc : public Mbc { -public: - virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const { - return (addr< 0x4000) == (bank == 0); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - } -}; - -class Mbc0 : public DefaultMbc { - MemPtrs &memptrs; - bool enableRam; - -public: - explicit Mbc0(MemPtrs &memptrs) - : memptrs(memptrs), - enableRam(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - if (P < 0x2000) { - enableRam = (data & 0xF) == 0xA; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.enableRam = enableRam; - } - - virtual void loadState(const SaveState::Mem &ss) { - enableRam = ss.enableRam; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(enableRam); - } -}; - -static inline unsigned rambanks(const MemPtrs &memptrs) { - return static_cast(memptrs.rambankdataend() - memptrs.rambankdata()) / 0x2000; -} - -static inline unsigned rombanks(const MemPtrs &memptrs) { - return static_cast(memptrs.romdataend() - memptrs.romdata() ) / 0x4000; -} - -class Mbc1 : public DefaultMbc { - MemPtrs &memptrs; - unsigned char rombank; - unsigned char rambank; - bool enableRam; - bool rambankMode; - - static unsigned adjustedRombank(unsigned bank) { return bank & 0x1F ? bank : bank | 1; } - void setRambank() const { memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, rambank & (rambanks(memptrs) - 1)); } - void setRombank() const { memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); } - -public: - explicit Mbc1(MemPtrs &memptrs) - : memptrs(memptrs), - rombank(1), - rambank(0), - enableRam(false), - rambankMode(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P >> 13 & 3) { - case 0: - enableRam = (data & 0xF) == 0xA; - setRambank(); - break; - case 1: - rombank = rambankMode ? data & 0x1F : (rombank & 0x60) | (data & 0x1F); - setRombank(); - break; - case 2: - if (rambankMode) { - rambank = data & 3; - setRambank(); - } else { - rombank = (data << 5 & 0x60) | (rombank & 0x1F); - setRombank(); - } - - break; - case 3: - // Pretty sure this should take effect immediately, but I have a policy not to change old behavior - // unless I have something (eg. a verified test or a game) that justifies it. - rambankMode = data & 1; - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.rambank = rambank; - ss.enableRam = enableRam; - ss.rambankMode = rambankMode; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - rambank = ss.rambank; - enableRam = ss.enableRam; - rambankMode = ss.rambankMode; - setRambank(); - setRombank(); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(rambank); - NSS(enableRam); - NSS(rambankMode); - } -}; - -class Mbc1Multi64 : public Mbc { - MemPtrs &memptrs; - unsigned char rombank; - bool enableRam; - bool rombank0Mode; - - static unsigned adjustedRombank(unsigned bank) { return bank & 0x1F ? bank : bank | 1; } - - void setRombank() const { - if (rombank0Mode) { - const unsigned rb = toMulti64Rombank(rombank); - memptrs.setRombank0(rb & 0x30); - memptrs.setRombank(adjustedRombank(rb)); - } else { - memptrs.setRombank0(0); - memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); - } - } - -public: - explicit Mbc1Multi64(MemPtrs &memptrs) - : memptrs(memptrs), - rombank(1), - enableRam(false), - rombank0Mode(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P >> 13 & 3) { - case 0: - enableRam = (data & 0xF) == 0xA; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - break; - case 1: - rombank = (rombank & 0x60) | (data & 0x1F); - memptrs.setRombank(adjustedRombank(rombank0Mode ? toMulti64Rombank(rombank) : rombank & (rombanks(memptrs) - 1))); - break; - case 2: - rombank = (data << 5 & 0x60) | (rombank & 0x1F); - setRombank(); - break; - case 3: - rombank0Mode = data & 1; - setRombank(); - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.enableRam = enableRam; - ss.rambankMode = rombank0Mode; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - enableRam = ss.enableRam; - rombank0Mode = ss.rambankMode; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - setRombank(); - } - - virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const { - return (addr < 0x4000) == ((bank & 0xF) == 0); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(enableRam); - NSS(rombank0Mode); - } -}; - -class Mbc2 : public DefaultMbc { - MemPtrs &memptrs; - unsigned char rombank; - bool enableRam; - -public: - explicit Mbc2(MemPtrs &memptrs) - : memptrs(memptrs), - rombank(1), - enableRam(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P & 0x6100) { - case 0x0000: - enableRam = (data & 0xF) == 0xA; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - break; - case 0x2100: - rombank = data & 0xF; - memptrs.setRombank(rombank & (rombanks(memptrs) - 1)); - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.enableRam = enableRam; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - enableRam = ss.enableRam; - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); - memptrs.setRombank(rombank & (rombanks(memptrs) - 1)); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(enableRam); - } -}; - -class Mbc3 : public DefaultMbc { - MemPtrs &memptrs; - Rtc *const rtc; - unsigned char rombank; - unsigned char rambank; - bool enableRam; - - static unsigned adjustedRombank(unsigned bank) { return bank & 0x7F ? bank : bank | 1; } - void setRambank() const { - unsigned flags = enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0; - - if (rtc) { - rtc->set(enableRam, rambank); - - if (rtc->getActive()) - flags |= MemPtrs::RTC_EN; - } - - memptrs.setRambank(flags, rambank & (rambanks(memptrs) - 1)); - } - // we adjust the rombank before masking with size? this seems correct, as how would the mbc - // know that high rom address outputs were not connected - void setRombank() const { memptrs.setRombank(adjustedRombank(rombank) & (rombanks(memptrs) - 1)); } - -public: - Mbc3(MemPtrs &memptrs, Rtc *const rtc) - : memptrs(memptrs), - rtc(rtc), - rombank(1), - rambank(0), - enableRam(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P >> 13 & 3) { - case 0: - enableRam = (data & 0xF) == 0xA; - setRambank(); - break; - case 1: - rombank = data & 0x7F; - setRombank(); - break; - case 2: - rambank = data; - setRambank(); - break; - case 3: - if (rtc) - rtc->latch(data); - - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.rambank = rambank; - ss.enableRam = enableRam; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - rambank = ss.rambank; - enableRam = ss.enableRam; - setRambank(); - setRombank(); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(rambank); - NSS(enableRam); - } -}; - -class HuC1 : public DefaultMbc { - MemPtrs &memptrs; - unsigned char rombank; - unsigned char rambank; - bool enableRam; - bool rambankMode; - - void setRambank() const { - memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : MemPtrs::READ_EN, - rambankMode ? rambank & (rambanks(memptrs) - 1) : 0); - } - - void setRombank() const { memptrs.setRombank((rambankMode ? rombank : rambank << 6 | rombank) & (rombanks(memptrs) - 1)); } - -public: - explicit HuC1(MemPtrs &memptrs) - : memptrs(memptrs), - rombank(1), - rambank(0), - enableRam(false), - rambankMode(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P >> 13 & 3) { - case 0: - enableRam = (data & 0xF) == 0xA; - setRambank(); - break; - case 1: - rombank = data & 0x3F; - setRombank(); - break; - case 2: - rambank = data & 3; - rambankMode ? setRambank() : setRombank(); - break; - case 3: - rambankMode = data & 1; - setRambank(); - setRombank(); - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.rambank = rambank; - ss.enableRam = enableRam; - ss.rambankMode = rambankMode; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - rambank = ss.rambank; - enableRam = ss.enableRam; - rambankMode = ss.rambankMode; - setRambank(); - setRombank(); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(rambank); - NSS(enableRam); - NSS(rambankMode); - } -}; - -class Mbc5 : public DefaultMbc { - MemPtrs &memptrs; - unsigned short rombank; - unsigned char rambank; - bool enableRam; - - static unsigned adjustedRombank(const unsigned bank) { return bank; } - void setRambank() const { memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, rambank & (rambanks(memptrs) - 1)); } - void setRombank() const { memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); } - -public: - explicit Mbc5(MemPtrs &memptrs) - : memptrs(memptrs), - rombank(1), - rambank(0), - enableRam(false) - { - } - - virtual void romWrite(const unsigned P, const unsigned data) { - switch (P >> 13 & 3) { - case 0: - enableRam = (data & 0xF) == 0xA; - setRambank(); - break; - case 1: - rombank = P < 0x3000 ? (rombank & 0x100) | data - : (data << 8 & 0x100) | (rombank & 0xFF); - setRombank(); - break; - case 2: - rambank = data & 0xF; - setRambank(); - break; - case 3: - break; - } - } - - virtual void saveState(SaveState::Mem &ss) const { - ss.rombank = rombank; - ss.rambank = rambank; - ss.enableRam = enableRam; - } - - virtual void loadState(const SaveState::Mem &ss) { - rombank = ss.rombank; - rambank = ss.rambank; - enableRam = ss.enableRam; - setRambank(); - setRombank(); - } - - virtual void SyncState(NewState *ns, bool isReader) - { - NSS(rombank); - NSS(rambank); - NSS(enableRam); - } -}; - -static bool hasRtc(const unsigned headerByte0x147) { - switch (headerByte0x147) { - case 0x0F: - case 0x10: return true; - default: return false; - } -} - -} - -void Cartridge::setStatePtrs(SaveState &state) { - state.mem.vram.set(memptrs.vramdata(), memptrs.vramdataend() - memptrs.vramdata()); - state.mem.sram.set(memptrs.rambankdata(), memptrs.rambankdataend() - memptrs.rambankdata()); - state.mem.wram.set(memptrs.wramdata(0), memptrs.wramdataend() - memptrs.wramdata(0)); -} - -void Cartridge::loadState(const SaveState &state) { - rtc.loadState(state); - mbc->loadState(state.mem); -} - -static void enforce8bit(unsigned char *data, unsigned long sz) { - if (static_cast(0x100)) - while (sz--) - *data++ &= 0xFF; -} - -static unsigned pow2ceil(unsigned n) { - --n; - n |= n >> 1; - n |= n >> 2; - n |= n >> 4; - n |= n >> 8; - ++n; - - return n; -} - -int Cartridge::loadROM(const char *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { - //const std::auto_ptr rom(newFileInstance(romfile)); - - //if (rom->fail()) - // return -1; - - unsigned rambanks = 1; - unsigned rombanks = 2; - bool cgb = false; - enum Cartridgetype { PLAIN, MBC1, MBC2, MBC3, MBC5, HUC1 } type = PLAIN; - - { - unsigned char header[0x150]; - //rom->read(reinterpret_cast(header), sizeof header); - if (romfilelength >= sizeof header) - std::memcpy(header, romfiledata, sizeof header); - else - return -1; - - switch (header[0x0147]) { - case 0x00: std::puts("Plain ROM loaded."); type = PLAIN; break; - case 0x01: std::puts("MBC1 ROM loaded."); type = MBC1; break; - case 0x02: std::puts("MBC1 ROM+RAM loaded."); type = MBC1; break; - case 0x03: std::puts("MBC1 ROM+RAM+BATTERY loaded."); type = MBC1; break; - case 0x05: std::puts("MBC2 ROM loaded."); type = MBC2; break; - case 0x06: std::puts("MBC2 ROM+BATTERY loaded."); type = MBC2; break; - case 0x08: std::puts("Plain ROM with additional RAM loaded."); type = PLAIN; break; - case 0x09: std::puts("Plain ROM with additional RAM and Battery loaded."); type = PLAIN; break; - case 0x0B: std::puts("MM01 ROM not supported."); return -1; - case 0x0C: std::puts("MM01 ROM not supported."); return -1; - case 0x0D: std::puts("MM01 ROM not supported."); return -1; - case 0x0F: std::puts("MBC3 ROM+TIMER+BATTERY loaded."); type = MBC3; break; - case 0x10: std::puts("MBC3 ROM+TIMER+RAM+BATTERY loaded."); type = MBC3; break; - case 0x11: std::puts("MBC3 ROM loaded."); type = MBC3; break; - case 0x12: std::puts("MBC3 ROM+RAM loaded."); type = MBC3; break; - case 0x13: std::puts("MBC3 ROM+RAM+BATTERY loaded."); type = MBC3; break; - case 0x15: std::puts("MBC4 ROM not supported."); return -1; - case 0x16: std::puts("MBC4 ROM not supported."); return -1; - case 0x17: std::puts("MBC4 ROM not supported."); return -1; - case 0x19: std::puts("MBC5 ROM loaded."); type = MBC5; break; - case 0x1A: std::puts("MBC5 ROM+RAM loaded."); type = MBC5; break; - case 0x1B: std::puts("MBC5 ROM+RAM+BATTERY loaded."); type = MBC5; break; - case 0x1C: std::puts("MBC5+RUMBLE ROM not supported."); type = MBC5; break; - case 0x1D: std::puts("MBC5+RUMBLE+RAM ROM not suported."); type = MBC5; break; - case 0x1E: std::puts("MBC5+RUMBLE+RAM+BATTERY ROM not supported."); type = MBC5; break; - case 0xFC: std::puts("Pocket Camera ROM not supported."); return -1; - case 0xFD: std::puts("Bandai TAMA5 ROM not supported."); return -1; - case 0xFE: std::puts("HuC3 ROM not supported."); return -1; - case 0xFF: std::puts("HuC1 ROM+RAM+BATTERY loaded."); type = HUC1; break; - default: std::puts("Wrong data-format, corrupt or unsupported ROM."); return -1; - } - - /*switch (header[0x0148]) { - case 0x00: rombanks = 2; break; - case 0x01: rombanks = 4; break; - case 0x02: rombanks = 8; break; - case 0x03: rombanks = 16; break; - case 0x04: rombanks = 32; break; - case 0x05: rombanks = 64; break; - case 0x06: rombanks = 128; break; - case 0x07: rombanks = 256; break; - case 0x08: rombanks = 512; break; - case 0x52: rombanks = 72; break; - case 0x53: rombanks = 80; break; - case 0x54: rombanks = 96; break; - default: return -1; - } - - std::printf("rombanks: %u\n", rombanks);*/ - - switch (header[0x0149]) { - case 0x00: /*std::puts("No RAM");*/ rambanks = type == MBC2; break; - case 0x01: /*std::puts("2kB RAM");*/ /*rambankrom=1; break;*/ - case 0x02: /*std::puts("8kB RAM");*/ - rambanks = 1; - break; - case 0x03: /*std::puts("32kB RAM");*/ - rambanks = 4; - break; - case 0x04: /*std::puts("128kB RAM");*/ - rambanks = 16; - break; - case 0x05: /*std::puts("undocumented kB RAM");*/ - rambanks = 16; - break; - default: /*std::puts("Wrong data-format, corrupt or unsupported ROM loaded.");*/ - rambanks = 16; - break; - } - - cgb = header[0x0143] >> 7 & (1 ^ forceDmg); - //cgb = forceDmg ? false : true; - std::printf("cgb: %d\n", cgb); - } - - std::printf("rambanks: %u\n", rambanks); - - const std::size_t filesize = romfilelength; //rom->size(); - rombanks = std::max(pow2ceil(filesize / 0x4000), 2u); - std::printf("rombanks: %u\n", static_cast(filesize / 0x4000)); - - mbc.reset(); - - memptrs.reset(rombanks, rambanks, cgb ? 8 : 2); - rtc.set(false, 0); - - //rom->rewind(); - //rom->read(reinterpret_cast(memptrs.romdata()), (filesize / 0x4000) * 0x4000ul); - - std::memcpy(memptrs.romdata(), romfiledata, (filesize / 0x4000) * 0x4000ul); - std::memset(memptrs.romdata() + (filesize / 0x4000) * 0x4000ul, 0xFF, (rombanks - filesize / 0x4000) * 0x4000ul); - enforce8bit(memptrs.romdata(), rombanks * 0x4000ul); - - //if (rom->fail()) - // return -1; - - switch (type) { - case PLAIN: mbc.reset(new Mbc0(memptrs)); break; - case MBC1: - if (!rambanks && rombanks == 64 && multicartCompat) { - std::puts("Multi-ROM \"MBC1\" presumed"); - mbc.reset(new Mbc1Multi64(memptrs)); - } else - mbc.reset(new Mbc1(memptrs)); - - break; - case MBC2: mbc.reset(new Mbc2(memptrs)); break; - case MBC3: mbc.reset(new Mbc3(memptrs, hasRtc(memptrs.romdata()[0x147]) ? &rtc : 0)); break; - case MBC5: mbc.reset(new Mbc5(memptrs)); break; - case HUC1: mbc.reset(new HuC1(memptrs)); break; - } - - return 0; -} - -static bool hasBattery(const unsigned char headerByte0x147) { - switch (headerByte0x147) { - case 0x03: - case 0x06: - case 0x09: - case 0x0F: - case 0x10: - case 0x13: - case 0x1B: - case 0x1E: - case 0xFF: return true; - default: return false; - } -} - -void Cartridge::loadSavedata(const char *data) { - if (hasBattery(memptrs.romdata()[0x147])) { - int length = memptrs.rambankdataend() - memptrs.rambankdata(); - std::memcpy(memptrs.rambankdata(), data, length); - data += length; - enforce8bit(memptrs.rambankdata(), length); - } - - if (hasRtc(memptrs.romdata()[0x147])) { - unsigned long basetime; - std::memcpy(&basetime, data, 4); - rtc.setBaseTime(basetime); - } -} - -int Cartridge::saveSavedataLength() { - int ret = 0; - if (hasBattery(memptrs.romdata()[0x147])) { - ret = memptrs.rambankdataend() - memptrs.rambankdata(); - } - if (hasRtc(memptrs.romdata()[0x147])) { - ret += 4; - } - return ret; -} - -void Cartridge::saveSavedata(char *dest) { - if (hasBattery(memptrs.romdata()[0x147])) { - int length = memptrs.rambankdataend() - memptrs.rambankdata(); - std::memcpy(dest, memptrs.rambankdata(), length); - dest += length; - } - - if (hasRtc(memptrs.romdata()[0x147])) { - const unsigned long basetime = rtc.getBaseTime(); - std::memcpy(dest, &basetime, 4); - } -} - -bool Cartridge::getMemoryArea(int which, unsigned char **data, int *length) const { - if (!data || !length) - return false; - - switch (which) - { - case 0: - *data = memptrs.vramdata(); - *length = memptrs.vramdataend() - memptrs.vramdata(); - return true; - case 1: - *data = memptrs.romdata(); - *length = memptrs.romdataend() - memptrs.romdata(); - return true; - case 2: - *data = memptrs.wramdata(0); - *length = memptrs.wramdataend() - memptrs.wramdata(0); - return true; - case 3: - *data = memptrs.rambankdata(); - *length = memptrs.rambankdataend() - memptrs.rambankdata(); - return true; - - default: - return false; - } - return false; -} - -SYNCFUNC(Cartridge) -{ - SSS(memptrs); - SSS(rtc); - TSS(mbc); -} - -} +/*************************************************************************** + * Copyright (C) 2007-2010 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "cartridge.h" +#include "../savestate.h" +#include +#include +#include +#include + +namespace gambatte { + +namespace { + +static unsigned toMulti64Rombank(const unsigned rombank) { + return (rombank >> 1 & 0x30) | (rombank & 0xF); +} + +class DefaultMbc : public Mbc { +public: + virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const { + return (addr< 0x4000) == (bank == 0); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + } +}; + +class Mbc0 : public DefaultMbc { + MemPtrs &memptrs; + bool enableRam; + +public: + explicit Mbc0(MemPtrs &memptrs) + : memptrs(memptrs), + enableRam(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + if (P < 0x2000) { + enableRam = (data & 0xF) == 0xA; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.enableRam = enableRam; + } + + virtual void loadState(const SaveState::Mem &ss) { + enableRam = ss.enableRam; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(enableRam); + } +}; + +static inline unsigned rambanks(const MemPtrs &memptrs) { + return static_cast(memptrs.rambankdataend() - memptrs.rambankdata()) / 0x2000; +} + +static inline unsigned rombanks(const MemPtrs &memptrs) { + return static_cast(memptrs.romdataend() - memptrs.romdata() ) / 0x4000; +} + +class Mbc1 : public DefaultMbc { + MemPtrs &memptrs; + unsigned char rombank; + unsigned char rambank; + bool enableRam; + bool rambankMode; + + static unsigned adjustedRombank(unsigned bank) { return bank & 0x1F ? bank : bank | 1; } + void setRambank() const { memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, rambank & (rambanks(memptrs) - 1)); } + void setRombank() const { memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); } + +public: + explicit Mbc1(MemPtrs &memptrs) + : memptrs(memptrs), + rombank(1), + rambank(0), + enableRam(false), + rambankMode(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P >> 13 & 3) { + case 0: + enableRam = (data & 0xF) == 0xA; + setRambank(); + break; + case 1: + rombank = rambankMode ? data & 0x1F : (rombank & 0x60) | (data & 0x1F); + setRombank(); + break; + case 2: + if (rambankMode) { + rambank = data & 3; + setRambank(); + } else { + rombank = (data << 5 & 0x60) | (rombank & 0x1F); + setRombank(); + } + + break; + case 3: + // Pretty sure this should take effect immediately, but I have a policy not to change old behavior + // unless I have something (eg. a verified test or a game) that justifies it. + rambankMode = data & 1; + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.rambank = rambank; + ss.enableRam = enableRam; + ss.rambankMode = rambankMode; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + rambank = ss.rambank; + enableRam = ss.enableRam; + rambankMode = ss.rambankMode; + setRambank(); + setRombank(); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(rambank); + NSS(enableRam); + NSS(rambankMode); + } +}; + +class Mbc1Multi64 : public Mbc { + MemPtrs &memptrs; + unsigned char rombank; + bool enableRam; + bool rombank0Mode; + + static unsigned adjustedRombank(unsigned bank) { return bank & 0x1F ? bank : bank | 1; } + + void setRombank() const { + if (rombank0Mode) { + const unsigned rb = toMulti64Rombank(rombank); + memptrs.setRombank0(rb & 0x30); + memptrs.setRombank(adjustedRombank(rb)); + } else { + memptrs.setRombank0(0); + memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); + } + } + +public: + explicit Mbc1Multi64(MemPtrs &memptrs) + : memptrs(memptrs), + rombank(1), + enableRam(false), + rombank0Mode(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P >> 13 & 3) { + case 0: + enableRam = (data & 0xF) == 0xA; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + break; + case 1: + rombank = (rombank & 0x60) | (data & 0x1F); + memptrs.setRombank(adjustedRombank(rombank0Mode ? toMulti64Rombank(rombank) : rombank & (rombanks(memptrs) - 1))); + break; + case 2: + rombank = (data << 5 & 0x60) | (rombank & 0x1F); + setRombank(); + break; + case 3: + rombank0Mode = data & 1; + setRombank(); + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.enableRam = enableRam; + ss.rambankMode = rombank0Mode; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + enableRam = ss.enableRam; + rombank0Mode = ss.rambankMode; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + setRombank(); + } + + virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned addr, unsigned bank) const { + return (addr < 0x4000) == ((bank & 0xF) == 0); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(enableRam); + NSS(rombank0Mode); + } +}; + +class Mbc2 : public DefaultMbc { + MemPtrs &memptrs; + unsigned char rombank; + bool enableRam; + +public: + explicit Mbc2(MemPtrs &memptrs) + : memptrs(memptrs), + rombank(1), + enableRam(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P & 0x6100) { + case 0x0000: + enableRam = (data & 0xF) == 0xA; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + break; + case 0x2100: + rombank = data & 0xF; + memptrs.setRombank(rombank & (rombanks(memptrs) - 1)); + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.enableRam = enableRam; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + enableRam = ss.enableRam; + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, 0); + memptrs.setRombank(rombank & (rombanks(memptrs) - 1)); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(enableRam); + } +}; + +class Mbc3 : public DefaultMbc { + MemPtrs &memptrs; + Rtc *const rtc; + unsigned char rombank; + unsigned char rambank; + bool enableRam; + + static unsigned adjustedRombank(unsigned bank) { return bank & 0x7F ? bank : bank | 1; } + void setRambank() const { + unsigned flags = enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0; + + if (rtc) { + rtc->set(enableRam, rambank); + + if (rtc->getActive()) + flags |= MemPtrs::RTC_EN; + } + + memptrs.setRambank(flags, rambank & (rambanks(memptrs) - 1)); + } + // we adjust the rombank before masking with size? this seems correct, as how would the mbc + // know that high rom address outputs were not connected + void setRombank() const { memptrs.setRombank(adjustedRombank(rombank) & (rombanks(memptrs) - 1)); } + +public: + Mbc3(MemPtrs &memptrs, Rtc *const rtc) + : memptrs(memptrs), + rtc(rtc), + rombank(1), + rambank(0), + enableRam(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P >> 13 & 3) { + case 0: + enableRam = (data & 0xF) == 0xA; + setRambank(); + break; + case 1: + rombank = data & 0x7F; + setRombank(); + break; + case 2: + rambank = data; + setRambank(); + break; + case 3: + if (rtc) + rtc->latch(data); + + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.rambank = rambank; + ss.enableRam = enableRam; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + rambank = ss.rambank; + enableRam = ss.enableRam; + setRambank(); + setRombank(); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(rambank); + NSS(enableRam); + } +}; + +class HuC1 : public DefaultMbc { + MemPtrs &memptrs; + unsigned char rombank; + unsigned char rambank; + bool enableRam; + bool rambankMode; + + void setRambank() const { + memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : MemPtrs::READ_EN, + rambankMode ? rambank & (rambanks(memptrs) - 1) : 0); + } + + void setRombank() const { memptrs.setRombank((rambankMode ? rombank : rambank << 6 | rombank) & (rombanks(memptrs) - 1)); } + +public: + explicit HuC1(MemPtrs &memptrs) + : memptrs(memptrs), + rombank(1), + rambank(0), + enableRam(false), + rambankMode(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P >> 13 & 3) { + case 0: + enableRam = (data & 0xF) == 0xA; + setRambank(); + break; + case 1: + rombank = data & 0x3F; + setRombank(); + break; + case 2: + rambank = data & 3; + rambankMode ? setRambank() : setRombank(); + break; + case 3: + rambankMode = data & 1; + setRambank(); + setRombank(); + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.rambank = rambank; + ss.enableRam = enableRam; + ss.rambankMode = rambankMode; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + rambank = ss.rambank; + enableRam = ss.enableRam; + rambankMode = ss.rambankMode; + setRambank(); + setRombank(); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(rambank); + NSS(enableRam); + NSS(rambankMode); + } +}; + +class Mbc5 : public DefaultMbc { + MemPtrs &memptrs; + unsigned short rombank; + unsigned char rambank; + bool enableRam; + + static unsigned adjustedRombank(const unsigned bank) { return bank; } + void setRambank() const { memptrs.setRambank(enableRam ? MemPtrs::READ_EN | MemPtrs::WRITE_EN : 0, rambank & (rambanks(memptrs) - 1)); } + void setRombank() const { memptrs.setRombank(adjustedRombank(rombank & (rombanks(memptrs) - 1))); } + +public: + explicit Mbc5(MemPtrs &memptrs) + : memptrs(memptrs), + rombank(1), + rambank(0), + enableRam(false) + { + } + + virtual void romWrite(const unsigned P, const unsigned data) { + switch (P >> 13 & 3) { + case 0: + enableRam = (data & 0xF) == 0xA; + setRambank(); + break; + case 1: + rombank = P < 0x3000 ? (rombank & 0x100) | data + : (data << 8 & 0x100) | (rombank & 0xFF); + setRombank(); + break; + case 2: + rambank = data & 0xF; + setRambank(); + break; + case 3: + break; + } + } + + virtual void saveState(SaveState::Mem &ss) const { + ss.rombank = rombank; + ss.rambank = rambank; + ss.enableRam = enableRam; + } + + virtual void loadState(const SaveState::Mem &ss) { + rombank = ss.rombank; + rambank = ss.rambank; + enableRam = ss.enableRam; + setRambank(); + setRombank(); + } + + virtual void SyncState(NewState *ns, bool isReader) + { + NSS(rombank); + NSS(rambank); + NSS(enableRam); + } +}; + +static bool hasRtc(const unsigned headerByte0x147) { + switch (headerByte0x147) { + case 0x0F: + case 0x10: return true; + default: return false; + } +} + +} + +void Cartridge::setStatePtrs(SaveState &state) { + state.mem.vram.set(memptrs.vramdata(), memptrs.vramdataend() - memptrs.vramdata()); + state.mem.sram.set(memptrs.rambankdata(), memptrs.rambankdataend() - memptrs.rambankdata()); + state.mem.wram.set(memptrs.wramdata(0), memptrs.wramdataend() - memptrs.wramdata(0)); +} + +void Cartridge::loadState(const SaveState &state) { + rtc.loadState(state); + mbc->loadState(state.mem); +} + +static void enforce8bit(unsigned char *data, unsigned long sz) { + if (static_cast(0x100)) + while (sz--) + *data++ &= 0xFF; +} + +static unsigned pow2ceil(unsigned n) { + --n; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + ++n; + + return n; +} + +int Cartridge::loadROM(const char *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { + //const std::auto_ptr rom(newFileInstance(romfile)); + + //if (rom->fail()) + // return -1; + + unsigned rambanks = 1; + unsigned rombanks = 2; + bool cgb = false; + enum Cartridgetype { PLAIN, MBC1, MBC2, MBC3, MBC5, HUC1 } type = PLAIN; + + { + unsigned char header[0x150]; + //rom->read(reinterpret_cast(header), sizeof header); + if (romfilelength >= sizeof header) + std::memcpy(header, romfiledata, sizeof header); + else + return -1; + + switch (header[0x0147]) { + case 0x00: std::puts("Plain ROM loaded."); type = PLAIN; break; + case 0x01: std::puts("MBC1 ROM loaded."); type = MBC1; break; + case 0x02: std::puts("MBC1 ROM+RAM loaded."); type = MBC1; break; + case 0x03: std::puts("MBC1 ROM+RAM+BATTERY loaded."); type = MBC1; break; + case 0x05: std::puts("MBC2 ROM loaded."); type = MBC2; break; + case 0x06: std::puts("MBC2 ROM+BATTERY loaded."); type = MBC2; break; + case 0x08: std::puts("Plain ROM with additional RAM loaded."); type = PLAIN; break; + case 0x09: std::puts("Plain ROM with additional RAM and Battery loaded."); type = PLAIN; break; + case 0x0B: std::puts("MM01 ROM not supported."); return -1; + case 0x0C: std::puts("MM01 ROM not supported."); return -1; + case 0x0D: std::puts("MM01 ROM not supported."); return -1; + case 0x0F: std::puts("MBC3 ROM+TIMER+BATTERY loaded."); type = MBC3; break; + case 0x10: std::puts("MBC3 ROM+TIMER+RAM+BATTERY loaded."); type = MBC3; break; + case 0x11: std::puts("MBC3 ROM loaded."); type = MBC3; break; + case 0x12: std::puts("MBC3 ROM+RAM loaded."); type = MBC3; break; + case 0x13: std::puts("MBC3 ROM+RAM+BATTERY loaded."); type = MBC3; break; + case 0x15: std::puts("MBC4 ROM not supported."); return -1; + case 0x16: std::puts("MBC4 ROM not supported."); return -1; + case 0x17: std::puts("MBC4 ROM not supported."); return -1; + case 0x19: std::puts("MBC5 ROM loaded."); type = MBC5; break; + case 0x1A: std::puts("MBC5 ROM+RAM loaded."); type = MBC5; break; + case 0x1B: std::puts("MBC5 ROM+RAM+BATTERY loaded."); type = MBC5; break; + case 0x1C: std::puts("MBC5+RUMBLE ROM not supported."); type = MBC5; break; + case 0x1D: std::puts("MBC5+RUMBLE+RAM ROM not suported."); type = MBC5; break; + case 0x1E: std::puts("MBC5+RUMBLE+RAM+BATTERY ROM not supported."); type = MBC5; break; + case 0xFC: std::puts("Pocket Camera ROM not supported."); return -1; + case 0xFD: std::puts("Bandai TAMA5 ROM not supported."); return -1; + case 0xFE: std::puts("HuC3 ROM not supported."); return -1; + case 0xFF: std::puts("HuC1 ROM+RAM+BATTERY loaded."); type = HUC1; break; + default: std::puts("Wrong data-format, corrupt or unsupported ROM."); return -1; + } + + /*switch (header[0x0148]) { + case 0x00: rombanks = 2; break; + case 0x01: rombanks = 4; break; + case 0x02: rombanks = 8; break; + case 0x03: rombanks = 16; break; + case 0x04: rombanks = 32; break; + case 0x05: rombanks = 64; break; + case 0x06: rombanks = 128; break; + case 0x07: rombanks = 256; break; + case 0x08: rombanks = 512; break; + case 0x52: rombanks = 72; break; + case 0x53: rombanks = 80; break; + case 0x54: rombanks = 96; break; + default: return -1; + } + + std::printf("rombanks: %u\n", rombanks);*/ + + switch (header[0x0149]) { + case 0x00: /*std::puts("No RAM");*/ rambanks = type == MBC2; break; + case 0x01: /*std::puts("2kB RAM");*/ /*rambankrom=1; break;*/ + case 0x02: /*std::puts("8kB RAM");*/ + rambanks = 1; + break; + case 0x03: /*std::puts("32kB RAM");*/ + rambanks = 4; + break; + case 0x04: /*std::puts("128kB RAM");*/ + rambanks = 16; + break; + case 0x05: /*std::puts("undocumented kB RAM");*/ + rambanks = 16; + break; + default: /*std::puts("Wrong data-format, corrupt or unsupported ROM loaded.");*/ + rambanks = 16; + break; + } + + cgb = !forceDmg; + std::printf("cgb: %d\n", cgb); + } + + std::printf("rambanks: %u\n", rambanks); + + const std::size_t filesize = romfilelength; //rom->size(); + rombanks = std::max(pow2ceil(filesize / 0x4000), 2u); + std::printf("rombanks: %u\n", static_cast(filesize / 0x4000)); + + mbc.reset(); + memptrs.reset(rombanks, rambanks, cgb ? 8 : 2); + rtc.set(false, 0); + + //rom->rewind(); + //rom->read(reinterpret_cast(memptrs.romdata()), (filesize / 0x4000) * 0x4000ul); + std::memcpy(memptrs.romdata(), romfiledata, (filesize / 0x4000) * 0x4000ul); + std::memset(memptrs.romdata() + (filesize / 0x4000) * 0x4000ul, 0xFF, (rombanks - filesize / 0x4000) * 0x4000ul); + enforce8bit(memptrs.romdata(), rombanks * 0x4000ul); + + //if (rom->fail()) + // return -1; + + switch (type) { + case PLAIN: mbc.reset(new Mbc0(memptrs)); break; + case MBC1: + if (!rambanks && rombanks == 64 && multicartCompat) { + std::puts("Multi-ROM \"MBC1\" presumed"); + mbc.reset(new Mbc1Multi64(memptrs)); + } else + mbc.reset(new Mbc1(memptrs)); + + break; + case MBC2: mbc.reset(new Mbc2(memptrs)); break; + case MBC3: mbc.reset(new Mbc3(memptrs, hasRtc(memptrs.romdata()[0x147]) ? &rtc : 0)); break; + case MBC5: mbc.reset(new Mbc5(memptrs)); break; + case HUC1: mbc.reset(new HuC1(memptrs)); break; + } + + return 0; +} + +static bool hasBattery(const unsigned char headerByte0x147) { + switch (headerByte0x147) { + case 0x03: + case 0x06: + case 0x09: + case 0x0F: + case 0x10: + case 0x13: + case 0x1B: + case 0x1E: + case 0xFF: return true; + default: return false; + } +} + +void Cartridge::loadSavedata(const char *data) { + if (hasBattery(memptrs.romdata()[0x147])) { + int length = memptrs.rambankdataend() - memptrs.rambankdata(); + std::memcpy(memptrs.rambankdata(), data, length); + data += length; + enforce8bit(memptrs.rambankdata(), length); + } + + if (hasRtc(memptrs.romdata()[0x147])) { + unsigned long basetime; + std::memcpy(&basetime, data, 4); + rtc.setBaseTime(basetime); + } +} + +int Cartridge::saveSavedataLength() { + int ret = 0; + if (hasBattery(memptrs.romdata()[0x147])) { + ret = memptrs.rambankdataend() - memptrs.rambankdata(); + } + if (hasRtc(memptrs.romdata()[0x147])) { + ret += 4; + } + return ret; +} + +void Cartridge::saveSavedata(char *dest) { + if (hasBattery(memptrs.romdata()[0x147])) { + int length = memptrs.rambankdataend() - memptrs.rambankdata(); + std::memcpy(dest, memptrs.rambankdata(), length); + dest += length; + } + + if (hasRtc(memptrs.romdata()[0x147])) { + const unsigned long basetime = rtc.getBaseTime(); + std::memcpy(dest, &basetime, 4); + } +} + +bool Cartridge::getMemoryArea(int which, unsigned char **data, int *length) const { + if (!data || !length) + return false; + + switch (which) + { + case 0: + *data = memptrs.vramdata(); + *length = memptrs.vramdataend() - memptrs.vramdata(); + return true; + case 1: + *data = memptrs.romdata(); + *length = memptrs.romdataend() - memptrs.romdata(); + return true; + case 2: + *data = memptrs.wramdata(0); + *length = memptrs.wramdataend() - memptrs.wramdata(0); + return true; + case 3: + *data = memptrs.rambankdata(); + *length = memptrs.rambankdataend() - memptrs.rambankdata(); + return true; + + default: + return false; + } + return false; +} + +SYNCFUNC(Cartridge) +{ + SSS(memptrs); + SSS(rtc); + TSS(mbc); +} + +} diff --git a/libgambatte/src/mem/cartridge.h b/libgambatte/src/mem/cartridge.h index 0015924966..511c4438ae 100644 --- a/libgambatte/src/mem/cartridge.h +++ b/libgambatte/src/mem/cartridge.h @@ -1,112 +1,113 @@ -/*************************************************************************** - * Copyright (C) 2007-2010 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef CARTRIDGE_H -#define CARTRIDGE_H - -#include "memptrs.h" -#include "rtc.h" -#include "savestate.h" -#include -#include -#include -#include "newstate.h" - -namespace gambatte { - - //DOOM -//enum eAddressMappingType -//{ -// eAddressMappingType_ROM, -// eAddressMappingType_RAM -//}; -// -//struct AddressMapping -//{ -// int32_t address; -// eAddressMappingType type; -//}; - -class Mbc { -public: - virtual ~Mbc() {} - virtual void romWrite(unsigned P, unsigned data) = 0; - virtual void loadState(const SaveState::Mem &ss) = 0; - virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned address, unsigned rombank) const = 0; - //virtual void mapAddress(AddressMapping* mapping, unsigned address) const = 0; //DOOM - - templatevoid SyncState(NewState *ns) - { - // can't have virtual templates, so.. - SyncState(ns, isReader); - } - virtual void SyncState(NewState *ns, bool isReader) = 0; -}; - -class Cartridge { - MemPtrs memptrs; - Rtc rtc; - std::auto_ptr mbc; - -public: - void setStatePtrs(SaveState &); - void loadState(const SaveState &); - - bool loaded() const { return mbc.get(); } - - const unsigned char * rmem(unsigned area) const { return memptrs.rmem(area); } - unsigned char * wmem(unsigned area) const { return memptrs.wmem(area); } - unsigned char * vramdata() const { return memptrs.vramdata(); } - unsigned char * romdata(unsigned area) const { return memptrs.romdata(area); } - unsigned char * wramdata(unsigned area) const { return memptrs.wramdata(area); } - const unsigned char * rdisabledRam() const { return memptrs.rdisabledRam(); } - const unsigned char * rsrambankptr() const { return memptrs.rsrambankptr(); } - unsigned char * wsrambankptr() const { return memptrs.wsrambankptr(); } - unsigned char * vrambankptr() const { return memptrs.vrambankptr(); } - OamDmaSrc oamDmaSrc() const { return memptrs.oamDmaSrc(); } - - void setVrambank(unsigned bank) { memptrs.setVrambank(bank); } - void setWrambank(unsigned bank) { memptrs.setWrambank(bank); } - void setOamDmaSrc(OamDmaSrc oamDmaSrc) { memptrs.setOamDmaSrc(oamDmaSrc); } - - void mbcWrite(unsigned addr, unsigned data) { mbc->romWrite(addr, data); } - - bool isCgb() const { return gambatte::isCgb(memptrs); } - - void rtcWrite(unsigned data) { rtc.write(data); } - unsigned char rtcRead() const { return *rtc.getActive(); } - - void loadSavedata(const char *data); - int saveSavedataLength(); - void saveSavedata(char *dest); - - bool getMemoryArea(int which, unsigned char **data, int *length) const; - - int loadROM(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat); - const char * romTitle() const { return reinterpret_cast(memptrs.romdata() + 0x134); } - - void setRTCCallback(std::uint32_t (*callback)()) { - rtc.setRTCCallback(callback); - } - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007-2010 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef CARTRIDGE_H +#define CARTRIDGE_H + +#include "memptrs.h" +#include "rtc.h" +#include "savestate.h" +#include +#include +#include +#include "newstate.h" + +namespace gambatte { + + //DOOM +//enum eAddressMappingType +//{ +// eAddressMappingType_ROM, +// eAddressMappingType_RAM +//}; +// +//struct AddressMapping +//{ +// int32_t address; +// eAddressMappingType type; +//}; + +class Mbc { +public: + virtual ~Mbc() {} + virtual void romWrite(unsigned P, unsigned data) = 0; + virtual void loadState(const SaveState::Mem &ss) = 0; + virtual bool isAddressWithinAreaRombankCanBeMappedTo(unsigned address, unsigned rombank) const = 0; + //virtual void mapAddress(AddressMapping* mapping, unsigned address) const = 0; //DOOM + + templatevoid SyncState(NewState *ns) + { + // can't have virtual templates, so.. + SyncState(ns, isReader); + } + virtual void SyncState(NewState *ns, bool isReader) = 0; +}; + +class Cartridge { + MemPtrs memptrs; + Rtc rtc; + std::auto_ptr mbc; + +public: + void setStatePtrs(SaveState &); + void loadState(const SaveState &); + + bool loaded() const { return mbc.get(); } + + const unsigned char * rmem(unsigned area) const { return memptrs.rmem(area); } + unsigned char * wmem(unsigned area) const { return memptrs.wmem(area); } + unsigned char * vramdata() const { return memptrs.vramdata(); } + unsigned char * romdata(unsigned area) const { return memptrs.romdata(area); } + unsigned char * wramdata(unsigned area) const { return memptrs.wramdata(area); } + const unsigned char * rdisabledRam() const { return memptrs.rdisabledRam(); } + const unsigned char * rsrambankptr() const { return memptrs.rsrambankptr(); } + unsigned char * wsrambankptr() const { return memptrs.wsrambankptr(); } + unsigned char * vrambankptr() const { return memptrs.vrambankptr(); } + OamDmaSrc oamDmaSrc() const { return memptrs.oamDmaSrc(); } + unsigned curRomBank() const { return memptrs.curRomBank(); } + + void setVrambank(unsigned bank) { memptrs.setVrambank(bank); } + void setWrambank(unsigned bank) { memptrs.setWrambank(bank); } + void setOamDmaSrc(OamDmaSrc oamDmaSrc) { memptrs.setOamDmaSrc(oamDmaSrc); } + + void mbcWrite(unsigned addr, unsigned data) { mbc->romWrite(addr, data); } + + bool isCgb() const { return gambatte::isCgb(memptrs); } + + void rtcWrite(unsigned data) { rtc.write(data); } + unsigned char rtcRead() const { return *rtc.getActive(); } + + void loadSavedata(const char *data); + int saveSavedataLength(); + void saveSavedata(char *dest); + + bool getMemoryArea(int which, unsigned char **data, int *length) const; + + int loadROM(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat); + const char * romTitle() const { return reinterpret_cast(memptrs.romdata() + 0x134); } + + void setRTCCallback(std::uint32_t (*callback)()) { + rtc.setRTCCallback(callback); + } + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/mem/memptrs.cpp b/libgambatte/src/mem/memptrs.cpp index 1b0399770a..bd166e86cf 100644 --- a/libgambatte/src/mem/memptrs.cpp +++ b/libgambatte/src/mem/memptrs.cpp @@ -1,226 +1,223 @@ -/*************************************************************************** - * Copyright (C) 2007-2010 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#include "memptrs.h" -#include -#include - -namespace gambatte { - -MemPtrs::MemPtrs() -: rmem_(), wmem_(), romdata_(), wramdata_(), vrambankptr_(0), rsrambankptr_(0), - wsrambankptr_(0), memchunk_(0), rambankdata_(0), wramdataend_(0), oamDmaSrc_(OAM_DMA_SRC_OFF), - memchunk_len(0) -{ -} - -MemPtrs::~MemPtrs() { - delete []memchunk_; -} - -void MemPtrs::reset(const unsigned rombanks, const unsigned rambanks, const unsigned wrambanks) { - delete []memchunk_; - - memchunk_len = 0x4000 + rombanks * 0x4000ul + 0x4000 + rambanks * 0x2000ul + wrambanks * 0x1000ul + 0x4000; - memchunk_ = new unsigned char[memchunk_len]; - - romdata_[0] = romdata(); - - rambankdata_ = romdata_[0] + rombanks * 0x4000ul + 0x4000; - wramdata_[0] = rambankdata_ + rambanks * 0x2000ul; - wramdataend_ = wramdata_[0] + wrambanks * 0x1000ul; - - std::memset(rdisabledRamw(), 0xFF, 0x2000); - - oamDmaSrc_ = OAM_DMA_SRC_OFF; - rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; - rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000; - rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000; - setRombank(1); - setRambank(0, 0); - setVrambank(0); - setWrambank(1); - - // we save only the ram areas - memchunk_saveoffs = vramdata() - memchunk_; - memchunk_savelen = wramdataend() - memchunk_ - memchunk_saveoffs; -} - -void MemPtrs::setRombank0(const unsigned bank) { - - romdata_[0] = romdata() + bank * 0x4000ul; - - rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; - disconnectOamDmaAreas(); -} - -void MemPtrs::setRombank(const unsigned bank) { - - romdata_[1] = romdata() + bank * 0x4000ul - 0x4000; - - rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1]; - disconnectOamDmaAreas(); -} - -void MemPtrs::setRambank(const unsigned flags, const unsigned rambank) { - unsigned char *const srambankptr = flags & RTC_EN - ? 0 - : (rambankdata() != rambankdataend() - ? rambankdata_ + rambank * 0x2000ul - 0xA000 : wdisabledRam() - 0xA000); - - rsrambankptr_ = (flags & READ_EN) && srambankptr != wdisabledRam() - 0xA000 ? srambankptr : rdisabledRamw() - 0xA000; - wsrambankptr_ = flags & WRITE_EN ? srambankptr : wdisabledRam() - 0xA000; - rmem_[0xB] = rmem_[0xA] = rsrambankptr_; - wmem_[0xB] = wmem_[0xA] = wsrambankptr_; - disconnectOamDmaAreas(); -} - -void MemPtrs::setWrambank(const unsigned bank) { - wramdata_[1] = wramdata_[0] + ((bank & 0x07) ? (bank & 0x07) : 1) * 0x1000; - rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000; - disconnectOamDmaAreas(); -} - -void MemPtrs::setOamDmaSrc(const OamDmaSrc oamDmaSrc) { - rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; - rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1]; - rmem_[0xB] = rmem_[0xA] = rsrambankptr_; - wmem_[0xB] = wmem_[0xA] = wsrambankptr_; - rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000; - rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000; - rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000; - - oamDmaSrc_ = oamDmaSrc; - disconnectOamDmaAreas(); -} - -void MemPtrs::disconnectOamDmaAreas() { - if (isCgb(*this)) { - switch (oamDmaSrc_) { - case OAM_DMA_SRC_ROM: // fall through - case OAM_DMA_SRC_SRAM: - case OAM_DMA_SRC_INVALID: - std::fill(rmem_, rmem_ + 8, static_cast(0)); - rmem_[0xB] = rmem_[0xA] = 0; - wmem_[0xB] = wmem_[0xA] = 0; - break; - case OAM_DMA_SRC_VRAM: - break; - case OAM_DMA_SRC_WRAM: - rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0; - wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0; - break; - case OAM_DMA_SRC_OFF: - break; - } - } else { - switch (oamDmaSrc_) { - case OAM_DMA_SRC_ROM: // fall through - case OAM_DMA_SRC_SRAM: - case OAM_DMA_SRC_WRAM: - case OAM_DMA_SRC_INVALID: - std::fill(rmem_, rmem_ + 8, static_cast(0)); - rmem_[0xB] = rmem_[0xA] = 0; - wmem_[0xB] = wmem_[0xA] = 0; - rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0; - wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0; - break; - case OAM_DMA_SRC_VRAM: - break; - case OAM_DMA_SRC_OFF: - break; - } - } -} - -// all pointers here are relative to memchunk_ -#define MSS(a) RSS(a,memchunk_) -#define MSL(a) RSL(a,memchunk_) - -SYNCFUNC(MemPtrs) -{ - /* - int memchunk_len_old = memchunk_len; - int memchunk_saveoffs_old = memchunk_saveoffs; - int memchunk_savelen_old = memchunk_savelen; - */ - - NSS(memchunk_len); - NSS(memchunk_saveoffs); - NSS(memchunk_savelen); - - /* - if (isReader) - { - if (memchunk_len != memchunk_len_old || memchunk_saveoffs != memchunk_saveoffs_old || memchunk_savelen != memchunk_savelen_old) - __debugbreak(); - } - */ - - PSS(memchunk_ + memchunk_saveoffs, memchunk_savelen); - - MSS(rmem_[0x0]); - MSS(wmem_[0x0]); - MSS(rmem_[0x1]); - MSS(wmem_[0x1]); - MSS(rmem_[0x2]); - MSS(wmem_[0x2]); - MSS(rmem_[0x3]); - MSS(wmem_[0x3]); - MSS(rmem_[0x4]); - MSS(wmem_[0x4]); - MSS(rmem_[0x5]); - MSS(wmem_[0x5]); - MSS(rmem_[0x6]); - MSS(wmem_[0x6]); - MSS(rmem_[0x7]); - MSS(wmem_[0x7]); - MSS(rmem_[0x8]); - MSS(wmem_[0x8]); - MSS(rmem_[0x9]); - MSS(wmem_[0x9]); - MSS(rmem_[0xa]); - MSS(wmem_[0xa]); - MSS(rmem_[0xb]); - MSS(wmem_[0xb]); - MSS(rmem_[0xc]); - MSS(wmem_[0xc]); - MSS(rmem_[0xd]); - MSS(wmem_[0xd]); - MSS(rmem_[0xe]); - MSS(wmem_[0xe]); - MSS(rmem_[0xf]); - MSS(wmem_[0xf]); - //for (int i = 0; i < 0x10; i++) - //{ - // MSS(rmem_[i]); - // MSS(wmem_[i]); - //} - MSS(romdata_[0]); - MSS(romdata_[1]); - MSS(wramdata_[0]); - MSS(wramdata_[1]); - MSS(vrambankptr_); - MSS(rsrambankptr_); - MSS(wsrambankptr_); - MSS(rambankdata_); - MSS(wramdataend_); - NSS(oamDmaSrc_); -} - -} +/*************************************************************************** + * Copyright (C) 2007-2010 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "memptrs.h" +#include +#include + +namespace gambatte { + +MemPtrs::MemPtrs() +: rmem_(), wmem_(), romdata_(), wramdata_(), vrambankptr_(0), rsrambankptr_(0), + wsrambankptr_(0), memchunk_(0), rambankdata_(0), wramdataend_(0), oamDmaSrc_(OAM_DMA_SRC_OFF), + curRomBank_(1), + memchunk_len(0) +{ +} + +MemPtrs::~MemPtrs() { + delete []memchunk_; +} + +void MemPtrs::reset(const unsigned rombanks, const unsigned rambanks, const unsigned wrambanks) { + delete []memchunk_; + memchunk_len = 0x4000 + rombanks * 0x4000ul + 0x4000 + rambanks * 0x2000ul + wrambanks * 0x1000ul + 0x4000; + memchunk_ = new unsigned char[memchunk_len]; + + romdata_[0] = romdata(); + rambankdata_ = romdata_[0] + rombanks * 0x4000ul + 0x4000; + wramdata_[0] = rambankdata_ + rambanks * 0x2000ul; + wramdataend_ = wramdata_[0] + wrambanks * 0x1000ul; + + std::memset(rdisabledRamw(), 0xFF, 0x2000); + + oamDmaSrc_ = OAM_DMA_SRC_OFF; + rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; + rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000; + rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000; + setRombank(1); + setRambank(0, 0); + setVrambank(0); + setWrambank(1); + + // we save only the ram areas + memchunk_saveoffs = vramdata() - memchunk_; + memchunk_savelen = wramdataend() - memchunk_ - memchunk_saveoffs; +} + +void MemPtrs::setRombank0(const unsigned bank) { + romdata_[0] = romdata() + bank * 0x4000ul; + rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; + disconnectOamDmaAreas(); +} + +void MemPtrs::setRombank(const unsigned bank) { + curRomBank_ = bank; + romdata_[1] = romdata() + bank * 0x4000ul - 0x4000; + rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1]; + disconnectOamDmaAreas(); +} + +void MemPtrs::setRambank(const unsigned flags, const unsigned rambank) { + unsigned char *const srambankptr = flags & RTC_EN + ? 0 + : (rambankdata() != rambankdataend() + ? rambankdata_ + rambank * 0x2000ul - 0xA000 : wdisabledRam() - 0xA000); + + rsrambankptr_ = (flags & READ_EN) && srambankptr != wdisabledRam() - 0xA000 ? srambankptr : rdisabledRamw() - 0xA000; + wsrambankptr_ = flags & WRITE_EN ? srambankptr : wdisabledRam() - 0xA000; + rmem_[0xB] = rmem_[0xA] = rsrambankptr_; + wmem_[0xB] = wmem_[0xA] = wsrambankptr_; + disconnectOamDmaAreas(); +} + +void MemPtrs::setWrambank(const unsigned bank) { + wramdata_[1] = wramdata_[0] + ((bank & 0x07) ? (bank & 0x07) : 1) * 0x1000; + rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000; + disconnectOamDmaAreas(); +} + +void MemPtrs::setOamDmaSrc(const OamDmaSrc oamDmaSrc) { + rmem_[0x3] = rmem_[0x2] = rmem_[0x1] = rmem_[0x0] = romdata_[0]; + rmem_[0x7] = rmem_[0x6] = rmem_[0x5] = rmem_[0x4] = romdata_[1]; + rmem_[0xB] = rmem_[0xA] = rsrambankptr_; + wmem_[0xB] = wmem_[0xA] = wsrambankptr_; + rmem_[0xC] = wmem_[0xC] = wramdata_[0] - 0xC000; + rmem_[0xD] = wmem_[0xD] = wramdata_[1] - 0xD000; + rmem_[0xE] = wmem_[0xE] = wramdata_[0] - 0xE000; + + oamDmaSrc_ = oamDmaSrc; + disconnectOamDmaAreas(); +} + +void MemPtrs::disconnectOamDmaAreas() { + if (isCgb(*this)) { + switch (oamDmaSrc_) { + case OAM_DMA_SRC_ROM: // fall through + case OAM_DMA_SRC_SRAM: + case OAM_DMA_SRC_INVALID: + std::fill(rmem_, rmem_ + 8, static_cast(0)); + rmem_[0xB] = rmem_[0xA] = 0; + wmem_[0xB] = wmem_[0xA] = 0; + break; + case OAM_DMA_SRC_VRAM: + break; + case OAM_DMA_SRC_WRAM: + rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0; + wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0; + break; + case OAM_DMA_SRC_OFF: + break; + } + } else { + switch (oamDmaSrc_) { + case OAM_DMA_SRC_ROM: // fall through + case OAM_DMA_SRC_SRAM: + case OAM_DMA_SRC_WRAM: + case OAM_DMA_SRC_INVALID: + std::fill(rmem_, rmem_ + 8, static_cast(0)); + rmem_[0xB] = rmem_[0xA] = 0; + wmem_[0xB] = wmem_[0xA] = 0; + rmem_[0xE] = rmem_[0xD] = rmem_[0xC] = 0; + wmem_[0xE] = wmem_[0xD] = wmem_[0xC] = 0; + break; + case OAM_DMA_SRC_VRAM: + break; + case OAM_DMA_SRC_OFF: + break; + } + } +} + +// all pointers here are relative to memchunk_ +#define MSS(a) RSS(a,memchunk_) +#define MSL(a) RSL(a,memchunk_) + +SYNCFUNC(MemPtrs) +{ + /* + int memchunk_len_old = memchunk_len; + int memchunk_saveoffs_old = memchunk_saveoffs; + int memchunk_savelen_old = memchunk_savelen; + */ + + NSS(memchunk_len); + NSS(memchunk_saveoffs); + NSS(memchunk_savelen); + + /* + if (isReader) + { + if (memchunk_len != memchunk_len_old || memchunk_saveoffs != memchunk_saveoffs_old || memchunk_savelen != memchunk_savelen_old) + __debugbreak(); + } + */ + + PSS(memchunk_ + memchunk_saveoffs, memchunk_savelen); + + MSS(rmem_[0x0]); + MSS(wmem_[0x0]); + MSS(rmem_[0x1]); + MSS(wmem_[0x1]); + MSS(rmem_[0x2]); + MSS(wmem_[0x2]); + MSS(rmem_[0x3]); + MSS(wmem_[0x3]); + MSS(rmem_[0x4]); + MSS(wmem_[0x4]); + MSS(rmem_[0x5]); + MSS(wmem_[0x5]); + MSS(rmem_[0x6]); + MSS(wmem_[0x6]); + MSS(rmem_[0x7]); + MSS(wmem_[0x7]); + MSS(rmem_[0x8]); + MSS(wmem_[0x8]); + MSS(rmem_[0x9]); + MSS(wmem_[0x9]); + MSS(rmem_[0xa]); + MSS(wmem_[0xa]); + MSS(rmem_[0xb]); + MSS(wmem_[0xb]); + MSS(rmem_[0xc]); + MSS(wmem_[0xc]); + MSS(rmem_[0xd]); + MSS(wmem_[0xd]); + MSS(rmem_[0xe]); + MSS(wmem_[0xe]); + MSS(rmem_[0xf]); + MSS(wmem_[0xf]); + //for (int i = 0; i < 0x10; i++) + //{ + // MSS(rmem_[i]); + // MSS(wmem_[i]); + //} + MSS(romdata_[0]); + MSS(romdata_[1]); + MSS(wramdata_[0]); + MSS(wramdata_[1]); + MSS(vrambankptr_); + MSS(rsrambankptr_); + MSS(wsrambankptr_); + MSS(rambankdata_); + MSS(wramdataend_); + NSS(oamDmaSrc_); + NSS(curRomBank_); +} + +} diff --git a/libgambatte/src/mem/memptrs.h b/libgambatte/src/mem/memptrs.h index 5e4f772a26..68fd2b0006 100644 --- a/libgambatte/src/mem/memptrs.h +++ b/libgambatte/src/mem/memptrs.h @@ -1,93 +1,96 @@ -/*************************************************************************** - * Copyright (C) 2007-2010 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef MEMPTRS_H -#define MEMPTRS_H - -#include "newstate.h" - -namespace gambatte { - -enum OamDmaSrc { OAM_DMA_SRC_ROM, OAM_DMA_SRC_SRAM, OAM_DMA_SRC_VRAM, - OAM_DMA_SRC_WRAM, OAM_DMA_SRC_INVALID, OAM_DMA_SRC_OFF }; - -class MemPtrs { - const unsigned char *rmem_[0x10]; - unsigned char *wmem_[0x10]; - - unsigned char *romdata_[2]; - unsigned char *wramdata_[2]; - unsigned char *vrambankptr_; - unsigned char *rsrambankptr_; - unsigned char *wsrambankptr_; - unsigned char *memchunk_; - unsigned char *rambankdata_; - unsigned char *wramdataend_; - - OamDmaSrc oamDmaSrc_; - - int memchunk_len; - int memchunk_saveoffs; - int memchunk_savelen; - - MemPtrs(const MemPtrs &); - MemPtrs & operator=(const MemPtrs &); - void disconnectOamDmaAreas(); - unsigned char * rdisabledRamw() const { return wramdataend_ ; } - unsigned char * wdisabledRam() const { return wramdataend_ + 0x2000; } -public: - enum RamFlag { READ_EN = 1, WRITE_EN = 2, RTC_EN = 4 }; - - MemPtrs(); - ~MemPtrs(); - void reset(unsigned rombanks, unsigned rambanks, unsigned wrambanks); - - const unsigned char * rmem(unsigned area) const { return rmem_[area]; } - unsigned char * wmem(unsigned area) const { return wmem_[area]; } - unsigned char * vramdata() const { return rambankdata_ - 0x4000; } - unsigned char * vramdataend() const { return rambankdata_; } - unsigned char * romdata() const { return memchunk_ + 0x4000;} - unsigned char * romdata(unsigned area) const { return romdata_[area]; } - unsigned char * romdataend() const { return rambankdata_ - 0x4000; } - unsigned char * wramdata(unsigned area) const { return wramdata_[area]; } - unsigned char * wramdataend() const { return wramdataend_; } - unsigned char * rambankdata() const { return rambankdata_; } - unsigned char * rambankdataend() const { return wramdata_[0]; } - const unsigned char * rdisabledRam() const { return rdisabledRamw(); } - const unsigned char * rsrambankptr() const { return rsrambankptr_; } - unsigned char * wsrambankptr() const { return wsrambankptr_; } - unsigned char * vrambankptr() const { return vrambankptr_; } - OamDmaSrc oamDmaSrc() const { return oamDmaSrc_; } - - void setRombank0(unsigned bank); - void setRombank(unsigned bank); - void setRambank(unsigned ramFlags, unsigned rambank); - void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * 0x2000ul - 0x8000; } - void setWrambank(unsigned bank); - void setOamDmaSrc(OamDmaSrc oamDmaSrc); - - templatevoid SyncState(NewState *ns); -}; - -inline bool isCgb(const MemPtrs &memptrs) { - return memptrs.wramdataend() - memptrs.wramdata(0) == 0x8000; -} - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007-2010 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef MEMPTRS_H +#define MEMPTRS_H + +#include "newstate.h" + +namespace gambatte { + +enum OamDmaSrc { OAM_DMA_SRC_ROM, OAM_DMA_SRC_SRAM, OAM_DMA_SRC_VRAM, + OAM_DMA_SRC_WRAM, OAM_DMA_SRC_INVALID, OAM_DMA_SRC_OFF }; + +class MemPtrs { + const unsigned char *rmem_[0x10]; + unsigned char *wmem_[0x10]; + + unsigned char *romdata_[2]; + unsigned char *wramdata_[2]; + unsigned char *vrambankptr_; + unsigned char *rsrambankptr_; + unsigned char *wsrambankptr_; + unsigned char *memchunk_; + unsigned char *rambankdata_; + unsigned char *wramdataend_; + + OamDmaSrc oamDmaSrc_; + + unsigned curRomBank_; + + int memchunk_len; + int memchunk_saveoffs; + int memchunk_savelen; + + MemPtrs(const MemPtrs &); + MemPtrs & operator=(const MemPtrs &); + void disconnectOamDmaAreas(); + unsigned char * rdisabledRamw() const { return wramdataend_ ; } + unsigned char * wdisabledRam() const { return wramdataend_ + 0x2000; } +public: + enum RamFlag { READ_EN = 1, WRITE_EN = 2, RTC_EN = 4 }; + + MemPtrs(); + ~MemPtrs(); + void reset(unsigned rombanks, unsigned rambanks, unsigned wrambanks); + + const unsigned char * rmem(unsigned area) const { return rmem_[area]; } + unsigned char * wmem(unsigned area) const { return wmem_[area]; } + unsigned char * vramdata() const { return rambankdata_ - 0x4000; } + unsigned char * vramdataend() const { return rambankdata_; } + unsigned char * romdata() const { return memchunk_ + 0x4000; } + unsigned char * romdata(unsigned area) const { return romdata_[area]; } + unsigned char * romdataend() const { return rambankdata_ - 0x4000; } + unsigned char * wramdata(unsigned area) const { return wramdata_[area]; } + unsigned char * wramdataend() const { return wramdataend_; } + unsigned char * rambankdata() const { return rambankdata_; } + unsigned char * rambankdataend() const { return wramdata_[0]; } + const unsigned char * rdisabledRam() const { return rdisabledRamw(); } + const unsigned char * rsrambankptr() const { return rsrambankptr_; } + unsigned char * wsrambankptr() const { return wsrambankptr_; } + unsigned char * vrambankptr() const { return vrambankptr_; } + OamDmaSrc oamDmaSrc() const { return oamDmaSrc_; } + unsigned curRomBank() const { return curRomBank_; } + + void setRombank0(unsigned bank); + void setRombank(unsigned bank); + void setRambank(unsigned ramFlags, unsigned rambank); + void setVrambank(unsigned bank) { vrambankptr_ = vramdata() + bank * 0x2000ul - 0x8000; } + void setWrambank(unsigned bank); + void setOamDmaSrc(OamDmaSrc oamDmaSrc); + + templatevoid SyncState(NewState *ns); +}; + +inline bool isCgb(const MemPtrs &memptrs) { + return memptrs.wramdataend() - memptrs.wramdata(0) == 0x8000; +} + +} + +#endif diff --git a/libgambatte/src/mem/rtc.cpp b/libgambatte/src/mem/rtc.cpp index 3fc1ed1a2e..ae97d0876c 100644 --- a/libgambatte/src/mem/rtc.cpp +++ b/libgambatte/src/mem/rtc.cpp @@ -1,177 +1,177 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#include "rtc.h" -#include "../savestate.h" -#include - -namespace gambatte { - -Rtc::Rtc() -: activeData(NULL), - activeSet(NULL), - baseTime(0), - haltTime(0), - index(5), - dataDh(0), - dataDl(0), - dataH(0), - dataM(0), - dataS(0), - enabled(false), - lastLatchData(false), - timeCB(0) -{ -} - -void Rtc::doLatch() { - std::uint32_t tmp = ((dataDh & 0x40) ? haltTime : timeCB()) - baseTime; - - while (tmp > 0x1FF * 86400) { - baseTime += 0x1FF * 86400; - tmp -= 0x1FF * 86400; - dataDh |= 0x80; - } - - dataDl = (tmp / 86400) & 0xFF; - dataDh &= 0xFE; - dataDh |= ((tmp / 86400) & 0x100) >> 8; - tmp %= 86400; - - dataH = tmp / 3600; - tmp %= 3600; - - dataM = tmp / 60; - tmp %= 60; - - dataS = tmp; -} - -void Rtc::doSwapActive() { - if (!enabled || index > 4) { - activeData = NULL; - activeSet = NULL; - } else switch (index) { - case 0x00: - activeData = &dataS; - activeSet = &Rtc::setS; - break; - case 0x01: - activeData = &dataM; - activeSet = &Rtc::setM; - break; - case 0x02: - activeData = &dataH; - activeSet = &Rtc::setH; - break; - case 0x03: - activeData = &dataDl; - activeSet = &Rtc::setDl; - break; - case 0x04: - activeData = &dataDh; - activeSet = &Rtc::setDh; - break; - } -} - -void Rtc::loadState(const SaveState &state) { - baseTime = state.rtc.baseTime; - haltTime = state.rtc.haltTime; - dataDh = state.rtc.dataDh; - dataDl = state.rtc.dataDl; - dataH = state.rtc.dataH; - dataM = state.rtc.dataM; - dataS = state.rtc.dataS; - lastLatchData = state.rtc.lastLatchData; - - doSwapActive(); -} - -void Rtc::setDh(const unsigned new_dh) { - const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); - const std::uint32_t old_highdays = ((unixtime - baseTime) / 86400) & 0x100; - baseTime += old_highdays * 86400; - baseTime -= ((new_dh & 0x1) << 8) * 86400; - - if ((dataDh ^ new_dh) & 0x40) { - if (new_dh & 0x40) - haltTime = timeCB(); - else - baseTime += timeCB() - haltTime; - } -} - -void Rtc::setDl(const unsigned new_lowdays) { - const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); - const std::uint32_t old_lowdays = ((unixtime - baseTime) / 86400) & 0xFF; - baseTime += old_lowdays * 86400; - baseTime -= new_lowdays * 86400; -} - -void Rtc::setH(const unsigned new_hours) { - const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); - const std::uint32_t old_hours = ((unixtime - baseTime) / 3600) % 24; - baseTime += old_hours * 3600; - baseTime -= new_hours * 3600; -} - -void Rtc::setM(const unsigned new_minutes) { - const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); - const std::uint32_t old_minutes = ((unixtime - baseTime) / 60) % 60; - baseTime += old_minutes * 60; - baseTime -= new_minutes * 60; -} - -void Rtc::setS(const unsigned new_seconds) { - const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); - baseTime += (unixtime - baseTime) % 60; - baseTime -= new_seconds; -} - -SYNCFUNC(Rtc) -{ - EBS(activeData, 0); - EVS(activeData, &dataS, 1); - EVS(activeData, &dataM, 2); - EVS(activeData, &dataH, 3); - EVS(activeData, &dataDl, 4); - EVS(activeData, &dataDh, 5); - EES(activeData, NULL); - - EBS(activeSet, 0); - EVS(activeSet, &Rtc::setS, 1); - EVS(activeSet, &Rtc::setM, 2); - EVS(activeSet, &Rtc::setH, 3); - EVS(activeSet, &Rtc::setDl, 4); - EVS(activeSet, &Rtc::setDh, 5); - EES(activeSet, NULL); - - NSS(baseTime); - NSS(haltTime); - NSS(index); - NSS(dataDh); - NSS(dataDl); - NSS(dataH); - NSS(dataM); - NSS(dataS); - NSS(enabled); - NSS(lastLatchData); -} - -} +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "rtc.h" +#include "../savestate.h" +#include + +namespace gambatte { + +Rtc::Rtc() +: activeData(NULL), + activeSet(NULL), + baseTime(0), + haltTime(0), + index(5), + dataDh(0), + dataDl(0), + dataH(0), + dataM(0), + dataS(0), + enabled(false), + lastLatchData(false), + timeCB(0) +{ +} + +void Rtc::doLatch() { + std::uint32_t tmp = ((dataDh & 0x40) ? haltTime : timeCB()) - baseTime; + + while (tmp > 0x1FF * 86400) { + baseTime += 0x1FF * 86400; + tmp -= 0x1FF * 86400; + dataDh |= 0x80; + } + + dataDl = (tmp / 86400) & 0xFF; + dataDh &= 0xFE; + dataDh |= ((tmp / 86400) & 0x100) >> 8; + tmp %= 86400; + + dataH = tmp / 3600; + tmp %= 3600; + + dataM = tmp / 60; + tmp %= 60; + + dataS = tmp; +} + +void Rtc::doSwapActive() { + if (!enabled || index > 4) { + activeData = NULL; + activeSet = NULL; + } else switch (index) { + case 0x00: + activeData = &dataS; + activeSet = &Rtc::setS; + break; + case 0x01: + activeData = &dataM; + activeSet = &Rtc::setM; + break; + case 0x02: + activeData = &dataH; + activeSet = &Rtc::setH; + break; + case 0x03: + activeData = &dataDl; + activeSet = &Rtc::setDl; + break; + case 0x04: + activeData = &dataDh; + activeSet = &Rtc::setDh; + break; + } +} + +void Rtc::loadState(const SaveState &state) { + baseTime = state.rtc.baseTime; + haltTime = state.rtc.haltTime; + dataDh = state.rtc.dataDh; + dataDl = state.rtc.dataDl; + dataH = state.rtc.dataH; + dataM = state.rtc.dataM; + dataS = state.rtc.dataS; + lastLatchData = state.rtc.lastLatchData; + + doSwapActive(); +} + +void Rtc::setDh(const unsigned new_dh) { + const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); + const std::uint32_t old_highdays = ((unixtime - baseTime) / 86400) & 0x100; + baseTime += old_highdays * 86400; + baseTime -= ((new_dh & 0x1) << 8) * 86400; + + if ((dataDh ^ new_dh) & 0x40) { + if (new_dh & 0x40) + haltTime = timeCB(); + else + baseTime += timeCB() - haltTime; + } +} + +void Rtc::setDl(const unsigned new_lowdays) { + const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); + const std::uint32_t old_lowdays = ((unixtime - baseTime) / 86400) & 0xFF; + baseTime += old_lowdays * 86400; + baseTime -= new_lowdays * 86400; +} + +void Rtc::setH(const unsigned new_hours) { + const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); + const std::uint32_t old_hours = ((unixtime - baseTime) / 3600) % 24; + baseTime += old_hours * 3600; + baseTime -= new_hours * 3600; +} + +void Rtc::setM(const unsigned new_minutes) { + const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); + const std::uint32_t old_minutes = ((unixtime - baseTime) / 60) % 60; + baseTime += old_minutes * 60; + baseTime -= new_minutes * 60; +} + +void Rtc::setS(const unsigned new_seconds) { + const std::uint32_t unixtime = (dataDh & 0x40) ? haltTime : timeCB(); + baseTime += (unixtime - baseTime) % 60; + baseTime -= new_seconds; +} + +SYNCFUNC(Rtc) +{ + EBS(activeData, 0); + EVS(activeData, &dataS, 1); + EVS(activeData, &dataM, 2); + EVS(activeData, &dataH, 3); + EVS(activeData, &dataDl, 4); + EVS(activeData, &dataDh, 5); + EES(activeData, NULL); + + EBS(activeSet, 0); + EVS(activeSet, &Rtc::setS, 1); + EVS(activeSet, &Rtc::setM, 2); + EVS(activeSet, &Rtc::setH, 3); + EVS(activeSet, &Rtc::setDl, 4); + EVS(activeSet, &Rtc::setDh, 5); + EES(activeSet, NULL); + + NSS(baseTime); + NSS(haltTime); + NSS(index); + NSS(dataDh); + NSS(dataDl); + NSS(dataH); + NSS(dataM); + NSS(dataS); + NSS(enabled); + NSS(lastLatchData); +} + +} diff --git a/libgambatte/src/mem/rtc.h b/libgambatte/src/mem/rtc.h index 00652a2181..cdbd7e66d0 100644 --- a/libgambatte/src/mem/rtc.h +++ b/libgambatte/src/mem/rtc.h @@ -1,98 +1,98 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef RTC_H -#define RTC_H - -#include -#include "newstate.h" - -namespace gambatte { - -struct SaveState; - -class Rtc { -private: - unsigned char *activeData; - void (Rtc::*activeSet)(unsigned); - std::uint32_t baseTime; - std::uint32_t haltTime; - unsigned char index; - unsigned char dataDh; - unsigned char dataDl; - unsigned char dataH; - unsigned char dataM; - unsigned char dataS; - bool enabled; - bool lastLatchData; - std::uint32_t (*timeCB)(); - - void doLatch(); - void doSwapActive(); - void setDh(unsigned new_dh); - void setDl(unsigned new_lowdays); - void setH(unsigned new_hours); - void setM(unsigned new_minutes); - void setS(unsigned new_seconds); - -public: - Rtc(); - - const unsigned char* getActive() const { return activeData; } - std::uint32_t getBaseTime() const { return baseTime; } - - void setBaseTime(const std::uint32_t baseTime) { - this->baseTime = baseTime; -// doLatch(); - } - - void latch(const unsigned data) { - if (!lastLatchData && data == 1) - doLatch(); - - lastLatchData = data; - } - - void loadState(const SaveState &state); - - void set(const bool enabled, unsigned bank) { - bank &= 0xF; - bank -= 8; - - this->enabled = enabled; - this->index = bank; - - doSwapActive(); - } - - void write(const unsigned data) { -// if (activeSet) - (this->*activeSet)(data); - *activeData = data; - } - - void setRTCCallback(std::uint32_t (*callback)()) { - timeCB = callback; - } - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef RTC_H +#define RTC_H + +#include +#include "newstate.h" + +namespace gambatte { + +struct SaveState; + +class Rtc { +private: + unsigned char *activeData; + void (Rtc::*activeSet)(unsigned); + std::uint32_t baseTime; + std::uint32_t haltTime; + unsigned char index; + unsigned char dataDh; + unsigned char dataDl; + unsigned char dataH; + unsigned char dataM; + unsigned char dataS; + bool enabled; + bool lastLatchData; + std::uint32_t (*timeCB)(); + + void doLatch(); + void doSwapActive(); + void setDh(unsigned new_dh); + void setDl(unsigned new_lowdays); + void setH(unsigned new_hours); + void setM(unsigned new_minutes); + void setS(unsigned new_seconds); + +public: + Rtc(); + + const unsigned char* getActive() const { return activeData; } + std::uint32_t getBaseTime() const { return baseTime; } + + void setBaseTime(const std::uint32_t baseTime) { + this->baseTime = baseTime; +// doLatch(); + } + + void latch(const unsigned data) { + if (!lastLatchData && data == 1) + doLatch(); + + lastLatchData = data; + } + + void loadState(const SaveState &state); + + void set(const bool enabled, unsigned bank) { + bank &= 0xF; + bank -= 8; + + this->enabled = enabled; + this->index = bank; + + doSwapActive(); + } + + void write(const unsigned data) { +// if (activeSet) + (this->*activeSet)(data); + *activeData = data; + } + + void setRTCCallback(std::uint32_t (*callback)()) { + timeCB = callback; + } + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/memory.cpp b/libgambatte/src/memory.cpp index 0a52eb6686..a5fcc148ef 100644 --- a/libgambatte/src/memory.cpp +++ b/libgambatte/src/memory.cpp @@ -1,1118 +1,1161 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#include "memory.h" -#include "video.h" -#include "sound.h" -#include "savestate.h" -#include - -namespace gambatte { - -Memory::Memory(const Interrupter &interrupter_in) -: readCallback(0), - writeCallback(0), - execCallback(0), - cdCallback(0), - linkCallback(0), - getInput(0), - divLastUpdate(0), - lastOamDmaUpdate(DISABLED_TIME), - display(ioamhram, 0, VideoInterruptRequester(&intreq)), - interrupter(interrupter_in), - dmaSource(0), - dmaDestination(0), - oamDmaPos(0xFE), - serialCnt(0), - blanklcd(false), - LINKCABLE(false), - linkClockTrigger(false) -{ - intreq.setEventTime(144*456ul); - intreq.setEventTime(0); -} - -void Memory::setStatePtrs(SaveState &state) { - state.mem.ioamhram.set(ioamhram, sizeof ioamhram); - - cart.setStatePtrs(state); - display.setStatePtrs(state); - sound.setStatePtrs(state); -} - - -static inline int serialCntFrom(const unsigned long cyclesUntilDone, const bool cgbFast) { - return cgbFast ? (cyclesUntilDone + 0xF) >> 4 : (cyclesUntilDone + 0x1FF) >> 9; -} - -void Memory::loadState(const SaveState &state) { - sound.loadState(state); - display.loadState(state, state.mem.oamDmaPos < 0xA0 ? cart.rdisabledRam() : ioamhram); - tima.loadState(state, TimaInterruptRequester(intreq)); - cart.loadState(state); - intreq.loadState(state); - - divLastUpdate = state.mem.divLastUpdate; - intreq.setEventTime(state.mem.nextSerialtime > state.cpu.cycleCounter ? state.mem.nextSerialtime : state.cpu.cycleCounter); - intreq.setEventTime(state.mem.unhaltTime); - lastOamDmaUpdate = state.mem.lastOamDmaUpdate; - dmaSource = state.mem.dmaSource; - dmaDestination = state.mem.dmaDestination; - oamDmaPos = state.mem.oamDmaPos; - serialCnt = intreq.eventTime(SERIAL) != DISABLED_TIME - ? serialCntFrom(intreq.eventTime(SERIAL) - state.cpu.cycleCounter, ioamhram[0x102] & isCgb() * 2) - : 8; - - cart.setVrambank(ioamhram[0x14F] & isCgb()); - cart.setOamDmaSrc(OAM_DMA_SRC_OFF); - cart.setWrambank(isCgb() && (ioamhram[0x170] & 0x07) ? ioamhram[0x170] & 0x07 : 1); - - if (lastOamDmaUpdate != DISABLED_TIME) { - oamDmaInitSetup(); - - const unsigned oamEventPos = oamDmaPos < 0xA0 ? 0xA0 : 0x100; - - intreq.setEventTime(lastOamDmaUpdate + (oamEventPos - oamDmaPos) * 4); - } - - intreq.setEventTime((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : state.cpu.cycleCounter); - blanklcd = false; - - if (!isCgb()) - std::memset(cart.vramdata() + 0x2000, 0, 0x2000); -} - -void Memory::setEndtime(const unsigned long cycleCounter, const unsigned long inc) { - if (intreq.eventTime(BLIT) <= cycleCounter) - intreq.setEventTime(intreq.eventTime(BLIT) + (70224 << isDoubleSpeed())); - - intreq.setEventTime(cycleCounter + (inc << isDoubleSpeed())); -} - -void Memory::updateSerial(const unsigned long cc) { - if (!LINKCABLE) { - if (intreq.eventTime(SERIAL) != DISABLED_TIME) { - if (intreq.eventTime(SERIAL) <= cc) { - ioamhram[0x101] = (((ioamhram[0x101] + 1) << serialCnt) - 1) & 0xFF; - ioamhram[0x102] &= 0x7F; - intreq.setEventTime(DISABLED_TIME); - intreq.flagIrq(8); - } else { - const int targetCnt = serialCntFrom(intreq.eventTime(SERIAL) - cc, ioamhram[0x102] & isCgb() * 2); - ioamhram[0x101] = (((ioamhram[0x101] + 1) << (serialCnt - targetCnt)) - 1) & 0xFF; - serialCnt = targetCnt; - } - } - } - else { - if (intreq.eventTime(SERIAL) != DISABLED_TIME) { - if (intreq.eventTime(SERIAL) <= cc) { - linkClockTrigger = true; - intreq.setEventTime(DISABLED_TIME); - if (linkCallback) - linkCallback(); - } - } - } -} - -void Memory::updateTimaIrq(const unsigned long cc) { - while (intreq.eventTime(TIMA) <= cc) - tima.doIrqEvent(TimaInterruptRequester(intreq)); -} - -void Memory::updateIrqs(const unsigned long cc) { - updateSerial(cc); - updateTimaIrq(cc); - display.update(cc); -} - -unsigned long Memory::event(unsigned long cycleCounter) { - if (lastOamDmaUpdate != DISABLED_TIME) - updateOamDma(cycleCounter); - - switch (intreq.minEventId()) { - case UNHALT: - intreq.unhalt(); - intreq.setEventTime(DISABLED_TIME); - break; - case END: - intreq.setEventTime(DISABLED_TIME - 1); - - while (cycleCounter >= intreq.minEventTime() && intreq.eventTime(END) != DISABLED_TIME) - cycleCounter = event(cycleCounter); - - intreq.setEventTime(DISABLED_TIME); - - break; - case BLIT: - { - const bool lcden = ioamhram[0x140] >> 7 & 1; - unsigned long blitTime = intreq.eventTime(BLIT); - - if (lcden | blanklcd) { - display.updateScreen(blanklcd, cycleCounter); - intreq.setEventTime(DISABLED_TIME); - intreq.setEventTime(DISABLED_TIME); - - while (cycleCounter >= intreq.minEventTime()) - cycleCounter = event(cycleCounter); - } else - blitTime += 70224 << isDoubleSpeed(); - - blanklcd = lcden ^ 1; - intreq.setEventTime(blitTime); - } - break; - case SERIAL: - updateSerial(cycleCounter); - break; - case OAM: - intreq.setEventTime(lastOamDmaUpdate == DISABLED_TIME ? - static_cast(DISABLED_TIME) : intreq.eventTime(OAM) + 0xA0 * 4); - break; - case DMA: - { - const bool doubleSpeed = isDoubleSpeed(); - unsigned dmaSrc = dmaSource; - unsigned dmaDest = dmaDestination; - unsigned dmaLength = ((ioamhram[0x155] & 0x7F) + 0x1) * 0x10; - unsigned length = hdmaReqFlagged(intreq) ? 0x10 : dmaLength; - - ackDmaReq(&intreq); - - if ((static_cast(dmaDest) + length) & 0x10000) { - length = 0x10000 - dmaDest; - ioamhram[0x155] |= 0x80; - } - - dmaLength -= length; - - if (!(ioamhram[0x140] & 0x80)) - dmaLength = 0; - - { - unsigned long lOamDmaUpdate = lastOamDmaUpdate; - lastOamDmaUpdate = DISABLED_TIME; - - while (length--) { - const unsigned src = dmaSrc++ & 0xFFFF; - const unsigned data = ((src & 0xE000) == 0x8000 || src > 0xFDFF) ? 0xFF : read(src, cycleCounter); - - cycleCounter += 2 << doubleSpeed; - - if (cycleCounter - 3 > lOamDmaUpdate) { - oamDmaPos = (oamDmaPos + 1) & 0xFF; - lOamDmaUpdate += 4; - - if (oamDmaPos < 0xA0) { - if (oamDmaPos == 0) - startOamDma(lOamDmaUpdate - 1); - - ioamhram[src & 0xFF] = data; - } else if (oamDmaPos == 0xA0) { - endOamDma(lOamDmaUpdate - 1); - lOamDmaUpdate = DISABLED_TIME; - } - } - - nontrivial_write(0x8000 | (dmaDest++ & 0x1FFF), data, cycleCounter); - } - - lastOamDmaUpdate = lOamDmaUpdate; - } - - cycleCounter += 4; - - dmaSource = dmaSrc; - dmaDestination = dmaDest; - ioamhram[0x155] = ((dmaLength / 0x10 - 0x1) & 0xFF) | (ioamhram[0x155] & 0x80); - - if ((ioamhram[0x155] & 0x80) && display.hdmaIsEnabled()) { - if (lastOamDmaUpdate != DISABLED_TIME) - updateOamDma(cycleCounter); - - display.disableHdma(cycleCounter); - } - } - - break; - case TIMA: - tima.doIrqEvent(TimaInterruptRequester(intreq)); - break; - case VIDEO: - display.update(cycleCounter); - break; - case INTERRUPTS: - if (halted()) { - if (isCgb()) - cycleCounter += 4; - - intreq.unhalt(); - intreq.setEventTime(DISABLED_TIME); - } - - if (ime()) { - unsigned address; - const unsigned pendingIrqs = intreq.pendingIrqs(); - const unsigned n = pendingIrqs & -pendingIrqs; - - if (n < 8) { - static const unsigned char lut[] = { 0x40, 0x48, 0x48, 0x50 }; - address = lut[n-1]; - } else - address = 0x50 + n; - - intreq.ackIrq(n); - cycleCounter = interrupter.interrupt(address, cycleCounter, *this); - } - - break; - } - - return cycleCounter; -} - -unsigned long Memory::stop(unsigned long cycleCounter) { - cycleCounter += 4 << isDoubleSpeed(); - - if (ioamhram[0x14D] & isCgb()) { - sound.generate_samples(cycleCounter, isDoubleSpeed()); - - display.speedChange(cycleCounter); - ioamhram[0x14D] ^= 0x81; - - intreq.setEventTime((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : cycleCounter + (70224 << isDoubleSpeed())); - - if (intreq.eventTime(END) > cycleCounter) { - intreq.setEventTime(cycleCounter + (isDoubleSpeed() ? - (intreq.eventTime(END) - cycleCounter) << 1 : (intreq.eventTime(END) - cycleCounter) >> 1)); - } - // when switching speed, it seems that the CPU spontaneously restarts soon? - // otherwise, the cpu should be allowed to stay halted as long as needed - // so only execute this line when switching speed - intreq.setEventTime(cycleCounter + 0x20000 + isDoubleSpeed() * 8); - } - - intreq.halt(); - - return cycleCounter; -} - -static void decCycles(unsigned long &counter, const unsigned long dec) { - if (counter != DISABLED_TIME) - counter -= dec; -} - -void Memory::decEventCycles(const MemEventId eventId, const unsigned long dec) { - if (intreq.eventTime(eventId) != DISABLED_TIME) - intreq.setEventTime(eventId, intreq.eventTime(eventId) - dec); -} - -unsigned long Memory::resetCounters(unsigned long cycleCounter) { - if (lastOamDmaUpdate != DISABLED_TIME) - updateOamDma(cycleCounter); - - updateIrqs(cycleCounter); - - const unsigned long oldCC = cycleCounter; - - { - const unsigned long divinc = (cycleCounter - divLastUpdate) >> 8; - ioamhram[0x104] = (ioamhram[0x104] + divinc) & 0xFF; - divLastUpdate += divinc << 8; - } - - const unsigned long dec = cycleCounter < 0x10000 ? 0 : (cycleCounter & ~0x7FFFul) - 0x8000; - - decCycles(divLastUpdate, dec); - decCycles(lastOamDmaUpdate, dec); - decEventCycles(SERIAL, dec); - decEventCycles(OAM, dec); - decEventCycles(BLIT, dec); - decEventCycles(END, dec); - decEventCycles(UNHALT, dec); - - cycleCounter -= dec; - - intreq.resetCc(oldCC, cycleCounter); - tima.resetCc(oldCC, cycleCounter, TimaInterruptRequester(intreq)); - display.resetCc(oldCC, cycleCounter); - sound.resetCounter(cycleCounter, oldCC, isDoubleSpeed()); - - return cycleCounter; -} - -void Memory::updateInput() { - unsigned state = 0xF; - - if ((ioamhram[0x100] & 0x30) != 0x30 && getInput) { - unsigned input = (*getInput)(); - unsigned dpad_state = ~input >> 4; - unsigned button_state = ~input; - if (!(ioamhram[0x100] & 0x10)) - state &= dpad_state; - if (!(ioamhram[0x100] & 0x20)) - state &= button_state; - } - - if (state != 0xF && (ioamhram[0x100] & 0xF) == 0xF) - intreq.flagIrq(0x10); - - ioamhram[0x100] = (ioamhram[0x100] & -0x10u) | state; -} - -void Memory::updateOamDma(const unsigned long cycleCounter) { - const unsigned char *const oamDmaSrc = oamDmaSrcPtr(); - unsigned cycles = (cycleCounter - lastOamDmaUpdate) >> 2; - - while (cycles--) { - oamDmaPos = (oamDmaPos + 1) & 0xFF; - lastOamDmaUpdate += 4; - - if (oamDmaPos < 0xA0) { - if (oamDmaPos == 0) - startOamDma(lastOamDmaUpdate - 1); - - ioamhram[oamDmaPos] = oamDmaSrc ? oamDmaSrc[oamDmaPos] : cart.rtcRead(); - } else if (oamDmaPos == 0xA0) { - endOamDma(lastOamDmaUpdate - 1); - lastOamDmaUpdate = DISABLED_TIME; - break; - } - } -} - -void Memory::oamDmaInitSetup() { - if (ioamhram[0x146] < 0xA0) { - cart.setOamDmaSrc(ioamhram[0x146] < 0x80 ? OAM_DMA_SRC_ROM : OAM_DMA_SRC_VRAM); - } else if (ioamhram[0x146] < 0xFE - isCgb() * 0x1E) { - cart.setOamDmaSrc(ioamhram[0x146] < 0xC0 ? OAM_DMA_SRC_SRAM : OAM_DMA_SRC_WRAM); - } else - cart.setOamDmaSrc(OAM_DMA_SRC_INVALID); -} - -static const unsigned char * oamDmaSrcZero() { - static unsigned char zeroMem[0xA0]; - return zeroMem; -} - -const unsigned char * Memory::oamDmaSrcPtr() const { - switch (cart.oamDmaSrc()) { - case OAM_DMA_SRC_ROM: return cart.romdata(ioamhram[0x146] >> 6) + (ioamhram[0x146] << 8); - case OAM_DMA_SRC_SRAM: return cart.rsrambankptr() ? cart.rsrambankptr() + (ioamhram[0x146] << 8) : 0; - case OAM_DMA_SRC_VRAM: return cart.vrambankptr() + (ioamhram[0x146] << 8); - case OAM_DMA_SRC_WRAM: return cart.wramdata(ioamhram[0x146] >> 4 & 1) + (ioamhram[0x146] << 8 & 0xFFF); - case OAM_DMA_SRC_INVALID: - case OAM_DMA_SRC_OFF: break; - } - - return ioamhram[0x146] == 0xFF && !isCgb() ? oamDmaSrcZero() : cart.rdisabledRam(); -} - -void Memory::startOamDma(const unsigned long cycleCounter) { - display.oamChange(cart.rdisabledRam(), cycleCounter); -} - -void Memory::endOamDma(const unsigned long cycleCounter) { - oamDmaPos = 0xFE; - cart.setOamDmaSrc(OAM_DMA_SRC_OFF); - display.oamChange(ioamhram, cycleCounter); -} - -unsigned Memory::nontrivial_ff_read(const unsigned P, const unsigned long cycleCounter) { - if (lastOamDmaUpdate != DISABLED_TIME) - updateOamDma(cycleCounter); - - switch (P & 0x7F) { - case 0x00: - updateInput(); - break; - case 0x01: - case 0x02: - updateSerial(cycleCounter); - break; - case 0x04: - { - const unsigned long divcycles = (cycleCounter - divLastUpdate) >> 8; - ioamhram[0x104] = (ioamhram[0x104] + divcycles) & 0xFF; - divLastUpdate += divcycles << 8; - } - - break; - case 0x05: - ioamhram[0x105] = tima.tima(cycleCounter); - break; - case 0x0F: - updateIrqs(cycleCounter); - ioamhram[0x10F] = intreq.ifreg(); - break; - case 0x26: - if (ioamhram[0x126] & 0x80) { - sound.generate_samples(cycleCounter, isDoubleSpeed()); - ioamhram[0x126] = 0xF0 | sound.getStatus(); - } else - ioamhram[0x126] = 0x70; - - break; - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - sound.generate_samples(cycleCounter, isDoubleSpeed()); - return sound.waveRamRead(P & 0xF); - case 0x41: - return ioamhram[0x141] | display.getStat(ioamhram[0x145], cycleCounter); - case 0x44: - return display.getLyReg(cycleCounter/*+4*/); - case 0x69: - return display.cgbBgColorRead(ioamhram[0x168] & 0x3F, cycleCounter); - case 0x6B: - return display.cgbSpColorRead(ioamhram[0x16A] & 0x3F, cycleCounter); - default: break; - } - - return ioamhram[P - 0xFE00]; -} - -static bool isInOamDmaConflictArea(const OamDmaSrc oamDmaSrc, const unsigned addr, const bool cgb) { - struct Area { unsigned short areaUpper, exceptAreaLower, exceptAreaWidth, pad; }; - - static const Area cgbAreas[] = { - { 0xC000, 0x8000, 0x2000, 0 }, - { 0xC000, 0x8000, 0x2000, 0 }, - { 0xA000, 0x0000, 0x8000, 0 }, - { 0xFE00, 0x0000, 0xC000, 0 }, - { 0xC000, 0x8000, 0x2000, 0 }, - { 0x0000, 0x0000, 0x0000, 0 } - }; - - static const Area dmgAreas[] = { - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0xA000, 0x0000, 0x8000, 0 }, - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0xFE00, 0x8000, 0x2000, 0 }, - { 0x0000, 0x0000, 0x0000, 0 } - }; - - const Area *const a = cgb ? cgbAreas : dmgAreas; - - return addr < a[oamDmaSrc].areaUpper && addr - a[oamDmaSrc].exceptAreaLower >= a[oamDmaSrc].exceptAreaWidth; -} - -unsigned Memory::nontrivial_read(const unsigned P, const unsigned long cycleCounter) { - if (P < 0xFF80) { - if (lastOamDmaUpdate != DISABLED_TIME) { - updateOamDma(cycleCounter); - - if (isInOamDmaConflictArea(cart.oamDmaSrc(), P, isCgb()) && oamDmaPos < 0xA0) - return ioamhram[oamDmaPos]; - } - - if (P < 0xC000) { - if (P < 0x8000) - return cart.romdata(P >> 14)[P]; - - if (P < 0xA000) { - if (!display.vramAccessible(cycleCounter)) - return 0xFF; - - return cart.vrambankptr()[P]; - } - - if (cart.rsrambankptr()) - return cart.rsrambankptr()[P]; - - return cart.rtcRead(); - } - - if (P < 0xFE00) - return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; - - if (P >= 0xFF00) - return nontrivial_ff_read(P, cycleCounter); - - if (!display.oamReadable(cycleCounter) || oamDmaPos < 0xA0) - return 0xFF; - } - - return ioamhram[P - 0xFE00]; -} - -unsigned Memory::nontrivial_peek(const unsigned P) { - if (P < 0xC000) { - if (P < 0x8000) - return cart.romdata(P >> 14)[P]; - - if (P < 0xA000) { - return cart.vrambankptr()[P]; - } - - if (cart.rsrambankptr()) - return cart.rsrambankptr()[P]; - - return cart.rtcRead(); // verified side-effect free - } - if (P < 0xFE00) - return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; - if (P >= 0xFF00 && P < 0xFF80) - return nontrivial_ff_peek(P); - return ioamhram[P - 0xFE00]; -} - -unsigned Memory::nontrivial_ff_peek(const unsigned P) { - // some regs may be somewhat wrong with this - return ioamhram[P - 0xFE00]; -} - -void Memory::nontrivial_ff_write(const unsigned P, unsigned data, const unsigned long cycleCounter) { - if (lastOamDmaUpdate != DISABLED_TIME) - updateOamDma(cycleCounter); - - switch (P & 0xFF) { - case 0x00: - if ((data ^ ioamhram[0x100]) & 0x30) { - ioamhram[0x100] = (ioamhram[0x100] & ~0x30u) | (data & 0x30); - updateInput(); - } - return; - case 0x01: - updateSerial(cycleCounter); - break; - case 0x02: - updateSerial(cycleCounter); - - serialCnt = 8; - intreq.setEventTime((data & 0x81) == 0x81 - ? (data & isCgb() * 2 ? (cycleCounter & ~0x7ul) + 0x10 * 8 : (cycleCounter & ~0xFFul) + 0x200 * 8) - : static_cast(DISABLED_TIME)); - - data |= 0x7E - isCgb() * 2; - break; - case 0x04: - ioamhram[0x104] = 0; - divLastUpdate = cycleCounter; - return; - case 0x05: - tima.setTima(data, cycleCounter, TimaInterruptRequester(intreq)); - break; - case 0x06: - tima.setTma(data, cycleCounter, TimaInterruptRequester(intreq)); - break; - case 0x07: - data |= 0xF8; - tima.setTac(data, cycleCounter, TimaInterruptRequester(intreq)); - break; - case 0x0F: - updateIrqs(cycleCounter); - intreq.setIfreg(0xE0 | data); - return; - case 0x10: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr10(data); - data |= 0x80; - break; - case 0x11: - if (!sound.isEnabled()) { - if (isCgb()) - return; - - data &= 0x3F; - } - - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr11(data); - data |= 0x3F; - break; - case 0x12: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr12(data); - break; - case 0x13: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr13(data); - return; - case 0x14: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr14(data); - data |= 0xBF; - break; - case 0x16: - if (!sound.isEnabled()) { - if (isCgb()) - return; - - data &= 0x3F; - } - - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr21(data); - data |= 0x3F; - break; - case 0x17: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr22(data); - break; - case 0x18: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr23(data); - return; - case 0x19: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr24(data); - data |= 0xBF; - break; - case 0x1A: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr30(data); - data |= 0x7F; - break; - case 0x1B: - if (!sound.isEnabled() && isCgb()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr31(data); - return; - case 0x1C: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr32(data); - data |= 0x9F; - break; - case 0x1D: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr33(data); - return; - case 0x1E: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr34(data); - data |= 0xBF; - break; - case 0x20: - if (!sound.isEnabled() && isCgb()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr41(data); - return; - case 0x21: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr42(data); - break; - case 0x22: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr43(data); - break; - case 0x23: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_nr44(data); - data |= 0xBF; - break; - case 0x24: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.set_so_volume(data); - break; - case 0x25: - if (!sound.isEnabled()) return; - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.map_so(data); - break; - case 0x26: - if ((ioamhram[0x126] ^ data) & 0x80) { - sound.generate_samples(cycleCounter, isDoubleSpeed()); - - if (!(data & 0x80)) { - for (unsigned i = 0xFF10; i < 0xFF26; ++i) - ff_write(i, 0, cycleCounter); - - sound.setEnabled(false); - } else { - sound.reset(); - sound.setEnabled(true); - } - } - - data = (data & 0x80) | (ioamhram[0x126] & 0x7F); - break; - case 0x30: - case 0x31: - case 0x32: - case 0x33: - case 0x34: - case 0x35: - case 0x36: - case 0x37: - case 0x38: - case 0x39: - case 0x3A: - case 0x3B: - case 0x3C: - case 0x3D: - case 0x3E: - case 0x3F: - sound.generate_samples(cycleCounter, isDoubleSpeed()); - sound.waveRamWrite(P & 0xF, data); - break; - case 0x40: - if (ioamhram[0x140] != data) { - if ((ioamhram[0x140] ^ data) & 0x80) { - const unsigned lyc = display.getStat(ioamhram[0x145], cycleCounter) & 4; - const bool hdmaEnabled = display.hdmaIsEnabled(); - - display.lcdcChange(data, cycleCounter); - ioamhram[0x144] = 0; - ioamhram[0x141] &= 0xF8; - - if (data & 0x80) { - intreq.setEventTime(display.nextMode1IrqTime() + (blanklcd ? 0 : 70224 << isDoubleSpeed())); - } else { - ioamhram[0x141] |= lyc; - intreq.setEventTime(cycleCounter + (456 * 4 << isDoubleSpeed())); - - if (hdmaEnabled) - flagHdmaReq(&intreq); - } - } else - display.lcdcChange(data, cycleCounter); - - ioamhram[0x140] = data; - } - - return; - case 0x41: - display.lcdstatChange(data, cycleCounter); - data = (ioamhram[0x141] & 0x87) | (data & 0x78); - break; - case 0x42: - display.scyChange(data, cycleCounter); - break; - case 0x43: - display.scxChange(data, cycleCounter); - break; - case 0x45: - display.lycRegChange(data, cycleCounter); - break; - case 0x46: - if (lastOamDmaUpdate != DISABLED_TIME) - endOamDma(cycleCounter); - - lastOamDmaUpdate = cycleCounter; - intreq.setEventTime(cycleCounter + 8); - ioamhram[0x146] = data; - oamDmaInitSetup(); - return; - case 0x47: - if (!isCgb()) - display.dmgBgPaletteChange(data, cycleCounter); - - break; - case 0x48: - if (!isCgb()) - display.dmgSpPalette1Change(data, cycleCounter); - - break; - case 0x49: - if (!isCgb()) - display.dmgSpPalette2Change(data, cycleCounter); - - break; - case 0x4A: - display.wyChange(data, cycleCounter); - break; - case 0x4B: - display.wxChange(data, cycleCounter); - break; - - case 0x4D: - if (isCgb()) - ioamhram[0x14D] = (ioamhram[0x14D] & ~1u) | (data & 1); return; - case 0x4F: - if (isCgb()) { - cart.setVrambank(data & 1); - ioamhram[0x14F] = 0xFE | data; - } - - return; - case 0x50: - // this is the register that turns off the bootrom - // it can only ever be written to once (with 1) once boot rom finishes - return; - case 0x51: - dmaSource = data << 8 | (dmaSource & 0xFF); - return; - case 0x52: - dmaSource = (dmaSource & 0xFF00) | (data & 0xF0); - return; - case 0x53: - dmaDestination = data << 8 | (dmaDestination & 0xFF); - return; - case 0x54: - dmaDestination = (dmaDestination & 0xFF00) | (data & 0xF0); - return; - case 0x55: - if (isCgb()) { - ioamhram[0x155] = data & 0x7F; - - if (display.hdmaIsEnabled()) { - if (!(data & 0x80)) { - ioamhram[0x155] |= 0x80; - display.disableHdma(cycleCounter); - } - } else { - if (data & 0x80) { - if (ioamhram[0x140] & 0x80) { - display.enableHdma(cycleCounter); - } else - flagHdmaReq(&intreq); - } else - flagGdmaReq(&intreq); - } - } - - return; - case 0x56: - if (isCgb()) - ioamhram[0x156] = data | 0x3E; - - return; - case 0x68: - if (isCgb()) - ioamhram[0x168] = data | 0x40; - - return; - case 0x69: - if (isCgb()) { - const unsigned index = ioamhram[0x168] & 0x3F; - - display.cgbBgColorChange(index, data, cycleCounter); - - ioamhram[0x168] = (ioamhram[0x168] & ~0x3F) | ((index + (ioamhram[0x168] >> 7)) & 0x3F); - } - - return; - case 0x6A: - if (isCgb()) - ioamhram[0x16A] = data | 0x40; - - return; - case 0x6B: - if (isCgb()) { - const unsigned index = ioamhram[0x16A] & 0x3F; - - display.cgbSpColorChange(index, data, cycleCounter); - - ioamhram[0x16A] = (ioamhram[0x16A] & ~0x3F) | ((index + (ioamhram[0x16A] >> 7)) & 0x3F); - } - - return; - case 0x6C: - if (isCgb()) - ioamhram[0x16C] = data | 0xFE; - - return; - case 0x70: - if (isCgb()) { - cart.setWrambank((data & 0x07) ? (data & 0x07) : 1); - ioamhram[0x170] = data | 0xF8; - } - - return; - case 0x72: - case 0x73: - case 0x74: - if (isCgb()) - break; - - return; - case 0x75: - if (isCgb()) - ioamhram[0x175] = data | 0x8F; - - return; - case 0xFF: - intreq.setIereg(data); - break; - default: - return; - } - - ioamhram[P - 0xFE00] = data; -} - -void Memory::nontrivial_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { - if (lastOamDmaUpdate != DISABLED_TIME) { - updateOamDma(cycleCounter); - - if (isInOamDmaConflictArea(cart.oamDmaSrc(), P, isCgb()) && oamDmaPos < 0xA0) { - ioamhram[oamDmaPos] = data; - return; - } - } - - if (P < 0xFE00) { - if (P < 0xA000) { - if (P < 0x8000) { - cart.mbcWrite(P, data); - } else if (display.vramAccessible(cycleCounter)) { - display.vramChange(cycleCounter); - cart.vrambankptr()[P] = data; - } - } else if (P < 0xC000) { - if (cart.wsrambankptr()) - cart.wsrambankptr()[P] = data; - else - cart.rtcWrite(data); - } else - cart.wramdata(P >> 12 & 1)[P & 0xFFF] = data; - } else if (P - 0xFF80u >= 0x7Fu) { - if (P < 0xFF00) { - if (display.oamWritable(cycleCounter) && oamDmaPos >= 0xA0 && (P < 0xFEA0 || isCgb())) { - display.oamChange(cycleCounter); - ioamhram[P - 0xFE00] = data; - } - } else - nontrivial_ff_write(P, data, cycleCounter); - } else - ioamhram[P - 0xFE00] = data; -} - -int Memory::loadROM(const char *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { - if (const int fail = cart.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat)) - return fail; - - sound.init(cart.isCgb()); - display.reset(ioamhram, cart.vramdata(), cart.isCgb()); - - return 0; -} - -unsigned Memory::fillSoundBuffer(const unsigned long cycleCounter) { - sound.generate_samples(cycleCounter, isDoubleSpeed()); - return sound.fillBuffer(); -} - -void Memory::setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32) { - display.setDmgPaletteColor(palNum, colorNum, rgb32); -} - -void Memory::setCgbPalette(unsigned *lut) { - display.setCgbPalette(lut); -} - -bool Memory::getMemoryArea(int which, unsigned char **data, int *length) { - if (!data || !length) - return false; - - switch (which) - { - case 4: // oam - *data = &ioamhram[0]; - *length = 160; - return true; - case 5: // hram - *data = &ioamhram[384]; - *length = 128; - return true; - case 6: // bgpal - *data = (unsigned char *)display.bgPalette(); - *length = 32; - return true; - case 7: // sppal - *data = (unsigned char *)display.spPalette(); - *length = 32; - return true; - default: // pass to cartridge - return cart.getMemoryArea(which, data, length); - } -} - -int Memory::LinkStatus(int which) -{ - switch (which) - { - case 256: // ClockSignaled - return linkClockTrigger; - case 257: // AckClockSignal - linkClockTrigger = false; - return 0; - case 258: // GetOut - return ioamhram[0x101] & 0xff; - case 259: // connect link cable - LINKCABLE = true; - return 0; - default: // ShiftIn - if (ioamhram[0x102] & 0x80) // was enabled - { - ioamhram[0x101] = which; - ioamhram[0x102] &= 0x7F; - intreq.flagIrq(8); - } - return 0; - } - - return -1; -} - -SYNCFUNC(Memory) -{ - SSS(cart); - NSS(ioamhram); - NSS(divLastUpdate); - NSS(lastOamDmaUpdate); - - SSS(intreq); - SSS(tima); - SSS(display); - SSS(sound); - //SSS(interrupter); // no state - - NSS(dmaSource); - NSS(dmaDestination); - NSS(oamDmaPos); - NSS(serialCnt); - NSS(blanklcd); - - NSS(LINKCABLE); - NSS(linkClockTrigger); -} - -} +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#include "memory.h" +#include "video.h" +#include "sound.h" +#include "savestate.h" +#include + +namespace gambatte { + +Memory::Memory(const Interrupter &interrupter_in, unsigned short &sp, unsigned short &pc) +: readCallback(0), + writeCallback(0), + execCallback(0), + cdCallback(0), + linkCallback(0), + getInput(0), + divLastUpdate(0), + lastOamDmaUpdate(DISABLED_TIME), + display(ioamhram, 0, VideoInterruptRequester(&intreq)), + interrupter(interrupter_in), + dmaSource(0), + dmaDestination(0), + oamDmaPos(0xFE), + serialCnt(0), + blanklcd(false), + LINKCABLE(false), + linkClockTrigger(false), + SP(sp), + PC(pc) +{ + intreq.setEventTime(144*456ul); + intreq.setEventTime(0); +} + +void Memory::setStatePtrs(SaveState &state) { + state.mem.ioamhram.set(ioamhram, sizeof ioamhram); + + cart.setStatePtrs(state); + display.setStatePtrs(state); + sound.setStatePtrs(state); +} + + +static inline int serialCntFrom(const unsigned long cyclesUntilDone, const bool cgbFast) { + return cgbFast ? (cyclesUntilDone + 0xF) >> 4 : (cyclesUntilDone + 0x1FF) >> 9; +} + +void Memory::loadState(const SaveState &state) { + biosMode = state.mem.biosMode; + cgbSwitching = state.mem.cgbSwitching; + agbMode = state.mem.agbMode; + gbIsCgb_ = state.mem.gbIsCgb; + sound.loadState(state); + display.loadState(state, state.mem.oamDmaPos < 0xA0 ? cart.rdisabledRam() : ioamhram); + tima.loadState(state, TimaInterruptRequester(intreq)); + cart.loadState(state); + intreq.loadState(state); + + divLastUpdate = state.mem.divLastUpdate; + intreq.setEventTime(state.mem.nextSerialtime > state.cpu.cycleCounter ? state.mem.nextSerialtime : state.cpu.cycleCounter); + intreq.setEventTime(state.mem.unhaltTime); + lastOamDmaUpdate = state.mem.lastOamDmaUpdate; + dmaSource = state.mem.dmaSource; + dmaDestination = state.mem.dmaDestination; + oamDmaPos = state.mem.oamDmaPos; + serialCnt = intreq.eventTime(SERIAL) != DISABLED_TIME + ? serialCntFrom(intreq.eventTime(SERIAL) - state.cpu.cycleCounter, ioamhram[0x102] & isCgb() * 2) + : 8; + + cart.setVrambank(ioamhram[0x14F] & isCgb()); + cart.setOamDmaSrc(OAM_DMA_SRC_OFF); + cart.setWrambank(isCgb() && (ioamhram[0x170] & 0x07) ? ioamhram[0x170] & 0x07 : 1); + + if (lastOamDmaUpdate != DISABLED_TIME) { + oamDmaInitSetup(); + + const unsigned oamEventPos = oamDmaPos < 0xA0 ? 0xA0 : 0x100; + + intreq.setEventTime(lastOamDmaUpdate + (oamEventPos - oamDmaPos) * 4); + } + + intreq.setEventTime((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : state.cpu.cycleCounter); + blanklcd = false; + + if (!isCgb()) + std::memset(cart.vramdata() + 0x2000, 0, 0x2000); +} + +void Memory::setEndtime(const unsigned long cycleCounter, const unsigned long inc) { + if (intreq.eventTime(BLIT) <= cycleCounter) + intreq.setEventTime(intreq.eventTime(BLIT) + (70224 << isDoubleSpeed())); + + intreq.setEventTime(cycleCounter + (inc << isDoubleSpeed())); +} + +void Memory::updateSerial(const unsigned long cc) { + if (!LINKCABLE) { + if (intreq.eventTime(SERIAL) != DISABLED_TIME) { + if (intreq.eventTime(SERIAL) <= cc) { + ioamhram[0x101] = (((ioamhram[0x101] + 1) << serialCnt) - 1) & 0xFF; + ioamhram[0x102] &= 0x7F; + intreq.setEventTime(DISABLED_TIME); + intreq.flagIrq(8); + } else { + const int targetCnt = serialCntFrom(intreq.eventTime(SERIAL) - cc, ioamhram[0x102] & isCgb() * 2); + ioamhram[0x101] = (((ioamhram[0x101] + 1) << (serialCnt - targetCnt)) - 1) & 0xFF; + serialCnt = targetCnt; + } + } + } + else { + if (intreq.eventTime(SERIAL) != DISABLED_TIME) { + if (intreq.eventTime(SERIAL) <= cc) { + linkClockTrigger = true; + intreq.setEventTime(DISABLED_TIME); + if (linkCallback) + linkCallback(); + } + } + } +} + +void Memory::updateTimaIrq(const unsigned long cc) { + while (intreq.eventTime(TIMA) <= cc) + tima.doIrqEvent(TimaInterruptRequester(intreq)); +} + +void Memory::updateIrqs(const unsigned long cc) { + updateSerial(cc); + updateTimaIrq(cc); + display.update(cc); +} + +unsigned long Memory::event(unsigned long cycleCounter) { + if (lastOamDmaUpdate != DISABLED_TIME) + updateOamDma(cycleCounter); + + switch (intreq.minEventId()) { + case UNHALT: + intreq.unhalt(); + intreq.setEventTime(DISABLED_TIME); + break; + case END: + intreq.setEventTime(DISABLED_TIME - 1); + + while (cycleCounter >= intreq.minEventTime() && intreq.eventTime(END) != DISABLED_TIME) + cycleCounter = event(cycleCounter); + + intreq.setEventTime(DISABLED_TIME); + + break; + case BLIT: + { + const bool lcden = ioamhram[0x140] >> 7 & 1; + unsigned long blitTime = intreq.eventTime(BLIT); + + if (lcden | blanklcd) { + display.updateScreen(blanklcd, cycleCounter); + intreq.setEventTime(DISABLED_TIME); + intreq.setEventTime(DISABLED_TIME); + + while (cycleCounter >= intreq.minEventTime()) + cycleCounter = event(cycleCounter); + } else + blitTime += 70224 << isDoubleSpeed(); + + blanklcd = lcden ^ 1; + intreq.setEventTime(blitTime); + } + break; + case SERIAL: + updateSerial(cycleCounter); + break; + case OAM: + intreq.setEventTime(lastOamDmaUpdate == DISABLED_TIME ? + static_cast(DISABLED_TIME) : intreq.eventTime(OAM) + 0xA0 * 4); + break; + case DMA: + { + const bool doubleSpeed = isDoubleSpeed(); + unsigned dmaSrc = dmaSource; + unsigned dmaDest = dmaDestination; + unsigned dmaLength = ((ioamhram[0x155] & 0x7F) + 0x1) * 0x10; + unsigned length = hdmaReqFlagged(intreq) ? 0x10 : dmaLength; + + ackDmaReq(&intreq); + + if ((static_cast(dmaDest) + length) & 0x10000) { + length = 0x10000 - dmaDest; + ioamhram[0x155] |= 0x80; + } + + dmaLength -= length; + + if (!(ioamhram[0x140] & 0x80)) + dmaLength = 0; + + { + unsigned long lOamDmaUpdate = lastOamDmaUpdate; + lastOamDmaUpdate = DISABLED_TIME; + + while (length--) { + const unsigned src = dmaSrc++ & 0xFFFF; + const unsigned data = ((src & 0xE000) == 0x8000 || src > 0xFDFF) ? 0xFF : read(src, cycleCounter); + + cycleCounter += 2 << doubleSpeed; + + if (cycleCounter - 3 > lOamDmaUpdate) { + oamDmaPos = (oamDmaPos + 1) & 0xFF; + lOamDmaUpdate += 4; + + if (oamDmaPos < 0xA0) { + if (oamDmaPos == 0) + startOamDma(lOamDmaUpdate - 1); + + ioamhram[src & 0xFF] = data; + } else if (oamDmaPos == 0xA0) { + endOamDma(lOamDmaUpdate - 1); + lOamDmaUpdate = DISABLED_TIME; + } + } + + nontrivial_write(0x8000 | (dmaDest++ & 0x1FFF), data, cycleCounter); + } + + lastOamDmaUpdate = lOamDmaUpdate; + } + + cycleCounter += 4; + + dmaSource = dmaSrc; + dmaDestination = dmaDest; + ioamhram[0x155] = ((dmaLength / 0x10 - 0x1) & 0xFF) | (ioamhram[0x155] & 0x80); + + if ((ioamhram[0x155] & 0x80) && display.hdmaIsEnabled()) { + if (lastOamDmaUpdate != DISABLED_TIME) + updateOamDma(cycleCounter); + + display.disableHdma(cycleCounter); + } + } + + break; + case TIMA: + tima.doIrqEvent(TimaInterruptRequester(intreq)); + break; + case VIDEO: + display.update(cycleCounter); + break; + case INTERRUPTS: + if (halted()) { + if (gbIsCgb_) + cycleCounter += 4; + + intreq.unhalt(); + intreq.setEventTime(DISABLED_TIME); + } + + if (ime()) { + unsigned address; + + cycleCounter += 12; + display.update(cycleCounter); + SP = (SP - 2) & 0xFFFF; + write(SP + 1, PC >> 8, cycleCounter); + unsigned ie = intreq.iereg(); + + cycleCounter += 4; + display.update(cycleCounter); + write(SP, PC & 0xFF, cycleCounter); + const unsigned pendingIrqs = ie & intreq.ifreg(); + + cycleCounter += 4; + display.update(cycleCounter); + const unsigned n = pendingIrqs & -pendingIrqs; + + if (n == 0) { + address = 0; + } + else if (n < 8) { + static const unsigned char lut[] = { 0x40, 0x48, 0x48, 0x50 }; + address = lut[n-1]; + } else + address = 0x50 + n; + + intreq.ackIrq(n); + PC = address; + } + + break; + } + + return cycleCounter; +} + +unsigned long Memory::stop(unsigned long cycleCounter) { + cycleCounter += 4 << isDoubleSpeed(); + + if (ioamhram[0x14D] & isCgb()) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + + display.speedChange(cycleCounter); + ioamhram[0x14D] ^= 0x81; + + intreq.setEventTime((ioamhram[0x140] & 0x80) ? display.nextMode1IrqTime() : cycleCounter + (70224 << isDoubleSpeed())); + + if (intreq.eventTime(END) > cycleCounter) { + intreq.setEventTime(cycleCounter + (isDoubleSpeed() ? + (intreq.eventTime(END) - cycleCounter) << 1 : (intreq.eventTime(END) - cycleCounter) >> 1)); + } + // when switching speed, it seems that the CPU spontaneously restarts soon? + // otherwise, the cpu should be allowed to stay halted as long as needed + // so only execute this line when switching speed + intreq.halt(); + intreq.setEventTime(cycleCounter + 0x20000 + isDoubleSpeed() * 8); + } + else { + if ((ioamhram[0x100] & 0x30) == 0x30) { + di(); + intreq.halt(); + } + else { + intreq.halt(); + intreq.setEventTime(cycleCounter + 0x20000 + isDoubleSpeed() * 8); + } + } + + return cycleCounter; +} + +static void decCycles(unsigned long &counter, const unsigned long dec) { + if (counter != DISABLED_TIME) + counter -= dec; +} + +void Memory::decEventCycles(const MemEventId eventId, const unsigned long dec) { + if (intreq.eventTime(eventId) != DISABLED_TIME) + intreq.setEventTime(eventId, intreq.eventTime(eventId) - dec); +} + +unsigned long Memory::resetCounters(unsigned long cycleCounter) { + if (lastOamDmaUpdate != DISABLED_TIME) + updateOamDma(cycleCounter); + + updateIrqs(cycleCounter); + + const unsigned long oldCC = cycleCounter; + + { + const unsigned long divinc = (cycleCounter - divLastUpdate) >> 8; + ioamhram[0x104] = (ioamhram[0x104] + divinc) & 0xFF; + divLastUpdate += divinc << 8; + } + + const unsigned long dec = cycleCounter < 0x10000 ? 0 : (cycleCounter & ~0x7FFFul) - 0x8000; + + decCycles(divLastUpdate, dec); + decCycles(lastOamDmaUpdate, dec); + decEventCycles(SERIAL, dec); + decEventCycles(OAM, dec); + decEventCycles(BLIT, dec); + decEventCycles(END, dec); + decEventCycles(UNHALT, dec); + + cycleCounter -= dec; + + intreq.resetCc(oldCC, cycleCounter); + tima.resetCc(oldCC, cycleCounter, TimaInterruptRequester(intreq)); + display.resetCc(oldCC, cycleCounter); + sound.resetCounter(cycleCounter, oldCC, isDoubleSpeed()); + + return cycleCounter; +} + +void Memory::updateInput() { + unsigned state = 0xF; + + if ((ioamhram[0x100] & 0x30) != 0x30 && getInput) { + unsigned input = (*getInput)(); + unsigned dpad_state = ~input >> 4; + unsigned button_state = ~input; + if (!(ioamhram[0x100] & 0x10)) + state &= dpad_state; + if (!(ioamhram[0x100] & 0x20)) + state &= button_state; + } + + if (state != 0xF && (ioamhram[0x100] & 0xF) == 0xF) + intreq.flagIrq(0x10); + + ioamhram[0x100] = (ioamhram[0x100] & -0x10u) | state; +} + +void Memory::updateOamDma(const unsigned long cycleCounter) { + const unsigned char *const oamDmaSrc = oamDmaSrcPtr(); + unsigned cycles = (cycleCounter - lastOamDmaUpdate) >> 2; + + while (cycles--) { + oamDmaPos = (oamDmaPos + 1) & 0xFF; + lastOamDmaUpdate += 4; + + if (oamDmaPos < 0xA0) { + if (oamDmaPos == 0) + startOamDma(lastOamDmaUpdate - 1); + + ioamhram[oamDmaPos] = oamDmaSrc ? oamDmaSrc[oamDmaPos] : cart.rtcRead(); + } else if (oamDmaPos == 0xA0) { + endOamDma(lastOamDmaUpdate - 1); + lastOamDmaUpdate = DISABLED_TIME; + break; + } + } +} + +void Memory::oamDmaInitSetup() { + if (ioamhram[0x146] < 0xA0) { + cart.setOamDmaSrc(ioamhram[0x146] < 0x80 ? OAM_DMA_SRC_ROM : OAM_DMA_SRC_VRAM); + } else if (ioamhram[0x146] < 0xFE - isCgb() * 0x1E) { + cart.setOamDmaSrc(ioamhram[0x146] < 0xC0 ? OAM_DMA_SRC_SRAM : OAM_DMA_SRC_WRAM); + } else + cart.setOamDmaSrc(OAM_DMA_SRC_INVALID); +} + +static const unsigned char * oamDmaSrcZero() { + static unsigned char zeroMem[0xA0]; + return zeroMem; +} + +const unsigned char * Memory::oamDmaSrcPtr() const { + switch (cart.oamDmaSrc()) { + case OAM_DMA_SRC_ROM: return cart.romdata(ioamhram[0x146] >> 6) + (ioamhram[0x146] << 8); + case OAM_DMA_SRC_SRAM: return cart.rsrambankptr() ? cart.rsrambankptr() + (ioamhram[0x146] << 8) : 0; + case OAM_DMA_SRC_VRAM: return cart.vrambankptr() + (ioamhram[0x146] << 8); + case OAM_DMA_SRC_WRAM: return cart.wramdata(ioamhram[0x146] >> 4 & 1) + (ioamhram[0x146] << 8 & 0xFFF); + case OAM_DMA_SRC_INVALID: + case OAM_DMA_SRC_OFF: break; + } + + return ioamhram[0x146] == 0xFF && !isCgb() ? oamDmaSrcZero() : cart.rdisabledRam(); +} + +void Memory::startOamDma(const unsigned long cycleCounter) { + display.oamChange(cart.rdisabledRam(), cycleCounter); +} + +void Memory::endOamDma(const unsigned long cycleCounter) { + oamDmaPos = 0xFE; + cart.setOamDmaSrc(OAM_DMA_SRC_OFF); + display.oamChange(ioamhram, cycleCounter); +} + +unsigned Memory::nontrivial_ff_read(const unsigned P, const unsigned long cycleCounter) { + if (lastOamDmaUpdate != DISABLED_TIME) + updateOamDma(cycleCounter); + + switch (P & 0x7F) { + case 0x00: + updateInput(); + break; + case 0x01: + case 0x02: + updateSerial(cycleCounter); + break; + case 0x04: + { + const unsigned long divcycles = (cycleCounter - divLastUpdate) >> 8; + ioamhram[0x104] = (ioamhram[0x104] + divcycles) & 0xFF; + divLastUpdate += divcycles << 8; + } + + break; + case 0x05: + ioamhram[0x105] = tima.tima(cycleCounter); + break; + case 0x0F: + updateIrqs(cycleCounter); + ioamhram[0x10F] = intreq.ifreg(); + break; + case 0x26: + if (ioamhram[0x126] & 0x80) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + ioamhram[0x126] = 0xF0 | sound.getStatus(); + } else + ioamhram[0x126] = 0x70; + + break; + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + sound.generate_samples(cycleCounter, isDoubleSpeed()); + return sound.waveRamRead(P & 0xF); + case 0x41: + return ioamhram[0x141] | display.getStat(ioamhram[0x145], cycleCounter); + case 0x44: + return display.getLyReg(cycleCounter/*+4*/); + case 0x69: + return display.cgbBgColorRead(ioamhram[0x168] & 0x3F, cycleCounter); + case 0x6B: + return display.cgbSpColorRead(ioamhram[0x16A] & 0x3F, cycleCounter); + default: break; + } + + return ioamhram[P - 0xFE00]; +} + +static bool isInOamDmaConflictArea(const OamDmaSrc oamDmaSrc, const unsigned addr, const bool cgb) { + struct Area { unsigned short areaUpper, exceptAreaLower, exceptAreaWidth, pad; }; + + static const Area cgbAreas[] = { + { 0xC000, 0x8000, 0x2000, 0 }, + { 0xC000, 0x8000, 0x2000, 0 }, + { 0xA000, 0x0000, 0x8000, 0 }, + { 0xFE00, 0x0000, 0xC000, 0 }, + { 0xC000, 0x8000, 0x2000, 0 }, + { 0x0000, 0x0000, 0x0000, 0 } + }; + + static const Area dmgAreas[] = { + { 0xFE00, 0x8000, 0x2000, 0 }, + { 0xFE00, 0x8000, 0x2000, 0 }, + { 0xA000, 0x0000, 0x8000, 0 }, + { 0xFE00, 0x8000, 0x2000, 0 }, + { 0xFE00, 0x8000, 0x2000, 0 }, + { 0x0000, 0x0000, 0x0000, 0 } + }; + + const Area *const a = cgb ? cgbAreas : dmgAreas; + + return addr < a[oamDmaSrc].areaUpper && addr - a[oamDmaSrc].exceptAreaLower >= a[oamDmaSrc].exceptAreaWidth; +} + +unsigned Memory::nontrivial_read(const unsigned P, const unsigned long cycleCounter) { + if (P < 0xFF80) { + if (lastOamDmaUpdate != DISABLED_TIME) { + updateOamDma(cycleCounter); + + if (isInOamDmaConflictArea(cart.oamDmaSrc(), P, isCgb()) && oamDmaPos < 0xA0) + return ioamhram[oamDmaPos]; + } + + if (P < 0xC000) { + if (P < 0x8000) + return cart.romdata(P >> 14)[P]; + + if (P < 0xA000) { + if (!display.vramAccessible(cycleCounter)) + return 0xFF; + + return cart.vrambankptr()[P]; + } + + if (cart.rsrambankptr()) + return cart.rsrambankptr()[P]; + + return cart.rtcRead(); + } + + if (P < 0xFE00) + return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; + + if (P >= 0xFF00) + return nontrivial_ff_read(P, cycleCounter); + + if (!display.oamReadable(cycleCounter) || oamDmaPos < 0xA0) + return 0xFF; + } + + return ioamhram[P - 0xFE00]; +} + +unsigned Memory::nontrivial_peek(const unsigned P) { + if (P < 0xC000) { + if (P < 0x8000) + return cart.romdata(P >> 14)[P]; + + if (P < 0xA000) { + return cart.vrambankptr()[P]; + } + + if (cart.rsrambankptr()) + return cart.rsrambankptr()[P]; + + return cart.rtcRead(); // verified side-effect free + } + if (P < 0xFE00) + return cart.wramdata(P >> 12 & 1)[P & 0xFFF]; + if (P >= 0xFF00 && P < 0xFF80) + return nontrivial_ff_peek(P); + return ioamhram[P - 0xFE00]; +} + +unsigned Memory::nontrivial_ff_peek(const unsigned P) { + // some regs may be somewhat wrong with this + return ioamhram[P - 0xFE00]; +} + +void Memory::nontrivial_ff_write(const unsigned P, unsigned data, const unsigned long cycleCounter) { + if (lastOamDmaUpdate != DISABLED_TIME) + updateOamDma(cycleCounter); + + switch (P & 0xFF) { + case 0x00: + if ((data ^ ioamhram[0x100]) & 0x30) { + ioamhram[0x100] = (ioamhram[0x100] & ~0x30u) | (data & 0x30); + updateInput(); + } + return; + case 0x01: + updateSerial(cycleCounter); + break; + case 0x02: + updateSerial(cycleCounter); + + serialCnt = 8; + intreq.setEventTime((data & 0x81) == 0x81 + ? (data & isCgb() * 2 ? (cycleCounter & ~0x7ul) + 0x10 * 8 : (cycleCounter & ~0xFFul) + 0x200 * 8) + : static_cast(DISABLED_TIME)); + + data |= 0x7E - isCgb() * 2; + break; + case 0x04: + ioamhram[0x104] = 0; + divLastUpdate = cycleCounter; + return; + case 0x05: + tima.setTima(data, cycleCounter, TimaInterruptRequester(intreq)); + break; + case 0x06: + tima.setTma(data, cycleCounter, TimaInterruptRequester(intreq)); + break; + case 0x07: + data |= 0xF8; + tima.setTac(data, cycleCounter, TimaInterruptRequester(intreq)); + break; + case 0x0F: + updateIrqs(cycleCounter); + intreq.setIfreg(0xE0 | data); + return; + case 0x10: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr10(data); + data |= 0x80; + break; + case 0x11: + if (!sound.isEnabled()) { + if (isCgb()) + return; + + data &= 0x3F; + } + + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr11(data); + data |= 0x3F; + break; + case 0x12: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr12(data); + break; + case 0x13: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr13(data); + return; + case 0x14: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr14(data); + data |= 0xBF; + break; + case 0x16: + if (!sound.isEnabled()) { + if (isCgb()) + return; + + data &= 0x3F; + } + + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr21(data); + data |= 0x3F; + break; + case 0x17: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr22(data); + break; + case 0x18: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr23(data); + return; + case 0x19: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr24(data); + data |= 0xBF; + break; + case 0x1A: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr30(data); + data |= 0x7F; + break; + case 0x1B: + if (!sound.isEnabled() && isCgb()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr31(data); + return; + case 0x1C: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr32(data); + data |= 0x9F; + break; + case 0x1D: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr33(data); + return; + case 0x1E: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr34(data); + data |= 0xBF; + break; + case 0x20: + if (!sound.isEnabled() && isCgb()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr41(data); + return; + case 0x21: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr42(data); + break; + case 0x22: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr43(data); + break; + case 0x23: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_nr44(data); + data |= 0xBF; + break; + case 0x24: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.set_so_volume(data); + break; + case 0x25: + if (!sound.isEnabled()) return; + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.map_so(data); + break; + case 0x26: + if ((ioamhram[0x126] ^ data) & 0x80) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + + if (!(data & 0x80)) { + for (unsigned i = 0xFF10; i < 0xFF26; ++i) + ff_write(i, 0, cycleCounter); + + sound.setEnabled(false); + } else { + sound.reset(); + sound.setEnabled(true); + } + } + + data = (data & 0x80) | (ioamhram[0x126] & 0x7F); + break; + case 0x30: + case 0x31: + case 0x32: + case 0x33: + case 0x34: + case 0x35: + case 0x36: + case 0x37: + case 0x38: + case 0x39: + case 0x3A: + case 0x3B: + case 0x3C: + case 0x3D: + case 0x3E: + case 0x3F: + sound.generate_samples(cycleCounter, isDoubleSpeed()); + sound.waveRamWrite(P & 0xF, data); + break; + case 0x40: + if (ioamhram[0x140] != data) { + if ((ioamhram[0x140] ^ data) & 0x80) { + const unsigned lyc = display.getStat(ioamhram[0x145], cycleCounter) & 4; + const bool hdmaEnabled = display.hdmaIsEnabled(); + + display.lcdcChange(data, cycleCounter); + ioamhram[0x144] = 0; + ioamhram[0x141] &= 0xF8; + + if (data & 0x80) { + intreq.setEventTime(display.nextMode1IrqTime() + (blanklcd ? 0 : 70224 << isDoubleSpeed())); + } else { + ioamhram[0x141] |= lyc; + intreq.setEventTime(cycleCounter + (456 * 4 << isDoubleSpeed())); + + if (hdmaEnabled) + flagHdmaReq(&intreq); + } + } else + display.lcdcChange(data, cycleCounter); + + ioamhram[0x140] = data; + } + + return; + case 0x41: + display.lcdstatChange(data, cycleCounter); + data = (ioamhram[0x141] & 0x87) | (data & 0x78); + break; + case 0x42: + display.scyChange(data, cycleCounter); + break; + case 0x43: + display.scxChange(data, cycleCounter); + break; + case 0x45: + display.lycRegChange(data, cycleCounter); + break; + case 0x46: + if (lastOamDmaUpdate != DISABLED_TIME) + endOamDma(cycleCounter); + + lastOamDmaUpdate = cycleCounter; + intreq.setEventTime(cycleCounter + 8); + ioamhram[0x146] = data; + oamDmaInitSetup(); + return; + case 0x47: + if (!isCgb()) + display.dmgBgPaletteChange(data, cycleCounter); + + break; + case 0x48: + if (!isCgb()) + display.dmgSpPalette1Change(data, cycleCounter); + + break; + case 0x49: + if (!isCgb()) + display.dmgSpPalette2Change(data, cycleCounter); + + break; + case 0x4A: + display.wyChange(data, cycleCounter); + break; + case 0x4B: + display.wxChange(data, cycleCounter); + break; + case 0x4C: + if (biosMode) { + //flagClockReq(&intreq); + } + break; + case 0x4D: + if (isCgb()) + ioamhram[0x14D] = (ioamhram[0x14D] & ~1u) | (data & 1); return; + case 0x4F: + if (isCgb()) { + cart.setVrambank(data & 1); + ioamhram[0x14F] = 0xFE | data; + } + + return; + case 0x50: + biosMode = false; + if (cgbSwitching) { + display.copyCgbPalettesToDmg(); + display.setCgb(false); + cgbSwitching = false; + } + return; + case 0x51: + dmaSource = data << 8 | (dmaSource & 0xFF); + return; + case 0x52: + dmaSource = (dmaSource & 0xFF00) | (data & 0xF0); + return; + case 0x53: + dmaDestination = data << 8 | (dmaDestination & 0xFF); + return; + case 0x54: + dmaDestination = (dmaDestination & 0xFF00) | (data & 0xF0); + return; + case 0x55: + if (isCgb()) { + ioamhram[0x155] = data & 0x7F; + + if (display.hdmaIsEnabled()) { + if (!(data & 0x80)) { + ioamhram[0x155] |= 0x80; + display.disableHdma(cycleCounter); + } + } else { + if (data & 0x80) { + if (ioamhram[0x140] & 0x80) { + display.enableHdma(cycleCounter); + } else + flagHdmaReq(&intreq); + } else + flagGdmaReq(&intreq); + } + } + + return; + case 0x56: + if (isCgb()) + ioamhram[0x156] = data | 0x3E; + + return; + case 0x68: + if (isCgb()) + ioamhram[0x168] = data | 0x40; + + return; + case 0x69: + if (isCgb()) { + const unsigned index = ioamhram[0x168] & 0x3F; + + display.cgbBgColorChange(index, data, cycleCounter); + + ioamhram[0x168] = (ioamhram[0x168] & ~0x3F) | ((index + (ioamhram[0x168] >> 7)) & 0x3F); + } + + return; + case 0x6A: + if (isCgb()) + ioamhram[0x16A] = data | 0x40; + + return; + case 0x6B: + if (isCgb()) { + const unsigned index = ioamhram[0x16A] & 0x3F; + + display.cgbSpColorChange(index, data, cycleCounter); + + ioamhram[0x16A] = (ioamhram[0x16A] & ~0x3F) | ((index + (ioamhram[0x16A] >> 7)) & 0x3F); + } + + return; + case 0x6C: + ioamhram[0x16C] = data | 0xFE; + cgbSwitching = true; + + return; + case 0x70: + if (isCgb()) { + cart.setWrambank((data & 0x07) ? (data & 0x07) : 1); + ioamhram[0x170] = data | 0xF8; + } + + return; + case 0x72: + case 0x73: + case 0x74: + if (isCgb()) + break; + + return; + case 0x75: + if (isCgb()) + ioamhram[0x175] = data | 0x8F; + + return; + case 0xFF: + intreq.setIereg(data); + break; + default: + return; + } + + ioamhram[P - 0xFE00] = data; +} + +void Memory::nontrivial_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (lastOamDmaUpdate != DISABLED_TIME) { + updateOamDma(cycleCounter); + + if (isInOamDmaConflictArea(cart.oamDmaSrc(), P, isCgb()) && oamDmaPos < 0xA0) { + ioamhram[oamDmaPos] = data; + return; + } + } + + if (P < 0xFE00) { + if (P < 0xA000) { + if (P < 0x8000) { + cart.mbcWrite(P, data); + } else if (display.vramAccessible(cycleCounter)) { + display.vramChange(cycleCounter); + cart.vrambankptr()[P] = data; + } + } else if (P < 0xC000) { + if (cart.wsrambankptr()) + cart.wsrambankptr()[P] = data; + else + cart.rtcWrite(data); + } else + cart.wramdata(P >> 12 & 1)[P & 0xFFF] = data; + } else if (P - 0xFF80u >= 0x7Fu) { + if (P < 0xFF00) { + if (display.oamWritable(cycleCounter) && oamDmaPos >= 0xA0 && (P < 0xFEA0 || isCgb())) { + display.oamChange(cycleCounter); + ioamhram[P - 0xFE00] = data; + } + } else + nontrivial_ff_write(P, data, cycleCounter); + } else + ioamhram[P - 0xFE00] = data; +} + +int Memory::loadROM(const char *romfiledata, unsigned romfilelength, const bool forceDmg, const bool multicartCompat) { + if (const int fail = cart.loadROM(romfiledata, romfilelength, forceDmg, multicartCompat)) + return fail; + + sound.init(cart.isCgb()); + display.reset(ioamhram, cart.vramdata(), cart.isCgb()); + + return 0; +} + +unsigned Memory::fillSoundBuffer(const unsigned long cycleCounter) { + sound.generate_samples(cycleCounter, isDoubleSpeed()); + return sound.fillBuffer(); +} + +void Memory::setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32) { + display.setDmgPaletteColor(palNum, colorNum, rgb32); +} + +void Memory::setCgbPalette(unsigned *lut) { + display.setCgbPalette(lut); +} + +bool Memory::getMemoryArea(int which, unsigned char **data, int *length) { + if (!data || !length) + return false; + + switch (which) + { + case 4: // oam + *data = &ioamhram[0]; + *length = 160; + return true; + case 5: // hram + *data = &ioamhram[384]; + *length = 128; + return true; + case 6: // bgpal + *data = (unsigned char *)display.bgPalette(); + *length = 32; + return true; + case 7: // sppal + *data = (unsigned char *)display.spPalette(); + *length = 32; + return true; + default: // pass to cartridge + return cart.getMemoryArea(which, data, length); + } +} + +int Memory::LinkStatus(int which) +{ + switch (which) + { + case 256: // ClockSignaled + return linkClockTrigger; + case 257: // AckClockSignal + linkClockTrigger = false; + return 0; + case 258: // GetOut + return ioamhram[0x101] & 0xff; + case 259: // connect link cable + LINKCABLE = true; + return 0; + default: // ShiftIn + if (ioamhram[0x102] & 0x80) // was enabled + { + ioamhram[0x101] = which; + ioamhram[0x102] &= 0x7F; + intreq.flagIrq(8); + } + return 0; + } + + return -1; +} + +SYNCFUNC(Memory) +{ + SSS(cart); + NSS(ioamhram); + NSS(divLastUpdate); + NSS(lastOamDmaUpdate); + NSS(biosMode); + NSS(cgbSwitching); + NSS(agbMode); + NSS(gbIsCgb_); + + SSS(intreq); + SSS(tima); + SSS(display); + SSS(sound); + //SSS(interrupter); // no state + + NSS(dmaSource); + NSS(dmaDestination); + NSS(oamDmaPos); + NSS(serialCnt); + NSS(blanklcd); + + NSS(LINKCABLE); + NSS(linkClockTrigger); +} + +} diff --git a/libgambatte/src/memory.h b/libgambatte/src/memory.h index 9ea65bf4a1..95a2e18f16 100644 --- a/libgambatte/src/memory.h +++ b/libgambatte/src/memory.h @@ -1,298 +1,336 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef MEMORY_H -#define MEMORY_H - -#include "mem/cartridge.h" -#include "video.h" -#include "sound.h" -#include "interrupter.h" -#include "tima.h" -#include "newstate.h" -#include "gambatte.h" - -namespace gambatte { -class InputGetter; -class FilterInfo; - -class Memory { - Cartridge cart; - unsigned char ioamhram[0x200]; - - void (*readCallback)(unsigned); - void (*writeCallback)(unsigned); - void (*execCallback)(unsigned); - CDCallback cdCallback; - void (*linkCallback)(); - - unsigned (*getInput)(); - unsigned long divLastUpdate; - unsigned long lastOamDmaUpdate; - - InterruptRequester intreq; - Tima tima; - LCD display; - PSG sound; - Interrupter interrupter; - - unsigned short dmaSource; - unsigned short dmaDestination; - unsigned char oamDmaPos; - unsigned char serialCnt; - bool blanklcd; - - bool LINKCABLE; - bool linkClockTrigger; - - void decEventCycles(MemEventId eventId, unsigned long dec); - - void oamDmaInitSetup(); - void updateOamDma(unsigned long cycleCounter); - void startOamDma(unsigned long cycleCounter); - void endOamDma(unsigned long cycleCounter); - const unsigned char * oamDmaSrcPtr() const; - - unsigned nontrivial_ff_read(unsigned P, unsigned long cycleCounter); - unsigned nontrivial_read(unsigned P, unsigned long cycleCounter); - void nontrivial_ff_write(unsigned P, unsigned data, unsigned long cycleCounter); - void nontrivial_write(unsigned P, unsigned data, unsigned long cycleCounter); - - unsigned nontrivial_peek(unsigned P); - unsigned nontrivial_ff_peek(unsigned P); - - void updateSerial(unsigned long cc); - void updateTimaIrq(unsigned long cc); - void updateIrqs(unsigned long cc); - - bool isDoubleSpeed() const { return display.isDoubleSpeed(); } - -public: - explicit Memory(const Interrupter &interrupter); - - bool loaded() const { return cart.loaded(); } - const char * romTitle() const { return cart.romTitle(); } - - int debugGetLY() const { return display.debugGetLY(); } - - void setStatePtrs(SaveState &state); - void loadState(const SaveState &state/*, unsigned long oldCc*/); - void loadSavedata(const char *data) { cart.loadSavedata(data); } - int saveSavedataLength() {return cart.saveSavedataLength(); } - void saveSavedata(char *dest) { cart.saveSavedata(dest); } - void updateInput(); - - bool getMemoryArea(int which, unsigned char **data, int *length); // { return cart.getMemoryArea(which, data, length); } - - unsigned long stop(unsigned long cycleCounter); - bool isCgb() const { return display.isCgb(); } - bool ime() const { return intreq.ime(); } - bool halted() const { return intreq.halted(); } - unsigned long nextEventTime() const { return intreq.minEventTime(); } - - void setLayers(unsigned mask) { display.setLayers(mask); } - - bool isActive() const { return intreq.eventTime(END) != DISABLED_TIME; } - - long cyclesSinceBlit(const unsigned long cc) const { - return cc < intreq.eventTime(BLIT) ? -1 : static_cast((cc - intreq.eventTime(BLIT)) >> isDoubleSpeed()); - } - - void halt() { intreq.halt(); } - void ei(unsigned long cycleCounter) { if (!ime()) { intreq.ei(cycleCounter); } } - - void di() { intreq.di(); } - - unsigned ff_read(const unsigned P, const unsigned long cycleCounter) { - return P < 0xFF80 ? nontrivial_ff_read(P, cycleCounter) : ioamhram[P - 0xFE00]; - } - - struct CDMapResult - { - eCDLog_AddrType type; - unsigned addr; - }; - - CDMapResult CDMap(const unsigned P) const - { - if(P<0x4000) - { - CDMapResult ret = { eCDLog_AddrType_ROM, P }; - return ret; - } - else if(P<0x8000) - { - unsigned bank = cart.rmem(P>>12) - cart.rmem(0); - unsigned addr = P+bank; - CDMapResult ret = { eCDLog_AddrType_ROM, addr }; - return ret; - } - else if(P<0xA000) {} - else if(P<0xC000) - { - if(cart.wsrambankptr()) - { - //not bankable. but. we're not sure how much might be here - unsigned char *data; - int length; - bool has = cart.getMemoryArea(3,&data,&length); - unsigned addr = P&(length-1); - if(has && length!=0) - { - CDMapResult ret = { eCDLog_AddrType_CartRAM, addr }; - return ret; - } - } - } - else if(P<0xE000) - { - unsigned bank = cart.wramdata(P >> 12 & 1) - cart.wramdata(0); - unsigned addr = (P&0xFFF)+bank; - CDMapResult ret = { eCDLog_AddrType_WRAM, addr }; - return ret; - } - else if(P<0xFF80) {} - else - { - ////this is just for debugging, really, it's pretty useless - //CDMapResult ret = { eCDLog_AddrType_HRAM, (P-0xFF80) }; - //return ret; - } - - CDMapResult ret = { eCDLog_AddrType_None }; - return ret; - } - - - unsigned read(const unsigned P, const unsigned long cycleCounter) { - if (readCallback) - readCallback(P); - if(cdCallback) - { - CDMapResult map = CDMap(P); - if(map.type != eCDLog_AddrType_None) - cdCallback(map.addr,map.type,eCDLog_Flags_Data); - } - return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_read(P, cycleCounter); - } - - unsigned read_excb(const unsigned P, const unsigned long cycleCounter, bool first) { - if (execCallback) - execCallback(P); - if(cdCallback) - { - CDMapResult map = CDMap(P); - if(map.type != eCDLog_AddrType_None) - cdCallback(map.addr,map.type,first?eCDLog_Flags_ExecFirst : eCDLog_Flags_ExecOperand); - } - return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_read(P, cycleCounter); - } - - unsigned peek(const unsigned P) { - return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_peek(P); - } - - void write_nocb(const unsigned P, const unsigned data, const unsigned long cycleCounter) { - if (cart.wmem(P >> 12)) { - cart.wmem(P >> 12)[P] = data; - } else - nontrivial_write(P, data, cycleCounter); - } - - void write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { - if (cart.wmem(P >> 12)) { - cart.wmem(P >> 12)[P] = data; - } else - nontrivial_write(P, data, cycleCounter); - if (writeCallback) - writeCallback(P); - if(cdCallback) - { - CDMapResult map = CDMap(P); - if(map.type != eCDLog_AddrType_None) - cdCallback(map.addr,map.type,eCDLog_Flags_Data); - } - } - - void ff_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { - if (P - 0xFF80u < 0x7Fu) { - ioamhram[P - 0xFE00] = data; - } else - nontrivial_ff_write(P, data, cycleCounter); - if(cdCallback) - { - CDMapResult map = CDMap(P); - if(map.type != eCDLog_AddrType_None) - cdCallback(map.addr,map.type,eCDLog_Flags_Data); - } - } - - unsigned long event(unsigned long cycleCounter); - unsigned long resetCounters(unsigned long cycleCounter); - - int loadROM(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat); - - void setInputGetter(unsigned (*getInput)()) { - this->getInput = getInput; - } - - void setReadCallback(void (*callback)(unsigned)) { - this->readCallback = callback; - } - void setWriteCallback(void (*callback)(unsigned)) { - this->writeCallback = callback; - } - void setExecCallback(void (*callback)(unsigned)) { - this->execCallback = callback; - } - void setCDCallback(CDCallback cdc) { - this->cdCallback = cdc; - } - - void setScanlineCallback(void (*callback)(), int sl) { - display.setScanlineCallback(callback, sl); - } - - void setRTCCallback(std::uint32_t (*callback)()) { - cart.setRTCCallback(callback); - } - - void setLinkCallback(void (*callback)()) { - this->linkCallback = callback; - } - - void setEndtime(unsigned long cc, unsigned long inc); - - void setSoundBuffer(uint_least32_t *const buf) { sound.setBuffer(buf); } - unsigned fillSoundBuffer(unsigned long cc); - - void setVideoBuffer(uint_least32_t *const videoBuf, const int pitch) { - display.setVideoBuffer(videoBuf, pitch); - } - - void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32); - void setCgbPalette(unsigned *lut); - - int LinkStatus(int which); - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef MEMORY_H +#define MEMORY_H + +static unsigned char const agbOverride[0xD] = { 0xFF, 0x00, 0xCD, 0x03, 0x35, 0xAA, 0x31, 0x90, 0x94, 0x00, 0x00, 0x00, 0x00 }; + +#include "mem/cartridge.h" +#include "video.h" +#include "sound.h" +#include "interrupter.h" +#include "tima.h" +#include "newstate.h" +#include "gambatte.h" + +namespace gambatte { +class InputGetter; +class FilterInfo; + +class Memory { + Cartridge cart; + unsigned char ioamhram[0x200]; + unsigned char cgbBios[0x900]; + unsigned char dmgBios[0x100]; + bool biosMode; + bool cgbSwitching; + bool agbMode; + bool gbIsCgb_; + unsigned short &SP; + unsigned short &PC; + + void (*readCallback)(unsigned); + void (*writeCallback)(unsigned); + void (*execCallback)(unsigned); + CDCallback cdCallback; + void(*linkCallback)(); + + unsigned (*getInput)(); + unsigned long divLastUpdate; + unsigned long lastOamDmaUpdate; + + InterruptRequester intreq; + Tima tima; + LCD display; + PSG sound; + Interrupter interrupter; + + unsigned short dmaSource; + unsigned short dmaDestination; + unsigned char oamDmaPos; + unsigned char serialCnt; + bool blanklcd; + + bool LINKCABLE; + bool linkClockTrigger; + + void decEventCycles(MemEventId eventId, unsigned long dec); + + void oamDmaInitSetup(); + void updateOamDma(unsigned long cycleCounter); + void startOamDma(unsigned long cycleCounter); + void endOamDma(unsigned long cycleCounter); + const unsigned char * oamDmaSrcPtr() const; + + unsigned nontrivial_ff_read(unsigned P, unsigned long cycleCounter); + unsigned nontrivial_read(unsigned P, unsigned long cycleCounter); + void nontrivial_ff_write(unsigned P, unsigned data, unsigned long cycleCounter); + void nontrivial_write(unsigned P, unsigned data, unsigned long cycleCounter); + + unsigned nontrivial_peek(unsigned P); + unsigned nontrivial_ff_peek(unsigned P); + + void updateSerial(unsigned long cc); + void updateTimaIrq(unsigned long cc); + void updateIrqs(unsigned long cc); + + bool isDoubleSpeed() const { return display.isDoubleSpeed(); } + +public: + explicit Memory(const Interrupter &interrupter, unsigned short &sp, unsigned short &pc); + + bool loaded() const { return cart.loaded(); } + unsigned curRomBank() const { return cart.curRomBank(); } + const char * romTitle() const { return cart.romTitle(); } + + int debugGetLY() const { return display.debugGetLY(); } + + void setStatePtrs(SaveState &state); + void loadState(const SaveState &state/*, unsigned long oldCc*/); + void loadSavedata(const char *data) { cart.loadSavedata(data); } + int saveSavedataLength() {return cart.saveSavedataLength(); } + void saveSavedata(char *dest) { cart.saveSavedata(dest); } + void updateInput(); + + unsigned char* cgbBiosBuffer() { return (unsigned char*)cgbBios; } + unsigned char* dmgBiosBuffer() { return (unsigned char*)dmgBios; } + bool gbIsCgb() { return gbIsCgb_; } + + bool getMemoryArea(int which, unsigned char **data, int *length); // { return cart.getMemoryArea(which, data, length); } + + unsigned long stop(unsigned long cycleCounter); + bool isCgb() const { return display.isCgb(); } + bool ime() const { return intreq.ime(); } + bool halted() const { return intreq.halted(); } + unsigned long nextEventTime() const { return intreq.minEventTime(); } + + void setLayers(unsigned mask) { display.setLayers(mask); } + + bool isActive() const { return intreq.eventTime(END) != DISABLED_TIME; } + + long cyclesSinceBlit(const unsigned long cc) const { + return cc < intreq.eventTime(BLIT) ? -1 : static_cast((cc - intreq.eventTime(BLIT)) >> isDoubleSpeed()); + } + + void halt() { intreq.halt(); } + void ei(unsigned long cycleCounter) { if (!ime()) { intreq.ei(cycleCounter); } } + + void di() { intreq.di(); } + + unsigned ff_read(const unsigned P, const unsigned long cycleCounter) { + return P < 0xFF80 ? nontrivial_ff_read(P, cycleCounter) : ioamhram[P - 0xFE00]; + } + + struct CDMapResult + { + eCDLog_AddrType type; + unsigned addr; + }; + + CDMapResult CDMap(const unsigned P) const + { + if(P<0x4000) + { + CDMapResult ret = { eCDLog_AddrType_ROM, P }; + return ret; + } + else if(P<0x8000) + { + unsigned bank = cart.rmem(P>>12) - cart.rmem(0); + unsigned addr = P+bank; + CDMapResult ret = { eCDLog_AddrType_ROM, addr }; + return ret; + } + else if(P<0xA000) {} + else if(P<0xC000) + { + if(cart.wsrambankptr()) + { + //not bankable. but. we're not sure how much might be here + unsigned char *data; + int length; + bool has = cart.getMemoryArea(3,&data,&length); + unsigned addr = P&(length-1); + if(has && length!=0) + { + CDMapResult ret = { eCDLog_AddrType_CartRAM, addr }; + return ret; + } + } + } + else if(P<0xE000) + { + unsigned bank = cart.wramdata(P >> 12 & 1) - cart.wramdata(0); + unsigned addr = (P&0xFFF)+bank; + CDMapResult ret = { eCDLog_AddrType_WRAM, addr }; + return ret; + } + else if(P<0xFF80) {} + else + { + ////this is just for debugging, really, it's pretty useless + //CDMapResult ret = { eCDLog_AddrType_HRAM, (P-0xFF80) }; + //return ret; + } + + CDMapResult ret = { eCDLog_AddrType_None }; + return ret; + } + + + unsigned readBios(const unsigned P) { + if (gbIsCgb_) { + if (agbMode && P >= 0xF3 && P < 0x100) { + return (agbOverride[P - 0xF3] + cgbBios[P]) & 0xFF; + } + return cgbBios[P]; + } + return dmgBios[P]; + } + + unsigned read(const unsigned P, const unsigned long cycleCounter) { + if (readCallback) + readCallback(P); + if(cdCallback) + { + CDMapResult map = CDMap(P); + if(map.type != eCDLog_AddrType_None) + cdCallback(map.addr,map.type,eCDLog_Flags_Data); + } + if (biosMode && ((!gbIsCgb_ && P < 0x100) || (gbIsCgb_ && P < 0x900 && (P < 0x100 || P >= 0x200)))) { + return readBios(P); + } + return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_read(P, cycleCounter); + } + + unsigned read_excb(const unsigned P, const unsigned long cycleCounter, bool first) { + if (execCallback) + execCallback(P); + if(cdCallback) + { + CDMapResult map = CDMap(P); + if(map.type != eCDLog_AddrType_None) + cdCallback(map.addr,map.type,first?eCDLog_Flags_ExecFirst : eCDLog_Flags_ExecOperand); + } + if (biosMode && ((!gbIsCgb_ && P < 0x100) || (gbIsCgb_ && P < 0x900 && (P < 0x100 || P >= 0x200)))) { + return readBios(P); + } + return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_read(P, cycleCounter); + } + + unsigned peek(const unsigned P) { + if (biosMode && ((!gbIsCgb_ && P < 0x100) || (gbIsCgb_ && P < 0x900 && (P < 0x100 || P >= 0x200)))) { + return readBios(P); + } + return cart.rmem(P >> 12) ? cart.rmem(P >> 12)[P] : nontrivial_peek(P); + } + + void write_nocb(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (cart.wmem(P >> 12)) { + cart.wmem(P >> 12)[P] = data; + } else + nontrivial_write(P, data, cycleCounter); + } + + void write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (cart.wmem(P >> 12)) { + cart.wmem(P >> 12)[P] = data; + } else + nontrivial_write(P, data, cycleCounter); + if (writeCallback) + writeCallback(P); + if(cdCallback) + { + CDMapResult map = CDMap(P); + if(map.type != eCDLog_AddrType_None) + cdCallback(map.addr,map.type,eCDLog_Flags_Data); + } + } + + void ff_write(const unsigned P, const unsigned data, const unsigned long cycleCounter) { + if (P - 0xFF80u < 0x7Fu) { + ioamhram[P - 0xFE00] = data; + } else + nontrivial_ff_write(P, data, cycleCounter); + if(cdCallback) + { + CDMapResult map = CDMap(P); + if(map.type != eCDLog_AddrType_None) + cdCallback(map.addr,map.type,eCDLog_Flags_Data); + } + } + + unsigned long event(unsigned long cycleCounter); + unsigned long resetCounters(unsigned long cycleCounter); + + int loadROM(const char *romfiledata, unsigned romfilelength, bool forceDmg, bool multicartCompat); + + void setInputGetter(unsigned (*getInput)()) { + this->getInput = getInput; + } + + void setReadCallback(void (*callback)(unsigned)) { + this->readCallback = callback; + } + void setWriteCallback(void (*callback)(unsigned)) { + this->writeCallback = callback; + } + void setExecCallback(void (*callback)(unsigned)) { + this->execCallback = callback; + } + void setCDCallback(CDCallback cdc) { + this->cdCallback = cdc; + } + + void setScanlineCallback(void (*callback)(), int sl) { + display.setScanlineCallback(callback, sl); + } + + void setRTCCallback(std::uint32_t (*callback)()) { + cart.setRTCCallback(callback); + } + + void setLinkCallback(void(*callback)()) { + this->linkCallback = callback; + } + + void setEndtime(unsigned long cc, unsigned long inc); + + void setSoundBuffer(uint_least32_t *const buf) { sound.setBuffer(buf); } + unsigned fillSoundBuffer(unsigned long cc); + + void setVideoBuffer(uint_least32_t *const videoBuf, const int pitch) { + display.setVideoBuffer(videoBuf, pitch); + } + + void setDmgPaletteColor(unsigned palNum, unsigned colorNum, unsigned long rgb32); + void setCgbPalette(unsigned *lut); + + void blackScreen() { + display.blackScreen(); + } + + int LinkStatus(int which); + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/minkeeper.h b/libgambatte/src/minkeeper.h index 0031adf098..ad2d6f79d2 100644 --- a/libgambatte/src/minkeeper.h +++ b/libgambatte/src/minkeeper.h @@ -20,7 +20,7 @@ #define MINKEEPER_H #include -#include "newstate.h" +#include "newstate.h" namespace MinKeeperUtil { template struct CeiledLog2 { enum { R = 1 + CeiledLog2<(n + 1) / 2>::R }; }; @@ -108,12 +108,12 @@ public: unsigned long value(const int id) const { return values[id]; } // not sure if i understood everything in minkeeper correctly, so something might be missing here? - template - void SyncState(gambatte::NewState *ns) - { - NSS(values); - NSS(minValue_); - NSS(a); + template + void SyncState(gambatte::NewState *ns) + { + NSS(values); + NSS(minValue_); + NSS(a); } }; diff --git a/libgambatte/src/newstate.cpp b/libgambatte/src/newstate.cpp index fa01dbf6ef..9d4214674a 100644 --- a/libgambatte/src/newstate.cpp +++ b/libgambatte/src/newstate.cpp @@ -1,69 +1,69 @@ -#include "newstate.h" -#include -#include - -namespace gambatte { - -NewStateDummy::NewStateDummy() - :length(0) -{ -} -void NewStateDummy::Save(const void *ptr, size_t size, const char *name) -{ - length += size; -} -void NewStateDummy::Load(void *ptr, size_t size, const char *name) -{ -} - -NewStateExternalBuffer::NewStateExternalBuffer(char *buffer, long maxlength) - :buffer(buffer), length(0), maxlength(maxlength) -{ -} - -void NewStateExternalBuffer::Save(const void *ptr, size_t size, const char *name) -{ - if (maxlength - length >= (long)size) - { - std::memcpy(buffer + length, ptr, size); - } - length += size; -} - -void NewStateExternalBuffer::Load(void *ptr, size_t size, const char *name) -{ - char *dst = static_cast(ptr); - if (maxlength - length >= (long)size) - { - std::memcpy(dst, buffer + length, size); - } - length += size; -} - -NewStateExternalFunctions::NewStateExternalFunctions(const FPtrs *ff) - :Save_(ff->Save_), - Load_(ff->Load_), - EnterSection_(ff->EnterSection_), - ExitSection_(ff->ExitSection_) -{ -} - -void NewStateExternalFunctions::Save(const void *ptr, size_t size, const char *name) -{ - Save_(ptr, size, name); -} -void NewStateExternalFunctions::Load(void *ptr, size_t size, const char *name) -{ - Load_(ptr, size, name); -} -void NewStateExternalFunctions::EnterSection(const char *name) -{ - EnterSection_(name); -} -void NewStateExternalFunctions::ExitSection(const char *name) -{ - ExitSection_(name); -} - - -} +#include "newstate.h" +#include +#include + +namespace gambatte { + +NewStateDummy::NewStateDummy() + :length(0) +{ +} +void NewStateDummy::Save(const void *ptr, size_t size, const char *name) +{ + length += size; +} +void NewStateDummy::Load(void *ptr, size_t size, const char *name) +{ +} + +NewStateExternalBuffer::NewStateExternalBuffer(char *buffer, long maxlength) + :buffer(buffer), length(0), maxlength(maxlength) +{ +} + +void NewStateExternalBuffer::Save(const void *ptr, size_t size, const char *name) +{ + if (maxlength - length >= (long)size) + { + std::memcpy(buffer + length, ptr, size); + } + length += size; +} + +void NewStateExternalBuffer::Load(void *ptr, size_t size, const char *name) +{ + char *dst = static_cast(ptr); + if (maxlength - length >= (long)size) + { + std::memcpy(dst, buffer + length, size); + } + length += size; +} + +NewStateExternalFunctions::NewStateExternalFunctions(const FPtrs *ff) + :Save_(ff->Save_), + Load_(ff->Load_), + EnterSection_(ff->EnterSection_), + ExitSection_(ff->ExitSection_) +{ +} + +void NewStateExternalFunctions::Save(const void *ptr, size_t size, const char *name) +{ + Save_(ptr, size, name); +} +void NewStateExternalFunctions::Load(void *ptr, size_t size, const char *name) +{ + Load_(ptr, size, name); +} +void NewStateExternalFunctions::EnterSection(const char *name) +{ + EnterSection_(name); +} +void NewStateExternalFunctions::ExitSection(const char *name) +{ + ExitSection_(name); +} + + +} diff --git a/libgambatte/src/newstate.h b/libgambatte/src/newstate.h index 37e2a7038f..2457a83669 100644 --- a/libgambatte/src/newstate.h +++ b/libgambatte/src/newstate.h @@ -1,102 +1,102 @@ -#ifndef NEWSTATE_H -#define NEWSTATE_H - -#include -#include - -namespace gambatte { - -class NewState -{ -public: - virtual void Save(const void *ptr, size_t size, const char *name) = 0; - virtual void Load(void *ptr, size_t size, const char *name) = 0; - virtual void EnterSection(const char *name) { } - virtual void ExitSection(const char *name) { } -}; - -class NewStateDummy : public NewState -{ -private: - long length; -public: - NewStateDummy(); - long GetLength() { return length; } - void Rewind() { length = 0; } - virtual void Save(const void *ptr, size_t size, const char *name); - virtual void Load(void *ptr, size_t size, const char *name); -}; - -class NewStateExternalBuffer : public NewState -{ -private: - char *const buffer; - long length; - const long maxlength; -public: - NewStateExternalBuffer(char *buffer, long maxlength); - long GetLength() { return length; } - void Rewind() { length = 0; } - bool Overflow() { return length > maxlength; } - virtual void Save(const void *ptr, size_t size, const char *name); - virtual void Load(void *ptr, size_t size, const char *name); -}; - -struct FPtrs -{ - void (*Save_)(const void *ptr, size_t size, const char *name); - void (*Load_)(void *ptr, size_t size, const char *name); - void (*EnterSection_)(const char *name); - void (*ExitSection_)(const char *name); -}; - -class NewStateExternalFunctions : public NewState -{ -private: - void (*Save_)(const void *ptr, size_t size, const char *name); - void (*Load_)(void *ptr, size_t size, const char *name); - void (*EnterSection_)(const char *name); - void (*ExitSection_)(const char *name); -public: - NewStateExternalFunctions(const FPtrs *ff); - virtual void Save(const void *ptr, size_t size, const char *name); - virtual void Load(void *ptr, size_t size, const char *name); - virtual void EnterSection(const char *name); - virtual void ExitSection(const char *name); -}; - -// defines and explicitly instantiates -#define SYNCFUNC(x)\ - template void x::SyncState(NewState *ns);\ - template void x::SyncState(NewState *ns);\ - templatevoid x::SyncState(NewState *ns) - -// N = normal variable -// P = pointer to fixed size data -// S = "sub object" -// T = "ptr to sub object" -// R = pointer, store its offset from some other pointer -// E = general purpose cased value "enum" - - -// first line is default value in converted enum; last line is default value in argument x -#define EBS(x,d) do { int _ttmp = (d); if (isReader) ns->Load(&_ttmp, sizeof(_ttmp), #x); if (0) -#define EVS(x,v,n) else if (!isReader && (x) == (v)) _ttmp = (n); else if (isReader && _ttmp == (n)) (x) = (v) -#define EES(x,d) else if (isReader) (x) = (d); if (!isReader) ns->Save(&_ttmp, sizeof(_ttmp), #x); } while (0) - -#define RSS(x,b) do { if (isReader)\ -{ ptrdiff_t _ttmp; ns->Load(&_ttmp, sizeof(_ttmp), #x); (x) = (_ttmp == (ptrdiff_t)0xdeadbeef ? 0 : (b) + _ttmp); }\ - else\ -{ ptrdiff_t _ttmp = (x) == 0 ? 0xdeadbeef : (x) - (b); ns->Save(&_ttmp, sizeof(_ttmp), #x); } } while (0) - -#define PSS(x,s) do { if (isReader) ns->Load((x), (s), #x); else ns->Save((x), (s), #x); } while (0) - -#define NSS(x) do { if (isReader) ns->Load(&(x), sizeof(x), #x); else ns->Save(&(x), sizeof(x), #x); } while (0) - -#define SSS(x) do { ns->EnterSection(#x); (x).SyncState(ns); ns->ExitSection(#x); } while (0) - -#define TSS(x) do { ns->EnterSection(#x); (x)->SyncState(ns); ns->ExitSection(#x); } while (0) - -} - -#endif +#ifndef NEWSTATE_H +#define NEWSTATE_H + +#include +#include + +namespace gambatte { + +class NewState +{ +public: + virtual void Save(const void *ptr, size_t size, const char *name) = 0; + virtual void Load(void *ptr, size_t size, const char *name) = 0; + virtual void EnterSection(const char *name) { } + virtual void ExitSection(const char *name) { } +}; + +class NewStateDummy : public NewState +{ +private: + long length; +public: + NewStateDummy(); + long GetLength() { return length; } + void Rewind() { length = 0; } + virtual void Save(const void *ptr, size_t size, const char *name); + virtual void Load(void *ptr, size_t size, const char *name); +}; + +class NewStateExternalBuffer : public NewState +{ +private: + char *const buffer; + long length; + const long maxlength; +public: + NewStateExternalBuffer(char *buffer, long maxlength); + long GetLength() { return length; } + void Rewind() { length = 0; } + bool Overflow() { return length > maxlength; } + virtual void Save(const void *ptr, size_t size, const char *name); + virtual void Load(void *ptr, size_t size, const char *name); +}; + +struct FPtrs +{ + void (*Save_)(const void *ptr, size_t size, const char *name); + void (*Load_)(void *ptr, size_t size, const char *name); + void (*EnterSection_)(const char *name); + void (*ExitSection_)(const char *name); +}; + +class NewStateExternalFunctions : public NewState +{ +private: + void (*Save_)(const void *ptr, size_t size, const char *name); + void (*Load_)(void *ptr, size_t size, const char *name); + void (*EnterSection_)(const char *name); + void (*ExitSection_)(const char *name); +public: + NewStateExternalFunctions(const FPtrs *ff); + virtual void Save(const void *ptr, size_t size, const char *name); + virtual void Load(void *ptr, size_t size, const char *name); + virtual void EnterSection(const char *name); + virtual void ExitSection(const char *name); +}; + +// defines and explicitly instantiates +#define SYNCFUNC(x)\ + template void x::SyncState(NewState *ns);\ + template void x::SyncState(NewState *ns);\ + templatevoid x::SyncState(NewState *ns) + +// N = normal variable +// P = pointer to fixed size data +// S = "sub object" +// T = "ptr to sub object" +// R = pointer, store its offset from some other pointer +// E = general purpose cased value "enum" + + +// first line is default value in converted enum; last line is default value in argument x +#define EBS(x,d) do { int _ttmp = (d); if (isReader) ns->Load(&_ttmp, sizeof(_ttmp), #x); if (0) +#define EVS(x,v,n) else if (!isReader && (x) == (v)) _ttmp = (n); else if (isReader && _ttmp == (n)) (x) = (v) +#define EES(x,d) else if (isReader) (x) = (d); if (!isReader) ns->Save(&_ttmp, sizeof(_ttmp), #x); } while (0) + +#define RSS(x,b) do { if (isReader)\ +{ ptrdiff_t _ttmp; ns->Load(&_ttmp, sizeof(_ttmp), #x); (x) = (_ttmp == (ptrdiff_t)0xdeadbeef ? 0 : (b) + _ttmp); }\ + else\ +{ ptrdiff_t _ttmp = (x) == 0 ? 0xdeadbeef : (x) - (b); ns->Save(&_ttmp, sizeof(_ttmp), #x); } } while (0) + +#define PSS(x,s) do { if (isReader) ns->Load((x), (s), #x); else ns->Save((x), (s), #x); } while (0) + +#define NSS(x) do { if (isReader) ns->Load(&(x), sizeof(x), #x); else ns->Save(&(x), sizeof(x), #x); } while (0) + +#define SSS(x) do { ns->EnterSection(#x); (x).SyncState(ns); ns->ExitSection(#x); } while (0) + +#define TSS(x) do { ns->EnterSection(#x); (x)->SyncState(ns); ns->ExitSection(#x); } while (0) + +} + +#endif diff --git a/libgambatte/src/savestate.h b/libgambatte/src/savestate.h index 4110161ca0..7693299b5c 100644 --- a/libgambatte/src/savestate.h +++ b/libgambatte/src/savestate.h @@ -38,7 +38,7 @@ struct SaveState { void set(T *ptr, const unsigned long sz) { this->ptr = ptr; this->sz = sz; } friend class SaverList; - friend void setInitState(SaveState &, bool, bool, std::uint32_t); + friend void setInitState(SaveState &, bool, bool, bool, std::uint32_t); }; struct CPU { @@ -78,6 +78,10 @@ struct SaveState { bool enableRam; bool rambankMode; bool hdmaTransfer; + bool biosMode; + bool cgbSwitching; + bool agbMode; + bool gbIsCgb; } mem; struct PPU { @@ -113,6 +117,8 @@ struct SaveState { unsigned char wscx; bool weMaster; bool pendingLcdstatIrq; + bool isCgb; + bool trueColors; } ppu; struct SPU { diff --git a/libgambatte/src/sound.cpp b/libgambatte/src/sound.cpp index be28da8e1d..01685afc74 100644 --- a/libgambatte/src/sound.cpp +++ b/libgambatte/src/sound.cpp @@ -173,16 +173,16 @@ unsigned PSG::getStatus() const { } // the buffer and position are not saved, as they're set and flushed on each runfor() call -SYNCFUNC(PSG) -{ - SSS(ch1); - SSS(ch2); - SSS(ch3); - SSS(ch4); - NSS(lastUpdate); - NSS(soVol); - NSS(rsum); - NSS(enabled); -} +SYNCFUNC(PSG) +{ + SSS(ch1); + SSS(ch2); + SSS(ch3); + SSS(ch4); + NSS(lastUpdate); + NSS(soVol); + NSS(rsum); + NSS(enabled); +} } diff --git a/libgambatte/src/sound.h b/libgambatte/src/sound.h index 0d39db788e..7d430d613d 100644 --- a/libgambatte/src/sound.h +++ b/libgambatte/src/sound.h @@ -23,7 +23,7 @@ #include "sound/channel2.h" #include "sound/channel3.h" #include "sound/channel4.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -89,7 +89,7 @@ public: void map_so(unsigned nr51); unsigned getStatus() const; - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/sound/channel1.cpp b/libgambatte/src/sound/channel1.cpp index b99902f28e..eae98c966c 100644 --- a/libgambatte/src/sound/channel1.cpp +++ b/libgambatte/src/sound/channel1.cpp @@ -97,14 +97,14 @@ void Channel1::SweepUnit::loadState(const SaveState &state) { negging = state.spu.ch1.sweep.negging; } -template -void Channel1::SweepUnit::SyncState(NewState *ns) -{ - NSS(counter); - NSS(shadow); - NSS(nr0); - NSS(negging); -} +template +void Channel1::SweepUnit::SyncState(NewState *ns) +{ + NSS(counter); + NSS(shadow); + NSS(nr0); + NSS(negging); +} Channel1::Channel1() : staticOutputTest(*this, dutyUnit), @@ -250,26 +250,26 @@ void Channel1::update(uint_least32_t *buf, const unsigned long soBaseVol, unsign } } -SYNCFUNC(Channel1) -{ - SSS(lengthCounter); - SSS(dutyUnit); - SSS(envelopeUnit); - SSS(sweepUnit); - - EBS(nextEventUnit, 0); - EVS(nextEventUnit, &dutyUnit, 1); - EVS(nextEventUnit, &sweepUnit, 2); - EVS(nextEventUnit, &envelopeUnit, 3); - EVS(nextEventUnit, &lengthCounter, 4); - EES(nextEventUnit, NULL); - - NSS(cycleCounter); - NSS(soMask); - NSS(prevOut); - - NSS(nr4); - NSS(master); -} +SYNCFUNC(Channel1) +{ + SSS(lengthCounter); + SSS(dutyUnit); + SSS(envelopeUnit); + SSS(sweepUnit); + + EBS(nextEventUnit, 0); + EVS(nextEventUnit, &dutyUnit, 1); + EVS(nextEventUnit, &sweepUnit, 2); + EVS(nextEventUnit, &envelopeUnit, 3); + EVS(nextEventUnit, &lengthCounter, 4); + EES(nextEventUnit, NULL); + + NSS(cycleCounter); + NSS(soMask); + NSS(prevOut); + + NSS(nr4); + NSS(master); +} } diff --git a/libgambatte/src/sound/channel1.h b/libgambatte/src/sound/channel1.h index 3467547a58..5cd7c23ed2 100644 --- a/libgambatte/src/sound/channel1.h +++ b/libgambatte/src/sound/channel1.h @@ -1,97 +1,97 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef SOUND_CHANNEL1_H -#define SOUND_CHANNEL1_H - -#include "gbint.h" -#include "master_disabler.h" -#include "length_counter.h" -#include "duty_unit.h" -#include "envelope_unit.h" -#include "static_output_tester.h" -#include "newstate.h" - -namespace gambatte { - -struct SaveState; - -class Channel1 { - class SweepUnit : public SoundUnit { - MasterDisabler &disableMaster; - DutyUnit &dutyUnit; - unsigned short shadow; - unsigned char nr0; - bool negging; - - unsigned calcFreq(); - - public: - SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit); - void event(); - void nr0Change(unsigned newNr0); - void nr4Init(unsigned long cycleCounter); - void reset(); - void loadState(const SaveState &state); - - templatevoid SyncState(NewState *ns); - }; - - friend class StaticOutputTester; - - StaticOutputTester staticOutputTest; - DutyMasterDisabler disableMaster; - LengthCounter lengthCounter; - DutyUnit dutyUnit; - EnvelopeUnit envelopeUnit; - SweepUnit sweepUnit; - - SoundUnit *nextEventUnit; - - unsigned long cycleCounter; - unsigned long soMask; - unsigned long prevOut; - - unsigned char nr4; - bool master; - - void setEvent(); - -public: - Channel1(); - void setNr0(unsigned data); - void setNr1(unsigned data); - void setNr2(unsigned data); - void setNr3(unsigned data); - void setNr4(unsigned data); - - void setSo(unsigned long soMask); - bool isActive() const { return master; } - - void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); - - void reset(); - void init(bool cgb); - void loadState(const SaveState &state); - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_CHANNEL1_H +#define SOUND_CHANNEL1_H + +#include "gbint.h" +#include "master_disabler.h" +#include "length_counter.h" +#include "duty_unit.h" +#include "envelope_unit.h" +#include "static_output_tester.h" +#include "newstate.h" + +namespace gambatte { + +struct SaveState; + +class Channel1 { + class SweepUnit : public SoundUnit { + MasterDisabler &disableMaster; + DutyUnit &dutyUnit; + unsigned short shadow; + unsigned char nr0; + bool negging; + + unsigned calcFreq(); + + public: + SweepUnit(MasterDisabler &disabler, DutyUnit &dutyUnit); + void event(); + void nr0Change(unsigned newNr0); + void nr4Init(unsigned long cycleCounter); + void reset(); + void loadState(const SaveState &state); + + templatevoid SyncState(NewState *ns); + }; + + friend class StaticOutputTester; + + StaticOutputTester staticOutputTest; + DutyMasterDisabler disableMaster; + LengthCounter lengthCounter; + DutyUnit dutyUnit; + EnvelopeUnit envelopeUnit; + SweepUnit sweepUnit; + + SoundUnit *nextEventUnit; + + unsigned long cycleCounter; + unsigned long soMask; + unsigned long prevOut; + + unsigned char nr4; + bool master; + + void setEvent(); + +public: + Channel1(); + void setNr0(unsigned data); + void setNr1(unsigned data); + void setNr2(unsigned data); + void setNr3(unsigned data); + void setNr4(unsigned data); + + void setSo(unsigned long soMask); + bool isActive() const { return master; } + + void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + + void reset(); + void init(bool cgb); + void loadState(const SaveState &state); + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/sound/channel2.cpp b/libgambatte/src/sound/channel2.cpp index 78cb44fb07..b3135e2299 100644 --- a/libgambatte/src/sound/channel2.cpp +++ b/libgambatte/src/sound/channel2.cpp @@ -153,24 +153,24 @@ void Channel2::update(uint_least32_t *buf, const unsigned long soBaseVol, unsign } } -SYNCFUNC(Channel2) -{ - SSS(lengthCounter); - SSS(dutyUnit); - SSS(envelopeUnit); - - EBS(nextEventUnit, 0); - EVS(nextEventUnit, &dutyUnit, 1); - EVS(nextEventUnit, &envelopeUnit, 2); - EVS(nextEventUnit, &lengthCounter, 3); - EES(nextEventUnit, NULL); - - NSS(cycleCounter); - NSS(soMask); - NSS(prevOut); - - NSS(nr4); - NSS(master); -} +SYNCFUNC(Channel2) +{ + SSS(lengthCounter); + SSS(dutyUnit); + SSS(envelopeUnit); + + EBS(nextEventUnit, 0); + EVS(nextEventUnit, &dutyUnit, 1); + EVS(nextEventUnit, &envelopeUnit, 2); + EVS(nextEventUnit, &lengthCounter, 3); + EES(nextEventUnit, NULL); + + NSS(cycleCounter); + NSS(soMask); + NSS(prevOut); + + NSS(nr4); + NSS(master); +} } diff --git a/libgambatte/src/sound/channel2.h b/libgambatte/src/sound/channel2.h index 2f98309e6a..9cc2ef28b6 100644 --- a/libgambatte/src/sound/channel2.h +++ b/libgambatte/src/sound/channel2.h @@ -1,75 +1,75 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef SOUND_CHANNEL2_H -#define SOUND_CHANNEL2_H - -#include "gbint.h" -#include "length_counter.h" -#include "duty_unit.h" -#include "envelope_unit.h" -#include "static_output_tester.h" -#include "newstate.h" - -namespace gambatte { - -struct SaveState; - -class Channel2 { - friend class StaticOutputTester; - - StaticOutputTester staticOutputTest; - DutyMasterDisabler disableMaster; - LengthCounter lengthCounter; - DutyUnit dutyUnit; - EnvelopeUnit envelopeUnit; - - SoundUnit *nextEventUnit; - - unsigned long cycleCounter; - unsigned long soMask; - unsigned long prevOut; - - unsigned char nr4; - bool master; - - void setEvent(); - -public: - Channel2(); - void setNr1(unsigned data); - void setNr2(unsigned data); - void setNr3(unsigned data); - void setNr4(unsigned data); - - void setSo(unsigned long soMask); - // void deactivate() { disableMaster(); setEvent(); } - bool isActive() const { return master; } - - void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); - - void reset(); - void init(bool cgb); - void loadState(const SaveState &state); - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_CHANNEL2_H +#define SOUND_CHANNEL2_H + +#include "gbint.h" +#include "length_counter.h" +#include "duty_unit.h" +#include "envelope_unit.h" +#include "static_output_tester.h" +#include "newstate.h" + +namespace gambatte { + +struct SaveState; + +class Channel2 { + friend class StaticOutputTester; + + StaticOutputTester staticOutputTest; + DutyMasterDisabler disableMaster; + LengthCounter lengthCounter; + DutyUnit dutyUnit; + EnvelopeUnit envelopeUnit; + + SoundUnit *nextEventUnit; + + unsigned long cycleCounter; + unsigned long soMask; + unsigned long prevOut; + + unsigned char nr4; + bool master; + + void setEvent(); + +public: + Channel2(); + void setNr1(unsigned data); + void setNr2(unsigned data); + void setNr3(unsigned data); + void setNr4(unsigned data); + + void setSo(unsigned long soMask); + // void deactivate() { disableMaster(); setEvent(); } + bool isActive() const { return master; } + + void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + + void reset(); + void init(bool cgb); + void loadState(const SaveState &state); + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/sound/channel3.cpp b/libgambatte/src/sound/channel3.cpp index d4cf78414c..0d0d069f9f 100644 --- a/libgambatte/src/sound/channel3.cpp +++ b/libgambatte/src/sound/channel3.cpp @@ -196,27 +196,27 @@ void Channel3::update(uint_least32_t *buf, const unsigned long soBaseVol, unsign } } -SYNCFUNC(Channel3) -{ - NSS(waveRam); - - SSS(lengthCounter); - - NSS(cycleCounter); - NSS(soMask); - NSS(prevOut); - NSS(waveCounter); - NSS(lastReadTime); - - NSS(nr0); - NSS(nr3); - NSS(nr4); - NSS(wavePos); - NSS(rShift); - NSS(sampleBuf); - - NSS(master); - NSS(cgb); -} +SYNCFUNC(Channel3) +{ + NSS(waveRam); + + SSS(lengthCounter); + + NSS(cycleCounter); + NSS(soMask); + NSS(prevOut); + NSS(waveCounter); + NSS(lastReadTime); + + NSS(nr0); + NSS(nr3); + NSS(nr4); + NSS(wavePos); + NSS(rShift); + NSS(sampleBuf); + + NSS(master); + NSS(cgb); +} } diff --git a/libgambatte/src/sound/channel3.h b/libgambatte/src/sound/channel3.h index ae0ba2f64a..fe81789899 100644 --- a/libgambatte/src/sound/channel3.h +++ b/libgambatte/src/sound/channel3.h @@ -1,105 +1,105 @@ -/*************************************************************************** - * Copyright (C) 2007 by Sindre AamÃ¥s * - * aamas@stud.ntnu.no * - * * - * This program is free software; you can redistribute it and/or modify * - * it under the terms of the GNU General Public License version 2 as * - * published by the Free Software Foundation. * - * * - * 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 version 2 for more details. * - * * - * You should have received a copy of the GNU General Public License * - * version 2 along with this program; if not, write to the * - * Free Software Foundation, Inc., * - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * - ***************************************************************************/ -#ifndef SOUND_CHANNEL3_H -#define SOUND_CHANNEL3_H - -#include "gbint.h" -#include "master_disabler.h" -#include "length_counter.h" -#include "newstate.h" - -namespace gambatte { - -struct SaveState; - -class Channel3 { - class Ch3MasterDisabler : public MasterDisabler { - unsigned long &waveCounter; - - public: - Ch3MasterDisabler(bool &m, unsigned long &wC) : MasterDisabler(m), waveCounter(wC) {} - void operator()() { MasterDisabler::operator()(); waveCounter = SoundUnit::COUNTER_DISABLED; } - }; - - unsigned char waveRam[0x10]; - - Ch3MasterDisabler disableMaster; - LengthCounter lengthCounter; - - unsigned long cycleCounter; - unsigned long soMask; - unsigned long prevOut; - unsigned long waveCounter; - unsigned long lastReadTime; - - unsigned char nr0; - unsigned char nr3; - unsigned char nr4; - unsigned char wavePos; - unsigned char rShift; - unsigned char sampleBuf; - - bool master; - bool cgb; - - void updateWaveCounter(unsigned long cc); - -public: - Channel3(); - bool isActive() const { return master; } - void reset(); - void init(bool cgb); - void setStatePtrs(SaveState &state); - void loadState(const SaveState &state); - void setNr0(unsigned data); - void setNr1(unsigned data) { lengthCounter.nr1Change(data, nr4, cycleCounter); } - void setNr2(unsigned data); - void setNr3(unsigned data) { nr3 = data; } - void setNr4(unsigned data); - void setSo(unsigned long soMask); - void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); - - unsigned waveRamRead(unsigned index) const { - if (master) { - if (!cgb && cycleCounter != lastReadTime) - return 0xFF; - - index = wavePos >> 1; - } - - return waveRam[index]; - } - - void waveRamWrite(unsigned index, unsigned data) { - if (master) { - if (!cgb && cycleCounter != lastReadTime) - return; - - index = wavePos >> 1; - } - - waveRam[index] = data; - } - - templatevoid SyncState(NewState *ns); -}; - -} - -#endif +/*************************************************************************** + * Copyright (C) 2007 by Sindre AamÃ¥s * + * aamas@stud.ntnu.no * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License version 2 as * + * published by the Free Software Foundation. * + * * + * 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 version 2 for more details. * + * * + * You should have received a copy of the GNU General Public License * + * version 2 along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ +#ifndef SOUND_CHANNEL3_H +#define SOUND_CHANNEL3_H + +#include "gbint.h" +#include "master_disabler.h" +#include "length_counter.h" +#include "newstate.h" + +namespace gambatte { + +struct SaveState; + +class Channel3 { + class Ch3MasterDisabler : public MasterDisabler { + unsigned long &waveCounter; + + public: + Ch3MasterDisabler(bool &m, unsigned long &wC) : MasterDisabler(m), waveCounter(wC) {} + void operator()() { MasterDisabler::operator()(); waveCounter = SoundUnit::COUNTER_DISABLED; } + }; + + unsigned char waveRam[0x10]; + + Ch3MasterDisabler disableMaster; + LengthCounter lengthCounter; + + unsigned long cycleCounter; + unsigned long soMask; + unsigned long prevOut; + unsigned long waveCounter; + unsigned long lastReadTime; + + unsigned char nr0; + unsigned char nr3; + unsigned char nr4; + unsigned char wavePos; + unsigned char rShift; + unsigned char sampleBuf; + + bool master; + bool cgb; + + void updateWaveCounter(unsigned long cc); + +public: + Channel3(); + bool isActive() const { return master; } + void reset(); + void init(bool cgb); + void setStatePtrs(SaveState &state); + void loadState(const SaveState &state); + void setNr0(unsigned data); + void setNr1(unsigned data) { lengthCounter.nr1Change(data, nr4, cycleCounter); } + void setNr2(unsigned data); + void setNr3(unsigned data) { nr3 = data; } + void setNr4(unsigned data); + void setSo(unsigned long soMask); + void update(uint_least32_t *buf, unsigned long soBaseVol, unsigned long cycles); + + unsigned waveRamRead(unsigned index) const { + if (master) { + if (!cgb && cycleCounter != lastReadTime) + return 0xFF; + + index = wavePos >> 1; + } + + return waveRam[index]; + } + + void waveRamWrite(unsigned index, unsigned data) { + if (master) { + if (!cgb && cycleCounter != lastReadTime) + return; + + index = wavePos >> 1; + } + + waveRam[index] = data; + } + + templatevoid SyncState(NewState *ns); +}; + +} + +#endif diff --git a/libgambatte/src/sound/channel4.cpp b/libgambatte/src/sound/channel4.cpp index f1b355adc7..31c0590646 100644 --- a/libgambatte/src/sound/channel4.cpp +++ b/libgambatte/src/sound/channel4.cpp @@ -157,15 +157,15 @@ void Channel4::Lfsr::loadState(const SaveState &state) { nr3 = state.mem.ioamhram.get()[0x122]; } -template -void Channel4::Lfsr::SyncState(NewState *ns) -{ - NSS(counter); - NSS(backupCounter); - NSS(reg); - NSS(nr3); - NSS(master); -} +template +void Channel4::Lfsr::SyncState(NewState *ns) +{ + NSS(counter); + NSS(backupCounter); + NSS(reg); + NSS(nr3); + NSS(master); +} Channel4::Channel4() : staticOutputTest(*this, lfsr), @@ -295,25 +295,25 @@ void Channel4::update(uint_least32_t *buf, const unsigned long soBaseVol, unsign cycleCounter -= SoundUnit::COUNTER_MAX; } } - -SYNCFUNC(Channel4) -{ - SSS(lengthCounter); - SSS(envelopeUnit); - SSS(lfsr); - - EBS(nextEventUnit, 0); - EVS(nextEventUnit, &lfsr, 1); - EVS(nextEventUnit, &envelopeUnit, 2); - EVS(nextEventUnit, &lengthCounter, 3); - EES(nextEventUnit, NULL); - - NSS(cycleCounter); - NSS(soMask); - NSS(prevOut); - - NSS(nr4); - NSS(master); -} + +SYNCFUNC(Channel4) +{ + SSS(lengthCounter); + SSS(envelopeUnit); + SSS(lfsr); + + EBS(nextEventUnit, 0); + EVS(nextEventUnit, &lfsr, 1); + EVS(nextEventUnit, &envelopeUnit, 2); + EVS(nextEventUnit, &lengthCounter, 3); + EES(nextEventUnit, NULL); + + NSS(cycleCounter); + NSS(soMask); + NSS(prevOut); + + NSS(nr4); + NSS(master); +} } diff --git a/libgambatte/src/sound/channel4.h b/libgambatte/src/sound/channel4.h index 6365ad682f..785c98f2f6 100644 --- a/libgambatte/src/sound/channel4.h +++ b/libgambatte/src/sound/channel4.h @@ -24,7 +24,7 @@ #include "length_counter.h" #include "envelope_unit.h" #include "static_output_tester.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -52,7 +52,7 @@ class Channel4 { void killCounter() { counter = COUNTER_DISABLED; } void reviveCounter(unsigned long cc); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; class Ch4MasterDisabler : public MasterDisabler { @@ -97,7 +97,7 @@ public: void init(bool cgb); void loadState(const SaveState &state); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/sound/duty_unit.cpp b/libgambatte/src/sound/duty_unit.cpp index 2059239a75..02ba2a8a04 100644 --- a/libgambatte/src/sound/duty_unit.cpp +++ b/libgambatte/src/sound/duty_unit.cpp @@ -142,15 +142,15 @@ void DutyUnit::reviveCounter(const unsigned long cc) { setCounter(); } -SYNCFUNC(DutyUnit) -{ - NSS(counter); - NSS(nextPosUpdate); - NSS(period); - NSS(pos); - NSS(duty); - NSS(high); - NSS(enableEvents); -} +SYNCFUNC(DutyUnit) +{ + NSS(counter); + NSS(nextPosUpdate); + NSS(period); + NSS(pos); + NSS(duty); + NSS(high); + NSS(enableEvents); +} } diff --git a/libgambatte/src/sound/duty_unit.h b/libgambatte/src/sound/duty_unit.h index cdf4a64f32..0a5f3e9412 100644 --- a/libgambatte/src/sound/duty_unit.h +++ b/libgambatte/src/sound/duty_unit.h @@ -22,7 +22,7 @@ #include "sound_unit.h" #include "master_disabler.h" #include "../savestate.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -55,7 +55,7 @@ public: unsigned getFreq() const { return 2048 - (period >> 1); } void setFreq(unsigned newFreq, unsigned long cc); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; class DutyMasterDisabler : public MasterDisabler { diff --git a/libgambatte/src/sound/envelope_unit.cpp b/libgambatte/src/sound/envelope_unit.cpp index f482bbf7a3..7c81607fa0 100644 --- a/libgambatte/src/sound/envelope_unit.cpp +++ b/libgambatte/src/sound/envelope_unit.cpp @@ -98,11 +98,11 @@ void EnvelopeUnit::loadState(const SaveState::SPU::Env &estate, const unsigned n this->nr2 = nr2; } -SYNCFUNC(EnvelopeUnit) -{ - NSS(counter); - NSS(nr2); - NSS(volume); -} +SYNCFUNC(EnvelopeUnit) +{ + NSS(counter); + NSS(nr2); + NSS(volume); +} } diff --git a/libgambatte/src/sound/envelope_unit.h b/libgambatte/src/sound/envelope_unit.h index 68d6bdc54c..4a0587d141 100644 --- a/libgambatte/src/sound/envelope_unit.h +++ b/libgambatte/src/sound/envelope_unit.h @@ -21,7 +21,7 @@ #include "sound_unit.h" #include "../savestate.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -48,7 +48,7 @@ public: void reset(); void loadState(const SaveState::SPU::Env &estate, unsigned nr2, unsigned long cc); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/sound/length_counter.cpp b/libgambatte/src/sound/length_counter.cpp index cb3616a4ff..e1e9f00862 100644 --- a/libgambatte/src/sound/length_counter.cpp +++ b/libgambatte/src/sound/length_counter.cpp @@ -83,11 +83,11 @@ void LengthCounter::loadState(const SaveState::SPU::LCounter &lstate, const unsi lengthCounter = lstate.lengthCounter; } -SYNCFUNC(LengthCounter) -{ - NSS(counter); - NSS(lengthCounter); - NSS(cgb); -} +SYNCFUNC(LengthCounter) +{ + NSS(counter); + NSS(lengthCounter); + NSS(cgb); +} } diff --git a/libgambatte/src/sound/length_counter.h b/libgambatte/src/sound/length_counter.h index 533606171a..7250ac0a10 100644 --- a/libgambatte/src/sound/length_counter.h +++ b/libgambatte/src/sound/length_counter.h @@ -21,7 +21,7 @@ #include "sound_unit.h" #include "../savestate.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -42,7 +42,7 @@ public: void init(bool cgb); void loadState(const SaveState::SPU::LCounter &lstate, unsigned long cc); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/tima.cpp b/libgambatte/src/tima.cpp index c125bd57fb..5272443ba2 100644 --- a/libgambatte/src/tima.cpp +++ b/libgambatte/src/tima.cpp @@ -163,13 +163,13 @@ void Tima::doIrqEvent(const TimaInterruptRequester timaIrq) { timaIrq.setNextIrqEventTime(timaIrq.nextIrqEventTime() + ((256u - tma_) << timaClock[tac_ & 3])); } -SYNCFUNC(Tima) -{ - NSS(lastUpdate_); - NSS(tmatime_); - NSS(tima_); - NSS(tma_); - NSS(tac_); -} +SYNCFUNC(Tima) +{ + NSS(lastUpdate_); + NSS(tmatime_); + NSS(tima_); + NSS(tma_); + NSS(tac_); +} } diff --git a/libgambatte/src/tima.h b/libgambatte/src/tima.h index 4bb6c31d31..a3c0c995bb 100644 --- a/libgambatte/src/tima.h +++ b/libgambatte/src/tima.h @@ -60,7 +60,7 @@ public: void doIrqEvent(TimaInterruptRequester timaIrq); - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video.cpp b/libgambatte/src/video.cpp index fe17b72310..0437f51283 100644 --- a/libgambatte/src/video.cpp +++ b/libgambatte/src/video.cpp @@ -36,7 +36,14 @@ void LCD::setCgbPalette(unsigned *lut) { refreshPalettes(); } -unsigned long LCD::gbcToRgb32(const unsigned bgr15) { +unsigned long LCD::gbcToRgb32(const unsigned bgr15, bool trueColor) { + unsigned long const r = bgr15 & 0x1F; + unsigned long const g = bgr15 >> 5 & 0x1F; + unsigned long const b = bgr15 >> 10 & 0x1F; + + if (trueColor) + return (r << 19) | (g << 11) | (b << 3); + return cgbColorsRgb32[bgr15 & 0x7FFF]; } @@ -66,6 +73,10 @@ void LCD::reset(const unsigned char *const oamram, const unsigned char *vram, co refreshPalettes(); } +void LCD::setCgb(bool cgb) { + ppu.setCgb(cgb); +} + static unsigned long mode2IrqSchedule(const unsigned statReg, const LyCounter &lyCounter, const unsigned long cycleCounter) { if (!(statReg & 0x20)) return DISABLED_TIME; @@ -140,8 +151,8 @@ void LCD::loadState(const SaveState &state, const unsigned char *const oamram) { void LCD::refreshPalettes() { if (ppu.cgb()) { for (unsigned i = 0; i < 8 * 8; i += 2) { - ppu.bgPalette()[i >> 1] = gbcToRgb32( bgpData[i] | bgpData[i + 1] << 8); - ppu.spPalette()[i >> 1] = gbcToRgb32(objpData[i] | objpData[i + 1] << 8); + ppu.bgPalette()[i >> 1] = gbcToRgb32( bgpData[i] | bgpData[i + 1] << 8, isTrueColors()); + ppu.spPalette()[i >> 1] = gbcToRgb32(objpData[i] | objpData[i + 1] << 8, isTrueColors()); } } else { setDmgPalette(ppu.bgPalette() , dmgColorsRgb32 , bgpData[0]); @@ -150,6 +161,32 @@ void LCD::refreshPalettes() { } } +void LCD::copyCgbPalettesToDmg() { + for (unsigned i = 0; i < 4; i++) { + dmgColorsRgb32[i] = gbcToRgb32(bgpData[i * 2] | bgpData[i * 2 + 1] << 8, isTrueColors()); + } + for (unsigned i = 0; i < 8; i++) { + dmgColorsRgb32[i + 4] = gbcToRgb32(objpData[i * 2] | objpData[i * 2 + 1] << 8, isTrueColors()); + } +} + +void LCD::blackScreen() { + if (ppu.cgb()) { + for (unsigned i = 0; i < 8 * 8; i += 2) { + ppu.bgPalette()[i >> 1] = 0; + ppu.spPalette()[i >> 1] = 0; + } + } + else { + for (unsigned i = 0; i < 4; i++) { + dmgColorsRgb32[i] = 0; + } + for (unsigned i = 0; i < 8; i++) { + dmgColorsRgb32[i + 4] = 0; + } + } +} + namespace { template @@ -168,7 +205,7 @@ void LCD::updateScreen(const bool blanklcd, const unsigned long cycleCounter) { update(cycleCounter); if (blanklcd && ppu.frameBuf().fb()) { - const unsigned long color = ppu.cgb() ? gbcToRgb32(0xFFFF) : dmgColorsRgb32[0]; + const unsigned long color = ppu.cgb() ? gbcToRgb32(0xFFFF, isTrueColors()) : dmgColorsRgb32[0]; clear(ppu.frameBuf().fb(), color, ppu.frameBuf().pitch()); } } @@ -282,23 +319,23 @@ bool LCD::cgbpAccessible(const unsigned long cycleCounter) { } void LCD::doCgbColorChange(unsigned char *const pdata, - unsigned long *const palette, unsigned index, const unsigned data) { + unsigned long *const palette, unsigned index, const unsigned data, bool trueColor) { pdata[index] = data; index >>= 1; - palette[index] = gbcToRgb32(pdata[index << 1] | pdata[(index << 1) + 1] << 8); + palette[index] = gbcToRgb32(pdata[index << 1] | pdata[(index << 1) + 1] << 8, trueColor); } void LCD::doCgbBgColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) { if (cgbpAccessible(cycleCounter)) { update(cycleCounter); - doCgbColorChange(bgpData, ppu.bgPalette(), index, data); + doCgbColorChange(bgpData, ppu.bgPalette(), index, data, isTrueColors()); } } void LCD::doCgbSpColorChange(unsigned index, const unsigned data, const unsigned long cycleCounter) { if (cgbpAccessible(cycleCounter)) { update(cycleCounter); - doCgbColorChange(objpData, ppu.spPalette(), index, data); + doCgbColorChange(objpData, ppu.spPalette(), index, data, isTrueColors()); } } @@ -734,19 +771,19 @@ void LCD::setDmgPaletteColor(const unsigned palNum, const unsigned colorNum, con // don't need to save or load rgb32 color data -SYNCFUNC(LCD) -{ - SSS(ppu); - NSS(bgpData); - NSS(objpData); - SSS(eventTimes_); - SSS(m0Irq_); - SSS(lycIrq); - SSS(nextM0Time_); - - NSS(statReg); - NSS(m2IrqStatReg_); - NSS(m1IrqStatReg_); -} +SYNCFUNC(LCD) +{ + SSS(ppu); + NSS(bgpData); + NSS(objpData); + SSS(eventTimes_); + SSS(m0Irq_); + SSS(lycIrq); + SSS(nextM0Time_); + + NSS(statReg); + NSS(m2IrqStatReg_); + NSS(m1IrqStatReg_); +} } diff --git a/libgambatte/src/video.h b/libgambatte/src/video.h index cf4cd6e30a..67aacc3a9c 100644 --- a/libgambatte/src/video.h +++ b/libgambatte/src/video.h @@ -25,7 +25,7 @@ #include "interruptrequester.h" #include "minkeeper.h" #include -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -79,10 +79,10 @@ public: unsigned statReg() const { return statReg_; } template - void SyncState(NewState *ns) - { - NSS(statReg_); - NSS(lycReg_); + void SyncState(NewState *ns) + { + NSS(statReg_); + NSS(lycReg_); } }; @@ -120,12 +120,12 @@ class LCD { void flagIrq(const unsigned bit) { memEventRequester_.flagIrq(bit); } void flagHdmaReq() { memEventRequester_.flagHdmaReq(); } - template - void SyncState(NewState *ns) - { - SSS(eventMin_); - SSS(memEventMin_); - //SSS(memEventRequester_); // not needed + template + void SyncState(NewState *ns) + { + SSS(eventMin_); + SSS(memEventMin_); + //SSS(memEventRequester_); // not needed } }; @@ -147,8 +147,8 @@ class LCD { static void setDmgPalette(unsigned long *palette, const unsigned long *dmgColors, unsigned data); void setDmgPaletteColor(unsigned index, unsigned long rgb32); - unsigned long gbcToRgb32(const unsigned bgr15); - void doCgbColorChange(unsigned char *const pdata, unsigned long *const palette, unsigned index, const unsigned data); + unsigned long gbcToRgb32(const unsigned bgr15, bool trueColor); + void doCgbColorChange(unsigned char *const pdata, unsigned long *const palette, unsigned index, const unsigned data, bool trueColor); void refreshPalettes(); void setDBuffer(); @@ -175,6 +175,9 @@ public: void setCgbPalette(unsigned *lut); void setVideoBuffer(uint_least32_t *videoBuf, int pitch); void setLayers(unsigned mask) { ppu.setLayers(mask); } + void setCgb(bool cgb); + void copyCgbPalettesToDmg(); + void blackScreen(); int debugGetLY() const { return ppu.lyCounter().ly(); } @@ -267,13 +270,14 @@ public: bool isCgb() const { return ppu.cgb(); } bool isDoubleSpeed() const { return ppu.lyCounter().isDoubleSpeed(); } + bool isTrueColors() const { return ppu.trueColors(); } unsigned long *bgPalette() { return ppu.bgPalette(); } unsigned long *spPalette() { return ppu.spPalette(); } void setScanlineCallback(void (*callback)(), int sl) { scanlinecallback = callback; scanlinecallbacksl = sl; } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video/ly_counter.cpp b/libgambatte/src/video/ly_counter.cpp index 01ae017132..4b978dfeee 100644 --- a/libgambatte/src/video/ly_counter.cpp +++ b/libgambatte/src/video/ly_counter.cpp @@ -64,13 +64,13 @@ void LyCounter::setDoubleSpeed(const bool ds_in) { ds = ds_in; lineTime_ = 456U << ds_in; } - -SYNCFUNC(LyCounter) -{ - NSS(time_); - NSS(lineTime_); - NSS(ly_); - NSS(ds); -} + +SYNCFUNC(LyCounter) +{ + NSS(time_); + NSS(lineTime_); + NSS(ly_); + NSS(ds); +} } diff --git a/libgambatte/src/video/ly_counter.h b/libgambatte/src/video/ly_counter.h index f65d9ef7dc..9aca9877b1 100644 --- a/libgambatte/src/video/ly_counter.h +++ b/libgambatte/src/video/ly_counter.h @@ -19,7 +19,7 @@ #ifndef LY_COUNTER_H #define LY_COUNTER_H -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -52,7 +52,7 @@ public: void setDoubleSpeed(bool ds_in); unsigned long time() const { return time_; } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video/lyc_irq.cpp b/libgambatte/src/video/lyc_irq.cpp index d9a138a3fd..024ef35233 100644 --- a/libgambatte/src/video/lyc_irq.cpp +++ b/libgambatte/src/video/lyc_irq.cpp @@ -95,14 +95,14 @@ void LycIrq::lcdReset() { lycReg_ = lycRegSrc_; } -SYNCFUNC(LycIrq) -{ - NSS(time_); - NSS(lycRegSrc_); - NSS(statRegSrc_); - NSS(lycReg_); - NSS(statReg_); - NSS(cgb_); -} +SYNCFUNC(LycIrq) +{ + NSS(time_); + NSS(lycRegSrc_); + NSS(statRegSrc_); + NSS(lycReg_); + NSS(statReg_); + NSS(cgb_); +} } diff --git a/libgambatte/src/video/lyc_irq.h b/libgambatte/src/video/lyc_irq.h index 19577a802a..510039d996 100644 --- a/libgambatte/src/video/lyc_irq.h +++ b/libgambatte/src/video/lyc_irq.h @@ -19,7 +19,7 @@ #ifndef VIDEO_LYC_IRQ_H #define VIDEO_LYC_IRQ_H -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -54,7 +54,7 @@ public: regChange(statRegSrc_, lycReg, lyCounter, cc); } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video/next_m0_time.cpp b/libgambatte/src/video/next_m0_time.cpp index 15bf6441b6..79cc59d06c 100644 --- a/libgambatte/src/video/next_m0_time.cpp +++ b/libgambatte/src/video/next_m0_time.cpp @@ -7,9 +7,9 @@ void NextM0Time::predictNextM0Time(const PPU &ppu) { predictedNextM0Time_ = ppu.predictedNextXposTime(167); } -SYNCFUNC(NextM0Time) -{ - NSS(predictedNextM0Time_); -} - -} +SYNCFUNC(NextM0Time) +{ + NSS(predictedNextM0Time_); +} + +} diff --git a/libgambatte/src/video/next_m0_time.h b/libgambatte/src/video/next_m0_time.h index a47f337118..0a91c4d330 100644 --- a/libgambatte/src/video/next_m0_time.h +++ b/libgambatte/src/video/next_m0_time.h @@ -1,7 +1,7 @@ #ifndef NEXT_M0_TIME_H_ #define NEXT_M0_TIME_H_ -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -14,7 +14,7 @@ public: void invalidatePredictedNextM0Time() { predictedNextM0Time_ = 0; } unsigned predictedNextM0Time() const { return predictedNextM0Time_; } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video/ppu.cpp b/libgambatte/src/video/ppu.cpp index ed884f7b97..7ff497e44d 100644 --- a/libgambatte/src/video/ppu.cpp +++ b/libgambatte/src/video/ppu.cpp @@ -1630,6 +1630,8 @@ void PPU::loadState(const SaveState &ss, const unsigned char *const oamram) { p_.weMaster = ss.ppu.weMaster; p_.winDrawState = ss.ppu.winDrawState & (WIN_DRAW_START | WIN_DRAW_STARTED); p_.lastM0Time = p_.now - ss.ppu.lastM0Time; + p_.cgb = ss.ppu.isCgb; + p_.trueColors = ss.ppu.trueColors; loadSpriteList(p_, ss); if (m3loopState && videoCycles < 144 * 456L && p_.xpos < 168 @@ -1739,70 +1741,71 @@ void PPU::update(const unsigned long cc) { } } -SYNCFUNC(PPU) -{ - NSS(p_.bgPalette); - NSS(p_.spPalette); - NSS(p_.spriteList); - NSS(p_.spwordList); - NSS(p_.nextSprite); - NSS(p_.currentSprite); - - EBS(p_.nextCallPtr, 0); - EVS(p_.nextCallPtr, &M2::Ly0::f0_, 1); - EVS(p_.nextCallPtr, &M2::LyNon0::f0_, 2); - EVS(p_.nextCallPtr, &M2::LyNon0::f1_, 3); - EVS(p_.nextCallPtr, &M3Start::f0_, 4); - EVS(p_.nextCallPtr, &M3Start::f1_, 5); - EVS(p_.nextCallPtr, &M3Loop::Tile::f0_, 6); - EVS(p_.nextCallPtr, &M3Loop::Tile::f1_, 7); - EVS(p_.nextCallPtr, &M3Loop::Tile::f2_, 8); - EVS(p_.nextCallPtr, &M3Loop::Tile::f3_, 9); - EVS(p_.nextCallPtr, &M3Loop::Tile::f4_, 10); - EVS(p_.nextCallPtr, &M3Loop::Tile::f5_, 11); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f0_, 12); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f1_, 13); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f2_, 14); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f3_, 15); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f4_, 16); - EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f5_, 17); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f0_, 18); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f1_, 19); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f2_, 20); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f3_, 21); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f4_, 22); - EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f5_, 23); - EES(p_.nextCallPtr, NULL); - - NSS(p_.now); - NSS(p_.lastM0Time); - NSS(p_.cycles); - - NSS(p_.tileword); - NSS(p_.ntileword); - - SSS(p_.spriteMapper); - SSS(p_.lyCounter); - //SSS(p_.framebuf); // no state - - NSS(p_.lcdc); - NSS(p_.scy); - NSS(p_.scx); - NSS(p_.wy); - NSS(p_.wy2); - NSS(p_.wx); - NSS(p_.winDrawState); - NSS(p_.wscx); - NSS(p_.winYPos); - NSS(p_.reg0); - NSS(p_.reg1); - NSS(p_.attrib); - NSS(p_.nattrib); - NSS(p_.xpos); - NSS(p_.endx); - - NSS(p_.cgb); - NSS(p_.weMaster); -} +SYNCFUNC(PPU) +{ + NSS(p_.bgPalette); + NSS(p_.spPalette); + NSS(p_.spriteList); + NSS(p_.spwordList); + NSS(p_.nextSprite); + NSS(p_.currentSprite); + + EBS(p_.nextCallPtr, 0); + EVS(p_.nextCallPtr, &M2::Ly0::f0_, 1); + EVS(p_.nextCallPtr, &M2::LyNon0::f0_, 2); + EVS(p_.nextCallPtr, &M2::LyNon0::f1_, 3); + EVS(p_.nextCallPtr, &M3Start::f0_, 4); + EVS(p_.nextCallPtr, &M3Start::f1_, 5); + EVS(p_.nextCallPtr, &M3Loop::Tile::f0_, 6); + EVS(p_.nextCallPtr, &M3Loop::Tile::f1_, 7); + EVS(p_.nextCallPtr, &M3Loop::Tile::f2_, 8); + EVS(p_.nextCallPtr, &M3Loop::Tile::f3_, 9); + EVS(p_.nextCallPtr, &M3Loop::Tile::f4_, 10); + EVS(p_.nextCallPtr, &M3Loop::Tile::f5_, 11); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f0_, 12); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f1_, 13); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f2_, 14); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f3_, 15); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f4_, 16); + EVS(p_.nextCallPtr, &M3Loop::LoadSprites::f5_, 17); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f0_, 18); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f1_, 19); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f2_, 20); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f3_, 21); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f4_, 22); + EVS(p_.nextCallPtr, &M3Loop::StartWindowDraw::f5_, 23); + EES(p_.nextCallPtr, NULL); + + NSS(p_.now); + NSS(p_.lastM0Time); + NSS(p_.cycles); + + NSS(p_.tileword); + NSS(p_.ntileword); + + SSS(p_.spriteMapper); + SSS(p_.lyCounter); + //SSS(p_.framebuf); // no state + + NSS(p_.lcdc); + NSS(p_.scy); + NSS(p_.scx); + NSS(p_.wy); + NSS(p_.wy2); + NSS(p_.wx); + NSS(p_.winDrawState); + NSS(p_.wscx); + NSS(p_.winYPos); + NSS(p_.reg0); + NSS(p_.reg1); + NSS(p_.attrib); + NSS(p_.nattrib); + NSS(p_.xpos); + NSS(p_.endx); + + NSS(p_.cgb); + NSS(p_.trueColors); + NSS(p_.weMaster); +} } diff --git a/libgambatte/src/video/ppu.h b/libgambatte/src/video/ppu.h index 6ca279af9e..12e197408c 100644 --- a/libgambatte/src/video/ppu.h +++ b/libgambatte/src/video/ppu.h @@ -23,7 +23,7 @@ #include "video/sprite_mapper.h" #include "gbint.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { @@ -92,6 +92,7 @@ struct PPUPriv { unsigned char endx; bool cgb; + bool trueColors; bool weMaster; PPUPriv(NextM0Time &nextM0Time, const unsigned char *oamram, const unsigned char *vram); @@ -107,6 +108,7 @@ public: unsigned long * bgPalette() { return p_.bgPalette; } bool cgb() const { return p_.cgb; } + bool trueColors() const { return p_.trueColors; } void doLyCountEvent() { p_.lyCounter.doEvent(); } unsigned long doSpriteMapEvent(unsigned long time) { return p_.spriteMapper.doEvent(time); } const PPUFrameBuf & frameBuf() const { return p_.framebuf; } @@ -133,8 +135,10 @@ public: unsigned long * spPalette() { return p_.spPalette; } void update(unsigned long cc); void setLayers(unsigned mask) { p_.layersMask = mask; } + void setCgb(bool cgb) { p_.cgb = cgb; } + void setTrueColors(bool trueColors) { p_.trueColors = trueColors; } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/libgambatte/src/video/sprite_mapper.cpp b/libgambatte/src/video/sprite_mapper.cpp index 80b588c088..cd2e28b1b2 100644 --- a/libgambatte/src/video/sprite_mapper.cpp +++ b/libgambatte/src/video/sprite_mapper.cpp @@ -117,16 +117,16 @@ void SpriteMapper::OamReader::loadState(const SaveState &ss, const unsigned char change(lu); } -SYNCFUNC(SpriteMapper::OamReader) -{ - NSS(buf); - NSS(szbuf); - - NSS(lu); - NSS(lastChange); - NSS(largeSpritesSrc); - NSS(cgb_); -} +SYNCFUNC(SpriteMapper::OamReader) +{ + NSS(buf); + NSS(szbuf); + + NSS(lu); + NSS(lastChange); + NSS(largeSpritesSrc); + NSS(cgb_); +} void SpriteMapper::OamReader::enableDisplay(const unsigned long cc) { std::memset(buf, 0x00, sizeof(buf)); @@ -190,13 +190,13 @@ unsigned long SpriteMapper::doEvent(const unsigned long time) { return oamReader.changed() ? time + oamReader.lyCounter.lineTime() : static_cast(DISABLED_TIME); } -SYNCFUNC(SpriteMapper) -{ - NSS(spritemap); - NSS(num); - - SSS(nextM0Time_); - SSS(oamReader); -} +SYNCFUNC(SpriteMapper) +{ + NSS(spritemap); + NSS(num); + + SSS(nextM0Time_); + SSS(oamReader); +} } diff --git a/libgambatte/src/video/sprite_mapper.h b/libgambatte/src/video/sprite_mapper.h index 09e269d769..b6171f842d 100644 --- a/libgambatte/src/video/sprite_mapper.h +++ b/libgambatte/src/video/sprite_mapper.h @@ -21,7 +21,7 @@ #include "ly_counter.h" #include "../savestate.h" -#include "newstate.h" +#include "newstate.h" namespace gambatte { class NextM0Time; @@ -58,7 +58,7 @@ class SpriteMapper { void loadState(const SaveState &ss, const unsigned char *oamram); bool inactivePeriodAfterDisplayEnable(const unsigned long cc) const { return cc < lu; } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; enum { NEED_SORTING_MASK = 0x80 }; @@ -124,7 +124,7 @@ public: void loadState(const SaveState &state, const unsigned char *const oamram) { oamReader.loadState(state, oamram); mapSprites(); } bool inactivePeriodAfterDisplayEnable(unsigned long cc) const { return oamReader.inactivePeriodAfterDisplayEnable(cc); } - templatevoid SyncState(NewState *ns); + templatevoid SyncState(NewState *ns); }; } diff --git a/output/dll/libgambatte.dll b/output/dll/libgambatte.dll index ce430d492b72f907d3f012d4133338d153fb4040..252e3af447802b6c7b7ee1a88d616eef8331c65e 100644 GIT binary patch delta 86134 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�|mX5xWW+S9G!Shfweo$ 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 Wurp*Zt`L literal 186880 zcmd?SeSB2a@dtc2SzzUD7X`&)CDmBd2K~i`T4J!>3mdr6P*X)gr5cS=Y*7&s#DcQn zCXnl4HHucMw&F`IE!NUvLm(=ffF!&K0er!iYOucSH7aN=1XT9<&YZKa*@O-4^T+df z_%OL=&g;yaIdkT`+`E&ms?#zxO>+XmVNGkmlm2=6Z_j@^kvw?h`oY?BC%pWf21m)u z-FG{_*r1@qEF5^GHfBTlmM3 zm*9EmJAXa>*Zh3?^y~2a;yYK4r01dUJjM8>HdR5Fr?HOA8Nfe>}Wy$!mhcdKTk+>AkHl#lgAMef3YMJo($XNK?iW}!u z0DbHn#V7Hu_=x1n(zLR3XWwvL#dVrCuLiNGQ(KJZ5Aclp=S9l7GMl#7jf6I&yYOu2 zk}r7fESZscY)AS~_XK03)mYrva7Q@_>b?rtf6!*^vpY=G(L}Oz0ts_kc51Wrv)RZ4ItbRsK zxZ;|1t@APwUyk_v-FgFI+}PgG{nS{^TNU3o#J14*ftt4240_O2XuHMSgb=z3PBw|lv6U1-H(Z%M$H?X>`*gU@K1C;>7csrYt##Ip(I4dJlr z7WC0H)r39ZuSw}A7*$PUFhb2E8MkNYW@qN@-wF(Ukd##yL1RFh&suulERbzG>Kxie zL!4j)#yM4)646wW+dL^y`zSI@3e>H@LvPT$n&uu^O;u>3^lRN3>JT3M%`gI9SFss# zRND4)_d2A02!~>N=*;-~S_frlKFi;RM@-bLQ?flj zELwd2)$#SY>-~?RA0z2QplSt+$OgI}46;1Tb!-vcHT%B<2}M;^)qjE!O}`{Xy7@^? ziEtE~4O~Y>ak21^LET09TYT>Nx`{dj>aO1!dS#?Ts}5ESE)umKO3tnI;(?@QGx)jL z>CgkZ)BT&K>Mgq4*94`=*=V+g`I$%$>l3MIi!VEeI>-q0DVqyj43uPN8Bne)BQVVU ze;F7-S5mYXU<4*-XN8`6g7j7yk*QQoRWpq%y#W+zVUNtR zCpk+iXVGRjkOf6^Ky2RC8>8dfOGC+;jQBtwHz)zUd6M2?v#2UbDq8a)G+Gy(;bEPX zaRmSS@fg8B=|b=ild0)S@EOQbBEY*8O;vM@U_E>yMqgfGN%rW4Lko9SdYDr(45i@0 zd+kQ3NDN8nfRz0G9|(`*uMP$@6@SCy`OADV#^1G6fI2YRA(vB3Hyu*yl0Bj$Pqz$S z>N-9SS-(orXZaOG0lJWNhO|Ot5RzkvaV`o#3CDRTFb09)(7NA_l5Bg!!}@YzrF!36 z5#zLy@+5HjD~jocQ>A)Sle5Hn6545QDvh(hQTpf1=`Ho&!a{=rbQql99iO1nf1$n{pK2^%oLlt6|O??wRd0gsaY7 z?=S+zj@nEkaJ8d4Sh=5Q8Dp58p4pcgP=PEX95gN@uXFa;gH0nV`h|zx^gYbShR{2W z!lv24uVg$IwH|iO0bb0wdHC$PK*q|IyB<`AQpw2b`ip;w0jiKja9>E>3^oLf;AZlwz0B53-V z!BlOxqx1!6o3dpf8_}RwwTH2WG20ztX%p3^=UV*eN{vC~Xw4MQlNum2f52*1WDi$P zbrtzax;P+?khR4KaEhJbHp6TrpGd3tz7LKyW^h>BZJ`ZE!(jtcTAq2tS>eH09IZD{ zJK<%NnOtxXYKWm5jhfvRLotvpplJ;DxdNlsB=6iOHjCF$RZ_hD!P}}|nLZ{>G!sFn zkSKeA2NqS^*H`PjprfC7J#^l`sZEo7yJOJZ?-+CwjZB_gZ+N)0(AXqGB=fXLj zz#=Up9QM^#yXqkQjy|Eky@Q5R%ITqRdX`gCdzj8DvboVbBe0G}zac(10>34TU|>~% z;YMx*dM+n4?RhL;k78q_W$_9m)2cRq_jIe@IvU~DZ^+shV)Jx3_K>{SXjx3QfO1*) zkU3~sOcuqVorDnCi@lCXXyP~I?@})r5JO(V8j8nP9%~eYh~9q#GwuFtBJ92#DrMd0 zl8=xZSYS{ir-?Sk`aH;(*+7`JY#u*EnZ$<6N9SNp+sh9T2Y5r$J(hiGzziKnaW7L>vmi26@iG^vThp%E`&k&$UFA<4Pd zj(1MCmXO>L?290EhP!^et9A*=4WScimyjVwsDJGevcm`ss9i#47@>i+OGv8j`ilpN zrbBIK`AP6g$kvJ`v%SpR=V@wpT1%);n)W%Zd#OuMY;f%oGQSA9YnPDqMF_qJmbJA@ z)&h+vC)wZ2FT27BJd<5^7@bk|$%=m9PxRTb8kuOau9*jG@8h8(?jx2$tAdncTj-=c zFdmKDF<8Q44KrK4{~jb+&u6!xlA`?GCDv2f%Msi_RF_!KWY+;Hv7XDW<>&L+ems4F zYpV{LIT-aB6^STk87JP+1Pl?cy-HPe6y zlSC{8Nt`D^IUf#a6)l(t{f*Q$G!Hgo9t@Pp(mWH^+#nI23u|Ubg!;m&nF$1Tht`np zwT(PTVK)1d+0+|J^`b`M95$X0mXZ>p3;fh)f>AP{ZV{M_lp?V)rTy&dt{;0rhdW|1 zL(Q*)ozCfDmX#3*kmS7X`WCBZmZWYj7MWH}1(5JElCfPywDgA1sfy6oJDE^I6`|9P zNvJ1E=&2yoT_2&;+zc_3;GmWVed05pI9hR118wb2cQ7lPO-?0N!mSq`1rI+ly2OZ?cBFfw-#N{pN+0yiM2!?h(LrI@rJbmu7@ zgNoEN6`J&#rq!CHX%WG9k!MN*uqx#)4;*4i5~>K8nGOMieSry1o+v}xUL^`h@f8K; zW|vem3|StkYLqQ{{A)~CuaTmi-4q?`IyqjNI8YS0%HggVhb$#Rj^q-dMz&BRUC7~Y zAe}6NWs;S!+wP{`TqcbkO9J}}JSv;sa}8)K5<95HA_y8iZC{T$0?8z>a8L@aDdf_a zeo7xF)48_wp#hf3(^EaChl7?$<56fQ>@xSc&G#Jej?8vSaes`=|=^MtDAt9x#qEF@Zwjg`lC$C*fs6HF{Lm-xhvrjM{` zu}vQk2E{@6jt`=@NRJ;wa$usvy|&F4_$d~&+d}h^VlT%yL8_dJ1!*(9Y$0dsrdlE6 zO`a%BcEhAD3C@Bk26SUW;aLaOP(GQ|WVmzAu_%R zvqwl7Q#f%9a^BVsZ1Jb1z3lm+e{m$c?UuY|^a&Ri0zxO|{@T+ZJ~T#|>>yBTe{BB1dA6F{gU;L)B5aMP4pYm$Rz z7r+CG4c7xZh!jr9LISiwvoXUBn`odN!^(9+L)RF?9AIp(oD=BMC zfeEws$Fe3WFy~EQfmf@}KlllEKB21fpX<5vl>$?9aq3xtxiYH2XKYI{EX3ob$e~nM z&4BGLIX_AbR%vaaU3OHNzYn!hD>HZXewf!ZHM>DEcdBHLP{rKyJu>I6&VjE)Bh((1 zzS9&`t*z={Q4*?J+tg!g70Ki1<1HjN8YI0l$gPTi?@Iy*RRnDBnE+*wX^hw_F~7$Q z^0iC~C)|q!cYPy;;o_@N3@s<8i4D<5t^u8Jq)H%ccA#3(-O@9Hjj9z~$z-mCc_R}} zJhBO=MADAYGM&(b5kL{K5x`Y_8q1nE0yuB_BcM%5?Ms`xjZh`ES9+G38UfT?+z6Nr zrz1K7vRbJplX8?IN5mBI?m)yE{g2i%a)*QWHV*9-%_Fv$`^vEXIblwwxux$}bah59 zZYsZv`IUMON50wau_w%>bpMgx(z7h!>@O`>N8cQFYs`J#rhOR^mzR57I&b!3I3Q|t zR=DFNF@qhGv^=)Gst;y`xDNBq0^5``I%!m7T_p7j>Pa17Mci^i zQq3b$Y?zZM1C%o0#9ksfcJ@pxL!z2b4ly+X&BqdgHGVy)mcp_#*=|VvhK*PHO*Wot zW%~zjNmY@c;gmI|;e^=(AV8HhQNuZJ`WoJ@qk$x)r417bI9B;-C(` zPE^Bx{8Eywq8zs%M++~Y>8R--E17*IA`h`ZHME+bDL-cKDJ@M2DPAUg$h;7$c=@D9 zUfk7}v+n^-xsrEa(rWF1YHjZa+!~>(wJ&>Yt%BFV7sRcDPsS3U*X!U;)%=yRc|uk5 zU-#TR%_tXi#O)b9v=O^&a-)!(v!-^}bbUa}bcX2{Wb_aFfnXr!5Knr zw34RoUdY8++0`)Nk%38?PLhl+CrE0QPa81gCCz`Ig-fX7&PiJ{apy>h$4HX7*fvRH zbHt+2iam_QJT2)_v$Rj4Mo14#SMlPTc82U#JOyZT?uHld2`fy_4kbiWgMLEKK{#-?bW5}B}EWEXrQ2(!*M47+i=NWkmC zj{Q2Ef|ls;BZpk>4$(HCy%~wIvFWZBm|N5IvCt&<;y>PFb_iAM;6T2Ija7H`CZ51C zAX_OQH)a$Q39dVFm1xA*3Q|33u=AVXUC7hyCfR3AtH^4*R1t@?;Va>a_hiElf_!Pi zNx^>t6QOV5Eoq;lF{0-Wea!Zlr_A{EB}K$cH(_=u1PTHuoM^f^Z~CTtxRUOaeJou< zm2`3SuLtQyOgA+b9rsTARrokj(`{}b)18!K4RTbiApw$=wqKF+8EwGGok;tBWB_Nh zWKl7G8AB@p6?2P>Ath8X(_{>FrHZ*I7SkJ}k);GOp`8Uns1gXy6?-rmCz3^ku~MyX z$CMov#IIi@LD1GOS+l<(Np+pd{FscP404Oh4#*fO1m3%vFJq(-IOZl9BZa^*Q)G-3 z0>>1^VtOM4Tx{Smr@zZWAXEtf_ma{Q0=M1*e;_J^N}PI1?nnsFASo&Y&it^9kwPHG z?OGW_scO$=u8fgF;5;|T7%2pfxm3nTA#jX07SkIcgg+uFDg@5_pp211AU17N#z-M>3{8^J0m3oY$`~mGj+rE5q!2i!Fc#Ar zA&gN%_|ZRE2!tvjj7?Jr-1-Zc(4s>4RU-+3;zTu7B*SWDM1yVhkB0g}|lGkHz#x2wo+GAH2;%AXEv#m!=T7^#)9nQ6WsnDVd~` zgz!5gMTNkbg^ZCxAQtR9WQ-I7$NWOZNFi{{PDgpbmLFjZ1pF z?PWmyg6}>EGaD=G8k4SBRa`F2)9}`OcZ=P0ON z)(S*-kH?|p0ZwfeOHGQi_*#WLJK!BwBFa~Iz-^KZ*Kv$+;LtnfhnblN^vr`q*b(>( zBng_`lLR>hG^;*Si`EUuj2Nl8RKos?jFFnlF^|X?skt0u$r!1*9CN#jA?d4{Z{!&E zMpd`iZ^xXd$dX);{zT8lsH!;s&k}qBERR+W%N8h0v@ns~(WH2d#uDirtCC}h{Ei2c zV|hR?kmt||RkJ3Sa{}`KrBQ51?SgW>xR+!ncStrnEk2cwoE>v)CcIK|ry?g^EUE9y zF`m#ECmf8O?nYSQ(Uo$;LLEPH1~|bu1juZ)*Qcm~M3kxqB?z?-t7lR^jHJ+YP5!@Qq(}b+hYxsAzH)rTtjzi zu=cgCYLK$-WuyyAVd(JTJx69+<}OP`H(SeC+vSdlQ;yUGwh2jDaYPv?gCsgIP`Ceq z&2rVt3Hp?vqPeY+ZM~-W(WVy6uvT`}J6)^cakn^ ziQF3@oH03aBd7eit@cQHCZ5o-UB~FLtD`cgt}S*V9qr_l)pa+l+jmE`OteYk4o9F@l;>_u zIk@bzQw%9$s^`kFFAZHfv{&s+*Tyzl;p9P?mC{02Njuie2D zG)n;Tk&&uU93`{~)`Xl=gmfb;QIdIFf&Dj3WvQ}|%v6?py2(P4Q(1o1O%{@$%3^er zg`}vmoYhSh5~RwKfhzID~0MrEZ7z{&L5h zRM+F4d!@VUaStN}3`FlEQ~_5<9k~2Wd&S+Jc$C6s(Sp<3#%pv!)f#;=g7GIFNpH^3 zanfiUCq-{mxBQ7XKA;X^46%$(TB6N&bFFf!YQDd0o>0~Nw4R%nCoXZvNd}r%ZL6~+ zykNq2x6RE1?OXSxEs-)s((PMWB7`cEZb*lu*db8;O4_O*4Ufu%PoJVPNq&t<9YW1e z1RUJL1Q4nSn3)a%aWWYV3UJIM_ly-0_6=|#qMC9VcGS~6Rhgy8x>k}!s3NO^$-W-6#TQ51w*K!U_s9m45yv;xD!Tt9m2j6Q+HH(`-7z9 z3}cXCy2&b(Vdnx#Q&4_I=J}FLLKT_SJ(Jl*P!&(d3o1=1)vD&#wy=~4Rn0H$x%nOX(zA`}`+XJV3$D$pPPj|VwPG%zs(@0zwNDRpac>J)vaNcsp>^gYlseO<(JIp~Y& zQJUgeu9{!InZ-k>YW~rlo9`l?*PfE%DH5CLXAWl~&h0*5m67VMZ`eQN) zuo^#AFLT_5edg|Ha95uJ*FwI(vQp9UQ%MJ*pu=6QA%_S&lSGScW3o*Yj3O(tUE8g6 zu!Sn6^FB6zyQ8c^NoTd9s$mmLhfpP*=aMK&k`9qU#=1K);v_V!2RhuaHK+yI9BSu2zsq{>KTD3qlna+YyZSKVsd*COM`& z!8Cg8&ufoON}D3(u4Wb}p^B7Ol1PDXJ{{g3$qAjtuJI{wVB%b`nxpJJq&45~Rqghb z?GmcmeLJb$W4U0y702O%wX5crG%?$Rs^;JCv3Xo{J4o&`Zc9`nfR0XZ__}d!LaMb> zWov}0);{U6wXWBfraTt0{_NnU=WiZR%|9GuW(ifzf7x^MG-ytVxKJN;VuVLue%tYT z9yLUT{W4P2+*mOr;ymbq+g=1Q4nSa3Yw_n~5#bV6XX|@*oCmCLyTl(cZ^X ze_FC|DN^hgSr&vUQU<0;ihP$pltZLM-b)>J!BeEVL5x^Yy=cMT!ER=AxE(V9EoTD* z@g}s~BEy_M0H-Ciyh9>ygmaeI>YPXasbEM=A1Ew~iY6|MoU*)J3dAp`{{ycj%!MCC8T<5G&Q2<6@8M>Z8wd zR}-qbdMI~w%&&};R|^l zlD_VXQl9vtPs*aCAjc}2tVAlC3%r%vmYPJO(b4$WD8;3wmVU%W0=3z{@Qbuk>sU}#@sDy=$< zsa>709gFc~)p`*{A8ATv5IH50IEIbngYwt1ve^ak z1xP=7C_u@!E4~0}MGpli*?t|zEK!n37kVf_$@bIn1xN#WC_u?}r98-?u+j>j;G2TD z?W;F^hP&{}Ez!*n9o?=-vy_t5G_aBos+1(VS4#5IZ`kk*I3T6&v(}KpV9m8Twzz6q z$NFJ6;*cjQ8|w2OSfyn9Zax)a4Upxagh?+ck#-Dd+7(ip=$rkpeCj6I3*p4=ahBk3fvi1RwMEPiv52wPFe`rpln6;{uwe|8id4j?&S=bPCXZ? zGg6*=^F3Y^@Lh=HbndlgnNB^CoX)-TL@d2Gy_2VUr|CKF9YR&_j6P=X;0Altd2lSF zF&xh~nRUC6k6YL+NKCDjr$?X2R|wfYw=1iwBp(@%3eEQSmz}ys7NL%S2FMnXhw!)} zghq;}9`ehA)En^y36CpCXsjT2-9%ZCx+%UO;c*2CjTPiR9V`n{|HT(1Jgy+2v4Y&$ zuYSo2NF5trknp&IgvJW87Cag&*c&YvqqLy$@2mxcDlHh>D=qk{E;<-7-R}y~Y)>N* zvpo)V=ne)(tRKrT-+*A~vo&neo1Ub2m%WeQAz-zL=a~j%9M6Qtc;?YcuQ|rNQ8;?) zUJfah^crZb0Tm%ig6&Rx8;D*-3IvEnS0g;-A_ zg$U`c5NpguvJfjsq!1z96=GEx7Aw>nRWX#RjDD6?g;1p`CB0IWilxbB2(MB_Em5fQ zuL*`{25#P<16UCv91_wUhpZC$vJe>&+~SdS$1b7RKZ3KPI=|yx)w><9AwV6`@iGD= z+>T8Mkia?`RiHQ0Dpk_z`xllLp-Nhldz98d+Iz$fjy)cS{Lu;AD^MJf=OPlsFq3D< zuw*jBK3Srq(Pn#w-A|&VkA!ye4Hfya0Z?0_#3rS_DN$mU&}}iaH!M$8ET8lYvrMRB zd0LMw(_8VxGBsU_rlVY6gb!e;_%V_sVw|#lPok1{r~NI7O5UCJ0EtT8o%Yw#(WK5b z+nx5O5|z9=?RO+9d3V}7V`y)9FH^jqvWj^pRPla8n!G#h$!I#t`%f*!yVD*kNs_!f z?Q)a zl|DovdH2{KN>uXhv0sm&z2SY9;(g?wn0G=I?-gnC?y;w#=_v14EK$6BY@Z}a^6s&J zC{f9Kmi=9cO5U^VQzR;R&*E*3XuoIKhvk-nk(+8f^IDc;Xq$-EP) zcwdkv?^$*knvU{*)ndhamTgFqB=1>vfkY+mUiAcdvZ|nvU{*O^xE+YnMopB=27PLWvUZ z{7TB1EpkGGml>PW_^UrRW9mewlUO=lL4X9(u}!H#M>7H>gpPFx5c?ggRiHPF*DA)( zeVQ33R58A^N5-S`+89hsQLblHGuQT+a<(Ft?Nbo;KS}H!wFB@q{7(`?N9`&O6HiC& zN)A&GAGL4dFmZO&{yB$a5IznD>lA~N{=f_psu+BrM+W6hirAVvV4`6AQTR5o zeX`#|Xm&k30{@f5>QVbI9421Bu^;C!@%oMZAcu+9Z)}Uh#OpWqog5}!zp-!QF!B10 zeIvrh!RvCx>vc~tuY@XIAMKG>*_rL%*r&&L<|B)U<8SN}xC4p7Z|v>x1!QM_Wd}J- z41Q%l$6;ddD|;n}iNUYzM>$Lker4Bjm>B%Zu15Gc7+j$k^gqcA5~>({Djf#DvM+=y z5!Kf9Rm9*|b`BzC4}N8LV8WLS9?aXE4hB~$1|NHZ86;FO_)Izs9cMyIAsi+K-?jU2m>7K5K8&f}|0FT^uKg*8 ziNSa6eFz^1gAIzof39E#2~`X}pALiX+7Dyqk1}`)Gx)AugGkBXySBkZ5`!D;u^c7_ zH`sX`CI&ay*&HSYH`v2DObl+Y2XdGg++b@69|wa$#o*VEF@uCE1~(m(!Bfok4fZmoiX*Zek$%2j@35c5)F>IuupiWs4ikeJ_CgL5gBkW54ikeJ z_AfY03})C@BYYeT?o|v<`5iMzsABN#bQsLAhsHB_-+W>)!w%zEK{A+OH^ZNh3_9(4 z4ike;`_CLE2A%frIZO;X?fW@Q3_9(_93}>x_OB5>4hGv5gL5Bc1_@ORzMl?*PCFmG zL`UK3UlW5)`}8P-PJ17m2+5$!ewD+-pv&IEVPeo_zrbN)&}ILP!^EJ={sV`JL6`ko zgpY&4kYez`N0>oE6@#Co!=TH)6lXM126x^;47%(K5h;5RYiTBu?5M~75)Or*kDNVr zh{MF7$9|i`#GuD+<1jJku{Uv;81&c;2pUQcuul+D0C4*l3=O9duLBIVo4ikfZyNJWYpx-XwFfr)2&*3mJ=(oSaVPep4 z4@3Ak7<4HHhe!qqRSXX7kwG;RhWgnre-X#wMRRE;MC@9`N*4Y0ui+;}E+Y<#Tt>97 zLo6=O_Tt9AN6~uYL);mJDq4p!t=-;7`V!!!Vmz?PX9K=DkUmhNuspF*F~i`>}Xj@{qxXmD50f+r&HqtjPGh>k}eWJwYVI^5MiMGg@dl|+mE zBSiO3Q}R>;g|Y!c6{DlM@@~7Ti>BN$=QuQFjB5V*2be!XRr6zeZeH$4#A(Wr+5a0& z;j0BoQ@)FR>h5|e^7evPad2cAb3mx#z{ecm&wur9#}n8u?v4g`^#pi4Qd0~?$81Rl zp{yy{$RPqHNwnC5*i}mFLT{<6dAO{ZP{rY7uC-egK_3mpPt8OxH&3|@=csM!N-xen zqWA0Qw+`er8TBIvc-@^|`%Uo$gzi6dF#;Q4&wu_HNN|_>M;eAXBp^B6nl1SlC2;s$t$aU@$;bx%& z;=a~c6K2OGtv3Q@sCN6wb_rGO&P;0eSg$`6-AXp2mv5lXQq9-aG24Wy<|}$^-d*i~ ziDrJh{!oJeq+18qODT!b-3LbJsn)(FTO(ApwxGw>x_prP%3C5|L{l#%q)C8Z5%Al4 znO#B^0oCac5N9G^zZpg8_4PsC2AF!U);lF5~|2t z%4GIrB@$kNI1g0Dy#jGdy01XgDFSYj1Q4nSc;J`>$RF%@NxlNHdS=`!5S7taAl`v1 zpuXDWe=-ME9`!fUL#w>3g+IL&#y_2Z{@Z7X;Yh*5DIe_pr~XVAKHI~KpYp`dblJC} zxww9bd4rcBp?7oqau7jI#FPMc4WUXhk0RK^%OPFn_y>P=9CQ2%)%@P2+&rPG`KNkr zzKb__GMfKidxPV!?|m#oyHas*vgCkJ#lbVo0seHc`MvQ5U-WlR1AfsCzLA{cS1UU1 z5iChUL5I8AiyR{GToNtz_YmFfj7T;hvJy*twUmFTOnE@rJx~^z5gQa$op&=;gevJg zpF~lTuZ-JM^6s(vfzMrUTUJh}VstxK+^va=yoyFkC|mpk*TEw1Z~A6Hqt%O@vysqkcXjW;#CKOE zHjuj$Q>44v6c@!7cNd|Gi&vNn{OO|1={Yb7E1Zw9(|asZ_9{|dtYuONRiwO~L`nnw z4se_U^E0>}i4IKlkJ%eZYff)h?M{&G5~|vLKdIeR4h;P^Sfu}|ryqv{6H?8$EMc|@ zRn33WWApCn4bQU!bBetQ0Z2C;2j+lk?Mm4ip{lhndu&a)(s_H#=4H zZ!TtL302L1eeC8jFAs^mws{J-A5@-8Yoy1=yxg+L-(qj=#arNa-oLYtF)+dYre! z>ylo5)YN|YV4|#F`=qz-kVi3WRI|tlRSXa9k>RcbV)6CyB2Ra7Of|p7q7W-T5HKjGv=c$^{k~I^mI2_Hjc01G4mPh>i+&7e^z2$L? zuMKwh3BYXFp5yQu_%|SwllK#BMRzCfh}SSiN#vv|mNlVDB4b%1sro!UuYrGJB(g91 z9QJ#UMT%FEQtM|@2vwx`l1OP7p6E4m2wM_&Cq^320E{r%m0}SHNSD z%3rDoT(pp-Kqv@wSC2*x`~ox+8S51L+wAJ39kiq#n%m~}@+oQ@vF#%_0XxT}ASb&V9#JVncn1uP^&Sx8fn zLj)Ei(Lx#Dg|Y{J)d_N|IrojrY3Y<1R(gl@a5`_)-| zcfCHVdoe}qY^~yA)qEBap^A&8%tdTS$GYn{>}*LIq|_-=vLq>lDpDRuBBdcK(ax@6 z#~`gu{5A7SsA}XV>Gl(l`k8xr{(aBXKMf8oR=<&iFY2#^KY>#Z8r|vydOAKH zF?~SmbW8B&z#Z&xq$Rl(N^(_qu;d6;l6$IW$#wZE+lDFeZY*q5nx5=RML~ETQ$VPq z;F+E&P<4Be^Yq{I9LUI&3?4XG9~`p+RvKxSb_VsN(8*=IVcB9nMXI zl%OIdLy|(MB4tw&DGfP^*5N_+E7I!4R@KPw<}$y8sz$bRBWYWQw-tk zg7=sWy4ib_J1*QfKH0d2QS2%7(Z!B%Tf{%C$b;G%fd}`h=BLZ%302L%oz#3W@;8MB z8bW@Ur{6fZa6jm9lU&r#jz4i(L@;oqlnW(V4z-=-N3Y74HAIWqUS@ve!Ar4CA35a* z^sPpE7ux<4ybf48^0ngRK7vLUi+yNmtahh8AGulb7+*=kFGEtqG_Qw?jPqhTHP*$k ze(!W^P0;PzrQ^3t`=>C__$(lHSq8MpnOz!c(JYwwv$VdNR`n5m=3zi*gC7~2_D8?i zThtVrf4MgXbVkfpM5Kw^*(cMF?(L92<-P;Ez6nD;p$}me(TDA-51*LBDnzL2!}pW= zFhYnPmbwf`fEA4LK3yfT^+*AFEuKps2M7$Ar#cOt2ZNu z2z-)6k^NV88PXc!2UN}LXEUFKDn7sDT6-Mgy!Wj>7j_t`q$ZJ6hFX3|+dln?<1*Co zo9v!nB=z3T_z@^Q-A={T`?oPygetDSX0BoeAkusI?LzVZVs{q0`AA~ICVLIGpA!@* z1u3M(w8MC%B!y5#iW9;3t4s}f(DArU_D|TeNUIlJs*x{eF~5YWMh0>tY4>9OCDC3? z<;KAV<+k_&P8SA#k$Y`xep|l!-4pXYPt$vSBA<(YgkE!x*o!V-^X}@(?JjZ`?Mbv0 zyF2>onY&SE)q5JqhmlZ~%b%mx@vSDG{6T7{o4o^mMt7ba8EabcJ<7gFTJrQLc|LV3 z%ac$g&!Iibv&)g~dy|gOxf`x1I73oEsG{Jso+*HHM^jkTxw{SoL^nX7taN5UAA1@Q z?)J__mTLRyTbO%7RomIzc5L)S7N>Ih;+L(}Y&{BoN$lw0?UKkmW1oZsb#~ycd9a-> zU*@1fhaTRH??0V}VEnIexvOU*3liwgwfrfr-tAX^8^ytOI%fenitWGN%xn{?*dE1f z_c(~+H(oxwI68=UCgs-yB-j@)W}_&SZuORSb`2hGYHQTPvBc zsd#M~q>NFdYs0{8+nGNJ$R<3Z3O%a@XKsXyBl!QS(-KpaQAm{kK}AkdmOM4@EKqL{N@J${{Var z`09I_Hu(FRb}rybz+AwIxtewXpbhZ&C{24AupjWPb2RNofIWbI=W5y_z*2npdl%qy zz-ch`1%Oupe>hLma(|#{ivfQF>;W7FjL6rtwSet_%SUV4FMg6-lYk=7Wn%08yRd_A~`~k2Qum$iIpyC3w3!ps9@%%I3dB6@p zJK&B&3`fB40B1pNf5o#2um|t~;I6TnRtIW1F9O;C?*S;!mv{z#qG^u-)&pJz8~_aPA{{UZP@rqtWWaR5Jiy(6 zM*+_Qf&i*#51yWJn)Y45Siq%#UjeECdjX#V^2cl10>Ddvem+er1_;1WfL;VT0lx=4 z2iOYu7vO8a&nF-a@H*hT6QM(ZO@NaOO}ifO79gt_`U02>SO!=PXagJojK4_JZU)o= z)&O1u90r^Q3-S{{0PqjMUcgs?(_u$-zzu*UfL8%u11^A(F9l2oEC4(Rcoxw2r_fcwKr>($ zpdGLuK)c%a&DOLPfWHD>1iTD*8_)ru8|Q&J81I11fSrK7fPH|E0sjX0D`0m4%K=XT zRs)^~v;tlM%&bKJ0jdFYfJXr<0dx))1WcNXu?x5na2w!GfCYFE@HpVy+cEY469AV2 zt^xcKFc+{0;GPFL1I`A}(e6(G7XzjM$^i!eUjtlsUi{nSb^-ngumN)yfKI?YfQJE3 z1O5(p5wI07{s^w9`_ht(9rc_%uUI%g!^*O*QzoiT@2E{hkm~i_DHBz zw@0W|{k|e)qU!jGDHB!C|1xEw>iVChOjLb8CS{`P{E;aWRqywWPmJ&VGMDWB?Lv3v z?=En-mo*#Z&DPHbilJ53&s-Z->h|p=Vhi6CpH!}o(yiiBz*s<@F7&MN1Lts?jM zR-aWos<^xfF$K>1@Uzgnn4oN~^rEatr9vOd^?6Fl^|3`Gw&-S4ACw$34&{o=ccCaZ z(u?|H^cg6c7o)Evkv=pynp%=Hg6b+z-2^^H>B4z`ae1p=;Jh2+Xl8MYBXKO!t&0-H z0eQseFNx7_B+#En^7vo1H<0D!g8cKq-zdpnCg>iW$RBtE&BVzT#mQE%S_BRhC)@|n zm!kYF1rAh71nTZ(5J7PsL{A(@@sCp7nL|~f#vZEM4wA;8E};)B(1}KGj6Po%`bKx5 z@6tRF2(?MkPU;Q-{gQMav!UqsCWwEGq8|cEs<+q8ZTG6V^srI0rP7rjWb3<;X4yQW zDy(_n)fXCO)7eJh&fEKihuyizp=}TKyA<0DHNi?mj5QHq1g4&CnEz@wtnv8j_{P{k z-WVR%h!Q2DC4aXeS`D!`^yvba?wYqMUk?w{sBF?dYT09071_h}D`=K45hqL%2MqCf zu}O9h3$qb=3m))}Td^H9Eqjb1Dzd#qwtmeVsDDiLqy?%mT-v-^(0l-qe8{K+M9^P;b)D?>3O(6Dz9oln}6)9gvThJ z69vfr5ro!gkPNDtNCuP2_ZBz(D`QgNK0jC9Tr9Te4SkSddYuvQ`w?(^97e$6&=9ZR z?J2Hv4>9W89({#-h<63>)xcK+4+0MYZv);2yd8Ku@B_dP6q_MOv|2KFH^ zpZFq9%iPtkBVVzo8bc3j{_siG{H&sU9OAU#bgFKmj@7%nerxF4Sh`dPD+U*dDi3lN z~y$)6RZyEZeM3j_^wlp@}1#fk1ZrO2Ks{|yuG3O zu*~V`LkwgY;$)n0l|V-gAO4h5Xex!ujKCtg*k)M1?0FPBgJNfuSa&w~tUH5vVxGFQ z4bQ!Jw&NMX^8lXneAWxpzb!?6jkRDAQEHr{c@gK!E~O{cY8c{fV#N@gA-9zxr&=Xw zTHI~O1Wrfv45S7Y?H!2E@0MiGGOVHoBT#~26<9PJir-QcM4hO#h;p?L;NV`v43;7) zgs4*L1FL9WOHmtTY$<9l<;Q^>der#w7xstlmMscGjotP4KZbD^vUvm#HUg8g^FrIN z4u?y`^V!3}Qw@6m`E2CW?i$Ks-4l8NOHZ8Hz)sOVqL6)o?M3Qz+Fm^8mx!*~L`tDw zbIs2;Zuee(*%eb@dIHa6XC)}i0B{sj81X_LiWG^@Ss!*bg|HU++z=;ZJ9Pg(N3AbA zD|F&dVI%h9D5U0mmfFcMt|VV{EFO>niPZs11+3R#7`1A*<*B z9`h`g2;>%3fdB9H<*xl+|3?mQ?Rx4^bZmm^%q)`L|7~*-|mn{McD>nfbA!;sW~f9rK8z!ljGd_Mc~= znLt&8lLof7+M%~NvY-tc5jQ49oCl^qlFzU5Q3pigR#4mzf)W4Edl_b}QbY84tH070 zA7&?U0yWBmL42j4J8Y^n7P98;Sy#+RK_aUzYi9D}C8aL_;2p;aC#5kZn(4xLiwqq2^{ z@zvHsMbXI*TarMvA~25!}tdMV&heY_}Os7JmQ%R zE9L0Or0NO}%pT8Cz)mnSFZLLPJLaAj$&@KG{AVq>8*uR08Yt=i8cd#`+pmwedY)TF`JIA0T zQ5>lk{>HT=xtP4jqfkzZA;x)%#o=NBzDan+g~2(*+`{?9T=5;mT<&J0u+wl)>?|&P z$6Zqe859ScV~hgb!&xVD*3&twrF#tX4sc8|-s}X0Vm{=2^!C$@mhogECCTnZ4^U8K zZpN0-^AxN(=&oK9Nv2L4K>e}J$UW+=u15StxXK5{!^4iwb7&n9tLlUnh(A2+w}?b1 z+%bI4*(Cxbh}T2+aIN8r>x#|0h@MH}eAyRgQQc=!Uwpu%8rB`2*;r&_sE_H$m!j{; zrM4mLfyIHL=cC=w3v<8=dQH~RgS6oXc~iPO+eMAA+Uf#c)Y?*+#>vKg)l{p^uDR9~*^R=bmh|v`}ffRg~LQ zQW(75c~M|2e56Ypp)Ft1lyjg1bIdfF6)Rx6LEwxMgyuo!6(ghkoJ8{8Lvc{B&BbCz zXzOh1zVR4Fou1jH7`YIfaUuKJV-Gfstbj{x-%fHDdZ$s?G#mIEjAN=b54+|7-@>?g z`0Tkro|lKwcNW5N9$1634r(@+q;*i)JCH2f=@y=#vK2&Ja82uU;)2 z_AHAf*tE`%?uR4aqCqlY!@|}>z>8{ZN2P59OwEO@Ny_3L=>?F2adyjNw0Oz;3MXXm zKN#7wkcF~OkL2HspQx$zL^B@H*O6@Ws>2$xR@|Tq`k4SDbK`nhGU`+QPyAN{GezZJ zbhwtPO+S`DrV$X-c>Zp^f#O5Cvm8K50(}l(fQ)mm9hia3UcQWsTIZ?2N-~^`_Klz} zM*Bz5fs76|%w`XwjH)Ab@OEZ5kam;kl|7lM7C~%;H(*+V(%9k<`hlf2Jt{Az%0bE7 zLigY5(AI}3FDOQwFEGv#x)YH`)kgOD%+DN^JB+~ZsHfZ`!5q%xI6r>dgYy22^2)N2 zdP5h+m*3Cj10)7RycC#{h4q76HT+M!dntcN_WUOtq!K>tAIHo0dx`RuXjHxuRr0NM zO4P&RJ~4t0W%Oi7e>kEb{U9dY*$=B(VP@QFlC69E{-eA82o%7}DlOg!opp;tE6NX+ z1TMxzJm};V4oDRA!u9vL!g{TKbW)($3C!s#HiH?6_`0~NvlSAb{kFF{Tv@apUMvaQ zc`7->8x!LbU_4nzp4-~oC50^zdO$L1kViKA^6;l;9 zSnv|-_!;tn3vYBkX#UKW@fDP*Wjv14aBgqSU*M~4^dn6Aj~hU>2x5?AZ1#b3g(DnZ z$YbIi==#J*iE48>l^H@RJ^>Nfpzt8=Vh%znZ_7JZ!;&-Lel zVZ(A6mfAqo^^C2)c`f8Zw3xR#=Z#Bk5eth6JKKQLvve;=>kUjXqg^_EOQk8 zqVj=wP$NU}Y%z!9FJ(e4r*TAyfP6e*$S~U&ffR6j^}pA}xna+(1= zw-rpzL;&uT=EM&)%|jSb3-D+${rDTS*}I446i>y~CVXP`PKjsaZek^T$bei1va+B!0K)9V6v zIfT6{oVy>Jnx#hhOZqzW6BZM}{yy<;NnogHls6G(1e_+>Z(re#3Y?93okqE5oET^p z3|uxR(Ww--Gg@8&*4 z{T~zg{SbHpzv|SO8Q#T-5r!iv#PLQ>iEyk^hFuw{JdWkdQ`SK|PfVFYO#d)t3UPfl zr=;Mz3`0F~|3kcKh{L$O9=h@)nDocmvCA$tn(dkB(58?3is~hP7ahs?(O*y$3+}~Cz8zfVyWZ=i0X%A1xta%TzwsfpzpqRCb=*EVk@4-zSlX$HY$e6F zFH_0kK>HHGIZ$%@F?**p{Y|}r<{mm^`#dDBgJ=Ay(XzM{Q8ZKy>sd-Q0x!~6=Yi!A z27R)grCA9Nnv3x8%7-BzW%4mYKFIc?PBP-EPQOg3l@Gd&jw+VR2i?*Jwo*P;%SQtq z*0VwUZCShtf5{Gpa><9ZZk=odenW=ex^MR(Ak`tP!IyG@%wxEhpE2`kF)0SSsrmXt>2Sf5n^=N`y&W+W`w`lB}_;Btg@@=dg{0 zHnM(**Jv{|AV}_^N3lCA7OUK(5!j3ri)n7Q^r7zh#WX$F>Zc;~TMm5-p@%s%0-;AZ zbQ(f5eb?&WM(B4O8X206qceCltUiG&U`rghz9p`hX-%1IhzSO!UB4Sy!T(gk;lhQ1 zn)^mtmrQflPq;H9m@#2N?Zh&?x>wU$nQ3kfTT`;+=ZPTvQ9kREQovY1Ucu0N!6fc% zY_TrM!Gxr`DDcx8#Du99W=inb7k37r5!jL4B{B*ox~z!*pCBk{H(mLE$NJlYZHB-G4W>_W-DEHb4bK#ni z`Xe|G!MiC~KVg2YNn=4QqaR{0kAy37@QMu`y5ydmxusg%k3~yGe_!AtH(thZb`0l5#yfN>^vB<;RN)4oAQCi||2C9+VAwxV9W3dI|NXx=fa zsDcV8hS1RZ!KQu{gW*!S>$jNu`kC$hDzO8(4YdqJEgmY4Z$8qas!7Wu(;z~q#jq}( zLG_+tG=?_qeUOt%9W-7+70)$x!PO z4?3`9L@-d=$C_fG2Me4J!GE(b%JB)x1T)grV#EeE`VS&%0}FS;e^@gMhD`J=oH&q_ z(VFP`FLf|MPrS9O?g4A6M=D2P^m)M=W{oOa%Yrl-=x^xatI2s_4>#Vel^bGgZ+ya{ z8xgwL>l1I{x{9xGt9!|_5SY8(qPq?arC8h*m){S!U)$5}%WN$wyqh$y$hw=#O)9_J zg%ov@rpQ_}5E$7{QN4&P(D;JtyXi^STu_9+>o2PBrzpPsBU53GaRKOl`XVSY(XSiu zbr_jXS5?SNxFvM=0c6m{Qf@%E?&hC+aM#~Wd`X!ky^AEh-DIMsQqW7w3jex$(S^9Mu*F@o z8lC48RU}#6>!?G* zlgg`@wHB_7rr8m@&E`zqx{ss+eFigCs%!X3Q(;zT)KZ9y;=s@R}t>6z6-4DHC6O|I(PMLgoyj-jWhXT zS#h~Vt%3Xy9;kgdlJIcK2N9$fJis(!QfS4DfcBKYb5 zb}|GzsaFN1oeZ4-@k(*K>+d5m>$$B*n|Hr=bgN#=cu{Q?^~OfoUEEvi!4P%U-|^gB zMV^U6bw`Qpj-=j*c82PUaRstZNX&-p3*Ud=7w81l9T+X~!oX#i)es!+eN|){;dZsg zC3_%`ig~RSqF)LtXlzHu+s~q7WLySWM!FF3Craw^o(HGT{eT=Aq_Y!3C%kl-i+me< z$y@TWH}u_$6FvAeUUNs78z}f28UKntp*=W0GRjTrHM7&@UQ!5eyCg7d+6bC6V7JJa zy#EQ5DziD1S}N)>s9FQAPp@YT>bL3>4`h3`Q!90iD%Enbcmi?~IkM4}PJPDM$LW=+a2Y@~5A z`?P{JWTq;H%9HZVXhw7vnv^!@{enu3_V6ICC?$#ns}g)17ZuETQH}r=V0{wy9%!ym z{JZOULc`oFXu=W8+CT?JNRv1CX+LlOmvCG=!(F`!!&db^y-4q_-xd1t3pDAPWQ%xy zww96ovDQ#|VUnSP(wih(DWGL!fbbfrGC-o5b*-RU#ATt;jx5Ujbj^Y^QX()UmU7kk zK}(R}3f8dBlnZGCxh-@ei;hXOSmr{u7DpI5lPOYdQJ40LB9<)^r6`j06ds;EtWNgt zhuB<>_U|VG;`I9egR^#?-wWBxQ)0AO_7N|F#VMo&;|PZN=uwh(0NR7jjLz-2zS!2W zRMPTGJ`_OjW6_}czhDgy(t%>lUdXX(vmXYraLwBY8|7jsx(viomyHNooP%7bW!M$P zqL+KFK=vbfQ%n~*REb2(F$=?|dKL^&KSR`AJGhHDp|IS+5pv=35oh@1!!qeFulfK4 z1AT-%Y4d}Hit6x7if-KRn!z8^ZTi4zHWFX7v1l=eKDQ57@Fr)M6)8>vUd&mUe4Yw=i@riR}KU#OM-R8^Op@%p6P&8XK;cwPiT04!gY1y+b)cmkQ zBdQLEzrhS}5?n)Q1+4B_=V31$QtNQxUd9aCLe5-}Jufivo5GjoPCm*WxA~G&`QBq! z%sH<*Sa}M*oOOuET3@( z42&|L*je>ncqCL$Y;&*w*d2N}n|g~=z|g#a%`ssQDHwG%iWySS%m21>3`UgBqVIw-6 zzQCoW=Pjcw{={DfzzizLg0F`pF&~6TbC;1k z+6F4YZ3oOZVs~i6M`T8m#5TRI{(Zdb_5lDJdjqlhJPYs&U}gv2uLG+(bS~9yv&3#G$w=;U=Zxy3k-;2@eR~ zzq#sWjN&zPJzXgd?qMxn3kj{ETC4nxZFr#XQ5A8Lcc)93w2JJ&=lBvVc1t18ppl8e zq)C_QVEm%mckbIVmfooZSjp^nk$@{!H$ewA=q`AeKT7M zcxoy!3dXJ@T*DQGb1*e8x)LedsBP0If8x@oA2^U%) z@pqz8HH{^3&g01Bab)s1GP%v@{M%-fZ8STNV|)(v~|@ZsH57hA})K94RNauVj7BpW#B&#~o0%#u#oNi-GZAI#;wJf8?R zgn$da7s!M$@%q@k9a=n4-zg%e3_k`tZbVPCw1ZI}T2 zprS9O56XSXz}@k_;bE^MNlciW`;A`MG8^}w4;1Ad@?{?KS#u|wcV&eu&P6CoE`^Qq zE3g$hp%j3v&GN zI5-dCc&C9ZmO-{FcC0%s?({VA{#8{SD#F=jcHRcu+S116xvT$#ry;5uAVLFI?W=za zJlqCKDeIxOgH4`Fm*~Xj#>|i02b;1gGtTTZ!|sYo|30_>u)FeRlRs|n@yj2e5j;Cp zOsO$xW1->_u`IType7(|8I2hX9U3%v(5S(KhE0JSB5Fq=)xdm`s5;j2i6q`~#fgzLoTy!aGe?*oX(^6OWL}!jYN~=SUR= z#z!3~XCq1MU1m*M~|2%hYKB=?*m+T8^EL7N(DOmXa%pnhp+N&1lhnF3!w#|KSyLPRbAR zhAt4ldt95n08L;gi}$2s`?^iJyKt8mTe~MCpWNEzvluFI5lZk5uk9uB@GhTK3MR?h zt%3}3d$r;k`ZK{ijMoJG>#6l{rN_UXohDrND)`u;Rm{fwFyxG4OK1`05Upmr8#nWv zxXfd~$QYT`TRm~<{||TX9v@Y8^^MPDCS)MM36N+�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 DmB?v5