├── pch.cpp ├── introm.h ├── .gitignore ├── imgstore ├── whc4e0b23a683fc0.png ├── whc4e0b23ac916b8.png ├── whc4e0b23f6744b0.png └── whc4e0b23fd946e8.png ├── .gitmodules ├── sound.h ├── perftimer.h ├── pad.h ├── lcd.h ├── misc.h ├── ppu.h ├── CMakeLists.txt ├── gb.h ├── pch.h ├── apu.h ├── perftimer_linux.cpp ├── sm83.h ├── perftimer.cpp ├── cart.h ├── introm.cpp ├── pad.cpp ├── dmgemu.sln ├── README.md ├── sound.cpp ├── main.cpp ├── mem.h ├── dmgemu.vcxproj.filters ├── lcd.cpp ├── gb.cpp ├── misc.cpp ├── cart.cpp ├── ppu.cpp ├── mem.cpp ├── dmgemu.vcxproj ├── apu.cpp └── sm83.cpp /pch.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | -------------------------------------------------------------------------------- /introm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern uint8_t introm[256]; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | x64 3 | Debug 4 | Release 5 | *.sav 6 | *.vcxproj.user 7 | /build 8 | -------------------------------------------------------------------------------- /imgstore/whc4e0b23a683fc0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emu-russia/dmgemu/HEAD/imgstore/whc4e0b23a683fc0.png -------------------------------------------------------------------------------- /imgstore/whc4e0b23ac916b8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emu-russia/dmgemu/HEAD/imgstore/whc4e0b23ac916b8.png -------------------------------------------------------------------------------- /imgstore/whc4e0b23f6744b0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emu-russia/dmgemu/HEAD/imgstore/whc4e0b23f6744b0.png -------------------------------------------------------------------------------- /imgstore/whc4e0b23fd946e8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/emu-russia/dmgemu/HEAD/imgstore/whc4e0b23fd946e8.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "thirdparty/SDL2"] 2 | path = thirdparty/SDL2 3 | url = https://github.com/libsdl-org/SDL.git 4 | branch = SDL2 5 | -------------------------------------------------------------------------------- /sound.h: -------------------------------------------------------------------------------- 1 | // SO (sound output) terminal emulation 2 | 3 | #pragma once 4 | 5 | int InitSound(int freq); 6 | void FreeSound(void); 7 | void pop_sample(int l, int r); 8 | -------------------------------------------------------------------------------- /perftimer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void TimerInit(void); 4 | void Timer(void); 5 | uint32_t GetTimer(void); 6 | uint32_t GetTimerR(void); 7 | void TimerTest(); 8 | -------------------------------------------------------------------------------- /pad.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void pad_init(); 4 | void pad_shutdown(); 5 | uint8_t pad_hi(); 6 | uint8_t pad_lo(); 7 | void pad_sdl_process(SDL_Scancode scan, bool pressed); 8 | -------------------------------------------------------------------------------- /lcd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | extern int lcd_scale; 4 | extern int lcd_fpslimit; 5 | 6 | void lcd_refresh(int line); 7 | 8 | void sdl_win_init(int width, int height); 9 | void sdl_win_shutdown(); 10 | void sdl_win_update(); 11 | void sdl_win_blit(); 12 | void sdl_win_update_title(char* title); 13 | -------------------------------------------------------------------------------- /misc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void sys_error(const char *, ...); 4 | void rand_init(); 5 | void load_game(char *); 6 | void show_regs(); 7 | void load_SRAM(uint8_t*, long); 8 | void save_SRAM(uint8_t*, long); 9 | void log_init(char *); 10 | void log_shutdown(); 11 | void __log(const char *, ...); 12 | -------------------------------------------------------------------------------- /ppu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void ppu_init(); 4 | void ppu_shutdown(); 5 | void ppu_enumsprites(); 6 | void ppu_refreshline(); 7 | void ppu_vsync(); 8 | 9 | 10 | extern unsigned lcd_WYline; 11 | extern uint8_t linebuffer[192]; 12 | 13 | #define CONVPAL(to,data) \ 14 | to[0] = (data >> (0 << 1)) & 3;\ 15 | to[1] = (data >> (1 << 1)) & 3;\ 16 | to[2] = (data >> (2 << 1)) & 3;\ 17 | to[3] = (data >> (3 << 1)) & 3; 18 | 19 | extern unsigned mainpal[64]; 20 | #define BGP mainpal 21 | #define OBP0 (mainpal+4) 22 | #define OBP1 (mainpal+8) 23 | 24 | extern unsigned benchmark_sound,benchmark_gfx; 25 | extern uint8_t tilecache[512]; 26 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.0) 2 | project (dmgemu CXX) 3 | 4 | add_definitions (-D_LINUX) 5 | 6 | # Main application 7 | 8 | set(CMAKE_BUILD_TYPE Release) 9 | 10 | set(SDL_SHARED OFF) 11 | set(SDL_STATIC ON) 12 | 13 | find_package(SDL2 REQUIRED CONFIG REQUIRED COMPONENTS SDL2) 14 | find_package(SDL2 REQUIRED CONFIG COMPONENTS SDL2main) 15 | 16 | add_executable (dmgemu 17 | apu.cpp 18 | gb.cpp 19 | introm.cpp 20 | lcd.cpp 21 | main.cpp 22 | mem.cpp 23 | misc.cpp 24 | pad.cpp 25 | perftimer_linux.cpp 26 | ppu.cpp 27 | sm83.cpp 28 | sound.cpp 29 | cart.cpp 30 | ) 31 | 32 | target_link_libraries (dmgemu LINK_PUBLIC SDL2) 33 | -------------------------------------------------------------------------------- /gb.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* Interrupt flags*/ 4 | #define INT_NONE 0 5 | #define INT_VBLANK 1 6 | #define INT_LCDSTAT 2 7 | #define INT_TIMER 4 8 | #define INT_SERIAL 8 9 | #define INT_PAD 0x10 10 | #define INT_ALL 0x1F 11 | 12 | 13 | void gb_init(void); 14 | void gb_shutdown(void); 15 | void start(void); 16 | void check4LYC(void); 17 | void gb_reload_tima(unsigned data); 18 | 19 | //void check4LCDint(void); 20 | 21 | 22 | extern uint32_t gb_clk; 23 | extern uint32_t gb_timerclk; // time before next timer interrupt 24 | extern uint32_t gb_divbase; 25 | extern uint32_t gb_timbase; 26 | extern uint8_t gb_timshift; 27 | 28 | #define BENCHMARK 0 29 | 30 | extern unsigned benchmark_sound, benchmark_gfx; 31 | extern int sound_enabled; -------------------------------------------------------------------------------- /pch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | /* platfrom includes */ 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #ifdef _WIN32 11 | #include 12 | #include 13 | #endif 14 | #define SDL_MAIN_HANDLED 15 | #ifdef _WIN32 16 | #include "SDL.h" 17 | #else 18 | #include 19 | #endif 20 | #ifdef _LINUX 21 | #include 22 | #include 23 | #endif 24 | 25 | #define MAXULONG (uint32_t)(-1) 26 | 27 | /* project includes */ 28 | #include "misc.h" 29 | #include "cart.h" 30 | #include "mem.h" 31 | #include "sm83.h" 32 | #include "ppu.h" 33 | #include "lcd.h" 34 | #include "apu.h" 35 | #include "sound.h" 36 | #include "pad.h" 37 | #include "gb.h" 38 | #include "introm.h" 39 | // perftimer-good timer implementation for win32/x86MMX 40 | #include "perftimer.h" 41 | -------------------------------------------------------------------------------- /apu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define SO_FREQ (1<<20) 4 | 5 | #define RI_NR10 0x10 6 | #define RI_NR11 0x11 7 | #define RI_NR12 0x12 8 | #define RI_NR13 0x13 9 | #define RI_NR14 0x14 10 | #define RI_NR21 0x16 11 | #define RI_NR22 0x17 12 | #define RI_NR23 0x18 13 | #define RI_NR24 0x19 14 | #define RI_NR30 0x1A 15 | #define RI_NR31 0x1B 16 | #define RI_NR32 0x1C 17 | #define RI_NR33 0x1D 18 | #define RI_NR34 0x1E 19 | #define RI_NR41 0x20 20 | #define RI_NR42 0x21 21 | #define RI_NR43 0x22 22 | #define RI_NR44 0x23 23 | #define RI_NR50 0x24 24 | #define RI_NR51 0x25 25 | #define RI_NR52 0x26 26 | 27 | //extern uint32_t apu_clk; - same as gb_clk now 28 | extern uint32_t apu_clk_inner[2]; 29 | extern uint32_t apu_clk_nextchange; 30 | // ALL internal clock variables are exported (to be wrapped in gb.c) 31 | 32 | 33 | uint8_t apu_read(uint8_t); 34 | void apu_write(uint8_t, uint8_t); 35 | 36 | void apu_init(int freq); 37 | void apu_shutdown(); 38 | void apu_mix(); 39 | -------------------------------------------------------------------------------- /perftimer_linux.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | long oldtimer; 4 | long newtimer; 5 | 6 | double microtime() { 7 | 8 | struct timeval time; 9 | gettimeofday(&time, NULL); 10 | long microsec = ((unsigned long long)time.tv_sec * 1000000) + time.tv_usec; 11 | return microsec; 12 | } 13 | 14 | // Prepare data structures for timers, call at least once 15 | void TimerInit(void) { 16 | 17 | } 18 | 19 | // reset Timer 20 | void Timer(void) { 21 | 22 | oldtimer = microtime(); 23 | } 24 | 25 | // Get timer, no reset 26 | uint32_t GetTimer(void) { 27 | 28 | newtimer = microtime(); 29 | return newtimer - oldtimer; 30 | } 31 | 32 | // Get timer and reset 33 | uint32_t GetTimerR(void) { 34 | uint32_t t = GetTimer(); 35 | oldtimer = newtimer; 36 | return t; 37 | } 38 | 39 | void TimerTest() { 40 | Timer(); 41 | uint32_t stamp1 = GetTimer(); 42 | usleep(1000); 43 | uint32_t stamp2 = GetTimer(); 44 | printf("TimerTest, ticks between Sleep(1000), should be around 1'000'000: %d\n", (int)(stamp2 - stamp1)); 45 | } -------------------------------------------------------------------------------- /sm83.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // If the build is for processors like PowerPC (e.g. GameCube), then activate this macro. 4 | //#define BIGENDIAN 5 | 6 | #pragma pack(push, 1) 7 | union Z80reg { 8 | struct { 9 | #ifdef BIGENDIAN 10 | uint8_t h, l; 11 | #else 12 | uint8_t l, h; 13 | #endif 14 | }; 15 | uint16_t hl; 16 | unsigned align; // So 1 Z80reg instance will be always equal to CPU word size 17 | }; 18 | #pragma pack(pop) 19 | 20 | extern union Z80reg r_af, r_bc, r_de, r_hl; 21 | extern union Z80reg r_sp, r_pc; 22 | 23 | extern unsigned HALT, IME; 24 | 25 | #define R_AF r_af.hl 26 | #define R_BC r_bc.hl 27 | #define R_DE r_de.hl 28 | #define R_HL r_hl.hl 29 | #define R_SP r_sp.hl 30 | #define R_PC r_pc.hl 31 | 32 | #define R_A r_af.h 33 | #define R_F r_af.l 34 | #define R_B r_bc.h 35 | #define R_C r_bc.l 36 | #define R_D r_de.h 37 | #define R_E r_de.l 38 | #define R_H r_hl.h 39 | #define R_L r_hl.l 40 | 41 | /* flags */ 42 | 43 | #define ZF_POS 7 44 | #define NF_POS 6 45 | #define HF_POS 5 46 | #define CF_POS 4 47 | 48 | #define ZF ((uint8_t)(1< [!NOTE] 4 | > At the moment we are thinking about how we can refactor all this mess of code from 20 years ago (and whether it is necessary), because the DMG-CPU chip is being analyzed in a neighboring repository. hurray! not even 25 years have passed :) 5 | > I would like to update this emulator to be clock-accurate and playable (with acceptable fps) at the same time. Edits will be made point by point until the source code is "bent" towards what we see inside the chip. 6 | 7 | This is original "black-and-white" GameBoy (DMG-01) emulator. 8 | It supports variety of mappers, boot from internal ROM and has nice-looking LCD effect. 9 | Compatibility is pretty high. 10 | 11 | It is very portable with minimal code modifications and can be used for educational purposes. 12 | 13 | Controls: 14 | - Arrows: Arrows %) 15 | - A: SELECT 16 | - S: START 17 | - Z: B 18 | - X: A 19 | - Enter: press all buttons at once (to do game save or reset etc.) 20 | - F8: Switch frame limiter on/off (Limited to 1000 FPS) 21 | - F9: Switch LCD effect on/off 22 | - F12: Turn sound on/off 23 | 24 | ## Build for Windows 25 | 26 | Use Windows and VS2022. Open dmgemu.sln and click the Build button with your left heel. 27 | 28 | ## Build for Linux 29 | 30 | In general, the build process is typical for Linux. First you get all the sources from Git. Then you call CMake/make 31 | 32 | ``` 33 | # Get source 34 | # Choose a suitable folder to store a clone of the repository, cd there and then 35 | git clone https://github.com/emu-russia/dmgemu.git 36 | cd dmgemu 37 | 38 | # Preliminary squats 39 | mkdir build 40 | cd build 41 | cmake .. 42 | make 43 | 44 | # Find the executable file in the depths of the build folder 45 | ./dmgemu zelda.gb 46 | ``` 47 | 48 | If something doesn't work, you do it. You have red eyes for a reason. :penguin: 49 | 50 | ## Some screenshots 51 | 52 | ![whc4e0b23f6744b0](/imgstore/whc4e0b23f6744b0.png) 53 | ![whc4e0b23fd946e8](/imgstore/whc4e0b23fd946e8.png) 54 | 55 | ![whc4e0b23ac916b8](/imgstore/whc4e0b23ac916b8.png) 56 | ![whc4e0b23a683fc0](/imgstore/whc4e0b23a683fc0.png) 57 | 58 | Enjoy! :smile: 59 | 60 | Thanks to `E}|{`, for invaluable help with LCD and sound! 61 | 62 | Thanks to Gumpei Yokoi for this great handheld system and Nintendo for bunch of really awesome games. 63 | -------------------------------------------------------------------------------- /sound.cpp: -------------------------------------------------------------------------------- 1 | // SO (sound output) terminal emulation (via SDL2) 2 | #include "pch.h" 3 | 4 | #define WAV_CHANNELS 2 5 | #define WAV_SAMPLEBITS 8 6 | #define WAV_BUFFER_SIZE 512 // Length of one chunk for audio playback 7 | #define WAV_BUFFER_CHUNKS 32 // Reserve for circular buffer (total number of chunks) 8 | 9 | SDL_AudioSpec spec; 10 | SDL_AudioSpec spec_obtainted; 11 | SDL_AudioDeviceID dev_id; 12 | 13 | static void SDLCALL Mixer(void* unused, Uint8* stream, int len); 14 | 15 | int8_t* SampleBuf; 16 | int SampleBuf_WrPtr; // in stereo-samples 17 | int SampleBuf_RdPtr; // in stereo-samples 18 | int SampleBuf_Size; // in stereo-samples 19 | 20 | static void SDLCALL Mixer(void* unused, Uint8* stream, int len) 21 | { 22 | int dist = SampleBuf_WrPtr - SampleBuf_RdPtr; 23 | if (dist < 0) dist = -dist; 24 | if (dist * WAV_CHANNELS < len) { 25 | return; 26 | } 27 | 28 | for (int n = 0; n < len / WAV_CHANNELS; n++) { 29 | stream[2 * n] = SampleBuf[WAV_CHANNELS * SampleBuf_RdPtr]; 30 | stream[2 * n + 1] = SampleBuf[WAV_CHANNELS * SampleBuf_RdPtr + 1]; 31 | SampleBuf_RdPtr++; 32 | if (SampleBuf_RdPtr >= SampleBuf_Size) { 33 | SampleBuf_RdPtr = 0; 34 | } 35 | } 36 | } 37 | 38 | int InitSound(int freq) 39 | { 40 | SampleBuf_Size = WAV_BUFFER_SIZE * WAV_BUFFER_CHUNKS; 41 | SampleBuf = new int8_t[SampleBuf_Size * WAV_CHANNELS]; 42 | memset(SampleBuf, 0, SampleBuf_Size * WAV_CHANNELS); 43 | SampleBuf_WrPtr = 0; 44 | SampleBuf_RdPtr = 0; 45 | 46 | if (SDL_InitSubSystem(SDL_INIT_AUDIO) < 0) { 47 | __log ("SDL audio could not initialize! SDL_Error: %s\n", SDL_GetError()); 48 | return 0; 49 | } 50 | 51 | spec.freq = freq; 52 | spec.format = AUDIO_S8; 53 | spec.channels = WAV_CHANNELS; 54 | spec.samples = WAV_BUFFER_SIZE; 55 | spec.callback = Mixer; 56 | spec.userdata = nullptr; 57 | 58 | dev_id = SDL_OpenAudioDevice(NULL, 0, &spec, &spec_obtainted, 0); 59 | SDL_PauseAudioDevice(dev_id, 0); 60 | return 1; 61 | } 62 | 63 | void FreeSound(void) 64 | { 65 | SDL_CloseAudioDevice(dev_id); 66 | SDL_QuitSubSystem(SDL_INIT_AUDIO); 67 | if (SampleBuf != nullptr) { 68 | delete[] SampleBuf; 69 | SampleBuf = nullptr; 70 | } 71 | } 72 | 73 | void pop_sample(int l, int r) 74 | { 75 | if (SampleBuf != nullptr) { 76 | SampleBuf[WAV_CHANNELS * SampleBuf_WrPtr] = l; 77 | SampleBuf[WAV_CHANNELS * SampleBuf_WrPtr + 1] = r; 78 | SampleBuf_WrPtr++; 79 | 80 | if (SampleBuf_WrPtr >= SampleBuf_Size) 81 | { 82 | SampleBuf_WrPtr = 0; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | char *Open_File_Proc() 4 | { 5 | #ifdef _WIN32 6 | char prevc[256]; 7 | OPENFILENAME ofn; 8 | char szFileName[120]; 9 | char szFileTitle[120]; 10 | static char file_to_load[256]; 11 | 12 | _getcwd(prevc, 255); 13 | 14 | memset(&szFileName,0,sizeof(szFileName)); 15 | memset(&szFileTitle, 0, sizeof(szFileTitle)); 16 | 17 | ofn.lStructSize = sizeof(OPENFILENAME); 18 | ofn.hwndOwner = NULL; 19 | ofn.lpstrFilter = "GameBoy ROM\0*.gb\0All Files\0*.*\0"; 20 | ofn.lpstrCustomFilter = NULL; 21 | ofn.nMaxCustFilter = 0; 22 | ofn.nFilterIndex = 1; 23 | ofn.lpstrFile = szFileName; 24 | ofn.nMaxFile = 120; 25 | ofn.lpstrInitialDir = NULL; 26 | ofn.lpstrFileTitle = szFileTitle; 27 | ofn.nMaxFileTitle = 120; 28 | ofn.lpstrTitle = "Open GameBoy ROM\0"; 29 | ofn.lpstrDefExt = "GB"; 30 | ofn.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST | OFN_NOCHANGEDIR | OFN_PATHMUSTEXIST; 31 | 32 | if (GetOpenFileName ((LPOPENFILENAME)&ofn)) 33 | { 34 | strcpy(file_to_load, szFileName); 35 | chdir(prevc); 36 | return file_to_load; 37 | } 38 | else 39 | { 40 | chdir(prevc); 41 | return NULL; 42 | } 43 | #else 44 | return NULL; 45 | #endif 46 | } 47 | 48 | int main(int argc, char **argv) 49 | { 50 | char *name; 51 | 52 | rand_init(); 53 | TimerInit(); 54 | //TimerTest(); 55 | 56 | if(argc <= 1) 57 | { 58 | name = Open_File_Proc(); 59 | if(name) load_game(name); 60 | //else exit(1); 61 | } 62 | else 63 | { 64 | if(argv[1][0]=='"'){ 65 | argv[1]++; 66 | argv[1][strlen(argv[1]) - 1] = 0; 67 | } 68 | load_game(argv[1]); 69 | } 70 | 71 | gb_init(); 72 | start(); 73 | 74 | return 0; 75 | } 76 | 77 | #ifdef _WIN32 78 | 79 | /* platform Entry-point */ 80 | int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) 81 | { 82 | #ifdef _DEBUG 83 | AllocConsole(); 84 | freopen("CONOUT$", "w", stdout); 85 | #endif 86 | 87 | #define MAX_NUM_ARGVS 128 88 | int argc; 89 | char* argv[MAX_NUM_ARGVS]; 90 | 91 | // ParseCommandLine 92 | argc = 1; 93 | argv[0] = "exe"; 94 | 95 | while (*lpCmdLine && (argc < MAX_NUM_ARGVS)) 96 | { 97 | while (*lpCmdLine && ((*lpCmdLine <= 32) || (*lpCmdLine > 126))) 98 | lpCmdLine++; 99 | 100 | if (*lpCmdLine) 101 | { 102 | argv[argc] = lpCmdLine; 103 | argc++; 104 | 105 | while (*lpCmdLine && ((*lpCmdLine > 32) && (*lpCmdLine <= 126))) 106 | lpCmdLine++; 107 | 108 | if (*lpCmdLine) 109 | { 110 | *lpCmdLine = 0; 111 | lpCmdLine++; 112 | } 113 | } 114 | } 115 | 116 | return main (argc, argv); 117 | } 118 | 119 | #endif // _WIN32 120 | -------------------------------------------------------------------------------- /mem.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct SZstruct { 4 | int n; 5 | int sz; 6 | }; 7 | 8 | extern uint8_t vram[0x2000]; 9 | 10 | extern uint8_t hram[0x200]; 11 | 12 | 13 | #define R_PAD hram[0x100 + 0x00] 14 | 15 | #define R_DIV hram[0x100 + 0x04] 16 | #define R_TIMA hram[0x100 + 0x05] 17 | #define R_TMA hram[0x100 + 0x06] 18 | #define R_TAC hram[0x100 + 0x07] 19 | 20 | 21 | #define R_LCDC hram[0x100 + 0x40] 22 | #define R_STAT hram[0x100 + 0x41] 23 | #define R_SCY hram[0x100 + 0x42] 24 | #define R_SCX hram[0x100 + 0x43] 25 | #define R_LY hram[0x100 + 0x44] 26 | #define R_LYC hram[0x100 + 0x45] 27 | #define R_DMA hram[0x100 + 0x46] 28 | #define R_BGP hram[0x100 + 0x47] 29 | #define R_OBP0 hram[0x100 + 0x48] 30 | #define R_OBP1 hram[0x100 + 0x49] 31 | #define R_WY hram[0x100 + 0x4a] 32 | #define R_WX hram[0x100 + 0x4b] 33 | 34 | #define R_IF hram[0x100 + 0x0f] 35 | #define R_IE hram[0x100 + 0xff] 36 | 37 | #define R_NR10 hram[0x100 + 0x10] 38 | #define R_NR11 hram[0x100 + 0x11] 39 | #define R_NR12 hram[0x100 + 0x12] 40 | #define R_NR13 hram[0x100 + 0x13] 41 | #define R_NR14 hram[0x100 + 0x14] 42 | #define R_NR21 hram[0x100 + 0x16] 43 | #define R_NR22 hram[0x100 + 0x17] 44 | #define R_NR23 hram[0x100 + 0x18] 45 | #define R_NR24 hram[0x100 + 0x19] 46 | #define R_NR30 hram[0x100 + 0x1a] 47 | #define R_NR31 hram[0x100 + 0x1b] 48 | #define R_NR32 hram[0x100 + 0x1c] 49 | #define R_NR33 hram[0x100 + 0x1d] 50 | #define R_NR34 hram[0x100 + 0x1e] 51 | #define R_NR41 hram[0x100 + 0x20] 52 | #define R_NR42 hram[0x100 + 0x21] 53 | #define R_NR43 hram[0x100 + 0x22] 54 | #define R_NR44 hram[0x100 + 0x23] 55 | #define R_NR50 hram[0x100 + 0x24] 56 | #define R_NR51 hram[0x100 + 0x25] 57 | #define R_NR52 hram[0x100 + 0x26] 58 | 59 | #define R_BANK hram[0x100 + 0x50] 60 | 61 | #define HRAM(addr) hram[0x100 + (addr & 0xff)] 62 | #define RANGE(x, a, b) ((x >= a) && (x <= b)) 63 | 64 | void mem_init(); 65 | void mem_shutdown(); 66 | 67 | uint16_t mem_read16 (unsigned addr); 68 | void mem_write16 (unsigned addr,unsigned d); 69 | 70 | 71 | typedef uint8_t(mem_Read8)(unsigned); 72 | typedef void (mem_Write8)(unsigned, uint8_t); 73 | typedef mem_Read8 *mem_Read8P; 74 | typedef mem_Write8 *mem_Write8P; 75 | 76 | extern mem_Read8P mem_r8 [256]; 77 | extern mem_Write8P mem_w8 [256]; 78 | 79 | void MemMapR(unsigned from, unsigned to, mem_Read8P p); 80 | void MemMapW(unsigned from, unsigned to, mem_Write8P p); 81 | 82 | 83 | #define MEMMAP_W(a,b,p) MemMapW((a)>>8,(b)>>8,p) 84 | #define MEMMAP_R(a,b,p) MemMapR((a)>>8,(b)>>8,p) 85 | #define MAPROM(x) MEMMAP_R(0x4000,0x8000,x); 86 | #define MAPRAM(r,w) {MEMMAP_R(0xA000,0xC000/*cart.ram_end*/,r);MEMMAP_W(0xA000,0xC000,w);} 87 | 88 | void SETRAM(unsigned n); 89 | void SETROM(unsigned i, unsigned n); 90 | 91 | uint8_t mem_r8_emptyROM(unsigned addr); 92 | uint8_t mem_r8_emptyRAM(unsigned addr); 93 | uint8_t mem_r8_ROMbank1(unsigned addr); 94 | uint8_t mem_r8_RAMbank(unsigned addr); 95 | void mem_w8_RAMbank(unsigned addr, uint8_t n); 96 | void mem_w8_NULL(unsigned addr, uint8_t n); 97 | 98 | // There is no checks for allowed read. 99 | 100 | #define RD(n) mem_r8[(unsigned)(n)>>8]((unsigned)(n)) 101 | #define WR(n, d) mem_w8[(unsigned)(n)>>8]((unsigned)(n),(uint8_t)(d)); 102 | 103 | #define RD16(n) mem_read16(n) 104 | #define WR16(n, d) mem_write16(n,d) -------------------------------------------------------------------------------- /dmgemu.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {cab09b48-1718-4398-9ed8-e2a549e50f5e} 6 | cpp;c;cxx;rc;def;r;odl;idl;hpj;bat 7 | 8 | 9 | {1777c1fe-ea46-4b76-a146-6cf8bfe2f3c1} 10 | h;hpp;hxx;hm;inl 11 | 12 | 13 | 14 | 15 | Source Files 16 | 17 | 18 | Source Files 19 | 20 | 21 | Source Files 22 | 23 | 24 | Source Files 25 | 26 | 27 | Source Files 28 | 29 | 30 | Source Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | Source Files 37 | 38 | 39 | Source Files 40 | 41 | 42 | Source Files 43 | 44 | 45 | Source Files 46 | 47 | 48 | Source Files 49 | 50 | 51 | Source Files 52 | 53 | 54 | Source Files 55 | 56 | 57 | Source Files 58 | 59 | 60 | 61 | 62 | Header Files 63 | 64 | 65 | Header Files 66 | 67 | 68 | Header Files 69 | 70 | 71 | Header Files 72 | 73 | 74 | Header Files 75 | 76 | 77 | Header Files 78 | 79 | 80 | Header Files 81 | 82 | 83 | Header Files 84 | 85 | 86 | Header Files 87 | 88 | 89 | Header Files 90 | 91 | 92 | Header Files 93 | 94 | 95 | Header Files 96 | 97 | 98 | Header Files 99 | 100 | 101 | -------------------------------------------------------------------------------- /lcd.cpp: -------------------------------------------------------------------------------- 1 | // Displaying the picture on the LCD 2 | #include "pch.h" 3 | 4 | int lcd_scale = 4; 5 | int lcd_fpslimit = 1; 6 | int lcd_effect = 1; // possible values: 0,1 7 | int lcd_border = 0; 8 | 9 | int screen_width, screen_height; 10 | static uint32_t* pbuf; 11 | 12 | /* milk to cofee */ 13 | uint32_t dmg_pal[] = { 14 | 0xffe78f, // color #0 (milk) 15 | 0xdfb05f, // color #1 16 | 0x90783f, // color #2 17 | 0x4f381f, // color #3 (cofee) 18 | }; 19 | 20 | SDL_Surface* output_surface = nullptr; 21 | SDL_Window* output_window = nullptr; 22 | 23 | void lcd_refresh(int line) 24 | { 25 | int i; 26 | uint32_t* p = (uint32_t*)pbuf + 160 * line; 27 | if (lcd_effect == 1) 28 | for (i = 0; i < 160; i++) 29 | p[i] = (0x7F7F7F & (p[i] >> 1)) + (0x7F7F7F & (((uint32_t*)dmg_pal)[mainpal[(linebuffer + 8)[i] & 0x3F]] >> 1)); 30 | else 31 | for (i = 0; i < 160; i++) 32 | p[i] = ((uint32_t*)dmg_pal)[mainpal[(linebuffer + 8)[i] & 0x3F]]; 33 | } 34 | 35 | void sdl_win_init(int width, int height) 36 | { 37 | screen_width = width; 38 | screen_height = height; 39 | 40 | if (SDL_InitSubSystem(SDL_INIT_VIDEO) < 0) { 41 | __log ("SDL video could not initialize! SDL_Error: %s\n", SDL_GetError()); 42 | return; 43 | } 44 | 45 | char title[128]; 46 | sprintf(title, "GameBoy - %s", romhdr->title); 47 | 48 | SDL_Window* window = SDL_CreateWindow( 49 | title, 50 | SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 51 | screen_width * lcd_scale + 2 * lcd_border * lcd_scale, screen_height * lcd_scale + 2 * lcd_border * lcd_scale, 52 | 0); 53 | 54 | if (window == NULL) { 55 | __log ("SDL_CreateWindow failed: %s\n", SDL_GetError()); 56 | return; 57 | } 58 | 59 | SDL_Surface* surface = SDL_GetWindowSurface(window); 60 | 61 | if (surface == NULL) { 62 | __log ("SDL_GetWindowSurface failed: %s\n", SDL_GetError()); 63 | return; 64 | } 65 | 66 | // Initialize window to all black 67 | //SDL_FillSurfaceRect(surface, NULL, SDL_MapRGB(surface->format, 0, 0, 0)); 68 | SDL_UpdateWindowSurface(window); 69 | 70 | output_window = window; 71 | output_surface = surface; 72 | 73 | pbuf = new uint32_t[screen_width * screen_height]; 74 | memset(pbuf, 0, screen_width * screen_height * sizeof(uint32_t)); 75 | } 76 | 77 | void sdl_win_shutdown() 78 | { 79 | SDL_DestroyWindow(output_window); 80 | SDL_QuitSubSystem(SDL_INIT_VIDEO); 81 | delete[] pbuf; 82 | } 83 | 84 | void sdl_win_update() 85 | { 86 | SDL_Event event; 87 | while (SDL_PollEvent(&event)) { 88 | 89 | switch (event.type) { 90 | case SDL_QUIT: 91 | gb_shutdown(); 92 | exit(0); 93 | break; 94 | 95 | case SDL_KEYDOWN: 96 | case SDL_KEYUP: 97 | bool pressed = event.type == SDL_KEYDOWN; 98 | switch (event.key.keysym.scancode) { 99 | case SDL_SCANCODE_F12: 100 | if (!pressed) { 101 | (sound_enabled) ? apu_shutdown() : apu_init(44100); 102 | sound_enabled ^= 1; 103 | } 104 | break; 105 | case SDL_SCANCODE_F8: 106 | if (!pressed) { 107 | lcd_fpslimit ^= 1; 108 | } 109 | break; 110 | case SDL_SCANCODE_F9: 111 | if (!pressed) { 112 | lcd_effect ^= 1; 113 | } 114 | break; 115 | 116 | default: 117 | pad_sdl_process(event.key.keysym.scancode, pressed); 118 | break; 119 | } 120 | break; 121 | } 122 | } 123 | } 124 | 125 | void sdl_win_blit() 126 | { 127 | int w = screen_width; 128 | int h = screen_height; 129 | int ScaleFactor = lcd_scale; 130 | 131 | Uint32* const pixels = (Uint32*)output_surface->pixels; 132 | 133 | if (lcd_border != 0) { 134 | for (int n = 0; n < output_surface->w * output_surface->h; n++) { 135 | pixels[n] = dmg_pal[0]; 136 | } 137 | } 138 | 139 | for (int y = 0; y < h; y++) 140 | { 141 | for (int x = 0; x < w; x++) 142 | { 143 | uint32_t color = pbuf[w * y + x]; 144 | 145 | for (int s = 0; s < ScaleFactor; s++) { 146 | for (int t = 0; t < ScaleFactor; t++) { 147 | pixels[ScaleFactor * (x + lcd_border) + s + ((ScaleFactor * (y + lcd_border) + t) * output_surface->w)] = color; 148 | } 149 | } 150 | } 151 | } 152 | 153 | SDL_UpdateWindowSurface(output_window); 154 | } 155 | 156 | void sdl_win_update_title(char* title) 157 | { 158 | if (!output_window) 159 | return; 160 | SDL_SetWindowTitle(output_window, title); 161 | } 162 | -------------------------------------------------------------------------------- /gb.cpp: -------------------------------------------------------------------------------- 1 | /* GameBoy emu control */ 2 | #include "pch.h" 3 | 4 | int skip_introm = 0; 5 | unsigned benchmark_sound, benchmark_gfx; 6 | int sound_enabled = 1; 7 | 8 | /* run on emu start */ 9 | void gb_init() 10 | { 11 | // log_init("gbemu.log"); 12 | mem_init(); 13 | sm83_init(); 14 | pad_init(); 15 | ppu_init(); 16 | apu_init(44100); 17 | if (skip_introm) { 18 | R_BANK = 1; 19 | R_PC = 0x100; 20 | } 21 | __log("init OK."); 22 | } 23 | 24 | /* run on emu shutdown */ 25 | void gb_shutdown() 26 | { 27 | mem_shutdown(); 28 | pad_shutdown(); 29 | apu_shutdown(); 30 | ppu_shutdown(); 31 | log_shutdown(); 32 | } 33 | 34 | // ********************************************************************** 35 | 36 | uint32_t gb_clk; 37 | uint32_t gb_eventclk; // timer before next possible interrupt/LCD mode change 38 | uint32_t gb_timerclk; // time before next timer interrupt 39 | 40 | unsigned lcd_int_on; 41 | 42 | const char stat2LCDflg[8]={0x08,0x10,0x20,0x00, 0x48,0x50,0x60,0x40}; 43 | 44 | 45 | void check4LCDint(unsigned mode) { // Also called from mem.c!! 46 | //unsigned lcd_int_on_new=; // LYC is not processed here 47 | if(R_STAT&stat2LCDflg[mode]/* !lcd_int_on && */) {// check if interrupt is requested already 48 | R_IF|=INT_LCDSTAT; 49 | sm83_check4int(); // do int if possible 50 | } 51 | //lcd_int_on = lcd_int_on_new; 52 | } 53 | void check4LYC(void) { // Also called from mem.c!! 54 | register unsigned stnew = R_STAT&~4; 55 | if(R_LYC == R_LY) { 56 | stnew|=4; 57 | if(R_STAT < stnew) 58 | if(stnew&0x40) {// check if interrupt allowed 59 | R_IF|=INT_LCDSTAT; 60 | sm83_check4int(); // do int if possible 61 | } 62 | } 63 | R_STAT=stnew; 64 | } 65 | 66 | #define STAT_MODE(n) \ 67 | R_STAT = (R_STAT&0xFC)|n; \ 68 | check4LCDint(n); 69 | 70 | // ********************************************************************** 71 | uint32_t gb_divbase; 72 | uint32_t gb_timbase; 73 | /* these variables are added to current gb clock to obtain 74 | timer counter values in lower byte of result */ 75 | uint8_t gb_timshift; // input clock shift 1048576/(4,16,64,256) 76 | 77 | void gb_reload_tima(unsigned data) { // will only contain byte value 78 | gb_timbase = data-(int32_t)(gb_clk >> gb_timshift)-256; 79 | gb_timerclk = ((gb_clk>>gb_timshift)-gb_timbase)<gb_timerclk) { 85 | sm83_execute_until(gb_timerclk); 86 | gb_reload_tima(R_TMA); 87 | R_IF|=INT_TIMER; // request timer interrupt 88 | sm83_check4int(); 89 | }*/ 90 | sm83_execute_until(gb_eventclk); 91 | } 92 | 93 | // ********************************************************************** 94 | 95 | /* begin emulation */ 96 | void start() 97 | { 98 | 99 | unsigned i,gb_old; 100 | //uint8_t lcd_status_prev,lcd_status: 101 | //lcd_status_prev=lcd_status=0; 102 | lcd_int_on=0; 103 | gb_eventclk = gb_clk = gb_divbase = 0; 104 | gb_timerclk = 0x7FFFFFFF; 105 | 106 | while(1) { 107 | gb_old = gb_clk; 108 | R_LY=0; 109 | for(i=144;i!=0;i--) { 110 | check4LYC(); 111 | /* LCD during OAM-search (scan sprites) */ 112 | 113 | /* LCD during H-Blank */ 114 | //STAT_MODE(0); 115 | //gb_eventclk+=204; 116 | //sm83_execute_until(gb_eventclk); 117 | 118 | STAT_MODE(2); 119 | ppu_enumsprites(); 120 | execute(20); 121 | /* LCD during data transfer (draw line) */ 122 | STAT_MODE(3); /* no interrupt here !! */ 123 | ppu_refreshline(); 124 | execute(43); 125 | 126 | /* LCD during H-Blank */ 127 | STAT_MODE(0); 128 | execute(51); 129 | 130 | R_LY++; 131 | } 132 | //R_LY = 143; 133 | /* LCD during V-Blank (10 "empty" lines) */ 134 | //if(R_STAT & 0x10) // questionable 135 | R_IF|=INT_VBLANK; // Queue V-blank int 136 | sm83_check4int(); 137 | STAT_MODE(1); 138 | ppu_vsync(); 139 | for(i=10;i!=0;i--) { 140 | check4LYC(); 141 | execute(114); 142 | R_LY++; 143 | } 144 | // smallest of all- eventclk 145 | if(gb_eventclk > (4<<28)) { // wrap _ALL_ counters 146 | gb_clk -=(3<<28); 147 | gb_eventclk -=(3<<28); 148 | apu_clk_inner[1] -=(3<<28); 149 | apu_clk_nextchange-=(3<<28); 150 | if(gb_timerclktitle); 107 | f = fopen(name, "rb"); 108 | if(!f) 109 | { 110 | /* no support for BATTERY, yet ;) */ 111 | for(i=0; ititle); 130 | f = fopen(name, "wb"); 131 | if(!f) return; 132 | fwrite(ram_ptr, 1, size, f); 133 | fclose(f); 134 | } 135 | 136 | /* 137 | ************************************************************************* 138 | GB process logging 139 | ************************************************************************* 140 | */ 141 | 142 | static FILE *__log__ = NULL; 143 | 144 | void log_init(char *file) 145 | { 146 | __log__ = fopen(file, "w"); 147 | if(!__log__) return; 148 | } 149 | 150 | void log_shutdown() 151 | { 152 | if (__log__) { 153 | fclose(__log__); 154 | __log__ = nullptr; 155 | } 156 | } 157 | 158 | void __log(const char *fmt, ...) 159 | { 160 | va_list arg; 161 | char buf[0x1000]{}; 162 | 163 | if(__log__) 164 | { 165 | va_start(arg, fmt); 166 | vsprintf(buf, fmt, arg); 167 | va_end(arg); 168 | 169 | fprintf(__log__, "%s\n", buf); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /cart.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | Cartridge cart; 4 | 5 | #define Kbit *128 6 | #define Mbit *(128*1024) 7 | 8 | static SZstruct ROM_SIZES[] = { 9 | {0, 256 Kbit}, 10 | {1, 512 Kbit}, 11 | {2, 1 Mbit}, 12 | {3, 2 Mbit}, 13 | {4, 4 Mbit}, 14 | {5, 8 Mbit}, 15 | {6, 16 Mbit}, 16 | {7, 32 Mbit}, 17 | {8, 64 Mbit}, 18 | {52, 9 Mbit}, 19 | {53, 10 Mbit}, 20 | {54, 12 Mbit}, 21 | {64, 20 Mbit}, 22 | {65, 24 Mbit}, 23 | {74, 36 Mbit}, 24 | {75, 40 Mbit}, 25 | {76, 48 Mbit}, 26 | {-1,0} }; 27 | 28 | static SZstruct RAM_SIZES[] = { 29 | {0,0 Kbit}, 30 | {1,16 Kbit}, 31 | {2,64 Kbit}, 32 | {3,256 Kbit}, 33 | {4,1 Mbit}, 34 | {-1,0} }; 35 | 36 | rom_header_t* romhdr; 37 | 38 | static void do_header_chk(int fire) 39 | { 40 | uint8_t* buf = (uint8_t*)romhdr->title; 41 | int chk = 0; 42 | int i; 43 | 44 | for (i = 0; i < 25; i++) 45 | chk += buf[i]; 46 | 47 | chk = 0x100 - (chk + 25); 48 | 49 | if (fire && romhdr->header_chk != (uint8_t)chk) 50 | sys_error("header checksum failed!"); 51 | } 52 | 53 | /*static void do_global_chk(int fire) 54 | { 55 | long i; 56 | register u_long chk = 0; 57 | 58 | for(i=0; i> 8); 66 | 67 | if(fire && romhdr->global_chk != (uint16_t)chk) 68 | sys_error("global checksum failed!"); 69 | }*/ 70 | 71 | 72 | static int findsz(unsigned* sz, SZstruct* from, int what) { 73 | while (1) { 74 | if (from->n == -1) return 0; 75 | if (from->n == what) { 76 | *sz = from->sz; 77 | return -1; 78 | } 79 | from++; 80 | }; 81 | } 82 | 83 | void check_ROM_header() 84 | { 85 | if (cart.data == NULL) 86 | { 87 | static rom_header_t empty; 88 | romhdr = ∅ 89 | empty.type = 0; 90 | strcpy(cart.title, "NO CARD"); 91 | return; 92 | } 93 | romhdr = (rom_header_t*)(cart.data + 0x100); 94 | printf("MBC type: %d, romsize: %d, ramsize: %d\n", romhdr->type, romhdr->romsize, romhdr->ramsize); 95 | if (!findsz(&(cart.rom_size), ROM_SIZES, romhdr->romsize)) 96 | sys_error("Unknown ROM size"); 97 | if (!findsz(&(cart.ram_size), RAM_SIZES, romhdr->ramsize)) 98 | sys_error("Unknown RAM size"); 99 | do_header_chk(0); 100 | memcpy(cart.title, romhdr->title, 15); 101 | // do_global_chk(1); 102 | } 103 | 104 | /*********************************************************************** 105 | MBC1 support 106 | ***********************************************************************/ 107 | static struct MBC_ { 108 | int bank1; // MBC1 109 | int bank2; // MBC1 110 | int mode; // MBC1 111 | 112 | unsigned ramenabled; // All MBCs 113 | unsigned clock_present; // MBC3 114 | unsigned clock_latched; // MBC3 115 | } MBC; 116 | 117 | void mem_w8_MBC1_RAMcontrol(unsigned addr, uint8_t n) { 118 | if ((n & 0xF) == 0xA) { 119 | if (!MBC.ramenabled) MAPRAM(mem_r8_RAMbank, mem_w8_RAMbank); 120 | MBC.ramenabled = 1; 121 | } 122 | else { 123 | if (MBC.ramenabled) MAPRAM(mem_r8_emptyRAM, mem_w8_NULL); 124 | MBC.ramenabled = 0; 125 | } 126 | } 127 | void mbc1_update_mapping() 128 | { 129 | SETROM(0, MBC.mode ? (MBC.bank2 << 5) : 0); 130 | SETROM(1, (MBC.bank2 << 5) | MBC.bank1); 131 | SETRAM(MBC.mode ? MBC.bank2 : 0); 132 | } 133 | void mem_w8_MBC1_setBANK1(unsigned addr, uint8_t n) { 134 | MBC.bank1 = n & 0x1F; // 5 bits 135 | if (MBC.bank1 == 0) MBC.bank1 = 1; 136 | mbc1_update_mapping(); 137 | } 138 | void mem_w8_MBC1_setBANK2(unsigned addr, uint8_t n) { 139 | MBC.bank2 = (unsigned)n & 3; // 2 bits 140 | mbc1_update_mapping(); 141 | } 142 | void mem_w8_MBC1_mode(unsigned addr, uint8_t n) { 143 | MBC.mode = n & (unsigned)1; 144 | mbc1_update_mapping(); 145 | } 146 | 147 | void InitMBC1_ROM(void) { 148 | MEMMAP_W(0x2000, 0x4000, mem_w8_MBC1_setBANK1); 149 | MEMMAP_W(0x4000, 0x6000, mem_w8_MBC1_setBANK2); 150 | MEMMAP_W(0x6000, 0x8000, mem_w8_MBC1_mode); 151 | MAPROM(mem_r8_ROMbank1); 152 | 153 | MBC.ramenabled = 0; 154 | MBC.bank1 = 1; 155 | MBC.bank2 = 0; 156 | MBC.mode = 0; 157 | mbc1_update_mapping(); 158 | } 159 | 160 | void InitMBC1_RAM(void) { 161 | MEMMAP_W(0x0000, 0x2000, mem_w8_MBC1_RAMcontrol); 162 | } 163 | 164 | /*********************************************************************** 165 | MBC2 support 166 | ***********************************************************************/ 167 | 168 | 169 | // TODO: I don't know exactly how to map those 512x4 bits properly. 170 | void mem_w8_MBC2_set(unsigned addr, uint8_t n) { 171 | unsigned nn = n & 0xF; 172 | if (addr & 0x2100) { // ROM control 173 | if (!nn) nn = 1; 174 | SETROM(1, nn); 175 | } 176 | else { // RAM control 177 | if (nn == 0xA) { 178 | if (!MBC.ramenabled) MAPRAM(mem_r8_RAMbank, mem_w8_RAMbank); 179 | MBC.ramenabled = 1; 180 | } 181 | else { 182 | if (MBC.ramenabled) MAPRAM(mem_r8_emptyROM, mem_w8_NULL); 183 | MBC.ramenabled = 0; 184 | } 185 | } 186 | } 187 | 188 | void InitMBC2(void) { 189 | MEMMAP_W(0x0000, 0x4000, mem_w8_MBC2_set); 190 | MAPROM(mem_r8_ROMbank1); 191 | MAPRAM(mem_r8_emptyROM, mem_w8_NULL); 192 | SETRAM(0); 193 | MBC.ramenabled = 0; 194 | } 195 | 196 | /*********************************************************************** 197 | MBC3 support 198 | ***********************************************************************/ 199 | // TODO: no realtime clock support 200 | 201 | void mem_w8_MBC3_setROM(unsigned addr, uint8_t n) { 202 | //register unsigned nn=n&0x7F; // 7 bits 203 | //if(!MBC.mode2) nn|=cart.rom[1].bank&~0x1F; // 204 | if (!n) n = 1; 205 | SETROM(1, n); 206 | } 207 | void mem_w8_MBC3_setRAM_or_clock(unsigned addr, uint8_t n) { 208 | if (!(n & ~3)) { // set RAM bank 209 | if (cart.ram_nbanks) SETRAM(n); 210 | } 211 | else { // set RT clock register 212 | 213 | } 214 | } 215 | void mem_w8_MBC3_clocklatch(unsigned addr, uint8_t n) { 216 | 217 | } 218 | 219 | void InitMBC3_ROM(void) { 220 | MEMMAP_W(0x2000, 0x4000, mem_w8_MBC3_setROM); 221 | MEMMAP_W(0x4000, 0x6000, mem_w8_MBC3_setRAM_or_clock); 222 | MEMMAP_W(0x6000, 0x8000, mem_w8_MBC3_clocklatch); 223 | MAPROM(mem_r8_ROMbank1); 224 | MBC.ramenabled = 0; 225 | } 226 | 227 | void InitMBC3_RAM(void) { 228 | InitMBC1_RAM(); 229 | } 230 | 231 | /*********************************************************************** 232 | MBC5 support 233 | ***********************************************************************/ 234 | 235 | void mem_w8_MBC5_setROM0(unsigned addr, uint8_t n) { 236 | unsigned nn = (unsigned)n | (cart.rom[1].bank & 0x100); 237 | SETROM(1, nn); 238 | } 239 | void mem_w8_MBC5_setROM1(unsigned addr, uint8_t n) { 240 | unsigned nn = ((unsigned)n << 8) | (cart.rom[1].bank & 0xFF); 241 | SETROM(1, nn); 242 | } 243 | void mem_w8_MBC5_setRAM(unsigned addr, uint8_t n) { 244 | SETRAM(n); 245 | } 246 | 247 | void InitMBC5_ROM(void) { 248 | MEMMAP_W(0x2000, 0x3000, mem_w8_MBC5_setROM0); 249 | MEMMAP_W(0x3000, 0x4000, mem_w8_MBC5_setROM1); 250 | MEMMAP_W(0x4000, 0x6000, mem_w8_MBC5_setRAM); 251 | //MEMMAP_W(0x6000,0x8000,mem_w8_MBC3_clocklatch); 252 | MAPROM(mem_r8_ROMbank1); 253 | MBC.ramenabled = 0; 254 | } 255 | 256 | void InitMBC5_RAM(void) { 257 | InitMBC1_RAM(); 258 | } -------------------------------------------------------------------------------- /ppu.cpp: -------------------------------------------------------------------------------- 1 | // GameBoy PPU emulation 2 | #include "pch.h" 3 | 4 | unsigned lcd_WYline; 5 | uint8_t linebuffer[192]; // showed from 8-th byte 6 | unsigned mainpal[64]; // 0-3 BG palette 4-7,8-11 - sprite palettes 7 | 8 | void tilecache_init(void); 9 | 10 | void ppu_init() 11 | { 12 | tilecache_init(); 13 | sdl_win_init(160, 144); 14 | lcd_WYline=-1; 15 | } 16 | 17 | void ppu_shutdown() 18 | { 19 | sdl_win_shutdown(); 20 | } 21 | 22 | // ********************************************************************** 23 | 24 | typedef struct { 25 | uint8_t y, x, n, a; 26 | } SPR; 27 | 28 | //static SPR *spr = (SPR *)hram; 29 | #define spr ((SPR *)hram) 30 | // one pointer less :) 31 | 32 | #define SPR_PRI 0x80 33 | #define SPR_FLIPY 0x40 34 | #define SPR_FLIPX 0x20 35 | #define SPR_PAL 0x10 36 | 37 | SPR used_spr[40]; 38 | /* We must be able to gather 40 sprites first, then sort,then leave 10 39 | since sprites are as big as "int", there is no point in storing indexes 40 | */ 41 | 42 | unsigned num_sprites = 0; 43 | 44 | 45 | //tilecache is 256-color based 46 | // dir can take values: 1-left 2-right 47 | #define DIR_NORMAL 0x20 48 | #define DIR_XMIRROR 0x40 49 | 50 | 51 | uint8_t tilecachedata[0x2000*4*2]; 52 | uint8_t tilecache[512]; 53 | uint32_t bitxlat_t[16],bitxlat2_t[16],bitxlatM_t[16],bitxlat2M_t[16]; 54 | 55 | void tilecache_init(void) { 56 | unsigned i,tmp; 57 | memset(tilecache,0,sizeof(tilecache)); 58 | memset(tilecachedata,0,sizeof(tilecachedata)); 59 | for(i=0;i<16;i++) { 60 | bitxlat_t[i] = tmp = ((i<<24)+(i<<15)+(i<<6)+(i>>3))&0x01010101; // Higher bits first 61 | bitxlat2_t[i] = tmp << 1; 62 | bitxlatM_t[i] = tmp = (i+(i<<7)+(i<<14)+(i<<21))&0x01010101; // lower bits first 63 | bitxlat2M_t[i] = tmp << 1; 64 | } 65 | } 66 | 67 | static uint8_t* getcell(unsigned celln,unsigned dir) { 68 | uint8_t*dest=tilecachedata+((dir&0x40)<<9)+(celln<<6),*rp; 69 | uint32_t*wp; 70 | register unsigned b0,b1; 71 | unsigned i; 72 | if(!(tilecache[celln]&dir)) { 73 | tilecache[celln]|=dir; 74 | wp = (uint32_t*)dest; 75 | rp = vram+(celln<<4); 76 | 77 | if(dir&DIR_XMIRROR) 78 | for(i=0;i<8;i++) { 79 | wp[0]=bitxlatM_t[(b0=rp[0])&0xF]|bitxlat2M_t[(b1=rp[1])&0xF]; 80 | wp[1]=bitxlatM_t[b0>>4]|bitxlat2M_t[b1>>4]; 81 | wp+=2; 82 | rp+=2; 83 | } 84 | else 85 | for(i=0;i<8;i++) { 86 | wp[0]=bitxlat_t[(b0=rp[0])>>4]|bitxlat2_t[(b1=rp[1])>>4]; 87 | wp[1]=bitxlat_t[b0&0xF]|bitxlat2_t[b1&0xF]; 88 | wp+=2; 89 | rp+=2; 90 | } 91 | } 92 | return dest; 93 | } 94 | 95 | 96 | void ppu_enumsprites() 97 | { 98 | unsigned h = ((R_LCDC & 4)<<1)+8;// ? (16) : (8); sprite height 99 | unsigned line = (unsigned)(R_LY)+16; 100 | unsigned i,j,ntosort; 101 | num_sprites = 0; 102 | 103 | 104 | if(R_LCDC & 2) 105 | { 106 | 107 | /* link sprite list */ 108 | for(i=0; i<40; i++) 109 | if((line-(unsigned)spr[i].y)10) ntosort = 10; 115 | for(i=0;iused_spr[j].x) { 118 | SPR tmp = used_spr[i]; 119 | used_spr[i] = used_spr[j]; 120 | used_spr[j] = tmp; 121 | } 122 | } 123 | if(num_sprites>10) num_sprites = 10; 124 | } 125 | } 126 | 127 | // ********************************************************************** 128 | 129 | 130 | void ppu_refreshline(void) { 131 | 132 | signed char *tilemapptr; 133 | uint8_t*writeptr,*tmpptr; 134 | unsigned tileofs,tilepage,tilemapx; 135 | unsigned X,Y,LY,WX,WY,i,j; 136 | //unsigned tmp0,tmp1; 137 | unsigned spriteh = ((R_LCDC & 4)<<1)+7;// sprite height 138 | unsigned sprtilemask=~((R_LCDC & 4)>>2); 139 | uint8_t spr_pal,tmp; 140 | 141 | //memset(tilecache,0,sizeof(tilecache));// TODO: SLOW!!! for debug only!! 142 | 143 | benchmark_gfx-=GetTimer(); 144 | 145 | LY = R_LY; 146 | WX=167; 147 | 148 | /*Check for window*/ 149 | if((R_LCDC & 0x20) /*WIN enable*/ 150 | && ((WY=(unsigned)R_WY)<=LY)){ 151 | lcd_WYline++; // TODO: maybe incremented only if R_WX<167? 152 | if(R_WX<167 /*allowed position*/ ) 153 | WX = R_WX; 154 | } 155 | 156 | tilepage = ~(((unsigned)R_LCDC&0x10)<<4); 157 | /*Show background*/ 158 | if((R_LCDC & 1)/*BG enable*/ && (WX>7)/*not covered by WIN*/) { 159 | X = R_SCX; 160 | Y = LY + R_SCY; 161 | writeptr = linebuffer+8-(X&7); 162 | tilemapptr = (signed char *)(vram+0x1800+(((unsigned)R_LCDC & 8)<<7) // pointer to tile line 163 | +((Y&(31*8))<<2)); 164 | tilemapx = (X>>3)&31; 165 | tileofs = (Y&7)<<3; 166 | for(i=((X+WX)>>3)-(X>>3);i>0;i--) { 167 | tmpptr = getcell( 168 | (((signed int)tilemapptr[tilemapx] +256)&tilepage) 169 | ,DIR_NORMAL)+tileofs; 170 | ((unsigned *)writeptr)[0] = ((unsigned *)tmpptr)[0]; 171 | ((unsigned *)writeptr)[1] = ((unsigned *)tmpptr)[1]; // instead of memcpy 172 | tilemapx=(tilemapx+1)&31; 173 | writeptr+=8; 174 | } 175 | } 176 | 177 | //w_priority=(LCDC&2) 178 | /*Show window*/ 179 | if(WX<167) { 180 | //X = 0; 181 | Y = lcd_WYline; // internal WIN counter used instead of LY-WY; 182 | writeptr = linebuffer+1+WX; 183 | tilemapptr = (signed char *)(vram+0x1800+(((unsigned)R_LCDC & 0x40)<<4) // pointer to tile line 184 | +((Y&(31*8))<<2)); 185 | tileofs = (Y&7)<<3; 186 | 187 | for(i=((166+8)-WX)>>3;i>0;i--) { 188 | tmpptr = getcell( 189 | (((signed int)*(tilemapptr++) +256)&tilepage) 190 | ,DIR_NORMAL)+tileofs; 191 | ((unsigned *)writeptr)[0] = ((unsigned *)tmpptr)[0]; 192 | ((unsigned *)writeptr)[1] = ((unsigned *)tmpptr)[1]; // instead of memcpy 193 | writeptr+=8; 194 | } 195 | 196 | } 197 | 198 | //num_sprites=0; 199 | for(i=0;i>3), 205 | (used_spr[i].a&SPR_FLIPX)+DIR_NORMAL)+((Y&7)<<3); 206 | spr_pal=((used_spr[i].a&0x10)>>2)+4; 207 | if((used_spr[i].a & SPR_PRI)) { // below everything 208 | for(j=0;j<8;j++) if(tmpptr[j]) { 209 | tmp=writeptr[j]; 210 | if(!tmp) tmp = tmpptr[j]+spr_pal; 211 | writeptr[j]=tmp|0x80; // update pixel priority 212 | } 213 | } else {// above background, below previous sprites 214 | spr_pal|=0x80; 215 | for(j=0;j<8;j++) 216 | if(tmpptr[j] && ((signed char)writeptr[j]>=0)) 217 | writeptr[j]=tmpptr[j]+spr_pal; 218 | } 219 | } 220 | lcd_refresh(LY); 221 | benchmark_gfx+=GetTimer(); 222 | } 223 | 224 | 225 | void ppu_vsync() 226 | { 227 | static int first = 1; 228 | static int frame = 1; 229 | char title[64]; 230 | static unsigned oldtime[8][4],timepos=0; 231 | unsigned time,i,bm_g,bm_o,bm_s; 232 | lcd_WYline = -1; // reset internal WY line counter 233 | if(first) 234 | { 235 | TimerInit(); 236 | Timer(); 237 | first = 0; 238 | } 239 | 240 | time=GetTimer(); 241 | if(lcd_fpslimit) while((time=GetTimer()) < 1000000/60) ; 242 | else while ((time = GetTimer()) < 1000000/1000); // Limited to 1000 FPS 243 | Timer(); 244 | oldtime[timepos][0]=time; 245 | oldtime[timepos][1]=benchmark_gfx; 246 | oldtime[timepos][2]=benchmark_sound; 247 | benchmark_gfx = benchmark_sound = 0; 248 | timepos=(timepos+1)&7; 249 | 250 | sdl_win_blit(); 251 | sdl_win_update(); 252 | 253 | /* Frames Per Second */ 254 | frame++; 255 | 256 | if(!timepos) 257 | { 258 | time=bm_o=bm_s=bm_g=0; 259 | for(i=0;i<8;i++) { 260 | time+=oldtime[i][0]; 261 | bm_g+=oldtime[i][1]; 262 | bm_s+=oldtime[i][2]; 263 | } 264 | time/=8; 265 | bm_g/=8; 266 | bm_s/=8; 267 | bm_o=time-bm_s-bm_g; 268 | #if BENCHMARK 269 | if(time) 270 | sprintf(title, "GameBoy - %15s [%u fps]O:%02dG:%02dS:%02d", cart.title, 271 | 1000000/time,(bm_o*100)/time,(bm_g*100)/time,(bm_s*100)/time); 272 | else 273 | #endif 274 | sprintf(title, "GameBoy - %s [%u fps]", cart.title, 1000000/time); 275 | sdl_win_update_title(title); 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /mem.cpp: -------------------------------------------------------------------------------- 1 | /* GameBoy memory manager */ 2 | #include "pch.h" 3 | 4 | mem_Read8P mem_r8 [256]; 5 | mem_Write8P mem_w8 [256]; 6 | 7 | /* VRAM */ 8 | uint8_t vram[0x2000]; 9 | 10 | /* internal RAM ($C000-$DFFF) */ 11 | uint8_t ram[0x2000]; 12 | 13 | /* high memory, OAM and hardware registers ($FE00-$FFFF) */ 14 | uint8_t hram[0x200]; 15 | 16 | /*********************************************************************** 17 | memory handlers control 18 | ***********************************************************************/ 19 | 20 | 21 | // Map read memory range 22 | void MemMapR(unsigned from,unsigned to,mem_Read8P p) { 23 | unsigned i; 24 | for(i=from;i>4] = 0; 73 | } 74 | void mem_w8_RAM(unsigned addr, uint8_t n) {ram[addr&0x1FFF]=n;} 75 | void mem_w8_RAMbank(unsigned addr, uint8_t n) { 76 | if (cart.ram.ptr) { 77 | cart.ram.ptr[addr & cart.ram_amask] = n; 78 | } 79 | } 80 | 81 | void SETRAM(unsigned n) { 82 | n&=cart.ram_nmask; 83 | //if(n>=cart.ram_nbanks) 84 | //sys_error("RAM bank not present: [%X]",n); 85 | cart.ram.bank = n; 86 | cart.ram.ptr = cart.ramdata+n*0x2000; 87 | } 88 | void SETROM(unsigned i, unsigned n) { 89 | n&=cart.rom_nmask; 90 | //if(n>=cart.rom_nbanks) 91 | // sys_error("ROM bank not present: [%X]",(n)); 92 | cart.rom[i].bank = (n); 93 | cart.rom[i].ptr = cart.romdata+n*0x4000; 94 | } 95 | // Check for 0 is performed for lower 5 bits, not for whole address 96 | 97 | 98 | /*********************************************************************** 99 | null MBC :) support 100 | ***********************************************************************/ 101 | 102 | static void InitGenericRAM(void) { 103 | MEMMAP_W(0x0000,0x2000,mem_w8_MBC1_RAMcontrol); 104 | } 105 | 106 | /********************************************************************** 107 | I/O implementation 108 | ***********************************************************************/ 109 | 110 | uint8_t mem_r8_IO(unsigned addr) { 111 | __log("HRD %.4X [PC=%.4X]", addr, R_PC); 112 | if(addr >= 0xff00) { 113 | if(RANGE(addr, 0xff10, 0xff3f)) 114 | return apu_read((uint8_t)(addr & 0xff)); 115 | switch(addr & 0xff) { 116 | case 0x00 : 117 | { 118 | uint8_t pad = 0; 119 | if(R_PAD & 0x20) pad = pad_lo(); 120 | if(R_PAD & 0x10) pad = pad_hi(); 121 | return ~pad & 0xf; 122 | } 123 | case 0x4: // R_DIV - divider counter read 124 | return (uint8_t)((gb_clk>>6)+gb_divbase); 125 | case 0x5: // R_TIMA - timer accumulator read 126 | if(R_TAC&4) 127 | return (uint8_t)((gb_clk>>gb_timshift)+gb_timbase); // current value 128 | // otherwise old(frozen) value will be returned 129 | break; 130 | case 0x50: 131 | return R_BANK & 1; 132 | } 133 | } 134 | return hram[addr & 0x1ff]; 135 | } 136 | 137 | 138 | static const uint8_t timshift[4]={8,2,4,6}; 139 | 140 | void mem_w8_IO(unsigned addr, uint8_t data) { 141 | __log("HWR %.4X = %.2X [PC=%.4X]", addr, data, R_PC); 142 | 143 | if(addr>=0xFF00) { // OAM or hram? 144 | if(RANGE(addr, 0xff10, 0xff3f)) { 145 | apu_write((uint8_t)(addr & 0xff), data); 146 | return; 147 | } 148 | switch(addr & 0xff) { 149 | /* 150 | case 0x40: // LCDC 151 | // if((R_STAT & 3) != 1) return; 152 | break; 153 | */ 154 | case 0x04: // R_DIV - divider counter, reset to 0 when written 155 | gb_divbase = -(int32_t)(gb_clk >> 6); 156 | return; 157 | case 0x05: // R_TIMA - timer accumulator write 158 | gb_reload_tima(R_TIMA=data); 159 | // reload TIMA with new value,calculate time for next interrupt breakpoint 160 | return; 161 | case 0x07: // R_TAC 162 | if(!(R_TAC^data)) return; // nothing changed 163 | if(R_TAC&4) // Timer was enabled? 164 | R_TIMA=(uint8_t)((gb_clk>>gb_timshift)+gb_timbase); // refresh current value 165 | gb_timshift = timshift[data&3]; // new clock shift rate 166 | gb_timerclk = MAXULONG; 167 | if(data&4) // Timer clock enabled? 168 | gb_reload_tima(R_TIMA); // restart TIMA 169 | R_TAC=data; 170 | return; 171 | case 0x0F : // R_IF (interrupt request) 172 | R_IF = data; 173 | sm83_check4int(); 174 | return; 175 | case 0xFF : // R_IE (interrupt mask) 176 | R_IE = data; 177 | sm83_check4int(); 178 | return; 179 | //case 0x41: // STAT 180 | // R_STAT = (R_STAT &7)|(data&0xF8); 181 | //check4LCDint(); 182 | // return; 183 | case 0x44: R_LY = 0; return;// R_LY reset when written 184 | case 0x45: R_LYC=data; check4LYC(); // set new LYC, check for interrupt immediately 185 | return; 186 | // theoretically LYC interrupt can be caused by setting LYC=LY manually(or not?) 187 | case 0x46: // DMA 188 | { 189 | int i; // TODO: implement proper timing 190 | uint16_t spraddr = data << 8; 191 | for(i=0; i<0xa0; i++) 192 | hram[i]=RD(spraddr + i); 193 | //WR(0xfe00 | i, RD(spraddr | i)); 194 | } 195 | break; 196 | case 0x47: // BGP 197 | CONVPAL(BGP,data); 198 | break; 199 | case 0x48: // OBP0 200 | CONVPAL(OBP0,data); 201 | break; 202 | case 0x49: // OBP1 203 | CONVPAL(OBP1,data); 204 | break; 205 | case 0x50: // BANK 206 | R_BANK |= (data & 1); // Write 1 only 207 | break; 208 | } 209 | } 210 | hram[addr & 0x1ff] = data; 211 | } 212 | 213 | 214 | /* 215 | ************************************************************************* 216 | memory initialization 217 | ************************************************************************* 218 | */ 219 | 220 | /* fill zeroed/random data */ 221 | static void init_internal_RAM(int how) 222 | { 223 | int i; 224 | 225 | if(how) 226 | { 227 | /* fill by random data */ 228 | 229 | for(i=0; i> 8) & 0xff; 233 | } 234 | } 235 | else 236 | { 237 | /* just clear by zeroes */ 238 | 239 | memset(ram, 0, sizeof(ram)); 240 | } 241 | 242 | memset(hram, 0, sizeof(hram)); 243 | } 244 | 245 | static void mem_InitGeneric(void); 246 | 247 | void mem_init() 248 | { 249 | check_ROM_header(); 250 | init_internal_RAM(1); 251 | 252 | switch(romhdr->type) { 253 | case 5: 254 | case 6: 255 | cart.ram_size = 512; 256 | break; 257 | } 258 | mem_InitGeneric(); 259 | switch(romhdr->type) { 260 | case 0: // plain ROM only 261 | break; 262 | case 8: // ROM+RAM 263 | case 9: // ROM+RAM+BATTERY 264 | InitGenericRAM(); 265 | break; 266 | break; 267 | case 3: // MBC1 RAM+battery 268 | case 2: // MBC1 RAM 269 | InitMBC1_RAM(); 270 | case 1: // MBC1 271 | InitMBC1_ROM(); 272 | break; 273 | case 6: // MBC2 +battery 274 | case 5: // MBC2 275 | InitMBC2(); 276 | break; 277 | case 0x10: // MBC3/RAM/TIMER/BATT 278 | case 0x12: // MBC3/RAM 279 | case 0x13: // MBC3/RAM/BATT 280 | InitMBC3_RAM(); //TODO:set ram if only timer present? 281 | case 0xF: // MBC3/TIMER is not saved 282 | case 0x11: // MBC3 283 | InitMBC3_ROM(); 284 | break; 285 | case 0x1A: 286 | case 0x1B: 287 | case 0x1D: 288 | case 0x1E: 289 | InitMBC5_RAM(); 290 | case 0x19: // MBC5 291 | case 0x1C: // MBC5+rumble 292 | InitMBC5_ROM(); 293 | break; 294 | default: 295 | sys_error("unknown cart type - %X!", romhdr->type); 296 | } 297 | } 298 | 299 | void mem_shutdown() 300 | { 301 | switch(romhdr->type) { 302 | case 6: 303 | if(cart.ram_size>512) cart.ram_size=512; 304 | break; 305 | case 0xFF: // HuC-1 306 | case 3: // MBC1 + battery 307 | if(cart.ram_size>8192) cart.ram_size=8192; // Only 1st page saved 308 | //case 0xC: 309 | case 9: // plain ROM+simple RAM controller+BATTERY 310 | case 0xD: // MMM01 311 | case 0xF: 312 | case 0x10: 313 | case 0x13: // MBC3 314 | case 0x1B: 315 | case 0x1E: // MBC5 316 | case 0xFE: // HuC 3 317 | break; 318 | default: 319 | cart.ram_size = 0; 320 | } 321 | if(cart.ram_size) { 322 | save_SRAM(cart.ramdata,cart.ram_size); 323 | } 324 | if(cart.ramdata) free(cart.ramdata); 325 | free(cart.romdata); 326 | } 327 | 328 | /* mapper init */ 329 | 330 | static void mem_InitGeneric(void) { 331 | unsigned n; 332 | memset(cart.rom,0,sizeof(cart.rom)); 333 | memset(&cart.ram,0,sizeof(cart.ram)); 334 | cart.romdata = cart.data; 335 | cart.rom_nbanks = (cart.rom_size+0x3FFF)>>14; 336 | for(n = 1;n>13; 342 | cart.ram_end= 0xC000; // ram_end is obsolete, whole range is mapped always fo compatibility reasons (ram_amask used instead) 343 | cart.ram_amask = 0x1FFF; 344 | if(cart.ram_nbanks==1) { 345 | cart.ram_end = 0xA000+cart.ram_size; 346 | for(n=1;n 0x4000) { 374 | MAPROM(mem_r8_ROMbank1); 375 | } 376 | } 377 | 378 | /********************************************************************** 379 | 16 bit read/write subroutines 380 | **********************************************************************/ 381 | uint16_t mem_read16 (unsigned addr) { 382 | return (uint16_t)( 383 | (unsigned)mem_r8[addr>>8](addr)+ 384 | ((unsigned)mem_r8[(addr+1)>>8](addr+1)<<8)); 385 | } 386 | void mem_write16 (unsigned addr,unsigned d) { 387 | mem_w8[addr>>8](addr,(uint8_t)d); 388 | addr++; 389 | mem_w8[addr>>8](addr,(uint8_t)(d>>8)); 390 | } 391 | -------------------------------------------------------------------------------- /dmgemu.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Debug 10 | x64 11 | 12 | 13 | Release 14 | Win32 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {AA66F9F0-E4BE-4561-A90A-E8E59207B243} 23 | 10.0 24 | 25 | 26 | 27 | Application 28 | v143 29 | false 30 | MultiByte 31 | 32 | 33 | Application 34 | v143 35 | false 36 | MultiByte 37 | 38 | 39 | Application 40 | v143 41 | false 42 | MultiByte 43 | 44 | 45 | Application 46 | v143 47 | false 48 | MultiByte 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | $(SolutionDir)$(Platform)\$(Configuration)\ 72 | 73 | 74 | 75 | $(SolutionDir)$(Platform)\$(Configuration)\ 76 | 77 | 78 | 79 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 80 | Use 81 | pch.h 82 | Disabled 83 | MultiThreadedDebugDLL 84 | thirdparty/SDL2/include 85 | 86 | 87 | true 88 | _DEBUG;%(PreprocessorDefinitions) 89 | true 90 | Win32 91 | 92 | 93 | 0x0419 94 | _DEBUG;%(PreprocessorDefinitions) 95 | 96 | 97 | 98 | odbc32.lib;odbccp32.lib;winmm.lib;%(AdditionalDependencies) 99 | Windows 100 | 101 | 102 | 103 | 104 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 105 | Use 106 | pch.h 107 | Disabled 108 | false 109 | MultiThreadedDebugDLL 110 | thirdparty/SDL2/include 111 | 112 | 113 | true 114 | _DEBUG;%(PreprocessorDefinitions) 115 | true 116 | 117 | 118 | 0x0419 119 | _DEBUG;%(PreprocessorDefinitions) 120 | 121 | 122 | 123 | odbc32.lib;odbccp32.lib;winmm.lib;%(AdditionalDependencies) 124 | Windows 125 | 126 | 127 | 128 | 129 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 130 | Use 131 | pch.h 132 | AnySuitable 133 | Speed 134 | MultiThreadedDLL 135 | thirdparty/SDL2/include 136 | 137 | 138 | true 139 | NDEBUG;%(PreprocessorDefinitions) 140 | true 141 | Win32 142 | 143 | 144 | 0x0419 145 | NDEBUG;%(PreprocessorDefinitions) 146 | 147 | 148 | 149 | odbc32.lib;odbccp32.lib;winmm.lib;%(AdditionalDependencies) 150 | Windows 151 | 152 | 153 | 154 | 155 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 156 | Use 157 | pch.h 158 | AnySuitable 159 | Speed 160 | MultiThreadedDLL 161 | thirdparty/SDL2/include 162 | 163 | 164 | true 165 | NDEBUG;%(PreprocessorDefinitions) 166 | true 167 | 168 | 169 | 0x0419 170 | NDEBUG;%(PreprocessorDefinitions) 171 | 172 | 173 | 174 | odbc32.lib;odbccp32.lib;winmm.lib;%(AdditionalDependencies) 175 | Windows 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | true 184 | true 185 | true 186 | true 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | Create 197 | Create 198 | Create 199 | Create 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | {81ce8daf-ebb2-4761-8e45-b71abcca8c68} 223 | 224 | 225 | 226 | 227 | 228 | -------------------------------------------------------------------------------- /apu.cpp: -------------------------------------------------------------------------------- 1 | // GameBoy APU emulation 2 | #include "pch.h" 3 | 4 | /* Warning! like in core, sound clock is incremented 5 | at rate of 1048576Hz, not 1048576*4 Hz*/ 6 | 7 | typedef struct 8 | { 9 | int on; 10 | unsigned pos; 11 | unsigned cnt,encnt,swcnt; 12 | //int len, enlen, swlen; 13 | //int swfreq; 14 | unsigned freq,swfreq; 15 | unsigned envol;//, endir; 16 | } sndchan; 17 | 18 | typedef struct 19 | { 20 | unsigned outfreq,ratelo,ratehi,z0; 21 | sndchan ch[4]; 22 | uint8_t wave[16]; 23 | } apu; 24 | 25 | const static uint8_t dmgwave[16] = 26 | { 27 | 0xac, 0xdd, 0xda, 0x48, 28 | 0x36, 0x02, 0xcf, 0x16, 29 | 0x2c, 0x04, 0xe5, 0x2c, 30 | 0xac, 0xdd, 0xda, 0x48 31 | }; 32 | 33 | const static uint8_t sqwave[4][8] = 34 | { 35 | { 0, 0, 0xff, 0, 0, 0, 0, 0 }, 36 | { 0, 0xff, 0xff, 0, 0, 0, 0, 0 }, 37 | { 0xff, 0xff, 0xff, 0xff, 0, 0, 0, 0 }, 38 | { 0xff, 0, 0, 0xff, 0xff, 0xff, 0xff, 0xff } 39 | }; 40 | 41 | const static int divtab[8] = 42 | { 43 | 1, 44 | 2, 45 | 4, 46 | 6, 47 | 8, 48 | 10, 49 | 12, 50 | 14 51 | }; 52 | 53 | apu snd; 54 | 55 | static uint8_t noise7[16]; 56 | static uint8_t noise15[4096]; 57 | 58 | static void makenoise(uint8_t *to,int nbits); 59 | static void apu_reset(int freq); 60 | 61 | void apu_init(int freq) 62 | { 63 | InitSound(freq); 64 | makenoise(noise15,15); 65 | makenoise(noise7,7); 66 | 67 | apu_clk_inner[1]=0; 68 | apu_clk_inner[0] = gb_clk; 69 | apu_reset(freq); 70 | } 71 | 72 | void apu_shutdown() 73 | { 74 | FreeSound(); 75 | } 76 | 77 | // ********************************************************************** 78 | 79 | /* 80 | Questionable aspects of APU emulation: 81 | 82 | NR51 contains updated "sound enable" flags 83 | Frequency registers can update if sweep is operating 84 | _nothing_ else that changed in the process of sound generation 85 | Envelope/sweep divider/length register counters are reset through flipping bit 7(initial) of NR X7 86 | Frequency 87 | 88 | according to docs, only way to generate reset signal is to make sound stop by reaching 89 | zero volume envelope, counter-based stop mode will not really stop counters, 90 | but will only set channel output to zero. If you reset length counter, 91 | it will probably count again and sound will be hearable. 92 | real full stop mode is activated (only) by setting(or reaching) zero envelope with "down" 93 | direction counter 94 | int his implementation full stop is caused by 3 situations: length counter reaches 95 | 96 | everything not noted here is updated immediately 97 | 256/128/64 Hz counters are derived from corresponding bits of main counter 98 | (as probably assumed in reference schematics) 99 | */ 100 | 101 | 102 | 103 | //#define RATE (snd.rate) 104 | #define OUT_FREQ (snd.outfreq) 105 | #define RATELO (snd.ratelo) 106 | #define RATEHI (snd.ratehi) 107 | #define WAVE (snd.wave) /* ram.hi+0x30 */ 108 | #define S1 (snd.ch[0]) 109 | #define S2 (snd.ch[1]) 110 | #define S3 (snd.ch[2]) 111 | #define S4 (snd.ch[3]) 112 | 113 | uint32_t apu_clk_inner[2]; 114 | uint32_t apu_clk_nextchange; 115 | 116 | static void makenoise(uint8_t *to,int nbits) { 117 | unsigned i,j,counter,acc,tmp; 118 | counter = (1<0;i--) { 121 | acc = 0; 122 | for(j=8;j>0;j--) { 123 | acc=(acc<<1)|(counter&1); 124 | tmp=counter>>1; 125 | counter=tmp|(((tmp^counter)&1) << nbits); 126 | } 127 | *to++ = acc; 128 | } 129 | } 130 | 131 | /*static void s1_freq_d(int d) 132 | { 133 | if (RATE > (d<<4)) S1.freq = 0; 134 | else S1.freq = (RATE << 17)/d; 135 | }*/ 136 | 137 | static void s1_freq_d(unsigned freq) { 138 | unsigned d = 2048 - (2047&freq); 139 | if(OUT_FREQ) S1.freq = (SO_FREQ<<11)/(OUT_FREQ*d>>3); // 14 bits frac 140 | } 141 | 142 | static void s1_freq() { 143 | s1_freq_d(*(unsigned short*)&R_NR13); 144 | } 145 | 146 | static void s2_freq() { 147 | unsigned d = 2048 - (2047&*(unsigned short*)&R_NR23); 148 | if(OUT_FREQ) S2.freq = (SO_FREQ<<11)/(OUT_FREQ*d>>3); 149 | } 150 | 151 | static void s3_freq() { 152 | unsigned d = 2048 - (2047&*(unsigned short*)&R_NR33); 153 | if(OUT_FREQ) S3.freq = (SO_FREQ<<11)/(OUT_FREQ*d>>6); // +3,because there is no duty circuit 154 | } 155 | 156 | static void s4_freq() { 157 | if(OUT_FREQ) S4.freq = (SO_FREQ<<11)/(OUT_FREQ*divtab[R_NR43&7]) << 5 >> (R_NR43 >> 4); //17 bits frac 158 | } 159 | 160 | void apu_dirty() 161 | { 162 | s1_freq(); 163 | 164 | s2_freq(); 165 | s3_freq(); 166 | S3.cnt = (256-R_NR31); 167 | S1.cnt = (64-(R_NR11&63)); 168 | S2.cnt = (64-(R_NR21&63)); 169 | S4.cnt = (64-(R_NR41&63)); 170 | S1.encnt = 0; 171 | S1.swcnt = 0; 172 | S2.encnt = 0; 173 | S4.encnt = 0; 174 | 175 | S1.envol = R_NR12 >> 4; 176 | S2.envol = R_NR22 >> 4; 177 | S4.envol = R_NR42 >> 4; 178 | s4_freq(); 179 | } 180 | 181 | void apu_off() 182 | { 183 | memset(&snd.ch, 0, sizeof snd.ch); 184 | /*memset(&S1, 0, sizeof S1); 185 | memset(&S2, 0, sizeof S2); 186 | memset(&S3, 0, sizeof S3); 187 | memset(&S4, 0, sizeof S4);*/ 188 | R_NR10 = 0x80; 189 | R_NR11 = 0xBF; 190 | R_NR12 = 0xF3; 191 | R_NR14 = 0xBF; 192 | R_NR21 = 0x3F; 193 | R_NR22 = 0x00; 194 | R_NR24 = 0xBF; 195 | R_NR30 = 0x7F; 196 | R_NR31 = 0xFF; 197 | R_NR32 = 0x9F; 198 | R_NR33 = 0xBF; 199 | R_NR41 = 0xFF; 200 | R_NR42 = 0x00; 201 | R_NR43 = 0x00; 202 | R_NR44 = 0xBF; 203 | R_NR50 = 0x77; 204 | R_NR51 = 0xF3; 205 | R_NR52 = 0xF1; 206 | apu_dirty(); 207 | } 208 | 209 | static void apu_reset(int freq) 210 | { 211 | memset(&snd, 0, sizeof snd); 212 | if (freq) { 213 | // stupid 48 bit division :) 214 | RATEHI = SO_FREQ / (OUT_FREQ = freq); // higher part of rate 215 | RATELO = ((SO_FREQ-RATEHI*OUT_FREQ) << 16)/OUT_FREQ; // lower 16 bits 216 | } 217 | 218 | apu_clk_inner[0] = 0; 219 | apu_clk_inner[1] = gb_clk; 220 | apu_clk_nextchange = (gb_clk&~0xFFF)+0x1000; // 256 Hz divider 221 | memcpy(WAVE, dmgwave, 16); 222 | memcpy(&hram[0x100+0x30], WAVE, 16); 223 | apu_off(); 224 | R_NR52 |= 0x80; 225 | } 226 | 227 | // ********************************************************************** 228 | 229 | uint8_t apu_read(uint8_t r) 230 | { 231 | apu_mix(); 232 | return hram[0x100 + r]; 233 | } 234 | 235 | 236 | void apu_1off(void) { 237 | S1.on = 0;R_NR52&=~1; 238 | } 239 | void apu_2off(void) { 240 | S2.on = 0;R_NR52&=~2; 241 | } 242 | void apu_3off(void) { 243 | S3.on = 0;R_NR52&=~4; 244 | } 245 | void apu_4off(void) { 246 | S4.on = 0;R_NR52&=~8; 247 | } 248 | 249 | 250 | void s1_init() 251 | { 252 | if((R_NR12&0xF8) ==0) return; // envelope decoder blocks RS trigger 253 | //if (R_NR52&1 == 0) 254 | S1.pos = 0; 255 | R_NR52|=1; 256 | S1.on = 1; 257 | if(!S1.cnt) S1.cnt = 64; 258 | S1.encnt = 0; // This value counts up 259 | S1.envol = R_NR12 >> 4; 260 | S1.swcnt = 0; 261 | S1.swfreq = 2047&*(unsigned short*)&R_NR13; 262 | if(R_NR10&7) { 263 | S1.swfreq+=S1.swfreq>>(R_NR10&7); 264 | if(S1.swfreq>2047) apu_1off(); 265 | } 266 | } 267 | 268 | void s2_init() 269 | { 270 | if((R_NR22&0xF8) == 0) return; // envelope decoder blocks RS trigger 271 | //if (R_NR52&2 == 0) 272 | S2.pos = 0; 273 | R_NR52|=2; 274 | S2.on = 1; 275 | if(!S2.cnt) S2.cnt = 64; 276 | //S2.cnt = (64- (R_NR21&63)); 277 | S2.encnt = 0; // This value counts up 278 | S2.envol = R_NR22 >> 4; 279 | } 280 | 281 | void s3_init() 282 | { 283 | int i; 284 | //if (R_NR52&4 == 0) 285 | S3.pos = 0; 286 | R_NR52|=4; 287 | if(!S3.cnt) S3.cnt = 256; 288 | //S3.cnt = (64- (R_NR31&63)); 289 | S3.on=1; 290 | if (R_NR30 & 0x80) for (i = 0; i < 16; i++) 291 | hram[0x130+i] = 0x13 ^ hram[0x131+i]; //? 292 | } 293 | 294 | void s4_init() 295 | { 296 | S4.on = 0; 297 | if((R_NR42&0xF8) == 0) return; // envelope decoder blocks RS trigger 298 | //if(R_NR52&8 == 0) 299 | S4.pos = 0; 300 | R_NR52|=8; 301 | //S4.cnt = 0; 302 | S4.on = 1; 303 | if(!S4.cnt) S4.cnt = 64; 304 | //S4.cnt = (64- (R_NR41&63)); 305 | S4.encnt = 0; // This value counts up 306 | S4.envol = R_NR42 >> 4; 307 | } 308 | 309 | void apu_write(uint8_t r, uint8_t b) 310 | { 311 | if (!(R_NR52 & 128) && r != RI_NR52) return; 312 | if ((r & 0xF0) == 0x30) 313 | { 314 | if (!(R_NR52&8 && R_NR30&0x80)) 315 | WAVE[r-0x30] = hram[0x100+r] = b; 316 | return; 317 | } 318 | apu_mix(); 319 | switch (r) 320 | { 321 | case RI_NR10: 322 | R_NR10 = b; 323 | S1.swfreq = 2047&*(unsigned short*)&R_NR13; 324 | S1.swcnt = 0; // TODO? Is it true? 325 | break; 326 | case RI_NR11: 327 | R_NR11 = b; 328 | S1.cnt = 64-(b&63); 329 | break; 330 | case RI_NR12: 331 | R_NR12 = b; 332 | S1.envol = R_NR12 >> 4; 333 | if((b&0xF8) == 0) apu_1off(); // Forced OFF mode(as stated in patent docs) if 0 and down 334 | //S1.endir = (R_NR12>>3) & 1; 335 | //S1.endir |= S1.endir - 1; 336 | break; 337 | case RI_NR13: 338 | R_NR13 = b; 339 | s1_freq(); 340 | break; 341 | case RI_NR14: 342 | R_NR14 = b; 343 | s1_freq(); 344 | if (b & 128) s1_init(); 345 | break; 346 | case RI_NR21: 347 | R_NR21 = b; 348 | S2.cnt = 64-(b&63); 349 | break; 350 | case RI_NR22: 351 | R_NR22 = b; 352 | if((b&0xF8) == 0) apu_2off(); // Forced OFF mode(as stated in patent docs) if 0 and down 353 | S2.envol = R_NR22 >> 4; 354 | //S2.endir = (R_NR22>>3) & 1; 355 | //S2.endir |= S2.endir - 1; 356 | break; 357 | case RI_NR23: 358 | R_NR23 = b; 359 | s2_freq(); 360 | break; 361 | case RI_NR24: 362 | R_NR24 = b; 363 | s2_freq(); 364 | if (b & 128) s2_init(); 365 | break; 366 | case RI_NR30: 367 | R_NR30 = b; 368 | if (!(b & 128)) apu_3off(); 369 | break; 370 | case RI_NR31: 371 | R_NR31 = b; 372 | S3.cnt = 256-b; 373 | break; 374 | case RI_NR32: 375 | R_NR32 = b; 376 | break; 377 | case RI_NR33: 378 | R_NR33 = b; 379 | s3_freq(); 380 | break; 381 | case RI_NR34: 382 | R_NR34 = b; 383 | s3_freq(); 384 | if (b & 128) s3_init(); 385 | break; 386 | case RI_NR41: 387 | R_NR41 = b; 388 | S4.cnt = 64-(b&63); 389 | break; 390 | case RI_NR42: 391 | R_NR42 = b; 392 | S4.envol = R_NR42 >> 4; 393 | if((b&0xF8) == 0) apu_4off(); // Forced OFF mode(as stated in patent docs) if 0 and down 394 | break; 395 | case RI_NR43: 396 | R_NR43 = b; 397 | s4_freq(); 398 | break; 399 | case RI_NR44: 400 | R_NR44 = b; 401 | s4_freq(); 402 | if (b & 128) s4_init(); 403 | break; 404 | case RI_NR50: 405 | R_NR50 = b; 406 | break; 407 | case RI_NR51: 408 | R_NR51 = b; 409 | break; 410 | case RI_NR52: 411 | R_NR52 = b; 412 | if (!(R_NR52 & 128)) 413 | apu_off(); 414 | break; 415 | default: 416 | return; 417 | } 418 | } 419 | 420 | // ********************************************************************** 421 | 422 | 423 | 424 | /* 425 | Inner loop does mixing with constant volume/pitch parameters,saving lots of CPU time 426 | n is >0 427 | code is for 32-bit machines only! code is assumed to be "PSX friendly" 428 | */ 429 | 430 | /* 431 | TODO: 432 | this code can do redundant mixind in many cases, they must be optimized later 433 | for example: don't mix if both l&r outputs are disabled for a channel 434 | mono mixing can use some additional optimization. 435 | */ 436 | void apu_mix_basic(uint32_t apu_clk_new) { 437 | 438 | uint8_t *s1_waveptr; 439 | uint8_t *s2_waveptr; 440 | int l,r,lr[4][2]; 441 | unsigned s; 442 | uint32_t clk[2]; 443 | clk[0] = apu_clk_inner[0]; 444 | clk[1] = apu_clk_inner[1]; 445 | if(clk[1]>= apu_clk_new) return; 446 | s1_waveptr = (uint8_t*)(sqwave+((unsigned)R_NR11>>6)); 447 | s2_waveptr = (uint8_t*)(sqwave+((unsigned)R_NR21>>6)); 448 | //on[0]=R_NR52&S1.on; 449 | //on[1]=((unsigned)R_NR52>>1)&S2.on; 450 | //on[2]=((unsigned)R_NR52>>2)&((unsigned)R_NR30>>7)&S3.on; 451 | //on[3]=((unsigned)R_NR52>>3)&S4.on; 452 | lr[0][0]=(((signed)R_NR51<<31)>>31) & S1.envol; 453 | lr[0][1]=(((signed)R_NR51<<27)>>31) & S1.envol; 454 | lr[1][0]=(((signed)R_NR51<<30)>>31) & S2.envol; 455 | lr[1][1]=(((signed)R_NR51<<26)>>31) & S2.envol; 456 | lr[2][0]=(((signed)R_NR51<<29)>>31); 457 | lr[2][1]=(((signed)R_NR51<<25)>>31); 458 | lr[3][0]=(((signed)R_NR51<<28)>>31) & S4.envol; 459 | lr[3][1]=(((signed)R_NR51<<24)>>31) & S4.envol; 460 | do { 461 | l=r=0; 462 | // ---------- Channel 1 ------------ 463 | if (S1.on) { 464 | s = s1_waveptr[(S1.pos>>14)&7]; 465 | S1.pos += S1.freq; 466 | r+=lr[0][0]&s; 467 | l+=lr[0][1]&s; 468 | } 469 | // ---------- Channel 2 ------------ 470 | if (S2.on) { 471 | s = s2_waveptr[(S2.pos>>14)&7]; 472 | S2.pos += S2.freq; 473 | r+=lr[1][0]&s; 474 | l+=lr[1][1]&s; 475 | } 476 | 477 | // ---------- Channel 3 ------------ 478 | if (S3.on) { 479 | s = WAVE[(S3.pos>>18) & 0xF]; 480 | if (S3.pos & (1<<21)) s &= 15; 481 | else s >>= 4; 482 | S3.pos += S3.freq; 483 | s>>=((R_NR32>>5)&3)-1; // if 0, shift by 31 or more (mute) 484 | r+=lr[2][0]&s; 485 | l+=lr[2][1]&s; 486 | } 487 | // ---------- Channel 4 ------------ 488 | if (S4.on) { 489 | if (R_NR43 & 8) s = noise7[ 490 | (S4.pos>>20)&0xF] >> ((S4.pos>>17)&7); 491 | else s = noise15[ 492 | (S4.pos>>20)&0xFFF] >> ((S4.pos>>17)&7); 493 | s = ((signed)s<<31)>>31;//(-(signed)(s&1)); 494 | S4.pos += S4.freq; 495 | r+=lr[3][0]&s; 496 | l+=lr[3][1]&s; 497 | } 498 | 499 | 500 | l = l*(R_NR50 & 0x07)>>2 ; 501 | r = r*((R_NR50 & 0x70)>>4)>>2; 502 | 503 | pop_sample(l, r); 504 | 505 | clk[0]+=RATELO; 506 | clk[1]+=RATEHI+(clk[0]>>16); 507 | clk[0]&=0xFFFF; // 48 bit counter 508 | } while (clk[1]< apu_clk_new); 509 | apu_clk_inner[0] = clk[0]; 510 | apu_clk_inner[1] = clk[1]; 511 | } 512 | 513 | 514 | void apu_mix(void) { 515 | unsigned i,tmp,tmp2,swperiod,enperiod; 516 | uint8_t *pt; 517 | benchmark_sound-=GetTimer(); 518 | while (apu_clk_nextchange<(uint32_t)gb_clk) { 519 | apu_mix_basic(apu_clk_nextchange); 520 | // Change envelopes,counters/etc---------------- 521 | // Sound length check (256 Hz) 522 | if(R_NR52&128) { 523 | if(!(apu_clk_nextchange&0x1000) && (swperiod=R_NR10&0x70)) { 524 | S1.swcnt = (S1.swcnt+1) & 7; // counts up(with possible overflow) 525 | if(S1.swcnt == (swperiod>>4)) { // Check sweep (128 Hz) 526 | S1.swcnt = 0; 527 | tmp = S1.swfreq; 528 | s1_freq_d(tmp); 529 | tmp2 = tmp>>(R_NR10&7); // sweep shift 530 | if (R_NR10 & 8) // count direction 531 | tmp -= tmp2; // subtract freq (will never be <0) 532 | else { 533 | tmp += tmp2; // add freq 534 | if(tmp>=0x800) apu_1off(); // stop at sweep overflow(stated in GB faq) 535 | } 536 | S1.swfreq = (tmp &= 0x7FF); 537 | //*(unsigned short*)&R_NR13 = ((*(unsigned short*)&R_NR13)&~0x7FF)|tmp; // update freq (stated in GB faq) 538 | s1_freq_d(tmp); 539 | //s1_freq_d(2048 - tmp); 540 | } 541 | } 542 | // TODO: <=? or just =? (to emulate that bug) 543 | if((R_NR14&0x40) && S1.cnt) // Counter 1 544 | if(--S1.cnt<=0) apu_1off(); 545 | if ((R_NR24&0x40) && S2.cnt) // Counter 2 546 | if(--S2.cnt<=0) apu_2off(); 547 | if ((R_NR34&0x40) && S3.cnt) // Counter 3 548 | if(--S3.cnt<=0) apu_3off(); 549 | if ((R_NR44&0x40) && S4.cnt) // Counter 4 550 | if(--S4.cnt<=0) apu_4off(); 551 | 552 | if(!(apu_clk_nextchange&0x3000)) { // Check envelopes (64 Hz) 553 | // TODO: I think that envelope is wrapped over 15 when moving up, maybe I wrong 554 | for(i=0;i<4;i++) if(i!=2) { 555 | tmp = *(pt=(hram+0x112+i*5));//R_NR12; 556 | enperiod=tmp&7; 557 | if(snd.ch[i].on && enperiod) { 558 | snd.ch[i].encnt=(snd.ch[i].encnt+1)&7; 559 | if(snd.ch[i].encnt == enperiod) { 560 | snd.ch[i].encnt = 0; 561 | if(tmp&8) { 562 | tmp+=16; 563 | if(!(tmp & 0xF0)) { 564 | tmp-=16; 565 | //tmp&=0xF8; 566 | }// Volume up, decrement counter 567 | 568 | } else { 569 | tmp-=16; 570 | if((tmp & 0xF0) == 0xF0) { 571 | tmp+=16; 572 | //snd.ch[i].on=0; 573 | //tmp&=0xF8; 574 | } // Volume down, decrement counter 575 | //else snd.ch[i].on=0; 576 | } 577 | snd.ch[i].envol = (tmp>>4)&0xF; 578 | *pt = tmp; 579 | } 580 | } 581 | } 582 | } 583 | //---------------------------------------------- 584 | apu_clk_nextchange+=0x1000; // Warning, 14 lower buts must ALWAYS be 0 585 | } 586 | } 587 | apu_mix_basic(gb_clk); 588 | benchmark_sound+=GetTimer(); 589 | } 590 | -------------------------------------------------------------------------------- /sm83.cpp: -------------------------------------------------------------------------------- 1 | // SM83 interpreter. 2 | // The DMG SoC uses a custom SHARP SM83 core, which mostly uses the Z80 instruction set, but a completely proprietary implementation + additional opcodes and HLT/STOP modes. 3 | #include "pch.h" 4 | 5 | /* SM83 Context */ 6 | union Z80reg r_af, r_bc, r_de, r_hl; 7 | union Z80reg r_sp, r_pc; 8 | unsigned HALT, IME; 9 | 10 | #define OP(n) break; case 0x##n: 11 | #define CB(n) break; case 0x##n: 12 | 13 | /* not implemented/reserved instruction */ 14 | static void Undefined(void) 15 | { 16 | R_PC--; 17 | show_regs (); 18 | sys_error("Undefined SM83 opcode %02X at PC = %.4X",(unsigned)RD(R_PC), (unsigned)(R_PC)); 19 | } 20 | 21 | static uint8_t zr_t[256]; // Z flag value for the specified operand (essentially ZF=1 when the operand is 0) 22 | static uint8_t inc_t[256]; // INC instruction flags 23 | static uint8_t dec_t[256]; // DEC instruction flags 24 | static uint8_t swap_t[256]; // Preswapped values 25 | 26 | // ********************************************************************** 27 | 28 | /* 29 | ******* 30 | macros 31 | ******* 32 | */ 33 | 34 | #define _DAA() {\ 35 | if((tmp32&0xF)>9 || tmp8&HF) {\ 36 | tmp32 += 6;\ 37 | }\ 38 | if(tmp32 > 0x9F || tmp8&CF) {\ 39 | tmp32 += 0x60;\ 40 | }\ 41 | } 42 | 43 | #define _DAS() {\ 44 | if(tmp8&HF) {\ 45 | tmp32 = (tmp32 - 6) & 0xff;\ 46 | }\ 47 | if(tmp8&CF) {\ 48 | tmp32 -= 0x60;\ 49 | }\ 50 | } 51 | 52 | #define DAA() {\ 53 | tmp32=(unsigned)R_A;\ 54 | tmp8=R_F&(NF|HF|CF); \ 55 | if(tmp8&NF) _DAS() else _DAA();\ 56 | R_A = (uint8_t)tmp32;\ 57 | R_F = (tmp8 & ~HF) | (tmp32&0x100?CF:0) | zr_t[R_A];\ 58 | } 59 | 60 | 61 | #define PUSH(n) { R_SP--; WR(R_SP, n.h); R_SP--; WR(R_SP, n.l); } 62 | #define POP(n) { n.l=RD(R_SP); R_SP++; n.h=RD(R_SP); R_SP++; } 63 | 64 | 65 | 66 | #define PUSHAF { R_SP--; WR(R_SP, r_af.h); R_SP--; WR(R_SP, r_af.l); } 67 | #define POPAF { r_af.l=RD(R_SP)&(ZF|NF|HF|CF); R_SP++; r_af.h=RD(R_SP); R_SP++; } 68 | 69 | 70 | // TODO: Look in the ALU circuitry to see what the hell is going on here 71 | #define ADDSPX(n) \ 72 | tmp32 = (unsigned)R_SP + (int16_t)(int8_t)n;\ 73 | R_F = ((R_SP & 0xf) + ((int16_t)(int8_t)n & 0xf)) > 0xf ? HF : 0;\ 74 | R_F |= ((R_SP & 0xff) + ((int16_t)(int8_t)n & 0xff)) > 0xff ? CF : 0; 75 | 76 | #define LDHLSP(n) { ADDSPX(n);R_HL = (uint16_t)tmp32; } 77 | #define ADDSP(n) { ADDSPX(n);R_SP = (uint16_t)tmp32; } 78 | 79 | #define ADDHL(n) { \ 80 | tmp32 = (unsigned)R_HL + n; \ 81 | R_F = (R_F & ZF) | ((tmp32^(unsigned)R_HL^n) & 0x1000 ? HF : 0 ) | ((tmp32&0x10000)?CF:0);\ 82 | R_HL = (uint16_t)tmp32; } 83 | 84 | #define ADD(n) { \ 85 | tmp32 = (unsigned)R_A + n;\ 86 | R_F = ((tmp32^(unsigned)R_A^n)&0x10 ? HF : 0) | zr_t[(uint8_t)tmp32] | ((tmp32&0x100)?CF:0); \ 87 | R_A = (uint8_t)tmp32;\ 88 | } 89 | 90 | #define ADC(n) { \ 91 | tmp32 = (unsigned)R_A + n + ((R_F&CF)?1:0);\ 92 | R_F = ((tmp32^(unsigned)R_A^n)&0x10 ? HF : 0) | zr_t[(uint8_t)tmp32] | ((tmp32&0x100)?CF:0); \ 93 | R_A = (uint8_t)tmp32;\ 94 | } 95 | 96 | 97 | 98 | 99 | #define CP(n) { \ 100 | tmp32 = (unsigned)R_A - (unsigned)n; \ 101 | R_F = ((tmp32^R_A^n)&0x10 ? HF : 0) | (-(signed)(tmp32 >> 8)?CF:0) | zr_t[(uint8_t)tmp32] | NF;\ 102 | } 103 | 104 | #define SBC(n) { \ 105 | tmp32 = (unsigned)R_A - (unsigned)n - ((R_F&CF)?1:0); \ 106 | R_F = ((tmp32^R_A^n)&0x10 ? HF : 0) | (-(signed)(tmp32 >> 8)?CF:0) | zr_t[(uint8_t)tmp32] | NF;\ 107 | R_A = (uint8_t)tmp32;\ 108 | } 109 | 110 | #define SUB(n) { CP(n);R_A = (uint8_t)tmp32;} 111 | 112 | 113 | #define AND(n) { R_A &= n; R_F = zr_t[R_A] | HF; } 114 | #define OR(n) { R_A |= n; R_F = zr_t[R_A]; } 115 | #define XOR(n) { R_A ^= n; R_F = zr_t[R_A]; } 116 | 117 | 118 | #define INC(n) { n++; R_F = (uint8_t)((R_F & CF) | inc_t[n]); } 119 | #define DEC(n) { n--; R_F = (uint8_t)((R_F & CF) | dec_t[n]); } 120 | 121 | #define RLCA(r) { \ 122 | R_F = r & 0x80 ? CF : 0; \ 123 | r = (uint8_t)(((unsigned)r >> 7) | ((unsigned)r << 1)); } 124 | #define RRCA(r) { \ 125 | R_F = r & 1 ? CF : 0;\ 126 | r = (uint8_t)(((unsigned)r << 7) | ((unsigned)r >> 1)); } 127 | #define RLA(r) { \ 128 | tmp8 = r; \ 129 | r = (uint8_t)(((unsigned)r << 1) | (unsigned)(R_F & CF ? 1 : 0)); \ 130 | R_F = tmp8 & 0x80 ? CF : 0;} 131 | #define RRA(r) { \ 132 | tmp8 = r; \ 133 | r = (uint8_t)(((unsigned)r >> 1) | (unsigned)(R_F & CF ? 0x80 : 0)); \ 134 | R_F = tmp8 & 1 ? CF : 0; } 135 | 136 | 137 | #define RLC(r) { \ 138 | tmp32 = r; \ 139 | r = (uint8_t)(((unsigned)r >> 7) | ((unsigned)r << 1)); \ 140 | R_F = zr_t[r] | (tmp32 & 0x80 ? CF : 0); } 141 | #define RRC(r) { \ 142 | R_F = r & 1 ? CF : 0;\ 143 | r = (uint8_t)(((unsigned)r << 7) | ((unsigned)r >> 1)); \ 144 | R_F |= zr_t[r]; } 145 | #define RL(r) { \ 146 | tmp32 = r; \ 147 | r = (uint8_t)(((unsigned)r << 1) | (unsigned)(R_F & CF ? 1 : 0)); \ 148 | R_F = zr_t[r] | (uint8_t)(tmp32 & 0x80 ? CF : 0);} 149 | #define RR(r) { \ 150 | tmp32 = (unsigned)R_F & CF ? 0x80 : 0; \ 151 | R_F = r & 1 ? CF : 0;\ 152 | r = (uint8_t)(((unsigned)r >> 1) | tmp32);\ 153 | R_F|=zr_t[r];} 154 | 155 | 156 | #define SLA(r) { \ 157 | R_F = r & 0x80 ? CF : 0;\ 158 | r <<= 1; \ 159 | R_F |= zr_t[r]; } 160 | 161 | #define SRA(r) { \ 162 | R_F = r & 1 ? CF : 0;\ 163 | r = (uint8_t)((signed)(signed char)r>>1); \ 164 | R_F |= zr_t[r]; } 165 | 166 | 167 | #define SRL(r) { \ 168 | R_F = r & 1 ? CF : 0;\ 169 | r >>= 1; \ 170 | R_F |= zr_t[r]; } 171 | 172 | 173 | #define BIT(n, r) { R_F = (R_F & CF) | HF| zr_t[(1<memory addition will be removed 849 | */ 850 | //__log("Op %.2X",opcode); 851 | } 852 | } 853 | 854 | // ********************************************************************** 855 | 856 | // This makes code more readable, editable & compressable :) 857 | static void filltables(void) { 858 | unsigned i,p; 859 | for(i=0;i<256;i++) { 860 | swap_t[i] = (uint8_t)((i<<4)|(i>>4)); 861 | p = 0; 862 | if(i==0) p|=ZF; 863 | zr_t[i]=p; 864 | inc_t[i] = (i&0xF)==0x0? (p|HF) : p; 865 | p|=NF; 866 | dec_t[i] = (i&0xF)==0xF? (p|HF) : p; 867 | } 868 | } 869 | void sm83_init() 870 | { 871 | filltables(); 872 | R_PC = 0; 873 | HALT = IME = 0; 874 | } --------------------------------------------------------------------------------