├── tinygb.ini ├── src ├── core │ ├── serial.c │ ├── interrupts.c │ ├── cgb.c │ ├── joypad.c │ ├── timer.c │ ├── sound.c │ ├── memory.c │ ├── sgb.c │ ├── mbc.c │ └── display.c ├── include │ ├── sgb.h │ ├── ioports.h │ └── tinygb.h ├── log.c ├── config.c └── platform │ └── sdl │ ├── README-tinyfiledialogs.txt │ ├── main.c │ └── tinyfiledialogs.h ├── Makefile ├── LICENSE.txt └── README.md /tinygb.ini: -------------------------------------------------------------------------------- 1 | [controls] 2 | a=z 3 | b=x 4 | start=return 5 | select=rshift 6 | up=up 7 | down=down 8 | left=left 9 | right=right 10 | throttle=space 11 | 12 | [emulator] 13 | speed=100% ; +/- 4% 14 | palette=0 ; default monochrome palette 15 | scaling=3 ; basically zoom 16 | system=auto ; options: auto, gb, sgb2, cgb 17 | preference=cgb ; when above is set to auto, prefer CGB or GB on games that support both 18 | border=yes ; are borders enabled on sgb? 19 | -------------------------------------------------------------------------------- /src/core/serial.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | 8 | //#define SERIAL_LOG 9 | 10 | uint8_t sb = 0, sc = 0; 11 | 12 | void sb_write(uint8_t byte) { 13 | #ifdef SERIAL_LOG 14 | write_log("[serial] write to SB register value 0x%02X\n", byte); 15 | #endif 16 | 17 | sb = byte; 18 | } 19 | 20 | void sc_write(uint8_t byte) { 21 | #ifdef SERIAL_LOG 22 | write_log("[serial] write to SC register value 0x%02X\n", byte); 23 | #endif 24 | 25 | sc = byte; 26 | } -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | LD=gcc 3 | ARCH := $(shell $(CC) -dumpmachine | grep -q x86_64 && echo x86_64) 4 | 5 | CFLAGS=-c -Wall -Ofast $(shell sdl2-config --cflags) -I./src/include 6 | LDFLAGS=-Ofast $(shell sdl2-config --libs) 7 | 8 | ifeq ($(ARCH),x86_64) 9 | CFLAGS += -msse2 10 | LDFLAGS += -msse2 11 | endif 12 | 13 | SRC:=$(shell find ./src -type f -name "*.c") 14 | OBJ:=$(SRC:.c=.o) 15 | 16 | all: tinygb 17 | 18 | clean: 19 | @rm -f $(OBJ) 20 | @rm -f tinygb 21 | 22 | %.o: %.c 23 | @exec echo -e "\x1B[0;1;35m [ CC ]\x1B[0m $@" 24 | @$(CC) -o $@ $< ${CFLAGS} 25 | 26 | tinygb: $(OBJ) 27 | @exec echo -e "\x1B[0;1;36m [ LD ]\x1B[0m tinygb" 28 | @$(LD) $(OBJ) -o tinygb ${LDFLAGS} 29 | -------------------------------------------------------------------------------- /src/core/interrupts.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | 7 | //#define INTERRUPTS_LOG 8 | 9 | uint8_t io_if = 0, io_ie = 0; 10 | 11 | void if_write(uint8_t byte) { 12 | #ifdef INTERRUPTS_LOG 13 | write_log("[int] write to IF register with value 0x%02X\n", byte); 14 | #endif 15 | 16 | io_if = byte; 17 | } 18 | 19 | void ie_write(uint8_t byte) { 20 | #ifdef INTERRUPTS_LOG 21 | write_log("[int] write to IE register with value 0x%02X\n", byte); 22 | #endif 23 | 24 | io_ie = byte; 25 | } 26 | 27 | uint8_t ie_read() { 28 | return io_ie; 29 | } 30 | 31 | uint8_t if_read() { 32 | return io_if; 33 | } 34 | 35 | void send_interrupt(int n) { 36 | #ifdef INTERRUPTS_LOG 37 | //write_log("[int] sending interrupt 0x%02X\n", (n << 3) + 0x40); 38 | #endif 39 | 40 | io_if |= (1 << n); 41 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-25 Jewel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/core/cgb.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | 8 | //#define CGB_LOG 9 | 10 | /* Misc Color Gameboy functions that dont fit anywhere else */ 11 | 12 | int is_double_speed = 0; 13 | int prepare_speed_switch = 0; 14 | 15 | uint8_t cgb_read(uint16_t addr) { 16 | switch(addr) { 17 | case KEY1: 18 | return (is_double_speed & 1) << 7; 19 | case SVBK: 20 | return work_ram_bank; 21 | default: 22 | die(-1, "undefined read from IO port 0x%04X\n", addr); 23 | return 0xFF; 24 | } 25 | } 26 | 27 | void cgb_write(uint16_t addr, uint8_t byte) { 28 | switch(addr) { 29 | case KEY1: 30 | if(byte & 0x01) prepare_speed_switch = 1; 31 | else write_log("[cgb] undefined write to KEY1 register value 0x%02X without attempting a speed switch\n"); 32 | break; 33 | case RP: 34 | write_log("[cgb] unimplemented write to RP register value 0x%02X\n", byte); 35 | break; 36 | case SVBK: 37 | byte &= 7; 38 | if(!byte) byte++; 39 | #ifdef CGB_LOG 40 | write_log("[cgb] selecting WRAM bank %d\n", byte); 41 | #endif 42 | 43 | work_ram_bank = byte; 44 | break; 45 | default: 46 | die(-1, "undefined write to IO port 0x%04X value 0x%02X\n", addr, byte); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TinyGB 2 | TinyGB is a tiny and portable Game Boy emulator written entirely in C as a side project. I only wrote this to deepen my understanding of how direct hardware low-level programming works. 3 | 4 | ![Pokemon Red running on TinyGB](https://jewelcodes.io/tinygb.png) 5 | 6 | ## Roadmap 7 | - [x] (Almost) complete implementation of the Z80 CPU 8 | - [x] Monochrome display for the original Game Boy 9 | - [x] User input 10 | - [x] Memory bank controllers to save games and/or play ROMs larger than 32 KiB 11 | - [x] Super Game Boy color functions 12 | - [x] Game Boy Color functions 13 | - [x] Super Game Boy borders 14 | - [ ] Sound output 15 | - [ ] Serial port and linking 16 | - [ ] (Possibly) make the UI more user-friendly? 17 | 18 | ## Tested Playable Games 19 | The links below point to screenshots of the gameplay. 20 | * [Tetris](https://imgur.com/a/V1wYy1W) (as of 2 May 2022) 21 | * [Pokemon Red](https://imgur.com/a/uDA7G0F) (as of 5 May 2022) 22 | * [Pokemon Yellow](https://imgur.com/a/SVYOiTx) (as of 9 May 2022) 23 | * [Super Mario Land](https://imgur.com/a/bTEPuwy) (as of 18 July 2022) 24 | * [Pokemon Crystal](https://imgur.com/a/Ow5IKm4) (as of 2 August 2022) 25 | * [Zelda: Link's Awakening](https://imgur.com/a/RvQSW7A) (as of 12 August 2022) 26 | 27 | ## Requirements 28 | ```sh 29 | sudo apt install libsdl2 libsdl2-dev 30 | ``` 31 | 32 | ## Building 33 | ```sh 34 | git clone https://github.com/jewelcodes/tinygb.git 35 | cd tinygb 36 | make 37 | ``` 38 | 39 | ## Acknowledgements 40 | * [Pan Docs](https://gbdev.io/pandocs/) 41 | * [Mooneye Test Suite](https://github.com/Gekkio/mooneye-test-suite) 42 | 43 | ## License 44 | MIT as always. 45 | -------------------------------------------------------------------------------- /src/include/sgb.h: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | // SGB Commands 10 | #define SGB_PAL01 0x00 // these set palettes 11 | #define SGB_PAL23 0x01 12 | #define SGB_PAL03 0x02 13 | #define SGB_PAL12 0x03 14 | #define SGB_ATTR_BLK 0x04 15 | #define SGB_ATTR_LIN 0x05 16 | #define SGB_ATTR_DIV 0x06 17 | #define SGB_ATTR_CHR 0x07 18 | #define SGB_SOUND 0x08 19 | #define SGB_SOU_TRN 0x09 20 | #define SGB_PAL_SET 0x0A 21 | #define SGB_PAL_TRN 0x0B 22 | #define SGB_ATRC_EN 0x0C 23 | #define SGB_TEST_EN 0x0D 24 | #define SGB_ICON_EN 0x0E 25 | #define SGB_DATA_SND 0x0F // transfer SNES WRAM 26 | #define SGB_DATA_TRN 0x10 27 | #define SGB_MLT_REQ 0x11 // used to detect SGB functions 28 | #define SGB_JUMP 0x12 29 | #define SGB_CHR_TRN 0x13 30 | #define SGB_PCT_TRN 0x14 31 | #define SGB_ATTR_TRN 0x15 32 | #define SGB_ATTR_SET 0x16 33 | #define SGB_MASK_EN 0x17 34 | #define SGB_OBJ_TRN 0x18 35 | 36 | typedef struct { 37 | int stopped; 38 | uint8_t command_length; // length in lower 3 bits, 1-7, in number of packets; command in higher 5 bits 39 | uint8_t data[111]; // maximum data that can be transferred 40 | } sgb_command_t; 41 | 42 | typedef struct { 43 | uint32_t colors[4]; 44 | } sgb_palette_t; 45 | 46 | typedef struct { 47 | uint32_t colors[16]; 48 | } sgb_border_palette_t; 49 | 50 | typedef struct { 51 | int inside, outside, surrounding; // what the fuck does surrounding even mean bruh 52 | int palette_inside, palette_outside, palette_surrounding; 53 | int x1, y1, x2, y2; 54 | } sgb_attr_block_t; 55 | -------------------------------------------------------------------------------- /src/log.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | char log_buffer[1000]; 11 | 12 | FILE *log_file; 13 | 14 | void write_log(const char *text, ...) { 15 | va_list args; 16 | va_start(args, text); 17 | 18 | vsprintf(log_buffer, text, args); 19 | 20 | if(log_file) { 21 | fprintf(log_file, "%s", log_buffer); 22 | } 23 | fprintf(stdout, "%s", log_buffer); 24 | 25 | va_end(args); 26 | } 27 | 28 | void open_log() { 29 | log_file = fopen("tinygb.log", "w"); 30 | if(!log_file) 31 | fprintf(stderr, "unable to open tinygb.log for writing, will log to stdout\n"); 32 | 33 | write_log("log started\n"); 34 | } 35 | 36 | void die(int status, const char *msg, ...) { 37 | destroy_window(); 38 | 39 | if(ram) { 40 | #ifdef CGB_DEBUG 41 | if(is_cgb) { 42 | cgb_dump_bgpd(); 43 | cgb_dump_obpd(); 44 | } 45 | #endif 46 | 47 | cpu_log(); 48 | 49 | FILE *memdump = fopen("memory.bin", "wb"); 50 | if(!memdump) { 51 | write_log("failed to open memory.bin for writing\n"); 52 | } else { 53 | fwrite(ram, 1024, 1058, memdump); 54 | fflush(memdump); 55 | fclose(memdump); 56 | } 57 | 58 | free(ram); 59 | } 60 | 61 | if(vram) { 62 | FILE *vramdump = fopen("vram.bin", "wb"); 63 | if(!vramdump) { 64 | write_log("failed to open vram.bin for writing\n"); 65 | } else { 66 | fwrite(vram, 1, 16384, vramdump); 67 | fflush(vramdump); 68 | fclose(vramdump); 69 | } 70 | 71 | free(vram); 72 | } 73 | 74 | free(rom); 75 | 76 | if(!status || !msg) { 77 | if(log_file) fclose(log_file); 78 | exit(status); 79 | } 80 | 81 | va_list args; 82 | va_start(args, msg); 83 | 84 | vsprintf(log_buffer, msg, args); 85 | 86 | if(log_file) { 87 | fprintf(log_file, "quitting with exit code %d: %s", status, log_buffer); 88 | fflush(log_file); 89 | fclose(log_file); 90 | } 91 | fprintf(stdout, "quitting with exit code: %d: %s", status, log_buffer); 92 | 93 | va_end(args); 94 | 95 | exit(status); 96 | } 97 | -------------------------------------------------------------------------------- /src/core/joypad.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | 8 | uint8_t pressed_keys = 0; // high 4 bits directions, low 4 bits buttons 9 | 10 | int selection = 0; // 0 = buttons, 1 = directions 11 | 12 | #define BUTTON_A 0x01 13 | #define BUTTON_B 0x02 14 | #define BUTTON_SELECT 0x04 15 | #define BUTTON_START 0x08 16 | #define BUTTON_RIGHT 0x10 17 | #define BUTTON_LEFT 0x20 18 | #define BUTTON_UP 0x40 19 | #define BUTTON_DOWN 0x80 20 | 21 | uint8_t joypad_read(uint16_t addr) { 22 | if(is_sgb && sgb_interfere) return sgb_read(); 23 | uint8_t val; 24 | 25 | if(selection == 1) { 26 | // directions 27 | val = (~(pressed_keys >> 4)) & 0x0F; 28 | //write_log("[joypad] directions return value 0x%02X\n", val); 29 | } else if(selection == 0) { 30 | // buttons 31 | val = (~pressed_keys) & 0x0F; 32 | //write_log("[joypad] buttons return value 0x%02X\n", val); 33 | } else { 34 | val = 0x0F; 35 | } 36 | 37 | return val; 38 | } 39 | 40 | void joypad_write(uint16_t addr, uint8_t byte) { 41 | /*if(is_sgb && sgb_transferring) return sgb_write(byte); 42 | 43 | if(is_sgb && sgb_interfere) return sgb_write(byte); 44 | 45 | if(is_sgb) { 46 | if(!(byte & 0x20) && !(byte & 0x10)) { 47 | return sgb_write(byte); 48 | } 49 | }*/ 50 | 51 | if(is_sgb) { 52 | if(sgb_transferring || sgb_interfere) return sgb_write(byte); 53 | 54 | if(!(byte & 0x20) && !(byte & 0x10)) { 55 | return sgb_write(byte); 56 | } 57 | } 58 | 59 | byte = ~byte; 60 | byte &= 0x30; 61 | 62 | if(byte == 0x30 || !byte) { 63 | selection = 2; 64 | return; 65 | } 66 | 67 | if(byte & 0x20) { 68 | // button keys 69 | selection = 0; 70 | } else if(byte & 0x10) { 71 | // direction 72 | selection = 1; 73 | //write_log("[joypad] write value 0x%02X, selecting directions\n", (~byte) & 0xFF); 74 | } else { 75 | // undefined so we'll return ones 76 | selection = 2; 77 | } 78 | } 79 | 80 | void joypad_handle(int is_down, int key) { 81 | uint8_t val; 82 | switch(key) { 83 | case JOYPAD_RIGHT: 84 | val = BUTTON_RIGHT; 85 | break; 86 | case JOYPAD_LEFT: 87 | val = BUTTON_LEFT; 88 | break; 89 | case JOYPAD_UP: 90 | val = BUTTON_UP; 91 | break; 92 | case JOYPAD_DOWN: 93 | val = BUTTON_DOWN; 94 | break; 95 | case JOYPAD_A: 96 | val = BUTTON_A; 97 | break; 98 | case JOYPAD_B: 99 | val = BUTTON_B; 100 | break; 101 | case JOYPAD_START: 102 | val = BUTTON_START; 103 | break; 104 | case JOYPAD_SELECT: 105 | val = BUTTON_SELECT; 106 | break; 107 | default: 108 | die(-1, "undefined key %d in joypad_handle()\n", key); 109 | val = 0xFF; // unreachable 110 | } 111 | 112 | if(is_down) { 113 | pressed_keys |= val; 114 | } else { 115 | pressed_keys &= ~val; 116 | } 117 | } -------------------------------------------------------------------------------- /src/include/ioports.h: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #pragma once 6 | 7 | #include 8 | 9 | // misc registers 10 | #define P1 0xFF00 // joypad button status 11 | #define SB 0xFF01 // serial buffer 12 | #define SC 0xFF02 // serial control 13 | #define DIV 0xFF04 // timer divider 14 | #define TIMA 0xFF05 // timer counter 15 | #define TMA 0xFF06 // timer modulo 16 | #define TAC 0xFF07 // timer control 17 | #define IF 0xFF0F // interrupt flag 18 | 19 | // audio controller registers 20 | #define NR10 0xFF10 21 | #define NR11 0xFF11 22 | #define NR12 0xFF12 23 | #define NR13 0xFF13 24 | #define NR14 0xFF14 25 | #define NR21 0xFF16 26 | #define NR22 0xFF17 27 | #define NR23 0xFF18 28 | #define NR24 0xFF19 29 | #define NR30 0xFF1A 30 | #define NR31 0xFF1B 31 | #define NR32 0xFF1C 32 | #define NR33 0xFF1D 33 | #define NR34 0xFF1E 34 | #define NR41 0xFF20 35 | #define NR42 0xFF21 36 | #define NR43 0xFF22 37 | #define NR44 0xFF23 38 | #define NR50 0xFF24 39 | #define NR51 0xFF25 40 | #define NR52 0xFF26 41 | #define WAV00 0xFF30 42 | #define WAV01 0xFF31 43 | #define WAV02 0xFF32 44 | #define WAV03 0xFF33 45 | #define WAV04 0xFF34 46 | #define WAV05 0xFF35 47 | #define WAV06 0xFF36 48 | #define WAV07 0xFF37 49 | #define WAV08 0xFF38 50 | #define WAV09 0xFF39 51 | #define WAV10 0xFF3A 52 | #define WAV11 0xFF3B 53 | #define WAV12 0xFF3C 54 | #define WAV13 0xFF3D 55 | #define WAV14 0xFF3E 56 | #define WAV15 0xFF3F 57 | 58 | // display controller registers 59 | #define LCDC 0xFF40 60 | #define STAT 0xFF41 61 | #define SCY 0xFF42 62 | #define SCX 0xFF43 63 | #define LY 0xFF44 64 | #define LYC 0xFF45 65 | #define DMA 0xFF46 66 | #define BGP 0xFF47 67 | #define OBP0 0xFF48 68 | #define OBP1 0xFF49 69 | #define WY 0xFF4A 70 | #define WX 0xFF4B 71 | 72 | // CGB-only display controller registers 73 | #define VBK 0xFF4F // vram bank 74 | #define HDMA1 0xFF51 75 | #define HDMA2 0xFF52 76 | #define HDMA3 0xFF53 77 | #define HDMA4 0xFF54 78 | #define HDMA5 0xFF55 79 | #define BGPI 0xFF68 // bg palette index/data 80 | #define BGPD 0xFF69 81 | #define OBPI 0xFF6A // obj palette index/data 82 | #define OBPD 0xFF6B 83 | 84 | // misc CGB registers 85 | #define KEY1 0xFF4D 86 | #define RP 0xFF56 87 | #define SVBK 0xFF70 88 | 89 | /// interrupt enable control register 90 | #define IE 0xFFFF 91 | 92 | #define LCDC_ENABLE 0x80 93 | 94 | #define TAC_START 0x04 95 | 96 | #define IF_VLANK 0x01 97 | #define IF_STAT 0x02 98 | #define IF_TIMER 0x04 99 | #define IF_SERIAL 0x08 100 | #define IF_JOYPAD 0x10 101 | 102 | #define IE_VBLANK 0x01 103 | #define IE_STAT 0x02 104 | #define IE_TIMER 0x04 105 | #define IE_SERIAL 0x08 106 | #define IE_JOYPAD 0x10 107 | 108 | void if_write(uint8_t); 109 | void ie_write(uint8_t); 110 | 111 | extern uint8_t io_if, io_ie; 112 | 113 | typedef struct { 114 | uint8_t lcdc, stat, scy, scx, ly, lyc, dma, bgp, obp0, obp1, wx, wy, vbk, hdma1, hdma2, hdma3, hdma4, hdma5; 115 | uint8_t bgpi, bgpd[64], obpi, obpd[64]; 116 | } display_t; 117 | 118 | typedef struct { 119 | uint8_t div, tima, tma, tac; 120 | } timer_regs_t; 121 | 122 | typedef struct { 123 | uint8_t nr10, nr11, nr12, nr13, nr14; 124 | uint8_t nr21, nr22, nr23, nr24; 125 | uint8_t nr30, nr31, nr32, nr33, nr34; 126 | uint8_t nr41, nr42, nr43, nr44; 127 | uint8_t nr50, nr51, nr52; 128 | uint8_t wav[16]; 129 | } sound_t; 130 | 131 | typedef struct { 132 | int bank1, bank2, ram_enable, mode; 133 | } mbc1_t; 134 | 135 | typedef struct { 136 | int rom_bank, ram_rtc_bank, ram_rtc_enable, ram_rtc_toggle; 137 | int latch_data, old_latch_data; 138 | 139 | int h, m, s, d, halt; 140 | } mbc3_t; 141 | 142 | typedef struct { 143 | int rom_bank, ram_bank, ram_enable; 144 | } mbc5_t; 145 | -------------------------------------------------------------------------------- /src/core/timer.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | //#define TIMER_LOG 11 | 12 | timer_regs_t timer; 13 | int timer_cycles = 0; 14 | int div_cycles = 0; 15 | 16 | int timer_freqs[4] = { 17 | 4096, 262144, 65536, 16384 // Hz 18 | }; 19 | 20 | int current_timer_freq; 21 | 22 | void set_timer_freq(uint8_t freq) { 23 | freq &= 3; 24 | 25 | current_timer_freq = timer_freqs[freq]; 26 | 27 | // values that will be used to track timing 28 | double time_per_tick = 1000.0/(double)current_timer_freq; 29 | timing.cpu_cycles_timer = (int)((double)timing.cpu_cycles_ms * time_per_tick); 30 | 31 | if(is_double_speed) timing.cpu_cycles_timer >>= 1; 32 | 33 | write_log("[timer] set timer frequency to %d Hz\n", current_timer_freq); 34 | write_log("[timer] cpu cycles per tick = %d\n", timing.cpu_cycles_timer); 35 | 36 | /*if(timing.cpu_cycles_vline > timing.cpu_cycles_timer) { 37 | timing.main_cycles = GB_HEIGHT+10; 38 | } else { 39 | timing.main_cycles = (int)((double)round(TOTAL_REFRESH_TIME / (double)time_per_tick)); 40 | }*/ 41 | 42 | //write_log("[timer] main loop will repeat %d times per cycle\n", timing.main_cycles); 43 | } 44 | 45 | void timer_start() { 46 | memset(&timer, 0, sizeof(timer_regs_t)); 47 | 48 | write_log("[timer] timer started\n"); 49 | 50 | set_timer_freq(0); 51 | timing.cpu_cycles_div = 256; // standard speed 52 | } 53 | 54 | uint8_t timer_read(uint16_t addr) { 55 | switch(addr) { 56 | case DIV: 57 | #ifdef TIMER_LOG 58 | write_log("[timer] read value 0x%02X from DIV register\n", timer.div); 59 | #endif 60 | return timer.div; 61 | case TIMA: 62 | #ifdef TIMER_LOG 63 | write_log("[timer] read value 0x%02X from TIMA register\n", timer.tima); 64 | #endif 65 | return timer.tima; 66 | case TMA: 67 | #ifdef TIMER_LOG 68 | write_log("[timer] read value 0x%02X from TMA register\n", timer.tma); 69 | #endif 70 | return timer.tma; 71 | case TAC: 72 | #ifdef TIMER_LOG 73 | write_log("[timer] read value 0x%02X from TAC register\n", timer.tac); 74 | #endif 75 | return timer.tac; 76 | default: 77 | write_log("[memory] unimplemented read from I/O port 0x%04X\n", addr); 78 | die(-1, NULL); 79 | return 0xFF; 80 | } 81 | } 82 | 83 | void timer_write(uint16_t addr, uint8_t byte) { 84 | switch(addr) { 85 | case DIV: 86 | #ifdef TIMER_LOG 87 | write_log("[timer] write to DIV register; clearing to zero\n"); 88 | #endif 89 | timer.div = 0; // writing to DIV clears it to zero 90 | break; 91 | case TIMA: 92 | #ifdef TIMER_LOG 93 | write_log("[timer] write to TIMA register value 0x%02X\n", byte); 94 | #endif 95 | timer.tima = byte; 96 | break; 97 | case TMA: 98 | #ifdef TIMER_LOG 99 | write_log("[timer] write to TMA register value 0x%02X\n", byte); 100 | #endif 101 | timer.tma = byte; 102 | break; 103 | case TAC: 104 | #ifdef TIMER_LOG 105 | write_log("[timer] write to TAC register value 0x%02X\n", byte); 106 | #endif 107 | timer.tac = byte; 108 | set_timer_freq(byte & 3); 109 | break; 110 | default: 111 | write_log("[memory] unimplemented write to I/O port 0x%04X value 0x%02X\n", addr, byte); 112 | die(-1, NULL); 113 | } 114 | } 115 | 116 | /*void timer_cycle() { 117 | #ifdef TIMER_LOG 118 | //write_log("[timer] timer cycle\n"); 119 | #endif 120 | 121 | if(timer.tac & TAC_START) { 122 | timer.tima++; 123 | if(!timer.tima) { 124 | timer.tima = timer.tma; 125 | //write_log("[timer] timer interrupt\n"); 126 | } 127 | } 128 | }*/ 129 | 130 | void timer_cycle() { 131 | div_cycles += timing.last_instruction_cycles; 132 | 133 | if(div_cycles >= timing.cpu_cycles_div) { 134 | div_cycles -= timing.cpu_cycles_div; 135 | timer.div++; 136 | } 137 | 138 | if(!(timer.tac & TAC_START)) return; 139 | 140 | timer_cycles += timing.last_instruction_cycles; 141 | if(timer_cycles >= timing.cpu_cycles_timer) { 142 | timer_cycles -= timing.cpu_cycles_timer; 143 | timer.tima++; 144 | if(!timer.tima) { 145 | timer.tima = timer.tma; 146 | //write_log("[timer] sending timer interrupt\n"); 147 | send_interrupt(2); 148 | } 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/config.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define DEFAULT_A "z" 10 | #define DEFAULT_B "x" 11 | #define DEFAULT_START "return" 12 | #define DEFAULT_SELECT "rshift" 13 | #define DEFAULT_UP "up" 14 | #define DEFAULT_DOWN "down" 15 | #define DEFAULT_LEFT "left" 16 | #define DEFAULT_RIGHT "right" 17 | #define DEFAULT_THROTTLE "space" 18 | #define DEFAULT_SYSTEM "auto" 19 | #define DEFAULT_PREFERENCE "cgb" 20 | #define DEFAULT_BORDER "yes" 21 | #define DEFAULT_SCALING "2" 22 | #define DEFAULT_PALETTE "0" 23 | #define DEFAULT_SPEED "100" 24 | 25 | config_file_t config_file; 26 | 27 | int throttle_lo, throttle_hi; 28 | int target_speed; 29 | int config_system; 30 | int config_preference; 31 | int config_border; 32 | 33 | static FILE *file; 34 | 35 | static char nullstr[2]; 36 | static char line[200]; 37 | 38 | static void load_defaults() { 39 | config_file.a = DEFAULT_A; 40 | config_file.b = DEFAULT_B; 41 | config_file.start = DEFAULT_START; 42 | config_file.select = DEFAULT_SELECT; 43 | config_file.up = DEFAULT_UP; 44 | config_file.down = DEFAULT_DOWN; 45 | config_file.left = DEFAULT_LEFT; 46 | config_file.right = DEFAULT_RIGHT; 47 | 48 | config_file.throttle = DEFAULT_THROTTLE; 49 | 50 | config_file.system = DEFAULT_SYSTEM; 51 | config_file.preference = DEFAULT_PREFERENCE; 52 | config_file.border = DEFAULT_BORDER; 53 | config_file.scaling = DEFAULT_SCALING; 54 | config_file.palette = DEFAULT_PALETTE; 55 | config_file.speed = DEFAULT_SPEED; 56 | 57 | scaling = 2; 58 | monochrome_palette = 0; 59 | throttle_lo = 98; 60 | throttle_hi = 102; 61 | } 62 | 63 | static void lowercase(char *str) { 64 | for(int i = 0; str[i]; i++) { 65 | if(str[i] >= 'A' && str[i] <= 'Z') { 66 | str[i] += 32; 67 | } 68 | } 69 | } 70 | 71 | char *get_property(char *property) { 72 | fseek(file, 0L, SEEK_SET); 73 | 74 | int len = strlen(property); 75 | 76 | char *value; 77 | 78 | while(fgets(line, 199, file)) { 79 | lowercase(line); 80 | 81 | if(!memcmp(line, property, len)) { 82 | // found property 83 | int i = len; 84 | while(line[i] != '=' && line[i] != '\n' && line[i] != '\r') i++; 85 | 86 | if(line[i] == '\n' || line[i] == '\r') { 87 | // property has no value 88 | return nullstr; 89 | } 90 | 91 | while(line[i] == ' ' || line[i] == '=') i++; 92 | 93 | // now copy the value 94 | value = calloc(strlen(line+i)+1, 1); 95 | if(!value) { 96 | write_log("[config] unable to allocate memory for property '%s', assuming defaults\n", property); 97 | return nullstr; 98 | } 99 | 100 | int j = 0; 101 | while(line[i+j] != '\n' && line[i+j] != '\r' && line[i+j] != 0 && line[i+j] != ';' && line[i+j] != ' ') { 102 | value[j] = line[i+j]; 103 | j++; 104 | } 105 | 106 | write_log("[config] property '%s' is set to '%s'\n", property, value); 107 | return value; 108 | } 109 | } 110 | 111 | write_log("[config] property '%s' doesn't exist, assuming default\n", property); 112 | return nullstr; 113 | } 114 | 115 | void open_config() { 116 | nullstr[0] = 0; 117 | file = fopen("tinygb.ini", "r"); 118 | if(!file) { 119 | write_log("[config] unable to open tinygb.ini for reading, loading default settings\n"); 120 | 121 | load_defaults(); 122 | } 123 | else 124 | { 125 | config_file.a = get_property("a"); 126 | config_file.b = get_property("b"); 127 | config_file.start = get_property("start"); 128 | config_file.select = get_property("select"); 129 | config_file.up = get_property("up"); 130 | config_file.down = get_property("down"); 131 | config_file.left = get_property("left"); 132 | config_file.right = get_property("right"); 133 | config_file.throttle = get_property("throttle"); 134 | config_file.system = get_property("system"); 135 | config_file.preference = get_property("preference"); 136 | config_file.border = get_property("border"); 137 | config_file.scaling = get_property("scaling"); 138 | config_file.palette = get_property("palette"); 139 | config_file.speed = get_property("speed"); 140 | 141 | fclose(file); 142 | } 143 | 144 | if(!strcmp(config_file.system, "auto")) config_system = SYSTEM_AUTO; 145 | else if(!strcmp(config_file.system, "gb")) config_system = SYSTEM_GB; 146 | else if(!strcmp(config_file.system, "sgb2")) config_system = SYSTEM_SGB2; 147 | else if(!strcmp(config_file.system, "cgb")) config_system = SYSTEM_CGB; 148 | else config_system = SYSTEM_AUTO; // default 149 | 150 | if(!strcmp(config_file.preference, "cgb")) config_preference = PREFER_CGB; 151 | else if(!strcmp(config_file.preference, "gb")) config_preference = PREFER_GB; 152 | else config_preference = PREFER_CGB; // default 153 | 154 | if(!strcmp(config_file.border, "yes")) config_border = 1; 155 | else if(!strcmp(config_file.border, "no")) config_border = 0; 156 | else config_border = 1; // default 157 | 158 | scaling = atoi(config_file.scaling); 159 | if(!scaling) scaling = 2; // default 160 | 161 | monochrome_palette = atoi(config_file.palette); 162 | if(monochrome_palette > 9) monochrome_palette = 0; 163 | 164 | target_speed = atoi(config_file.speed); 165 | if(target_speed < 10 || target_speed > 500) { 166 | write_log("[config] target emulation speed must be between 10-500%%, defaulting to 100%%\n"); 167 | target_speed = 100; 168 | } 169 | 170 | throttle_lo = target_speed-SPEED_ALLOWANCE; 171 | throttle_hi = target_speed+SPEED_ALLOWANCE; 172 | } 173 | -------------------------------------------------------------------------------- /src/include/tinygb.h: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #pragma once 6 | 7 | #include 8 | #include 9 | //#include 10 | 11 | #define GB_WIDTH 160 12 | #define GB_HEIGHT 144 13 | 14 | #define SGB_WIDTH 256 15 | #define SGB_HEIGHT 224 16 | 17 | #define GB_CPU_SPEED 4194304 // Hz 18 | #define CGB_CPU_SPEED 8388608 19 | 20 | #define SYSTEM_AUTO 0 21 | #define SYSTEM_GB 1 22 | #define SYSTEM_SGB2 2 23 | #define SYSTEM_CGB 3 24 | 25 | #define PREFER_CGB 0 26 | #define PREFER_GB 1 27 | 28 | /* DISPLAY: 29 | 30 | Width 160 px 31 | Height 144 px 32 | Refresh rate 59.7 Hz 33 | Visible v-lines 144 lines 34 | Invisible v-lines 10 lines 35 | Total v-lines 154 lines 36 | Total refresh time 16.7504 ms 37 | Time per v-line 0.108769 ms 38 | Time for visible lines 15.6627 ms 39 | Time for invisible lines 1.08769 ms 40 | V-sync pause 1.08769 ms 41 | 42 | * VALUES TO BE USED TO KEEP THINGS IN SYNC: 43 | 44 | CPU Cycles/Millisecond CPU_SPEED/1000 45 | CPU Cycles/Refresh Line Above * Time per v-line 46 | 47 | */ 48 | 49 | #define REFRESH_RATE 59.7 // Hz 50 | #define TOTAL_REFRESH_TIME 16.7504 // ms 51 | #define REFRESH_TIME_LINE 0.108769 // ms 52 | #define VSYNC_PAUSE 1.08769 // ms 53 | #define OAM_SIZE 160 // bytes 54 | 55 | // CPU throttle 56 | #define THROTTLE_THRESHOLD 18 // ms 57 | #define SPEED_ALLOWANCE 4 // +/- 4% from the target speed 58 | 59 | #define JOYPAD_A 1 60 | #define JOYPAD_B 2 61 | #define JOYPAD_START 3 62 | #define JOYPAD_SELECT 4 63 | #define JOYPAD_RIGHT 5 64 | #define JOYPAD_LEFT 6 65 | #define JOYPAD_UP 7 66 | #define JOYPAD_DOWN 8 67 | 68 | typedef struct { 69 | int cpu_cycles_ms, cpu_cycles_vline, cpu_cycles_timer, cpu_cycles_div; 70 | int current_cycles; 71 | int main_cycles; // how many times we should cycle in main() 72 | int last_instruction_cycles; 73 | } timing_t; 74 | 75 | typedef struct { 76 | //uint16_t af, bc, de, hl, sp, pc, ime; 77 | uint16_t ime, sp, pc; 78 | 79 | union { 80 | uint16_t af; 81 | struct { 82 | uint8_t f; 83 | uint8_t a; 84 | }; 85 | }; 86 | 87 | union { 88 | uint16_t bc; 89 | struct { 90 | uint8_t c; 91 | uint8_t b; 92 | }; 93 | }; 94 | 95 | union { 96 | uint16_t de; 97 | struct { 98 | uint8_t e; 99 | uint8_t d; 100 | }; 101 | }; 102 | 103 | union { 104 | uint16_t hl; 105 | struct { 106 | uint8_t l; 107 | uint8_t h; 108 | }; 109 | }; 110 | } cpu_t; 111 | 112 | typedef struct { 113 | char *a, *b, *start, *select, *up, *down, *left, *right; 114 | char *throttle; 115 | char *speed, *palette, *scaling, *system, *preference, *border; 116 | } config_file_t; 117 | 118 | #define FLAG_ZF 0x80 119 | #define FLAG_N 0x40 120 | #define FLAG_H 0x20 121 | #define FLAG_CY 0x10 122 | 123 | extern long rom_size; 124 | extern void *rom, *ram, *vram; 125 | extern int is_cgb, is_sgb; 126 | 127 | extern char *rom_filename; 128 | 129 | extern int cpu_speed; 130 | 131 | extern int scaling, frameskip; 132 | extern int scaled_w, scaled_h; 133 | extern int throttle_hi, throttle_lo; 134 | 135 | //extern SDL_Window *window; 136 | //extern SDL_Surface *surface; 137 | 138 | extern timing_t timing; 139 | extern int mbc_type; 140 | 141 | extern config_file_t config_file; 142 | extern int target_speed; 143 | void update_window(uint32_t *); 144 | void update_border(uint32_t *); 145 | void destroy_window(); 146 | void delay(int); 147 | void resize_sgb_window(); 148 | 149 | void open_log(); 150 | void open_config(); 151 | void write_log(const char *, ...); 152 | void die(int, const char *, ...); 153 | void memory_start(); 154 | void cpu_start(); 155 | void display_start(); 156 | void timer_start(); 157 | void sound_start(); 158 | 159 | extern int config_system; 160 | extern int config_preference; 161 | extern int config_border; 162 | 163 | // cpu 164 | extern int throttle_enabled, throttle_time, cycles_per_throttle; 165 | void cpu_cycle(); 166 | void cpu_log(); 167 | 168 | // memory 169 | extern int work_ram_bank; 170 | uint8_t read_byte(uint16_t); 171 | uint16_t read_word(uint16_t); 172 | void write_byte(uint16_t, uint8_t); 173 | void copy_oam(void *); 174 | void mbc_start(void *); 175 | void mbc_write(uint16_t, uint8_t); 176 | uint8_t mbc_read(uint16_t); 177 | 178 | // interrupts 179 | uint8_t if_read(); 180 | uint8_t ie_read(); 181 | void send_interrupt(int); 182 | 183 | // display 184 | extern int drawn_frames, framecount; 185 | extern int monochrome_palette; 186 | void next_palette(); 187 | void prev_palette(); 188 | void scale_xline(uint32_t *, uint32_t *, int); 189 | void hflip_tile(uint32_t *, int, int); 190 | void vflip_tile(uint32_t *, int, int); 191 | void display_write(uint16_t, uint8_t); 192 | uint8_t display_read(uint16_t); 193 | void display_cycle(); 194 | void vram_write(uint16_t, uint8_t); 195 | uint8_t vram_read(uint16_t); 196 | 197 | // serial 198 | void sb_write(uint8_t); 199 | void sc_write(uint8_t); 200 | 201 | // timer 202 | void timer_write(uint16_t, uint8_t); 203 | uint8_t timer_read(uint16_t); 204 | void timer_cycle(); 205 | 206 | // sound 207 | void sound_write(uint16_t, uint8_t); 208 | uint8_t sound_read(uint16_t); 209 | 210 | // joypad 211 | extern uint8_t pressed_keys; 212 | void joypad_write(uint16_t, uint8_t); 213 | uint8_t joypad_read(uint16_t); 214 | void joypad_handle(int, int); 215 | 216 | // SGB functions 217 | extern int sgb_transferring, sgb_interfere, sgb_screen_mask, using_sgb_palette, using_sgb_border; 218 | extern int gb_x, gb_y; 219 | extern int sgb_scaled_h, sgb_scaled_w; 220 | void sgb_start(); 221 | void sgb_write(uint8_t); 222 | uint8_t sgb_read(); 223 | void sgb_recolor(uint32_t *, uint32_t *, int, uint32_t *); 224 | uint32_t truecolor(uint16_t); 225 | 226 | // CGB functions 227 | //#define CGB_DEBUG 228 | extern int is_double_speed; 229 | extern int prepare_speed_switch; 230 | void cgb_write(uint16_t, uint8_t); 231 | uint8_t cgb_read(uint16_t); 232 | void cgb_dump_bgpd(); 233 | void cgb_dump_obpd(); 234 | -------------------------------------------------------------------------------- /src/core/sound.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | //#define SOUND_LOG 10 | 11 | sound_t sound; 12 | 13 | void sound_start() { 14 | memset(&sound, 0, sizeof(sound_t)); 15 | 16 | sound.nr10 = 0x80; 17 | sound.nr11 = 0xBF; 18 | sound.nr12 = 0xF3; 19 | sound.nr14 = 0xBF; 20 | sound.nr21 = 0x3F; 21 | sound.nr22 = 0x00; 22 | sound.nr24 = 0xBF; 23 | sound.nr30 = 0x7F; 24 | sound.nr31 = 0xFF; 25 | sound.nr32 = 0x9F; 26 | sound.nr33 = 0xBF; 27 | sound.nr41 = 0xFF; 28 | sound.nr42 = 0x00; 29 | sound.nr43 = 0x00; 30 | sound.nr44 = 0xBF; 31 | sound.nr50 = 0x77; 32 | sound.nr51 = 0xF3; 33 | sound.nr52 = 0xF1; 34 | 35 | write_log("[sound] started sound device\n"); 36 | } 37 | 38 | uint8_t sound_read(uint16_t addr) { 39 | switch(addr) { 40 | case NR10: 41 | return sound.nr10; 42 | case NR11: 43 | return sound.nr11; 44 | case NR12: 45 | return sound.nr12; 46 | case NR13: 47 | return sound.nr13; 48 | case NR14: 49 | return sound.nr14; 50 | case NR21: 51 | return sound.nr21; 52 | case NR22: 53 | return sound.nr22; 54 | case NR23: 55 | return sound.nr23; 56 | case NR24: 57 | return sound.nr24; 58 | case NR30: 59 | return sound.nr30; 60 | case NR31: 61 | return sound.nr31; 62 | case NR32: 63 | return sound.nr32; 64 | case NR33: 65 | return sound.nr33; 66 | case NR34: 67 | return sound.nr34; 68 | case NR41: 69 | return sound.nr41; 70 | case NR42: 71 | return sound.nr42; 72 | case NR43: 73 | return sound.nr43; 74 | case NR44: 75 | return sound.nr44; 76 | case NR50: 77 | return sound.nr50; 78 | case NR51: 79 | return sound.nr51; 80 | case NR52: 81 | return sound.nr52; 82 | case WAV00: 83 | case WAV01: 84 | case WAV02: 85 | case WAV03: 86 | case WAV04: 87 | case WAV05: 88 | case WAV06: 89 | case WAV07: 90 | case WAV08: 91 | case WAV09: 92 | case WAV10: 93 | case WAV11: 94 | case WAV12: 95 | case WAV13: 96 | case WAV14: 97 | case WAV15: 98 | return sound.wav[addr-WAV00]; 99 | default: 100 | write_log("[memory] unimplemented read from I/O port 0x%04X\n", addr); 101 | die(-1, NULL); 102 | return 0xFF; 103 | } 104 | } 105 | 106 | void sound_write(uint16_t addr, uint8_t byte) { 107 | switch(addr) { 108 | case NR50: 109 | #ifdef SOUND_LOG 110 | write_log("[sound] write to NR50 register value 0x%02X\n", byte); 111 | #endif 112 | sound.nr50 = byte; 113 | return; 114 | case NR51: 115 | #ifdef SOUND_LOG 116 | write_log("[sound] write to NR51 register value 0x%02X\n", byte); 117 | #endif 118 | sound.nr51 = byte; 119 | return; 120 | case NR52: 121 | #ifdef SOUND_LOG 122 | write_log("[sound] write to NR52 register value 0x%02X, ignoring lower 7 bits\n", byte); 123 | #endif 124 | if(byte & 0x80) sound.nr52 |= 0x80; 125 | else sound.nr52 &= 0x7F; 126 | return; 127 | case NR10: 128 | #ifdef SOUND_LOG 129 | write_log("[sound] write to NR10 register value 0x%02X\n", byte); 130 | #endif 131 | sound.nr10 = byte; 132 | return; 133 | case NR11: 134 | #ifdef SOUND_LOG 135 | write_log("[sound] write to NR11 register value 0x%02X\n", byte); 136 | #endif 137 | sound.nr11 = byte; 138 | return; 139 | case NR12: 140 | #ifdef SOUND_LOG 141 | write_log("[sound] write to NR12 register value 0x%02X\n", byte); 142 | #endif 143 | sound.nr12 = byte; 144 | return; 145 | case NR13: 146 | #ifdef SOUND_LOG 147 | write_log("[sound] write to NR13 register value 0x%02X\n", byte); 148 | #endif 149 | sound.nr13 = byte; 150 | return; 151 | case NR14: 152 | #ifdef SOUND_LOG 153 | write_log("[sound] write to NR15 register value 0x%02X\n", byte); 154 | #endif 155 | sound.nr14 = byte; 156 | return; 157 | case NR21: 158 | #ifdef SOUND_LOG 159 | write_log("[sound] write to NR21 register value 0x%02X\n", byte); 160 | #endif 161 | sound.nr21 = byte; 162 | return; 163 | case NR22: 164 | #ifdef SOUND_LOG 165 | write_log("[sound] write to NR22 register value 0x%02X\n", byte); 166 | #endif 167 | sound.nr22 = byte; 168 | return; 169 | case NR23: 170 | #ifdef SOUND_LOG 171 | write_log("[sound] write to NR23 register value 0x%02X\n", byte); 172 | #endif 173 | sound.nr23 = byte; 174 | return; 175 | case NR24: 176 | #ifdef SOUND_LOG 177 | write_log("[sound] write to NR24 register value 0x%02X\n", byte); 178 | #endif 179 | sound.nr24 = byte; 180 | return; 181 | case NR30: 182 | #ifdef SOUND_LOG 183 | write_log("[sound] write to NR30 register value 0x%02X\n", byte); 184 | #endif 185 | sound.nr30 = byte; 186 | return; 187 | case NR31: 188 | #ifdef SOUND_LOG 189 | write_log("[sound] write to NR31 register value 0x%02X\n", byte); 190 | #endif 191 | sound.nr31 = byte; 192 | return; 193 | case NR32: 194 | #ifdef SOUND_LOG 195 | write_log("[sound] write to NR32 register value 0x%02X\n", byte); 196 | #endif 197 | sound.nr32 = byte; 198 | return; 199 | case NR33: 200 | #ifdef SOUND_LOG 201 | write_log("[sound] write to NR33 register value 0x%02X\n", byte); 202 | #endif 203 | sound.nr33 = byte; 204 | return; 205 | case NR34: 206 | #ifdef SOUND_LOG 207 | write_log("[sound] write to NR34 register value 0x%02X\n", byte); 208 | #endif 209 | sound.nr34 = byte; 210 | return; 211 | case WAV00: 212 | case WAV01: 213 | case WAV02: 214 | case WAV03: 215 | case WAV04: 216 | case WAV05: 217 | case WAV06: 218 | case WAV07: 219 | case WAV08: 220 | case WAV09: 221 | case WAV10: 222 | case WAV11: 223 | case WAV12: 224 | case WAV13: 225 | case WAV14: 226 | case WAV15: 227 | #ifdef SOUND_LOG 228 | write_log("[sound] write to WAX%02d register value 0x%02X\n", addr-WAV00, byte); 229 | #endif 230 | sound.wav[addr-WAV00] = byte; 231 | return; 232 | case NR41: 233 | #ifdef SOUND_LOG 234 | write_log("[sound] write to NR41 register value 0x%02X\n", byte); 235 | #endif 236 | sound.nr41 = byte; 237 | return; 238 | case NR42: 239 | #ifdef SOUND_LOG 240 | write_log("[sound] write to NR42 register value 0x%02X\n", byte); 241 | #endif 242 | sound.nr42 = byte; 243 | return; 244 | case NR43: 245 | #ifdef SOUND_LOG 246 | write_log("[sound] write to NR43 register value 0x%02X\n", byte); 247 | #endif 248 | sound.nr43 = byte; 249 | return; 250 | case NR44: 251 | #ifdef SOUND_LOG 252 | write_log("[sound] write to NR44 register value 0x%02X\n", byte); 253 | #endif 254 | sound.nr44 = byte; 255 | return; 256 | default: 257 | write_log("[memory] unimplemented write to I/O port 0x%04X value 0x%02X\n", addr, byte); 258 | die(-1, NULL); 259 | } 260 | } -------------------------------------------------------------------------------- /src/platform/sdl/README-tinyfiledialogs.txt: -------------------------------------------------------------------------------- 1 | tiny file dialogs ( cross-platform C C++ ) v3.8.8 [Apr 22, 2021] zlib licence 2 | _________ 3 | / \ Tray-popup InputBox PasswordBox MessageBox Notification Beep 4 | |tiny file| ColorPicker OpenFileDialog SaveFileDialog SelectFolderDialog 5 | | dialogs | ASCII UTF-8 (and also MBCS & UTF-16 for windows) 6 | \____ ___/ Native dialog library for WINDOWS MAC OSX GTK+ QT CONSOLE 7 | \| SSH support via automatic switch to console mode or X forwarding 8 | 9 | C89/C18 & C++98/C++20 compliant: tested with C & C++ compilers 10 | VisualStudio MinGW GCC Clang TinyCC OpenWatcom-v2 BorlandC SunCC ZapCC 11 | on Windows Mac Linux Bsd Solaris Minix Raspbian Flatpak 12 | using Gnome Kde Mate Enlightenment Cinnamon Budgie Unity Lxde Lxqt Xfce 13 | WindowMaker IceWm Cde Jds OpenBox Awesome Jwm Xdm Cwm 14 | 15 | Bindings for LUA and C# dll, Haskell, Fortran. Included in LWJGL(java), Rust, Allegrobasic 16 | 17 | http://tinyfiledialogs.sourceforge.net 18 | git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd 19 | ____________________________________________________________________________ 20 | | ________________________________________________________________________ | 21 | | | ____________________________________________________________________ | | 22 | | | | If you like tinyfiledialogs, please upvote my stackoverflow answer | | | 23 | | | | https://stackoverflow.com/a/47651444 | | | 24 | | | |____________________________________________________________________| | | 25 | | |________________________________________________________________________| | 26 | |____________________________________________________________________________| 27 | _____________________________________________________________________ 28 | | | 29 | | my email address is at the top of the header file tinyfiledialogs.h | 30 | |_____________________________________________________________________| 31 | ________________________________________________________________________________ 32 | | ____________________________________________________________________________ | 33 | | | | | 34 | | | on windows: | | 35 | | | - for UTF-16, use the wchar_t functions at the bottom of the header file | | 36 | | | - _wfopen() requires wchar_t | | 37 | | | | | 38 | | | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | | 39 | | | - but fopen() expects MBCS (not UTF-8) | | 40 | | | - if you want char to be MBCS: set tinyfd_winUtf8 = 0 | | 41 | | | | | 42 | | | - alternatively, tinyfiledialogs provides | | 43 | | | functions to convert between UTF-8, UTF-16 and MBCS | | 44 | | |____________________________________________________________________________| | 45 | |________________________________________________________________________________| 46 | 47 | void tinyfd_beep(); 48 | 49 | int tinyfd_notifyPopup( 50 | char const * aTitle , // NULL or "" 51 | char const * aMessage , // NULL or "" may contain \n \t 52 | char const * aIconType ); // "info" "warning" "error" 53 | 54 | int tinyfd_messageBox( 55 | char const * aTitle , // NULL or "" 56 | char const * aMessage , // NULL or "" may contain \n \t 57 | char const * aDialogType , // "ok" "okcancel" "yesno" "yesnocancel" 58 | char const * aIconType , // "info" "warning" "error" "question" 59 | int aDefaultButton ); 60 | // 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel 61 | 62 | char const * tinyfd_inputBox( 63 | char const * aTitle , // NULL or "" 64 | char const * aMessage , // NULL or "" (\n and \t have no effect) 65 | char const * aDefaultInput ); // NULL for a passwordBox, "" for an inputbox 66 | // returns NULL on cancel 67 | 68 | char const * tinyfd_saveFileDialog( 69 | char const * aTitle , // NULL or "" 70 | char const * aDefaultPathAndFile , // NULL or "" 71 | int aNumOfFilterPatterns , // 0 (1 in the following example) 72 | char const * const * aFilterPatterns , // NULL or char const * lFilterPatterns[1]={"*.txt"}; 73 | char const * aSingleFilterDescription ); // NULL or "text files" 74 | // returns NULL on cancel 75 | 76 | char const * tinyfd_openFileDialog( 77 | char const * aTitle , // NULL or "" 78 | char const * aDefaultPathAndFile , // NULL or "" 79 | int aNumOfFilterPatterns , // 0 (2 in the following example) 80 | char const * const * aFilterPatterns , // NULL or char const * lFilterPatterns[2]={"*.png","*.jpg"}; 81 | char const * aSingleFilterDescription , // NULL or "image files" 82 | int aAllowMultipleSelects ); // 0 83 | // in case of multiple files, the separator is | 84 | // returns NULL on cancel 85 | 86 | char const * tinyfd_selectFolderDialog( 87 | char const * aTitle , // NULL or "" 88 | char const * aDefaultPath ); // NULL or "" 89 | // returns NULL on cancel 90 | 91 | char const * tinyfd_colorChooser( 92 | char const * aTitle , // NULL or "" 93 | char const * aDefaultHexRGB , // NULL or "#FF0000” 94 | unsigned char const aDefaultRGB[3] , // unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; 95 | unsigned char aoResultRGB[3] ); // unsigned char lResultRGB[3]; 96 | // returns the hexcolor as a string "#FF0000" 97 | // aoResultRGB also contains the result 98 | // aDefaultRGB is used only if aDefaultHexRGB is NULL 99 | // aDefaultRGB and aoResultRGB can be the same array 100 | // returns NULL on cancel 101 | ___________________________________________________________________________________ 102 | | _______________________________________________________________________________ | 103 | | | | | 104 | | | wchar_t UTF-16 (windows only) prototypes are at the bottom of the header file | | 105 | | |_______________________________________________________________________________| | 106 | |___________________________________________________________________________________| 107 | 108 | - This is not for ios nor android (it works in termux though). 109 | - The files can be renamed with extension ".cpp" as the code is 100% compatible C C++ 110 | (just comment out << extern "C" >> in the header file) 111 | - Windows is fully supported from XP to 10 (maybe even older versions) 112 | - C# & LUA via dll, see files in the folder EXTRAS 113 | - OSX supported from 10.4 to latest (maybe even older versions) 114 | - Do not use " and ' as the dialogs will be display with a warning 115 | instead of the title, message, etc... 116 | - There's one file filter only, it may contain several patterns. 117 | - If no filter description is provided, 118 | the list of patterns will become the description. 119 | - On windows link against Comdlg32.lib and Ole32.lib 120 | (on windows the no linking claim is a lie) 121 | - On unix: it tries command line calls, so no such need (NO LINKING). 122 | - On unix you need one of the following: 123 | applescript, kdialog, zenity, matedialog, shellementary, qarma, yad, 124 | python (2 or 3)/tkinter/python-dbus (optional), Xdialog 125 | or curses dialogs (opens terminal if running without console). 126 | - One of those is already included on most (if not all) desktops. 127 | - In the absence of those it will use gdialog, gxmessage or whiptail 128 | with a textinputbox. 129 | - If nothing is found, it switches to basic console input, 130 | it opens a console if needed (requires xterm + bash). 131 | - for curses dialogs you must set tinyfd_allowCursesDialogs=1 132 | - You can query the type of dialog that will be used (pass "tinyfd_query" as aTitle) 133 | - String memory is preallocated statically for all the returned values. 134 | - File and path names are tested before return, they should be valid. 135 | - tinyfd_forceConsole=1; at run time, forces dialogs into console mode. 136 | - On windows, console mode only make sense for console applications. 137 | - On windows, console mode is not implemented for wchar_T UTF-16. 138 | - Mutiple selects are not possible in console mode. 139 | - The package dialog must be installed to run in curses dialogs in console mode. 140 | It is already installed on most unix systems. 141 | - On osx, the package dialog can be installed via 142 | http://macappstore.org/dialog or http://macports.org 143 | - On windows, for curses dialogs console mode, 144 | dialog.exe should be copied somewhere on your executable path. 145 | It can be found at the bottom of the following page: 146 | http://andrear.altervista.org/home/cdialog.php 147 | _________________________________________________________________ 148 | | | 149 | | The project provides an Hello World example: | 150 | | if a console is missing, it will use graphic dialogs | 151 | | if a graphical display is absent, it will use console dialogs | 152 | |_________________________________________________________________| 153 | 154 | OSX : 155 | $ clang -o hello.app hello.c tinyfiledialogs.c 156 | ( or gcc ) 157 | 158 | UNIX : 159 | $ gcc -o hello hello.c tinyfiledialogs.c 160 | ( or clang tcc owcc cc CC ) 161 | 162 | Windows : 163 | MinGW needs gcc >= v4.9 otherwise some headers are incomplete 164 | > gcc -o hello.exe hello.c tinyfiledialogs.c -LC:/mingw/lib -lcomdlg32 -lole32 165 | 166 | TinyCC needs >= v0.9.27 (+ tweaks - contact me) otherwise some headers are missing 167 | > tcc -o hello.exe hello.c tinyfiledialogs.c ^ 168 | -isystem C:\tcc\winapi-full-for-0.9.27\include\winapi ^ 169 | -lcomdlg32 -lole32 -luser32 -lshell32 170 | 171 | Borland C: > bcc32c -o hello.exe hello.c tinyfiledialogs.c 172 | OpenWatcom v2: create a character-mode executable project. 173 | 174 | VisualStudio : 175 | Create a console application project, 176 | it links against comdlg32.lib & ole32.lib. 177 | 178 | VisualStudio command line : 179 | > cl hello.c tinyfiledialogs.c comdlg32.lib ole32.lib user32.lib shell32.lib /W4 180 | -------------------------------------------------------------------------------- /src/core/memory.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define MEMORY_LOG 12 | 13 | /* 14 | *** MEMORY MAP *** 15 | 16 | 0000-3FFF 16KB ROM Bank 00 (in cartridge, fixed at bank 00) 17 | 4000-7FFF 16KB ROM Bank 01..NN (in cartridge, switchable bank number) 18 | 8000-9FFF 8KB Video RAM (VRAM) (switchable bank 0-1 in CGB Mode) 19 | A000-BFFF 8KB External RAM (in cartridge, switchable bank, if any) 20 | C000-CFFF 4KB Work RAM Bank 0 (WRAM) 21 | D000-DFFF 4KB Work RAM Bank 1 (WRAM) (switchable bank 1-7 in CGB Mode) 22 | E000-FDFF Same as C000-DDFF (ECHO) (typically not used) 23 | FE00-FE9F Sprite Attribute Table (OAM) 24 | FEA0-FEFF Not Usable 25 | FF00-FF7F I/O Ports 26 | FF80-FFFE High RAM (HRAM) 27 | FFFF Interrupt Enable Register 28 | 29 | */ 30 | 31 | void *ram = NULL, *rom = NULL; 32 | char game_title[17]; 33 | 34 | uint8_t *cartridge_type, *cgb_compatibility; 35 | 36 | int mbc_type; 37 | 38 | #define WORK_RAM (0) // +0 39 | #define HIGH_RAM (32768) // +32k assuming work RAM has a maximum of 8x4k banks 40 | #define CART_RAM (HIGH_RAM+128) // +128 because HRAM is exactly 127 bytes long but add one byte for alignment 41 | #define OAM (CART_RAM+1048576) // +1 MB 42 | 43 | int rom_bank = 1; 44 | int cart_ram_bank = 0; 45 | int work_ram_bank = 1; 46 | int is_cgb = 0, is_sgb = 0; 47 | 48 | void memory_start() { 49 | // rom was already initialized in main.c 50 | ram = calloc(1024, 1058); // 1 MB is the maximum RAM size in MBC5 + 33 KB for WRAM + HRAM 51 | if(!ram) { 52 | die(1, "[memory] unable to allocate RAM\n"); 53 | } 54 | 55 | // copy the game's title 56 | memset(game_title, 0, 17); 57 | memcpy(game_title, rom+0x134, 16); 58 | write_log("[rom] game title is %s\n", game_title); 59 | 60 | cgb_compatibility = (uint8_t *)rom + 0x143; 61 | if(*cgb_compatibility == 0x80) { 62 | write_log("[rom] game supports both CGB and original GB\n"); 63 | 64 | if(config_system == SYSTEM_AUTO) { 65 | if(config_preference == PREFER_CGB) is_cgb = 1; 66 | else is_cgb = 0; 67 | } else if(config_system == SYSTEM_GB || config_system == SYSTEM_SGB2) is_cgb = 0; 68 | else is_cgb = 1; 69 | } else if(*cgb_compatibility == 0xC0) { 70 | write_log("[rom] game only works on CGB\n"); 71 | if(config_system != SYSTEM_CGB && config_system != SYSTEM_AUTO) is_cgb = 0; 72 | else is_cgb = 1; 73 | } else if(!*cgb_compatibility) { 74 | write_log("[rom] game doesn't support CGB\n"); 75 | 76 | is_cgb = 0; 77 | 78 | if(config_system == SYSTEM_CGB) { 79 | write_log("[config] WARNING: config file is set to emulate CGB on ROM that doesn't support CGB, overriding like a real CGB would\n"); 80 | } 81 | } else { 82 | write_log("[rom] undefined CGB compatibility value 0x%02X, assuming game runs on original GB...\n", *cgb_compatibility); 83 | is_cgb = 0; 84 | } 85 | 86 | if(!is_cgb) { 87 | uint8_t *sgb_flag = (uint8_t *)rom + 0x146; 88 | if(*sgb_flag == 0x03) { 89 | write_log("[rom] game supports SGB; "); 90 | 91 | if(config_system == SYSTEM_AUTO || config_system == SYSTEM_SGB2) { 92 | write_log("SGB2 functions will be enabled\n"); 93 | is_sgb = 1; 94 | sgb_start(); 95 | } else { 96 | write_log("but SGB functions are disabled in config file\n"); 97 | is_sgb = 0; 98 | } 99 | } else { 100 | write_log("[rom] game doesn't support SGB\n"); 101 | is_sgb = 0; 102 | } 103 | } 104 | 105 | cartridge_type = (uint8_t *)rom + 0x147; 106 | 107 | switch(*cartridge_type) { 108 | case 0x00: 109 | mbc_type = 0; 110 | break; 111 | case 0x01: 112 | case 0x02: 113 | case 0x03: 114 | mbc_type = 1; 115 | break; 116 | /*case 0x05: 117 | case 0x06: 118 | mbc_type = 2; 119 | break;*/ 120 | case 0x0F: 121 | case 0x10: 122 | case 0x11: 123 | case 0x12: 124 | case 0x13: 125 | mbc_type = 3; 126 | break; 127 | case 0x19: 128 | case 0x1A: 129 | case 0x1B: 130 | /*case 0x1C: 131 | case 0x1D: 132 | case 0x1E:*/ // skip these because wtf is a "rumble" 133 | mbc_type = 5; 134 | break; 135 | default: 136 | die(-1, "[mbc] cartridge type is 0x%02X: unimplemented\n", *cartridge_type); 137 | break; 138 | } 139 | 140 | if(!mbc_type) { 141 | write_log("[mbc] cartridge type is 0x%02X: no MBC\n", *cartridge_type); 142 | } else { 143 | write_log("[mbc] cartridge type is 0x%02X: MBC%d\n", *cartridge_type, mbc_type); 144 | mbc_start(ram + CART_RAM); 145 | } 146 | } 147 | 148 | static inline uint8_t read_wram(int bank, uint16_t addr) { 149 | uint8_t *bytes = (uint8_t *)ram; 150 | return bytes[(bank * 4096) + addr + WORK_RAM]; 151 | } 152 | 153 | static inline uint8_t read_hram(uint16_t addr) { 154 | uint8_t *bytes = (uint8_t *)ram; 155 | return bytes[addr + HIGH_RAM]; 156 | } 157 | 158 | uint8_t read_io(uint16_t addr) { 159 | switch(addr) { 160 | case LCDC: 161 | case STAT: 162 | case SCY: 163 | case SCX: 164 | case LY: 165 | case LYC: 166 | case DMA: 167 | case BGP: 168 | case OBP0: 169 | case OBP1: 170 | case WX: 171 | case WY: 172 | case VBK: 173 | case HDMA1: 174 | case HDMA2: 175 | case HDMA3: 176 | case HDMA4: 177 | case HDMA5: 178 | case BGPI: 179 | case BGPD: 180 | case OBPI: 181 | case OBPD: 182 | return display_read(addr); 183 | case P1: 184 | return joypad_read(addr); 185 | case DIV: 186 | case TIMA: 187 | case TMA: 188 | case TAC: 189 | return timer_read(addr); 190 | case NR10: 191 | case NR11: 192 | case NR12: 193 | case NR13: 194 | case NR14: 195 | case NR21: 196 | case NR22: 197 | case NR23: 198 | case NR24: 199 | case NR30: 200 | case NR31: 201 | case NR32: 202 | case NR33: 203 | case NR34: 204 | case NR41: 205 | case NR42: 206 | case NR43: 207 | case NR44: 208 | case NR50: 209 | case NR51: 210 | case NR52: 211 | case WAV00: 212 | case WAV01: 213 | case WAV02: 214 | case WAV03: 215 | case WAV04: 216 | case WAV05: 217 | case WAV06: 218 | case WAV07: 219 | case WAV08: 220 | case WAV09: 221 | case WAV10: 222 | case WAV11: 223 | case WAV12: 224 | case WAV13: 225 | case WAV14: 226 | case WAV15: 227 | return sound_read(addr); 228 | case IF: 229 | return if_read(); 230 | case KEY1: 231 | case RP: 232 | case SVBK: 233 | return cgb_read(addr); 234 | default: 235 | write_log("[memory] warning: unimplemented read from IO port 0x%04X, returning ones\n", addr); 236 | } 237 | 238 | return 0xFF; // unreachable 239 | } 240 | 241 | static inline uint8_t read_oam(uint16_t addr) { 242 | uint8_t *bytes = (uint8_t *)ram; 243 | return bytes[OAM + addr]; 244 | } 245 | 246 | uint8_t read_byte(uint16_t addr) { 247 | uint8_t *rom_bytes = (uint8_t *)rom; 248 | if(!mbc_type && addr <= 0x7FFF) { 249 | return rom_bytes[addr]; 250 | } else if(addr <= 0x3FFF) { 251 | if(mbc_type == 1) return mbc_read(addr); // only MBC that allows banking at 0x0000-0x3FFF 252 | return rom_bytes[addr]; 253 | } else if(addr >= 0xC000 && addr <= 0xCFFF) { 254 | return read_wram(0, addr - 0xC000); 255 | } else if(addr >= 0xD000 && addr <= 0xDFFF) { 256 | return read_wram(work_ram_bank, addr - 0xD000); 257 | } else if(addr >= 0xE000 && addr <= 0xEFFF) { 258 | return read_wram(0, addr - 0xE000); // echo bank 0 259 | } else if(addr >= 0xF000 && addr <= 0xFDFF) { 260 | return read_wram(work_ram_bank, addr - 0xF000); // echo bank n 261 | } else if(addr >= 0xFF80 && addr <= 0xFFFE) { 262 | return read_hram(addr - 0xFF80); 263 | } else if(addr >= 0xFF00 && addr <= 0xFF7F) { 264 | return read_io(addr); 265 | } else if(addr == 0xFFFF) { 266 | return ie_read(); 267 | } else if(addr >= 0x8000 && addr <= 0x9FFF) { 268 | return vram_read(addr); 269 | } else if(addr >= 0xFE00 && addr <= 0xFE9F) { 270 | return read_oam(addr); 271 | } else if(addr <= 0x7FFF || (addr >= 0xA000 && addr <= 0xBFFF)) { 272 | return mbc_read(addr); 273 | } 274 | 275 | write_log("[memory] unimplemented read at address 0x%04X in MBC%d ROM\n", addr, mbc_type); 276 | die(-1, NULL); 277 | return 0xFF; // unreachable anyway 278 | } 279 | 280 | inline uint16_t read_word(uint16_t addr) { 281 | return (uint16_t)(read_byte(addr) | ((uint16_t)read_byte(addr+1) << 8)); 282 | } 283 | 284 | static inline void write_wram(int bank, uint16_t addr, uint8_t byte) { 285 | uint8_t *bytes = (uint8_t *)ram; 286 | bytes[(bank * 4096) + addr + WORK_RAM] = byte; 287 | } 288 | 289 | static inline void write_hram(uint16_t addr, uint8_t byte) { 290 | uint8_t *bytes = (uint8_t *)ram; 291 | bytes[addr + HIGH_RAM] = byte; 292 | } 293 | 294 | void write_io(uint16_t addr, uint8_t byte) { 295 | switch(addr) { 296 | case IF: 297 | return if_write(byte); 298 | case LCDC: 299 | case STAT: 300 | case SCY: 301 | case SCX: 302 | case LY: 303 | case LYC: 304 | case DMA: 305 | case BGP: 306 | case OBP0: 307 | case OBP1: 308 | case WX: 309 | case WY: 310 | case VBK: 311 | case HDMA1: 312 | case HDMA2: 313 | case HDMA3: 314 | case HDMA4: 315 | case HDMA5: 316 | case BGPI: 317 | case BGPD: 318 | case OBPI: 319 | case OBPD: 320 | return display_write(addr, byte); 321 | case SB: 322 | return sb_write(byte); 323 | case SC: 324 | return sc_write(byte); 325 | case DIV: 326 | case TIMA: 327 | case TMA: 328 | case TAC: 329 | return timer_write(addr, byte); 330 | case NR10: 331 | case NR11: 332 | case NR12: 333 | case NR13: 334 | case NR14: 335 | case NR21: 336 | case NR22: 337 | case NR23: 338 | case NR24: 339 | case NR30: 340 | case NR31: 341 | case NR32: 342 | case NR33: 343 | case NR34: 344 | case NR41: 345 | case NR42: 346 | case NR43: 347 | case NR44: 348 | case NR50: 349 | case NR51: 350 | case NR52: 351 | case WAV00: 352 | case WAV01: 353 | case WAV02: 354 | case WAV03: 355 | case WAV04: 356 | case WAV05: 357 | case WAV06: 358 | case WAV07: 359 | case WAV08: 360 | case WAV09: 361 | case WAV10: 362 | case WAV11: 363 | case WAV12: 364 | case WAV13: 365 | case WAV14: 366 | case WAV15: 367 | return sound_write(addr, byte); 368 | case P1: 369 | return joypad_write(addr, byte); 370 | case KEY1: 371 | case RP: 372 | case SVBK: 373 | return cgb_write(addr, byte); 374 | default: 375 | write_log("[memory] unimplemented write to I/O port 0x%04X value 0x%02X\n", addr, byte); 376 | return; 377 | } 378 | } 379 | 380 | static inline void write_oam(uint16_t addr, uint8_t byte) { 381 | uint8_t *bytes = (uint8_t *)ram; 382 | bytes[OAM + addr] = byte; 383 | } 384 | 385 | void write_byte(uint16_t addr, uint8_t byte) { 386 | /*#ifdef MEMORY_LOG 387 | write_log("[memory] write 0x%02X to 0x%04X\n", byte, addr); 388 | #endif*/ 389 | 390 | if(addr >= 0xC000 && addr <= 0xCFFF) { 391 | return write_wram(0, addr - 0xC000, byte); 392 | } else if(addr >= 0xD000 && addr <= 0xDFFF) { 393 | return write_wram(work_ram_bank, addr - 0xD000, byte); 394 | } else if(addr >= 0xE000 && addr <= 0xEFFF) { 395 | return write_wram(0, addr - 0xE000, byte); // echo bank 0 396 | } else if(addr >= 0xF000 && addr <= 0xFDFF) { 397 | return write_wram(work_ram_bank, addr - 0xF000, byte); // echo bank n 398 | } else if(addr >= 0xFF80 && addr <= 0xFFFE) { 399 | return write_hram(addr - 0xFF80, byte); 400 | } else if (addr >= 0xFF00 && addr <= 0xFF7F) { 401 | return write_io(addr, byte); 402 | } else if(addr == 0xFFFF) { 403 | return ie_write(byte); 404 | } else if(addr >= 0x8000 && addr <= 0x9FFF) { 405 | return vram_write(addr, byte); 406 | } else if(addr >= 0xFEA0 && addr <= 0xFEFF) { 407 | //write_log("[memory] undefined write at unusable address 0x%04X value 0x%02X, ignoring...\n", addr, byte); 408 | return; 409 | } else if(addr >= 0xFE00 && addr <= 0xFE9F) { 410 | return write_oam(addr - 0xFE00, byte); 411 | } else if(addr <= 0x7FFF || (addr >= 0xA000 && addr <= 0xBFFF)) { 412 | return mbc_write(addr, byte); 413 | } 414 | 415 | write_log("[memory] unimplemented write at address 0x%04X value 0x%02X in MBC%d ROM\n", addr, byte, mbc_type); 416 | die(-1, NULL); 417 | } 418 | 419 | inline void copy_oam(void *dst) { 420 | memcpy(dst, ram+OAM, OAM_SIZE); 421 | } -------------------------------------------------------------------------------- /src/platform/sdl/main.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "tinyfiledialogs.h" 11 | 12 | // SDL specific code 13 | 14 | long rom_size; 15 | int scaling = 4; 16 | int frameskip = 0; // no skip 17 | 18 | SDL_Window *window; 19 | SDL_Surface *surface; 20 | timing_t timing; 21 | char *rom_filename; 22 | 23 | // SDL Config 24 | SDL_Keycode key_a; 25 | SDL_Keycode key_b; 26 | SDL_Keycode key_start; 27 | SDL_Keycode key_select; 28 | SDL_Keycode key_up; 29 | SDL_Keycode key_down; 30 | SDL_Keycode key_left; 31 | SDL_Keycode key_right; 32 | SDL_Keycode key_throttle; 33 | 34 | SDL_Keycode sdl_get_key(char *keyname) { 35 | if(!keyname) return SDLK_UNKNOWN; 36 | 37 | if(!strcmp("a", keyname)) return SDLK_a; 38 | else if(!strcmp("b", keyname)) return SDLK_b; 39 | else if(!strcmp("c", keyname)) return SDLK_c; 40 | else if(!strcmp("d", keyname)) return SDLK_d; 41 | else if(!strcmp("e", keyname)) return SDLK_e; 42 | else if(!strcmp("f", keyname)) return SDLK_f; 43 | else if(!strcmp("g", keyname)) return SDLK_g; 44 | else if(!strcmp("h", keyname)) return SDLK_h; 45 | else if(!strcmp("i", keyname)) return SDLK_i; 46 | else if(!strcmp("j", keyname)) return SDLK_j; 47 | else if(!strcmp("k", keyname)) return SDLK_k; 48 | else if(!strcmp("l", keyname)) return SDLK_l; 49 | else if(!strcmp("m", keyname)) return SDLK_m; 50 | else if(!strcmp("n", keyname)) return SDLK_n; 51 | else if(!strcmp("o", keyname)) return SDLK_o; 52 | else if(!strcmp("p", keyname)) return SDLK_p; 53 | else if(!strcmp("q", keyname)) return SDLK_q; 54 | else if(!strcmp("r", keyname)) return SDLK_r; 55 | else if(!strcmp("s", keyname)) return SDLK_s; 56 | else if(!strcmp("t", keyname)) return SDLK_t; 57 | else if(!strcmp("u", keyname)) return SDLK_u; 58 | else if(!strcmp("v", keyname)) return SDLK_v; 59 | else if(!strcmp("w", keyname)) return SDLK_w; 60 | else if(!strcmp("x", keyname)) return SDLK_x; 61 | else if(!strcmp("y", keyname)) return SDLK_y; 62 | else if(!strcmp("z", keyname)) return SDLK_z; 63 | else if(!strcmp("0", keyname)) return SDLK_0; 64 | else if(!strcmp("1", keyname)) return SDLK_1; 65 | else if(!strcmp("2", keyname)) return SDLK_2; 66 | else if(!strcmp("3", keyname)) return SDLK_3; 67 | else if(!strcmp("4", keyname)) return SDLK_4; 68 | else if(!strcmp("5", keyname)) return SDLK_5; 69 | else if(!strcmp("6", keyname)) return SDLK_6; 70 | else if(!strcmp("7", keyname)) return SDLK_7; 71 | else if(!strcmp("8", keyname)) return SDLK_8; 72 | else if(!strcmp("9", keyname)) return SDLK_9; 73 | else if(!strcmp("space", keyname)) return SDLK_SPACE; 74 | else if(!strcmp("rshift", keyname)) return SDLK_RSHIFT; 75 | else if(!strcmp("lshift", keyname)) return SDLK_LSHIFT; 76 | else if(!strcmp("backspace", keyname)) return SDLK_BACKSPACE; 77 | else if(!strcmp("delete", keyname)) return SDLK_DELETE; 78 | else if(!strcmp("tab", keyname)) return SDLK_TAB; 79 | else if(!strcmp("escape", keyname)) return SDLK_ESCAPE; 80 | else if(!strcmp("exclamation", keyname)) return SDLK_EXCLAIM; 81 | else if(!strcmp("at", keyname)) return SDLK_AT; 82 | else if(!strcmp("hash", keyname)) return SDLK_HASH; 83 | else if(!strcmp("dollar", keyname)) return SDLK_DOLLAR; 84 | else if(!strcmp("percent", keyname)) return SDLK_PERCENT; 85 | else if(!strcmp("caret", keyname)) return SDLK_CARET; 86 | else if(!strcmp("ampersand", keyname)) return SDLK_AMPERSAND; 87 | else if(!strcmp("asterisk", keyname)) return SDLK_ASTERISK; 88 | else if(!strcmp("leftparenthesis", keyname)) return SDLK_LEFTPAREN; 89 | else if(!strcmp("rightparenthesis", keyname)) return SDLK_RIGHTPAREN; 90 | 91 | else return SDLK_UNKNOWN; 92 | } 93 | 94 | static void set_sdl_keys() { 95 | key_a = sdl_get_key(config_file.a); 96 | if(key_a == SDLK_UNKNOWN) key_a = SDLK_z; 97 | 98 | key_b = sdl_get_key(config_file.b); 99 | if(key_b == SDLK_UNKNOWN) key_b = SDLK_x; 100 | 101 | key_start = sdl_get_key(config_file.start); 102 | if(key_start == SDLK_UNKNOWN) key_start = SDLK_RETURN; 103 | 104 | key_select = sdl_get_key(config_file.select); 105 | if(key_select == SDLK_UNKNOWN) key_select = SDLK_RSHIFT; 106 | 107 | key_up = sdl_get_key(config_file.up); 108 | if(key_up == SDLK_UNKNOWN) key_up = SDLK_UP; 109 | 110 | key_down = sdl_get_key(config_file.down); 111 | if(key_down == SDLK_UNKNOWN) key_down = SDLK_DOWN; 112 | 113 | key_left = sdl_get_key(config_file.left); 114 | if(key_left == SDLK_UNKNOWN) key_left = SDLK_LEFT; 115 | 116 | key_right = sdl_get_key(config_file.right); 117 | if(key_right == SDLK_UNKNOWN) key_right = SDLK_RIGHT; 118 | 119 | key_throttle = sdl_get_key(config_file.throttle); 120 | if(key_throttle == SDLK_UNKNOWN) key_throttle = SDLK_SPACE; 121 | } 122 | 123 | inline void delay(int ms) { 124 | SDL_Delay(ms); 125 | } 126 | 127 | void destroy_window() { 128 | SDL_DestroyWindow(window); 129 | SDL_Quit(); 130 | } 131 | 132 | void update_window(uint32_t *framebuffer) { 133 | void *src, *dst; 134 | 135 | if(surface->format->BytesPerPixel == 4) { 136 | // 32-bpp 137 | for(int i = 0; i < scaled_h; i++) { 138 | src = (void *)(framebuffer + (i * scaled_w)); 139 | 140 | if(!using_sgb_border) { 141 | dst = (void *)(surface->pixels + (i * surface->pitch)); 142 | } else { 143 | dst = (void *)(surface->pixels + ((i + gb_y) * surface->pitch) + (gb_x * 4)); 144 | } 145 | memcpy(dst, src, scaled_w*4); 146 | } 147 | } else { 148 | die(-1, "unimplemented non 32-bpp surfaces\n"); 149 | } 150 | 151 | //framecount++; 152 | if(framecount > frameskip) { 153 | SDL_UpdateWindowSurface(window); 154 | framecount = 0; 155 | drawn_frames++; 156 | } 157 | } 158 | 159 | void update_border(uint32_t *framebuffer) { 160 | void *src, *dst; 161 | 162 | if(surface->format->BytesPerPixel == 4) { 163 | // 32-bpp 164 | for(int i = 0; i < sgb_scaled_h; i++) { 165 | src = (void *)(framebuffer + (i * sgb_scaled_w)); 166 | dst = (void *)(surface->pixels + (i * surface->pitch)); 167 | 168 | memcpy(dst, src, sgb_scaled_w*4); 169 | } 170 | } else { 171 | die(-1, "unimplemented non 32-bpp surfaces\n"); 172 | } 173 | 174 | //SDL_UpdateWindowSurface(window); 175 | } 176 | 177 | void resize_sgb_window() { 178 | SDL_SetWindowSize(window, SGB_WIDTH*scaling, SGB_HEIGHT*scaling); 179 | SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); 180 | surface = SDL_GetWindowSurface(window); 181 | } 182 | 183 | int main(int argc, char **argv) { 184 | const char *extensions[3] = { "*.gb", "*.gbc", "*.dmg" }; 185 | 186 | if(argc != 2) { 187 | //fprintf(stdout, "usage: %s rom_name\n", argv[0]); 188 | //return -1; 189 | rom_filename = tinyfd_openFileDialog("Open ROM file", NULL, 3, extensions, "Game Boy ROM files", 0); 190 | if(!rom_filename) return -1; 191 | } else { 192 | rom_filename = argv[1]; 193 | } 194 | 195 | open_log(); 196 | open_config(); 197 | set_sdl_keys(); 198 | 199 | // open the rom 200 | FILE *rom_file = fopen(rom_filename, "r"); 201 | if(!rom_file) { 202 | write_log("unable to open %s for reading\n", argv[1]); 203 | return -1; 204 | } 205 | 206 | fseek(rom_file, 0L, SEEK_END); 207 | rom_size = ftell(rom_file); 208 | fseek(rom_file, 0L, SEEK_SET); 209 | 210 | write_log("loading rom from file %s, %d KiB\n", argv[1], rom_size/1024); 211 | 212 | rom = malloc(rom_size); 213 | if(!rom) { 214 | write_log("unable to allocate memory\n"); 215 | fclose(rom_file); 216 | return -1; 217 | } 218 | 219 | if(!fread(rom, 1, rom_size, rom_file)) { 220 | write_log("an error occured while reading from rom file\n"); 221 | fclose(rom_file); 222 | free(rom); 223 | return -1; 224 | } 225 | 226 | fclose(rom_file); 227 | 228 | // make the main window 229 | if(SDL_Init(SDL_INIT_VIDEO) < 0) { 230 | write_log("failed to init SDL: %s\n", SDL_GetError()); 231 | free(rom); 232 | return -1; 233 | } 234 | 235 | window = SDL_CreateWindow("tinygb", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, GB_WIDTH*scaling, GB_HEIGHT*scaling, SDL_WINDOW_SHOWN); 236 | if(!window) { 237 | write_log("couldn't create SDL window: %s\n", SDL_GetError()); 238 | free(rom); 239 | SDL_Quit(); 240 | return -1; 241 | } 242 | 243 | surface = SDL_GetWindowSurface(window); 244 | 245 | write_log("SDL pixel format: %s\n", SDL_GetPixelFormatName(surface->format->format)); 246 | write_log("SDL bits per pixel: %d\n", surface->format->BitsPerPixel); 247 | write_log("SDL bytes per pixel: %d\n", surface->format->BytesPerPixel); 248 | write_log("SDL Rmask: 0x%06X\n", surface->format->Rmask); 249 | write_log("SDL Gmask: 0x%06X\n", surface->format->Gmask); 250 | write_log("SDL Bmask: 0x%06X\n", surface->format->Bmask); 251 | write_log("SDL Amask: 0x%08X\n", surface->format->Amask); 252 | 253 | // disgustingly lazy thing for now 254 | if(surface->format->BytesPerPixel != 3 && surface->format->BytesPerPixel != 4 && 255 | surface->format->Rmask != 0xFF0000 && surface->format->Gmask != 0xFF00 && surface->format->Bmask != 0xFF) { 256 | die(-1, "unsupported surface format; only RGB 24-bpp or 32-bpp are supported\n"); 257 | } 258 | 259 | SDL_UpdateWindowSurface(window); 260 | 261 | // start emulation 262 | memory_start(); 263 | cpu_start(); 264 | display_start(); 265 | timer_start(); 266 | sound_start(); 267 | 268 | SDL_Event e; 269 | int key, is_down; 270 | time_t rawtime; 271 | struct tm *timeinfo; 272 | int sec = 500; // any invalid number 273 | char new_title[256]; 274 | int percentage; 275 | int throttle_underflow = 0; 276 | int throttle_target = throttle_lo + SPEED_ALLOWANCE; 277 | 278 | while(1) { 279 | key = 0; 280 | is_down = 0; 281 | 282 | while(SDL_PollEvent(&e)) { 283 | switch(e.type) { 284 | case SDL_QUIT: 285 | SDL_DestroyWindow(window); 286 | SDL_Quit(); 287 | die(0, ""); 288 | case SDL_KEYDOWN: 289 | case SDL_KEYUP: 290 | is_down = (e.type == SDL_KEYDOWN); 291 | 292 | key = 0; 293 | 294 | // convert SDL keys to internal keys 295 | // TODO: read these keys from a config file 296 | /*switch(e.key.keysym.sym) { 297 | case key_left: 298 | key = JOYPAD_LEFT; 299 | break; 300 | case key_right: 301 | key = JOYPAD_RIGHT; 302 | break; 303 | case key_up: 304 | key = JOYPAD_UP; 305 | break; 306 | case key_down: 307 | key = JOYPAD_DOWN; 308 | break; 309 | case key_a: 310 | key = JOYPAD_A; 311 | break; 312 | case key_b: 313 | key = JOYPAD_B; 314 | break; 315 | case key_start: 316 | key = JOYPAD_START; 317 | break; 318 | case key_select: 319 | key = JOYPAD_SELECT; 320 | break; 321 | case key_throttle: 322 | if(is_down) throttle_enabled = 0; 323 | else throttle_enabled = 1; 324 | default: 325 | key = 0; 326 | break; 327 | }*/ 328 | 329 | if(e.key.keysym.sym == key_left) key = JOYPAD_LEFT; 330 | else if(e.key.keysym.sym == key_right) key = JOYPAD_RIGHT; 331 | else if(e.key.keysym.sym == key_up) key = JOYPAD_UP; 332 | else if(e.key.keysym.sym == key_down) key = JOYPAD_DOWN; 333 | else if(e.key.keysym.sym == key_a) key = JOYPAD_A; 334 | else if(e.key.keysym.sym == key_b) key = JOYPAD_B; 335 | else if(e.key.keysym.sym == key_start) key = JOYPAD_START; 336 | else if(e.key.keysym.sym == key_select) key = JOYPAD_SELECT; 337 | else if(e.key.keysym.sym == key_throttle) { 338 | if(is_down) { 339 | //write_log("disabling throttle\n"); 340 | throttle_enabled = 0; 341 | } else { 342 | //write_log("enabling throttle\n"); 343 | throttle_enabled = 1; 344 | } 345 | } else if((e.key.keysym.sym == SDLK_PLUS || e.key.keysym.sym == SDLK_EQUALS) && is_down) next_palette(); 346 | else if(e.key.keysym.sym == SDLK_MINUS && is_down) prev_palette(); 347 | else key = 0; 348 | 349 | break; 350 | default: 351 | break; 352 | } 353 | } 354 | 355 | if(key) joypad_handle(is_down, key); 356 | 357 | for(timing.current_cycles = 0; timing.current_cycles < timing.main_cycles; ) { 358 | cpu_cycle(); 359 | display_cycle(); 360 | timer_cycle(); 361 | } 362 | 363 | 364 | time(&rawtime); 365 | timeinfo = localtime(&rawtime); 366 | 367 | if(sec != timeinfo->tm_sec) { 368 | sec = timeinfo->tm_sec; 369 | percentage = (drawn_frames * 1000) / 597; 370 | sprintf(new_title, "tinygb (%d fps - %d%%)", drawn_frames, percentage); 371 | SDL_SetWindowTitle(window, new_title); 372 | 373 | // adjust cpu throttle according to acceptable fps (98%-102%) 374 | if(throttle_enabled) { 375 | if(percentage < throttle_lo) { 376 | // emulation is too slow 377 | if(!throttle_time) { 378 | // throttle_time--; 379 | 380 | if(!throttle_underflow) { 381 | throttle_underflow = 1; 382 | write_log("WARNING: CPU throttle interval has underflown, emulation may be too slow\n"); 383 | } 384 | } else { 385 | //write_log("too slow; decreasing throttle time: %d\n", throttle_time); 386 | 387 | // this will speed up the speed adjustments for a more natural feel 388 | if(percentage < (throttle_target/3)) throttle_time /= 3; 389 | else if(percentage < (throttle_target/2)) throttle_time /= 2; 390 | else throttle_time--; 391 | } 392 | 393 | // prevent this from going too low 394 | if(throttle_time <= (THROTTLE_THRESHOLD/3)) { 395 | cycles_per_throttle += (cycles_per_throttle/5); // delay 20% less often 396 | throttle_time = (THROTTLE_THRESHOLD/3); 397 | } 398 | } else if(percentage > throttle_hi) { 399 | // emulation is too fast 400 | //write_log("too fast; increasing throttle time: %d\n", throttle_time); 401 | 402 | if(throttle_time) { 403 | // to make sure we're not multiplying zero 404 | if(percentage > (throttle_target*3)) throttle_time *= 3; 405 | else if(percentage > (throttle_target*2)) throttle_time *= 2; 406 | else throttle_time++; 407 | } 408 | else { 409 | throttle_time++; 410 | } 411 | 412 | // prevent unnecessary lag 413 | if(throttle_time > THROTTLE_THRESHOLD) { 414 | cycles_per_throttle -= (cycles_per_throttle/5); // delay 20% more often 415 | throttle_time = THROTTLE_THRESHOLD; 416 | } 417 | } 418 | } 419 | 420 | drawn_frames = 0; 421 | } 422 | } 423 | 424 | die(0, ""); 425 | return 0; 426 | } -------------------------------------------------------------------------------- /src/platform/sdl/tinyfiledialogs.h: -------------------------------------------------------------------------------- 1 | /* If you are using a C++ compiler to compile tinyfiledialogs.c (maybe renamed with an extension ".cpp") 2 | then comment out << extern "C" >> bellow in this header file) */ 3 | 4 | /*_________ 5 | / \ tinyfiledialogs.h v3.8.8 [Apr 22, 2021] zlib licence 6 | |tiny file| Unique header file created [November 9, 2014] 7 | | dialogs | Copyright (c) 2014 - 2021 Guillaume Vareille http://ysengrin.com 8 | \____ ___/ http://tinyfiledialogs.sourceforge.net 9 | \| git clone http://git.code.sf.net/p/tinyfiledialogs/code tinyfd 10 | ____________________________________________ 11 | | | 12 | | email: tinyfiledialogs at ysengrin.com | 13 | |____________________________________________| 14 | ________________________________________________________________________________ 15 | | ____________________________________________________________________________ | 16 | | | | | 17 | | | on windows: | | 18 | | | - for UTF-16, use the wchar_t functions at the bottom of the header file | | 19 | | | - _wfopen() requires wchar_t | | 20 | | | | | 21 | | | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | | 22 | | | - but fopen() expects MBCS (not UTF-8) | | 23 | | | - if you want char to be MBCS: set tinyfd_winUtf8 to 0 | | 24 | | | | | 25 | | | - alternatively, tinyfiledialogs provides | | 26 | | | functions to convert between UTF-8, UTF-16 and MBCS | | 27 | | |____________________________________________________________________________| | 28 | |________________________________________________________________________________| 29 | 30 | If you like tinyfiledialogs, please upvote my stackoverflow answer 31 | https://stackoverflow.com/a/47651444 32 | 33 | - License - 34 | This software is provided 'as-is', without any express or implied 35 | warranty. In no event will the authors be held liable for any damages 36 | arising from the use of this software. 37 | Permission is granted to anyone to use this software for any purpose, 38 | including commercial applications, and to alter it and redistribute it 39 | freely, subject to the following restrictions: 40 | 1. The origin of this software must not be misrepresented; you must not 41 | claim that you wrote the original software. If you use this software 42 | in a product, an acknowledgment in the product documentation would be 43 | appreciated but is not required. 44 | 2. Altered source versions must be plainly marked as such, and must not be 45 | misrepresented as being the original software. 46 | 3. This notice may not be removed or altered from any source distribution. 47 | */ 48 | 49 | #ifndef TINYFILEDIALOGS_H 50 | #define TINYFILEDIALOGS_H 51 | 52 | #ifdef __cplusplus 53 | /* if tinydialogs.c is compiled as C++ code rather than C code, you may need to comment this out 54 | and the corresponding closing bracket near the end of this file. */ 55 | extern "C" { 56 | #endif 57 | 58 | /******************************************************************************************************/ 59 | /**************************************** UTF-8 on Windows ********************************************/ 60 | /******************************************************************************************************/ 61 | #ifdef _WIN32 62 | /* On windows, if you want to use UTF-8 ( instead of the UTF-16/wchar_t functions at the end of this file ) 63 | Make sure your code is really prepared for UTF-8 (on windows, functions like fopen() expect MBCS and not UTF-8) */ 64 | extern int tinyfd_winUtf8; /* on windows char strings can be 1:UTF-8(default) or 0:MBCS */ 65 | /* for MBCS change this to 0, in tinyfiledialogs.c or in your code */ 66 | 67 | /* Here are some functions to help you convert between UTF-16 UTF-8 MBSC */ 68 | char * tinyfd_utf8toMbcs(char const * aUtf8string); 69 | char * tinyfd_utf16toMbcs(wchar_t const * aUtf16string); 70 | wchar_t * tinyfd_mbcsTo16(char const * aMbcsString); 71 | char * tinyfd_mbcsTo8(char const * aMbcsString); 72 | wchar_t * tinyfd_utf8to16(char const * aUtf8string); 73 | char * tinyfd_utf16to8(wchar_t const * aUtf16string); 74 | #endif 75 | /******************************************************************************************************/ 76 | /******************************************************************************************************/ 77 | /******************************************************************************************************/ 78 | 79 | /************* 3 funtions for C# (you don't need this in C or C++) : */ 80 | char const * tinyfd_getGlobalChar(char const * aCharVariableName); /* returns NULL on error */ 81 | int tinyfd_getGlobalInt(char const * aIntVariableName); /* returns -1 on error */ 82 | int tinyfd_setGlobalInt(char const * aIntVariableName, int aValue); /* returns -1 on error */ 83 | /* aCharVariableName: "tinyfd_version" "tinyfd_needs" "tinyfd_response" 84 | aIntVariableName : "tinyfd_verbose" "tinyfd_silent" "tinyfd_allowCursesDialogs" 85 | "tinyfd_forceConsole" "tinyfd_assumeGraphicDisplay" "tinyfd_winUtf8" 86 | **************/ 87 | 88 | 89 | extern char tinyfd_version[8]; /* contains tinyfd current version number */ 90 | extern char tinyfd_needs[]; /* info about requirements */ 91 | extern int tinyfd_verbose; /* 0 (default) or 1 : on unix, prints the command line calls */ 92 | extern int tinyfd_silent; /* 1 (default) or 0 : on unix, hide errors and warnings from called dialogs */ 93 | 94 | /* Curses dialogs are difficult to use, on windows they are only ascii and uses the unix backslah */ 95 | extern int tinyfd_allowCursesDialogs; /* 0 (default) or 1 */ 96 | 97 | extern int tinyfd_forceConsole; /* 0 (default) or 1 */ 98 | /* for unix & windows: 0 (graphic mode) or 1 (console mode). 99 | 0: try to use a graphic solution, if it fails then it uses console mode. 100 | 1: forces all dialogs into console mode even when an X server is present, 101 | it can use the package dialog or dialog.exe. 102 | on windows it only make sense for console applications */ 103 | 104 | extern int tinyfd_assumeGraphicDisplay; /* 0 (default) or 1 */ 105 | /* some systems don't set the environment variable DISPLAY even when a graphic display is present. 106 | set this to 1 to tell tinyfiledialogs to assume the existence of a graphic display */ 107 | 108 | extern char tinyfd_response[1024]; 109 | /* if you pass "tinyfd_query" as aTitle, 110 | the functions will not display the dialogs 111 | but will return 0 for console mode, 1 for graphic mode. 112 | tinyfd_response is then filled with the retain solution. 113 | possible values for tinyfd_response are (all lowercase) 114 | for graphic mode: 115 | windows_wchar windows applescript kdialog zenity zenity3 matedialog 116 | shellementary qarma yad python2-tkinter python3-tkinter python-dbus 117 | perl-dbus gxmessage gmessage xmessage xdialog gdialog 118 | for console mode: 119 | dialog whiptail basicinput no_solution */ 120 | 121 | void tinyfd_beep(void); 122 | 123 | int tinyfd_notifyPopup( 124 | char const * aTitle, /* NULL or "" */ 125 | char const * aMessage, /* NULL or "" may contain \n \t */ 126 | char const * aIconType); /* "info" "warning" "error" */ 127 | /* return has only meaning for tinyfd_query */ 128 | 129 | int tinyfd_messageBox( 130 | char const * aTitle , /* NULL or "" */ 131 | char const * aMessage , /* NULL or "" may contain \n \t */ 132 | char const * aDialogType , /* "ok" "okcancel" "yesno" "yesnocancel" */ 133 | char const * aIconType , /* "info" "warning" "error" "question" */ 134 | int aDefaultButton ) ; 135 | /* 0 for cancel/no , 1 for ok/yes , 2 for no in yesnocancel */ 136 | 137 | char * tinyfd_inputBox( 138 | char const * aTitle , /* NULL or "" */ 139 | char const * aMessage , /* NULL or "" (\n and \t have no effect) */ 140 | char const * aDefaultInput ) ; /* NULL passwordBox, "" inputbox */ 141 | /* returns NULL on cancel */ 142 | 143 | char * tinyfd_saveFileDialog( 144 | char const * aTitle , /* NULL or "" */ 145 | char const * aDefaultPathAndFile , /* NULL or "" */ 146 | int aNumOfFilterPatterns , /* 0 (1 in the following example) */ 147 | char const * const * aFilterPatterns , /* NULL or char const * lFilterPatterns[1]={"*.txt"} */ 148 | char const * aSingleFilterDescription ) ; /* NULL or "text files" */ 149 | /* returns NULL on cancel */ 150 | 151 | char * tinyfd_openFileDialog( 152 | char const * aTitle, /* NULL or "" */ 153 | char const * aDefaultPathAndFile, /* NULL or "" */ 154 | int aNumOfFilterPatterns , /* 0 (2 in the following example) */ 155 | char const * const * aFilterPatterns, /* NULL or char const * lFilterPatterns[2]={"*.png","*.jpg"}; */ 156 | char const * aSingleFilterDescription, /* NULL or "image files" */ 157 | int aAllowMultipleSelects ) ; /* 0 or 1 */ 158 | /* in case of multiple files, the separator is | */ 159 | /* returns NULL on cancel */ 160 | 161 | char * tinyfd_selectFolderDialog( 162 | char const * aTitle, /* NULL or "" */ 163 | char const * aDefaultPath); /* NULL or "" */ 164 | /* returns NULL on cancel */ 165 | 166 | char * tinyfd_colorChooser( 167 | char const * aTitle, /* NULL or "" */ 168 | char const * aDefaultHexRGB, /* NULL or "#FF0000" */ 169 | unsigned char const aDefaultRGB[3] , /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */ 170 | unsigned char aoResultRGB[3] ) ; /* unsigned char lResultRGB[3]; */ 171 | /* returns the hexcolor as a string "#FF0000" */ 172 | /* aoResultRGB also contains the result */ 173 | /* aDefaultRGB is used only if aDefaultHexRGB is NULL */ 174 | /* aDefaultRGB and aoResultRGB can be the same array */ 175 | /* returns NULL on cancel */ 176 | 177 | 178 | /************ WINDOWS ONLY SECTION ************************/ 179 | #ifdef _WIN32 180 | 181 | /* windows only - utf-16 version */ 182 | int tinyfd_notifyPopupW( 183 | wchar_t const * aTitle, /* NULL or L"" */ 184 | wchar_t const * aMessage, /* NULL or L"" may contain \n \t */ 185 | wchar_t const * aIconType); /* L"info" L"warning" L"error" */ 186 | 187 | /* windows only - utf-16 version */ 188 | int tinyfd_messageBoxW( 189 | wchar_t const * aTitle, /* NULL or L"" */ 190 | wchar_t const * aMessage, /* NULL or L"" may contain \n \t */ 191 | wchar_t const * aDialogType, /* L"ok" L"okcancel" L"yesno" */ 192 | wchar_t const * aIconType, /* L"info" L"warning" L"error" L"question" */ 193 | int aDefaultButton ); /* 0 for cancel/no , 1 for ok/yes */ 194 | /* returns 0 for cancel/no , 1 for ok/yes */ 195 | 196 | /* windows only - utf-16 version */ 197 | wchar_t * tinyfd_inputBoxW( 198 | wchar_t const * aTitle, /* NULL or L"" */ 199 | wchar_t const * aMessage, /* NULL or L"" (\n nor \t not respected) */ 200 | wchar_t const * aDefaultInput); /* NULL passwordBox, L"" inputbox */ 201 | 202 | /* windows only - utf-16 version */ 203 | wchar_t * tinyfd_saveFileDialogW( 204 | wchar_t const * aTitle, /* NULL or L"" */ 205 | wchar_t const * aDefaultPathAndFile, /* NULL or L"" */ 206 | int aNumOfFilterPatterns, /* 0 (1 in the following example) */ 207 | wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[1]={L"*.txt"} */ 208 | wchar_t const * aSingleFilterDescription); /* NULL or L"text files" */ 209 | /* returns NULL on cancel */ 210 | 211 | /* windows only - utf-16 version */ 212 | wchar_t * tinyfd_openFileDialogW( 213 | wchar_t const * aTitle, /* NULL or L"" */ 214 | wchar_t const * aDefaultPathAndFile, /* NULL or L"" */ 215 | int aNumOfFilterPatterns , /* 0 (2 in the following example) */ 216 | wchar_t const * const * aFilterPatterns, /* NULL or wchar_t const * lFilterPatterns[2]={L"*.png","*.jpg"} */ 217 | wchar_t const * aSingleFilterDescription, /* NULL or L"image files" */ 218 | int aAllowMultipleSelects ) ; /* 0 or 1 */ 219 | /* in case of multiple files, the separator is | */ 220 | /* returns NULL on cancel */ 221 | 222 | /* windows only - utf-16 version */ 223 | wchar_t * tinyfd_selectFolderDialogW( 224 | wchar_t const * aTitle, /* NULL or L"" */ 225 | wchar_t const * aDefaultPath); /* NULL or L"" */ 226 | /* returns NULL on cancel */ 227 | 228 | /* windows only - utf-16 version */ 229 | wchar_t * tinyfd_colorChooserW( 230 | wchar_t const * aTitle, /* NULL or L"" */ 231 | wchar_t const * aDefaultHexRGB, /* NULL or L"#FF0000" */ 232 | unsigned char const aDefaultRGB[3], /* unsigned char lDefaultRGB[3] = { 0 , 128 , 255 }; */ 233 | unsigned char aoResultRGB[3]); /* unsigned char lResultRGB[3]; */ 234 | /* returns the hexcolor as a string L"#FF0000" */ 235 | /* aoResultRGB also contains the result */ 236 | /* aDefaultRGB is used only if aDefaultHexRGB is NULL */ 237 | /* aDefaultRGB and aoResultRGB can be the same array */ 238 | /* returns NULL on cancel */ 239 | 240 | #endif /*_WIN32 */ 241 | 242 | #ifdef __cplusplus 243 | } /*extern "C"*/ 244 | #endif 245 | 246 | #endif /* TINYFILEDIALOGS_H */ 247 | 248 | /* 249 | ________________________________________________________________________________ 250 | | ____________________________________________________________________________ | 251 | | | | | 252 | | | on windows: | | 253 | | | - for UTF-16, use the wchar_t functions at the bottom of the header file | | 254 | | | - _wfopen() requires wchar_t | | 255 | | | | | 256 | | | - in tinyfiledialogs, char is UTF-8 by default (since v3.6) | | 257 | | | - but fopen() expects MBCS (not UTF-8) | | 258 | | | - if you want char to be MBCS: set tinyfd_winUtf8 to 0 | | 259 | | | | | 260 | | | - alternatively, tinyfiledialogs provides | | 261 | | | functions to convert between UTF-8, UTF-16 and MBCS | | 262 | | |____________________________________________________________________________| | 263 | |________________________________________________________________________________| 264 | 265 | - This is not for ios nor android (it works in termux though). 266 | - The files can be renamed with extension ".cpp" as the code is 100% compatible C C++ 267 | (just comment out << extern "C" >> in the header file) 268 | - Windows is fully supported from XP to 10 (maybe even older versions) 269 | - C# & LUA via dll, see files in the folder EXTRAS 270 | - OSX supported from 10.4 to latest (maybe even older versions) 271 | - Do not use " and ' as the dialogs will be displayed with a warning 272 | instead of the title, message, etc... 273 | - There's one file filter only, it may contain several patterns. 274 | - If no filter description is provided, 275 | the list of patterns will become the description. 276 | - On windows link against Comdlg32.lib and Ole32.lib 277 | (on windows the no linking claim is a lie) 278 | - On unix: it tries command line calls, so no such need (NO LINKING). 279 | - On unix you need one of the following: 280 | applescript, kdialog, zenity, matedialog, shellementary, qarma, yad, 281 | python (2 or 3)/tkinter/python-dbus (optional), Xdialog 282 | or curses dialogs (opens terminal if running without console). 283 | - One of those is already included on most (if not all) desktops. 284 | - In the absence of those it will use gdialog, gxmessage or whiptail 285 | with a textinputbox. If nothing is found, it switches to basic console input, 286 | it opens a console if needed (requires xterm + bash). 287 | - for curses dialogs you must set tinyfd_allowCursesDialogs=1 288 | - You can query the type of dialog that will be used (pass "tinyfd_query" as aTitle) 289 | - String memory is preallocated statically for all the returned values. 290 | - File and path names are tested before return, they should be valid. 291 | - tinyfd_forceConsole=1; at run time, forces dialogs into console mode. 292 | - On windows, console mode only make sense for console applications. 293 | - On windows, console mode is not implemented for wchar_T UTF-16. 294 | - Mutiple selects are not possible in console mode. 295 | - The package dialog must be installed to run in curses dialogs in console mode. 296 | It is already installed on most unix systems. 297 | - On osx, the package dialog can be installed via 298 | http://macappstore.org/dialog or http://macports.org 299 | - On windows, for curses dialogs console mode, 300 | dialog.exe should be copied somewhere on your executable path. 301 | It can be found at the bottom of the following page: 302 | http://andrear.altervista.org/home/cdialog.php 303 | */ 304 | -------------------------------------------------------------------------------- /src/core/sgb.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Super Gameboy implementation 11 | 12 | #define SGB_LOG 13 | 14 | int sgb_transferring = 0; // interfering with writes to 0xFF00 15 | int sgb_interfere = 0; // interfering with reads from 0xFF00 16 | int sgb_current_bit = 0; 17 | int sgb_command_size; 18 | int using_sgb_palette = 0; 19 | int using_sgb_border = 0; 20 | int gb_x, gb_y; 21 | int sgb_scaled_h, sgb_scaled_w; 22 | 23 | sgb_command_t sgb_command; 24 | sgb_palette_t sgb_palettes[4]; 25 | sgb_attr_block_t sgb_attr_blocks[18]; // maximum 26 | 27 | int sgb_attr_block_count = 0; 28 | int sgb_screen_mask = 0; 29 | 30 | uint8_t sgb_current_joypad = 0x0F; // 0x0C-0x0F 31 | int sgb_joypad_count; 32 | uint8_t sgb_joypad_return; 33 | 34 | uint8_t *sgb_palette_data; 35 | uint8_t *sgb_tiles; 36 | uint8_t *sgb_border_map; 37 | 38 | uint32_t *sgb_border; 39 | uint32_t *sgb_scaled_border; 40 | sgb_border_palette_t sgb_border_palettes[4]; 41 | uint32_t sgb_color_zero; 42 | 43 | void render_sgb_border(); 44 | 45 | void sgb_start() { 46 | sgb_palette_data = calloc(1, 4096); 47 | sgb_tiles = calloc(1, 8192); 48 | sgb_border_map = calloc(1, 4096); 49 | 50 | sgb_border = calloc(SGB_WIDTH*SGB_HEIGHT, 4); 51 | if(scaling != 1) sgb_scaled_border = calloc(SGB_WIDTH*SGB_HEIGHT, 4*scaling*scaling*4); 52 | else sgb_scaled_border = sgb_border; 53 | 54 | if(!sgb_palette_data || !sgb_tiles || !sgb_border_map || !sgb_border || !sgb_scaled_border) { 55 | write_log("[sgb] unable to allocate memory\n"); 56 | die(-1, ""); 57 | } 58 | 59 | sgb_scaled_h = SGB_HEIGHT*scaling; 60 | sgb_scaled_w = SGB_WIDTH*scaling; 61 | } 62 | 63 | inline uint32_t truecolor(uint16_t color16) { 64 | uint32_t color32; 65 | int r, g, b; 66 | 67 | r = color16 & 31; 68 | g = (color16 >> 5) & 31; 69 | b = (color16 >> 10) & 31; 70 | 71 | r <<= 3; // x8 72 | g <<= 3; 73 | b <<= 3; 74 | 75 | color32 = (r << 16) | (g << 8) | b; 76 | return color32; 77 | } 78 | 79 | void sgb_vram_transfer(uint8_t *dst) { 80 | // transfers data from VRAM into SNES memory 81 | uint8_t lcdc = read_byte(LCDC); // display control IO port 82 | if(!(lcdc & LCDC_ENABLE)) { 83 | write_log("[sgb] warning: attempting to transfer data from VRAM when display is disabled, returning zeroes\n"); 84 | memset(dst, 0, 4096); 85 | return; 86 | } 87 | 88 | uint16_t tiles; 89 | 90 | if(lcdc & 0x10) tiles = 0x8000; 91 | else tiles = 0x8800; 92 | 93 | for(int i = 0; i < 4096; i++) { 94 | dst[i] = read_byte(tiles+i); 95 | } 96 | } 97 | 98 | void create_sgb_palette(int sgb_palette, int system_palette) { 99 | uint16_t *data = (uint16_t *)(sgb_palette_data + (system_palette * 8)); 100 | 101 | sgb_palettes[sgb_palette].colors[0] = truecolor(data[0]); 102 | sgb_palettes[sgb_palette].colors[1] = truecolor(data[1]); 103 | sgb_palettes[sgb_palette].colors[2] = truecolor(data[2]); 104 | sgb_palettes[sgb_palette].colors[3] = truecolor(data[3]); 105 | 106 | if(sgb_palettes[sgb_palette].colors[0] != sgb_color_zero) { 107 | sgb_color_zero = sgb_palettes[sgb_palette].colors[0]; 108 | if(using_sgb_border) render_sgb_border(); 109 | } 110 | 111 | #ifdef SGB_LOG 112 | for(int i = 0; i < 4; i++) { 113 | int r, g, b; 114 | r = (sgb_palettes[sgb_palette].colors[i] >> 16) & 0xFF; 115 | g = (sgb_palettes[sgb_palette].colors[i] >> 8) & 0xFF; 116 | b = sgb_palettes[sgb_palette].colors[i] & 0xFF; 117 | 118 | write_log("[sgb] SGB palette %d color %d = \e[38;2;%d;%d;%dm#%06X\e[0m\n", sgb_palette, i, r, g, b, sgb_palettes[sgb_palette].colors[i]); 119 | } 120 | #endif 121 | } 122 | 123 | void create_sgb_border_palettes() { 124 | uint8_t *data = (uint8_t *)(sgb_border_map + 0x800); 125 | uint16_t color16; 126 | uint32_t color32; 127 | 128 | for(int i = 0; i < 4; i++) { 129 | for(int j = 0; j < 16; j++) { 130 | color16 = data[(i * 32)+(j*2)] & 0xFF; 131 | color16 |= (data[(i * 32)+(j*2)+1]) << 8; 132 | 133 | color32 = truecolor(color16); 134 | 135 | #ifdef SGB_LOG 136 | int r = (color32 >> 16) & 0xFF; 137 | int g = (color32 >> 8) & 0xFF; 138 | int b = color32 & 0xFF; 139 | 140 | write_log("[sgb] SGB border palette %d color %d = \e[38;2;%d;%d;%dm#%06X\e[0m\n", i, j, r, g, b, color32); 141 | #endif 142 | 143 | sgb_border_palettes[i].colors[j] = color32; 144 | } 145 | } 146 | 147 | //sgb_color_zero = sgb_border_palettes[3].colors[0]; 148 | } 149 | 150 | void plot_sgb_tile(int x, int y, uint8_t tile, int palette, int xflip, int yflip) { 151 | //write_log("[sgb] plotting tile %d at x,y %d,%d with palette number %d\n", tile, x, y, palette); 152 | int xp = x << 3; 153 | int yp = y << 3; 154 | 155 | uint8_t *ptr = (uint8_t *)((sgb_tiles) + (tile * 32)); 156 | 157 | uint8_t color_index; 158 | uint8_t data3, data2, data1, data0; 159 | 160 | uint32_t color; 161 | 162 | // 8x8 tiles 163 | for(int i = 0; i < 8; i++) { 164 | for(int j = 7; j >= 0; j--) { 165 | data3 = (ptr[16+1] >> (j)) & 1; 166 | data2 = (ptr[16] >> (j)) & 1; 167 | data1 = (ptr[1] >> (j)) & 1; 168 | data0 = (ptr[0] >> (j)) & 1; 169 | 170 | data3 <<= 3; 171 | data2 <<= 2; 172 | data1 <<= 1; 173 | 174 | color_index = data3 | data2 | data1 | data0; 175 | 176 | //write_log("color index: %d\n", color_index); 177 | 178 | if(color_index) color = sgb_border_palettes[palette].colors[color_index]; 179 | else color = sgb_color_zero; 180 | sgb_border[(yp * 256) + xp] = color; 181 | xp++; 182 | } 183 | 184 | yp++; 185 | xp = x << 3; 186 | ptr += 2; 187 | } 188 | 189 | if(xflip) hflip_tile(sgb_border, x << 3, y << 3); 190 | if(yflip) vflip_tile(sgb_border, x << 3, y << 3); 191 | } 192 | 193 | void render_sgb_border() { 194 | if(sgb_screen_mask) return; 195 | 196 | #ifdef SGB_LOG 197 | write_log("[sgb] SGB border was modified, rendering...\n"); 198 | #endif 199 | 200 | create_sgb_border_palettes(); 201 | 202 | uint8_t *map = sgb_border_map; 203 | 204 | uint8_t tile, palette, xflip, yflip; 205 | 206 | // sgb border is 32x28 tiles 207 | for(int i = 0; i < 28; i++) { 208 | for(int j = 0; j < 32; j++) { 209 | //write_log("map entry %d,%d is 0x%04X\n", j, i, (uint16_t)(map[0] | (map[1] << 8))); 210 | 211 | tile = map[0]; 212 | palette = (map[1] >> 2) & 3; 213 | if(map[1] & 0x40) xflip = 1; 214 | else xflip = 0; 215 | 216 | if(map[1] & 0x80) yflip = 1; 217 | else yflip = 0; 218 | 219 | map += 2; 220 | 221 | plot_sgb_tile(j, i, tile, palette, xflip, yflip); 222 | } 223 | } 224 | 225 | // scale up the buffer 226 | if(scaling != 1) { 227 | for(int y = 0; y < sgb_scaled_h; y++) { 228 | uint32_t *dst = sgb_scaled_border + (y * sgb_scaled_w); 229 | uint32_t *src = sgb_border + ((y / scaling) * SGB_WIDTH); 230 | 231 | scale_xline(dst, src, sgb_scaled_w); 232 | } 233 | } 234 | 235 | update_border(sgb_scaled_border); 236 | } 237 | 238 | // 239 | // individual SGB commands 240 | // 241 | 242 | void sgb_mlt_req() { 243 | if(sgb_command.data[0] & 0x01) { 244 | if(sgb_command.data[0] & 0x02) { 245 | // four players 246 | sgb_joypad_count = 4; 247 | } else { 248 | sgb_joypad_count = 2; 249 | } 250 | 251 | #ifdef SGB_LOG 252 | write_log("[sgb] MLT_REQ: enabled %d multiplayer joypads\n", sgb_joypad_count); 253 | #endif 254 | sgb_current_joypad = 0x0F; 255 | sgb_interfere = 1; 256 | } else { 257 | #ifdef SGB_LOG 258 | write_log("[sgb] MLT_REQ: disabled multiplayer joypads\n"); 259 | #endif 260 | sgb_joypad_count = 1; 261 | sgb_interfere = 0; 262 | } 263 | } 264 | 265 | void sgb_mask_en() { 266 | sgb_command.data[0] %= 3; 267 | sgb_screen_mask = sgb_command.data[0]; 268 | 269 | #ifdef SGB_LOG 270 | if(sgb_command.data[0] == 0) { 271 | write_log("[sgb] MASK_EN: cancelling screen mask\n"); 272 | } else if(sgb_command.data[0] == 1) { 273 | write_log("[sgb] MASK_EN: freezing current screen\n"); 274 | } else if(sgb_command.data[0] == 2) { 275 | write_log("[sgb] MASK_EN: freezing screen at black\n"); 276 | } else { 277 | write_log("[sgb] MASK_EN: freezing screen at color zero\n"); 278 | } 279 | #endif 280 | 281 | if(using_sgb_border) render_sgb_border(); 282 | } 283 | 284 | void sgb_pal_trn() { 285 | #ifdef SGB_LOG 286 | write_log("[sgb] PAL_TRN: transferring palette data from VRAM to SNES\n"); 287 | #endif 288 | 289 | sgb_vram_transfer(sgb_palette_data); 290 | } 291 | 292 | void sgb_pal_set() { 293 | uint16_t *palette_numbers = (uint16_t *)(&sgb_command.data[0]); 294 | 295 | for(int i = 0; i < 4; i++) { 296 | #ifdef SGB_LOG 297 | write_log("[sgb] PAL_SET: palette %d -> system palette %d\n", i, palette_numbers[i]); 298 | #endif 299 | 300 | create_sgb_palette(i, palette_numbers[i]); 301 | } 302 | } 303 | 304 | void sgb_attr_blk() { 305 | #ifdef SGB_LOG 306 | write_log("[sgb] ATTR_BLK: setting color attributes with %d datasets\n", sgb_command.data[0]); 307 | #endif 308 | 309 | sgb_attr_block_count = sgb_command.data[0]; 310 | 311 | memset(&sgb_attr_blocks, 0, sizeof(sgb_attr_block_t)*18); 312 | 313 | uint8_t *ptr = &sgb_command.data[1]; 314 | for(int i = 0; i < sgb_command.data[0]; i++) { 315 | //write_log("[sgb] ATTR_BLK entry %d: flags 0x%02X from X/Y %d/%d to %d/%d\n", i, ptr[0], ptr[2], ptr[3], ptr[4], ptr[5]); 316 | if(ptr[0] & 0x01) sgb_attr_blocks[i].inside = 1; 317 | if(ptr[0] & 0x02) sgb_attr_blocks[i].surrounding = 1; 318 | if(ptr[0] & 0x04) sgb_attr_blocks[i].outside = 1; 319 | 320 | sgb_attr_blocks[i].palette_inside = ptr[1] & 3; 321 | sgb_attr_blocks[i].palette_surrounding = (ptr[1] >> 2) & 3; 322 | sgb_attr_blocks[i].palette_outside = (ptr[1] >> 4) & 3; 323 | 324 | sgb_attr_blocks[i].x1 = ptr[2] * 8; 325 | sgb_attr_blocks[i].y1 = ptr[3] * 8; 326 | sgb_attr_blocks[i].x2 = (ptr[4] + 1) * 8; 327 | sgb_attr_blocks[i].y2 = (ptr[5] + 1) * 8; 328 | 329 | #ifdef SGB_LOG 330 | write_log("[sgb] %d: flags 0x%02X from X,Y %d,%d to %d,%d", i, ptr[0], sgb_attr_blocks[i].x1, sgb_attr_blocks[i].y1, sgb_attr_blocks[i].x2, sgb_attr_blocks[i].y2); 331 | if(ptr[0]) { 332 | write_log(", "); 333 | if(sgb_attr_blocks[i].inside) { 334 | write_log("in = %d ", sgb_attr_blocks[i].palette_inside); 335 | } 336 | 337 | if(sgb_attr_blocks[i].outside) { 338 | write_log("out = %d ", sgb_attr_blocks[i].palette_outside); 339 | } 340 | 341 | if(sgb_attr_blocks[i].surrounding) { 342 | write_log("surround = %d ", sgb_attr_blocks[i].palette_surrounding); 343 | } 344 | } 345 | 346 | write_log("\n"); 347 | #endif 348 | 349 | ptr += 6; 350 | } 351 | 352 | using_sgb_palette = 1; 353 | } 354 | 355 | void sgb_chr_trn() { 356 | #ifdef SGB_LOG 357 | write_log("[sgb] CHR_TRN: transferring data for tiles %s from VRAM to SNES\n", (sgb_command.data[0] & 1) ? "0x80-0xFF" : "0x00-0x7F"); 358 | #endif 359 | 360 | if(sgb_command.data[0] & 1) { 361 | sgb_vram_transfer(sgb_tiles+4096); 362 | } else { 363 | sgb_vram_transfer(sgb_tiles); 364 | } 365 | 366 | if(using_sgb_border) render_sgb_border(); 367 | } 368 | 369 | void sgb_pct_trn() { 370 | #ifdef SGB_LOG 371 | write_log("[sgb] PCT_TRN: transferring data for SGB border from VRAM to SNES\n"); 372 | #endif 373 | 374 | sgb_vram_transfer(sgb_border_map); 375 | 376 | if(config_border) { 377 | if(!using_sgb_border) { 378 | resize_sgb_window(); 379 | } 380 | 381 | using_sgb_border = 1; 382 | gb_x = (SGB_WIDTH / 2) - (GB_WIDTH / 2); 383 | gb_y = (SGB_HEIGHT / 2) - (GB_HEIGHT / 2); 384 | gb_x *= scaling; 385 | gb_y *= scaling; 386 | 387 | render_sgb_border(); 388 | } 389 | } 390 | 391 | void handle_sgb_command() { 392 | uint8_t command; 393 | command = sgb_command.command_length >> 3; 394 | 395 | switch(command) { 396 | case SGB_MLT_REQ: 397 | return sgb_mlt_req(); 398 | case SGB_MASK_EN: 399 | return sgb_mask_en(); 400 | case SGB_PAL_TRN: 401 | return sgb_pal_trn(); 402 | case SGB_PAL_SET: 403 | return sgb_pal_set(); 404 | case SGB_ATTR_BLK: 405 | return sgb_attr_blk(); 406 | case SGB_CHR_TRN: 407 | return sgb_chr_trn(); 408 | case SGB_PCT_TRN: 409 | return sgb_pct_trn(); 410 | default: 411 | write_log("[sgb] unimplemented command 0x%02X, ignoring...\n", command); 412 | return; 413 | } 414 | } 415 | 416 | // for joypad.c 417 | 418 | void sgb_write(uint8_t byte) { 419 | uint8_t p14 = (byte >> 4) & 1; 420 | uint8_t p15 = (byte >> 5) & 1; 421 | 422 | if(!sgb_transferring && !p14 && !p15) { 423 | // reset signal 424 | sgb_transferring = 1; 425 | 426 | if(sgb_current_bit >= sgb_command_size) { 427 | sgb_current_bit = 0; 428 | memset(&sgb_command, 0, sizeof(sgb_command_t)); 429 | } else { 430 | // continuing a transfer 431 | sgb_command.stopped = 1; 432 | sgb_current_bit--; 433 | //write_log("continuing a transfer from bit %d\n", sgb_current_bit); 434 | } 435 | } 436 | 437 | if(!sgb_transferring && sgb_interfere) { 438 | // here the program is trying to read SGB state 439 | if(p14 && p15) { 440 | // both ones, return current joypad 441 | sgb_joypad_return = sgb_current_joypad; 442 | 443 | write_log("[sgb] current joypad is 0x%02X\n", sgb_joypad_return); 444 | 445 | sgb_current_joypad--; 446 | if(sgb_current_joypad < 0x0C) sgb_current_joypad = 0x0F; // wrap 447 | } else if(!p14 && p15) { 448 | // p14 = 0; p15 = 1; read directions 449 | if(sgb_joypad_return == 0x0F) sgb_joypad_return = (~(pressed_keys >> 4)) & 0x0F; 450 | else sgb_joypad_return = 0x0F; 451 | } else if(p14 && !p15) { 452 | // p14 = 1; p15 = 0; read buttons 453 | if(sgb_joypad_return == 0x0F) sgb_joypad_return = (~pressed_keys) & 0x0F; 454 | else sgb_joypad_return = 0x0F; 455 | } else { 456 | write_log("[sgb] unhandled unreachable code\n"); 457 | die(-1, ""); 458 | } 459 | 460 | return; 461 | } 462 | 463 | if(p14 == p15) { 464 | // if both zero, it's a reset pulse, ignore 465 | // likewise if both 1, it's a "wait" pulse, also ignore 466 | return; 467 | } 468 | 469 | // here we know they're different, so keep going 470 | if(!p14) { 471 | // a zero bit is being transferred 472 | // check if the PREVIOUS bit was a stop bit 473 | if(sgb_command.stopped) { 474 | sgb_command.stopped = 0; 475 | goto count; 476 | } else { 477 | // previous bit was NOT a stop bit, check if the current one is 478 | if((sgb_current_bit >= 128) && !(sgb_current_bit % 128)) { 479 | // this is a stop bit 480 | sgb_command.stopped = 1; 481 | sgb_transferring = 0; 482 | 483 | //write_log("[sgb] stop bit at %d\n", sgb_current_bit); 484 | 485 | sgb_command_size = (sgb_command.command_length & 7) * 16 * 8; // in bits 486 | if(sgb_current_bit >= sgb_command_size) { 487 | handle_sgb_command(); 488 | return; 489 | } 490 | } 491 | 492 | // nope, still not a stop bit 493 | sgb_command.stopped = 0; 494 | goto count; 495 | } 496 | } 497 | 498 | if(!p15) { 499 | // a one bit is being transferred 500 | int byte_number = sgb_current_bit / 8; 501 | int bit_number = sgb_current_bit % 8; 502 | 503 | if(!byte_number) { 504 | // command/length byte 505 | sgb_command.command_length |= (1 << bit_number); 506 | } else { 507 | // any other byte 508 | sgb_command.data[byte_number-1] |= (1 << bit_number); 509 | } 510 | } 511 | 512 | count: 513 | //write_log("write bit %d\n", sgb_current_bit); 514 | sgb_current_bit++; 515 | } 516 | 517 | inline uint8_t sgb_read() { 518 | return sgb_joypad_return; 519 | } 520 | 521 | inline int get_index_from_palette(uint32_t color, uint32_t *palette) { 522 | for(int i = 0; i < 4; i++) { 523 | if(palette[i] == color) return i; 524 | } 525 | 526 | write_log("[sgb] somehow landed on a color that isn't in an existing palette, quitting due to data corruption\n"); 527 | die(-1, ""); 528 | return -1; // unreachale 529 | } 530 | 531 | inline int get_palette_from_pos(int x, int y) { 532 | // THESE HAVE TO BE READ IN REVERSE ORDER 533 | // aka priority is for the one stated later 534 | for(int i = sgb_attr_block_count - 1; i >= 0; i--) { 535 | // check if inside or outside, in that order 536 | if(sgb_attr_blocks[i].inside) { 537 | if(x >= sgb_attr_blocks[i].x1 && x <= sgb_attr_blocks[i].x2 && y >= sgb_attr_blocks[i].y1 && y <= sgb_attr_blocks[i].y2) { 538 | return sgb_attr_blocks[i].palette_inside; 539 | } 540 | } 541 | 542 | if(sgb_attr_blocks[i].outside) { 543 | if(!(x >= sgb_attr_blocks[i].x1 && x <= sgb_attr_blocks[i].x2 && y >= sgb_attr_blocks[i].y1 && y <= sgb_attr_blocks[i].y2)) { 544 | return sgb_attr_blocks[i].palette_outside; 545 | } 546 | } 547 | 548 | if(sgb_attr_blocks[i].surrounding) { 549 | if((x >= sgb_attr_blocks[i].x1 && x <= sgb_attr_blocks[i].x2 && y >= sgb_attr_blocks[i].y1 && y <= sgb_attr_blocks[i].y2)) { 550 | return sgb_attr_blocks[i].palette_surrounding; 551 | } 552 | } 553 | } 554 | 555 | // somehow couldn't return anything, so just return zero 556 | return 0; 557 | } 558 | 559 | // recolors one line 560 | void sgb_recolor(uint32_t *dst, uint32_t *src, int ly, uint32_t *bw_palette) { 561 | int color_index, sgb_palette; 562 | for(int i = 0; i < GB_WIDTH; i++) { 563 | color_index = get_index_from_palette(src[i], bw_palette); 564 | sgb_palette = get_palette_from_pos(i, ly); 565 | 566 | dst[i] = sgb_palettes[sgb_palette].colors[color_index]; 567 | } 568 | } -------------------------------------------------------------------------------- /src/core/mbc.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | //#define MBC_LOG 13 | 14 | // Memory Bank Controller Implementation 15 | 16 | /* 17 | 18 | No MBC: 19 | - 32 KiB ROM is mapped directly at 0x0000-0x7FFF 20 | - Writes to this region are ignored 21 | 22 | MBC1: (ROM up to ALMOST 2 MiB and RAM up to 32 KiB) 23 | - Memory regions: 24 | - 0xA000-0xBFFF up to 4 banks of 8 KiB RAM 25 | - 0x0000-0x1FFF RAM enable (0x00 = disable, 0x0A in the lower 4 bits = enable) 26 | - 0x2000-0x3FFF BANK1: lower 5 bits of ROM bank select; value zero is read as one (i.e. 0x00 and 0x01 both select the same bank) 27 | - 0x4000-0x5FFF BANK2: upper 2 bits of ROM bank select OR RAM bank select according to next register 28 | - 0x6000-0x7FFF ROM/RAM banking toggle (0 = ROM, 1 = RAM) 29 | - In mode 0, ROM bank (BANK2 << 5) | BANK1 is available at 0x4000-0x7FFF and RAM bank zero is available at 0xA000-0xBFFF 30 | - In mode 1, ROM bank (BANK1) is available at 0x4000-0x7FFF and RAM bank (BANK2) is available at 0xA000-0xBFFF 31 | 32 | MBC3: (ROM up to full 2 MiB and RAM up to 32 KiB and real-time clock) 33 | - Memory regions: 34 | - 0xA000-0xBFFF up to 4 banks of 8 KiB RAM or RTC registers 35 | - 0x0000-0x1FFF RAM/RTC enable (0x00 = disable, 0x0A in the lower 4 bits = enable) 36 | - 0x2000-0x3FFF ROM bank select (full 7 bits, highest bit ignored); value zero is read as one just like MBC1 37 | - 0x4000-0x5FFF RAM bank select or RTC register select (0-3 = RAM bank, 0x08-0x0C = RTC register) 38 | - 0x6000-0x7FFF latch clock data (writing zero -> one latches the data onto the RTC registers) 39 | 40 | - RTC registers according to RTC register select: 41 | - 0x08 seconds 42 | - 0x09 minutes 43 | - 0x0A hours 44 | - 0x0B lower 8 bits of day counter 45 | - 0x0C: 46 | Bit 0 highest bit of day counter 47 | Bit 6 halt flag (0 = running, 1 = clock stopped) 48 | Bit 7 day counter carry bit (1 = overflown) 49 | 50 | MBC5: (ROM up to 8 MiB and RAM up to 128 KiB) 51 | - Memory regions: 52 | - 0xA000-0xBFFF up to 16 banks of 8 KiB RAM 53 | - 0x0000-0x1FFF RAM enable (0x00 = disable, 0x0A = enable) (*) 54 | - 0x2000-0x2FFF ROM bank select (low 8 bits) (**) 55 | - 0x3000-0x3FFF ROM bank select (9th bit in bit 0, all other bits ignored) 56 | - 0x4000-0x5FFF RAM bank select (low 4 bits, upper 4 bits ignored) 57 | 58 | (*) Unlike MBC1 and MBC3, the RAM enable register in MBC5 is a full 8-bit 59 | register, and ONLY 0x0A enables RAM, and not just in the low nibble. 60 | (**) This is the only known MBC that allows ROM bank 0 to appear in the 61 | 0x4000-0x7FFF region of memory by writing zero to the ROM select 62 | register. The MBC does not increment zeroes, unlike MBC1 and MBC3. 63 | 64 | */ 65 | 66 | mbc1_t mbc1; 67 | mbc3_t mbc3; 68 | mbc5_t mbc5; 69 | 70 | uint8_t *ex_ram; // pointer to cart RAM 71 | int ex_ram_size; 72 | char *ex_ram_filename; 73 | int ex_ram_modified = 0; 74 | 75 | int ex_ram_size_banks; 76 | int rom_size_banks; 77 | 78 | void mbc_start(void *cart_ram) { 79 | ex_ram_filename = calloc(strlen(rom_filename) + 5, 1); 80 | if(!ex_ram_filename) { 81 | write_log("[mbc] unable to allocate memory for filename\n"); 82 | die(-1, ""); 83 | } 84 | 85 | strcpy(ex_ram_filename, rom_filename); 86 | strcpy(ex_ram_filename+strlen(rom_filename), ".mbc"); 87 | 88 | ex_ram = (uint8_t *)cart_ram; 89 | 90 | uint8_t *rom_bytes = (uint8_t *)rom; 91 | switch(rom_bytes[0x149]) { 92 | case 0: 93 | ex_ram_size = 0; 94 | break; 95 | case 1: 96 | ex_ram_size = 2048; // bytes 97 | break; 98 | case 2: 99 | ex_ram_size = 8192; // bytes 100 | break; 101 | case 3: 102 | ex_ram_size = 32768; 103 | break; 104 | case 4: 105 | ex_ram_size = 131072; // 128 KiB for MBC5 106 | break; 107 | default: 108 | write_log("[mbc] undefined RAM size value 0x%02X, assuming 128 KiB RAM\n", rom_bytes[0x149]); 109 | ex_ram_size = 131072; // biggest possible value to stay on the safest size 110 | } 111 | 112 | ex_ram_size_banks = ex_ram_size / 8192; 113 | rom_size_banks = rom_size / 16384; 114 | 115 | switch(mbc_type) { 116 | case 1: 117 | mbc1.bank1 = 1; 118 | mbc1.bank2 = 0; 119 | mbc1.ram_enable = 0; 120 | mbc1.mode = 0; 121 | break; 122 | case 3: 123 | mbc3.ram_rtc_bank = 0; 124 | mbc3.rom_bank = 1; 125 | mbc3.ram_rtc_enable = 0; 126 | mbc3.ram_rtc_toggle = 0; // RAM 127 | break; 128 | case 5: 129 | mbc5.ram_bank = 0; 130 | mbc5.rom_bank = 1; 131 | mbc5.ram_enable = 0; 132 | break; 133 | default: 134 | write_log("[mbc] unimplemented MBC type %d\n", mbc_type); 135 | die(-1, ""); 136 | } 137 | 138 | write_log("[mbc] MBC started with %d KiB of external RAM\n", ex_ram_size/1024); 139 | if(ex_ram_size) { 140 | write_log("[mbc] battery-backed RAM will read from and dumped to %s\n", ex_ram_filename); 141 | 142 | // read ram file 143 | FILE *file = fopen(ex_ram_filename, "r"); 144 | if(!file) { 145 | write_log("[mbc] unable to open %s for reading, assuming no RAM file\n", ex_ram_filename); 146 | return; 147 | } 148 | 149 | if(!fread(ex_ram, 1, ex_ram_size, file)) { 150 | write_log("[mbc] unable to read from file %s, assuming no RAM file\n", ex_ram_filename); 151 | memset(ex_ram, 0, ex_ram_size); 152 | fclose(file); 153 | return; 154 | } 155 | 156 | fclose(file); 157 | } 158 | 159 | write_log("[mbc] ROM size in banks is %d\n", rom_size_banks); 160 | } 161 | 162 | void write_ramfile() { 163 | if(!ex_ram_size || !ex_ram_modified) return; 164 | 165 | remove(ex_ram_filename); 166 | FILE *file = fopen(ex_ram_filename, "wb"); 167 | if(!file) { 168 | write_log("[mbc] unable to open %s for writing\n", ex_ram_filename); 169 | return; 170 | } 171 | 172 | if(fwrite(ex_ram, 1, ex_ram_size, file) != ex_ram_size) { 173 | write_log("[mbc] unable to write to file %s\n", ex_ram_filename); 174 | fclose(file); 175 | return; 176 | } 177 | 178 | fflush(file); 179 | fclose(file); 180 | write_log("[mbc] wrote RAM file to %s\n", ex_ram_filename); 181 | } 182 | 183 | // MBC3 functions here 184 | static inline uint8_t mbc3_read(uint16_t addr) { 185 | uint8_t *rom_bytes = (uint8_t *)rom; 186 | if(addr >= 0x4000 && addr <= 0x7FFF) { 187 | addr -= 0x4000; 188 | return rom_bytes[(mbc3.rom_bank * 16384) + addr]; 189 | } else if(addr >= 0xA000 && addr <= 0xBFFF) { 190 | if(!mbc3.ram_rtc_enable) { 191 | write_log("[mbc] warning: attempt to read from address 0x%04X when external RAM/RTC is disabled, returning ones\n", addr); 192 | return 0xFF; 193 | } 194 | 195 | if(mbc3.ram_rtc_bank <= 3) { 196 | // ram 197 | return ex_ram[(mbc3.ram_rtc_bank * 8192) + (addr - 0xA000)]; 198 | } else if(mbc3.ram_rtc_bank >= 0x08 && mbc3.ram_rtc_bank <= 0x0C) { 199 | // rtc 200 | time_t rawtime; 201 | struct tm *timeinfo; 202 | 203 | time(&rawtime); 204 | timeinfo = localtime(&rawtime); 205 | 206 | uint8_t status; 207 | 208 | switch(mbc3.ram_rtc_bank) { 209 | case 0x08: 210 | if(timeinfo->tm_sec == 60) return 59; 211 | else return timeinfo->tm_sec; 212 | case 0x09: 213 | return timeinfo->tm_min; 214 | case 0x0A: 215 | return timeinfo->tm_hour; 216 | case 0x0B: 217 | // lower 8 bits 218 | return timeinfo->tm_yday & 0xFF; 219 | case 0x0C: 220 | status = (timeinfo->tm_yday >> 8) & 1; // highest bit 221 | if(mbc3.halt) status |= 0x40; // halt flag 222 | return status; 223 | default: 224 | write_log("[mbc] undefined read from RTC/RAM bank 0x%02X address 0x%04X, returning ones\n", mbc3.ram_rtc_bank, addr); 225 | return 0xFF; 226 | } 227 | } else { 228 | // undefined 229 | write_log("[mbc] undefined read from RTC/RAM bank 0x%02X address 0x%04X, returning ones\n", mbc3.ram_rtc_bank, addr); 230 | return 0xFF; 231 | } 232 | } else { 233 | write_log("[mbc] unimplemented read at address 0x%04X in MBC%d\n", addr, mbc_type); 234 | die(-1, NULL); 235 | return 0xFF; // unreachable 236 | } 237 | } 238 | 239 | static inline void mbc3_write(uint16_t addr, uint8_t byte) { 240 | if(addr >= 0x2000 && addr <= 0x3FFF) { 241 | byte &= 0x7F; 242 | if(!byte) byte = 1; 243 | 244 | #ifdef MBC_LOG 245 | write_log("[mbc] selecting ROM bank %d\n", byte); 246 | #endif 247 | 248 | mbc3.rom_bank = byte; 249 | } else if(addr >= 0x4000 && addr <= 0x5FFF) { 250 | byte &= 0x0F; 251 | 252 | #ifdef MBC_LOG 253 | if(byte <= 3) { 254 | write_log("[mbc] selecting RAM bank %d\n", byte); 255 | } else if(byte >= 0x08 && byte <= 0x0C) { 256 | write_log("[mbc] selecting RTC register 0x%02X\n", byte); 257 | } else { 258 | write_log("[mbc] selecting undefined RAM/RTC register %d, ignoring...\n", byte); 259 | } 260 | #endif 261 | 262 | mbc3.ram_rtc_bank = byte; 263 | } else if(addr >= 0x0000 && addr <= 0x1FFF) { 264 | byte &= 0x0F; 265 | if(byte == 0x0A) { 266 | mbc3.ram_rtc_enable = 1; 267 | ex_ram_modified = 0; 268 | #ifdef MBC_LOG 269 | write_log("[mbc] enabled access to external RAM and RTC\n"); 270 | #endif 271 | } else { 272 | mbc3.ram_rtc_enable = 0; 273 | #ifdef MBC_LOG 274 | write_log("[mbc] disabled access to external RAM and RTC\n"); 275 | #endif 276 | 277 | // dump the ram file here 278 | write_ramfile(); 279 | } 280 | } else if(addr >= 0xA000 && addr <= 0xBFFF) { 281 | if(!mbc3.ram_rtc_enable) { 282 | write_log("[mbc] warning: attempt to write to address 0x%04X value 0x%02X when external RAM/RTC is disabled\n", addr, byte); 283 | return; 284 | } 285 | 286 | if(mbc3.ram_rtc_bank <= 3) { 287 | // ram 288 | ex_ram[(mbc3.ram_rtc_bank * 8192) + (addr - 0xA000)] = byte; 289 | ex_ram_modified = 1; 290 | } else { 291 | // rtc 292 | //write_log("[mbc] TODO: implement writing to RTC registers (register 0x%02X value 0x%02X)\n", mbc3.ram_rtc_bank, byte); 293 | return; // ignore for now 294 | } 295 | } else if(addr >= 0x6000 && addr <= 0x7FFF) { 296 | mbc3.old_latch_data = mbc3.latch_data; 297 | mbc3.latch_data = byte; 298 | } else { 299 | write_log("[mbc] unimplemented write at address 0x%04X value 0x%02X in MBC%d\n", addr, byte, mbc_type); 300 | die(-1, NULL); 301 | } 302 | } 303 | 304 | // MBC1 functions here 305 | static inline void mbc1_write(uint16_t addr, uint8_t byte) { 306 | if(addr >= 0x2000 && addr <= 0x3FFF) { 307 | byte &= 0x1F; // lower 5 bits 308 | mbc1.bank1 = byte; 309 | } else if(addr >= 0x4000 && addr <= 0x5FFF) { 310 | byte &= 3; // 2 bits 311 | mbc1.bank2 = byte; 312 | } else if(addr >= 0x6000 && addr <= 0x7FFF) { 313 | byte &= 1; // one bit 314 | mbc1.mode = byte; 315 | } else if(addr >= 0x0000 && addr <= 0x1FFF) { 316 | byte &= 0x0F; 317 | if(byte == 0x0A) { 318 | mbc1.ram_enable = 1; 319 | ex_ram_modified = 0; 320 | #ifdef MBC_LOG 321 | write_log("[mbc] enabled access to external RAM\n"); 322 | #endif 323 | } else { 324 | mbc1.ram_enable = 0; 325 | #ifdef MBC_LOG 326 | write_log("[mbc] disabled access to external RAM\n"); 327 | #endif 328 | 329 | write_ramfile(); 330 | } 331 | } else if(addr >= 0xA000 && addr <= 0xBFFF) { 332 | // ram 333 | if(!mbc1.ram_enable) { 334 | write_log("[mbc] warning: attempt to write to address 0x%04X value 0x%02X when external RAM is disabled\n", addr, byte); 335 | return; 336 | } 337 | 338 | int ram_bank; 339 | if(mbc1.mode) ram_bank = mbc1.bank2 & 3; 340 | else ram_bank = 0; 341 | 342 | ex_ram[(ram_bank * 8192) + (addr - 0xA000)] = byte; 343 | ex_ram_modified = 1; 344 | } else { 345 | write_log("[mbc] unimplemented write at address 0x%04X value 0x%02X in MBC%d\n", addr, byte, mbc_type); 346 | die(-1, NULL); 347 | } 348 | } 349 | 350 | static inline uint8_t mbc1_read(uint16_t addr) { 351 | int rom_bank, ram_bank; 352 | uint8_t *rom_bytes = (uint8_t *)rom; 353 | 354 | if(addr >= 0x0000 && addr <= 0x3FFF) { 355 | /*if(mbc1.mode) { 356 | rom_bank = mbc1.bank2 << 5; 357 | } else { 358 | rom_bank = 0; 359 | }*/ 360 | 361 | rom_bank = 0; 362 | 363 | return rom_bytes[(rom_bank * 16384) + addr]; 364 | } else if(addr >= 0x4000 && addr <= 0x7FFF) { 365 | //rom_bank = (mbc1.bank2 << 5) | mbc1.bank1; 366 | 367 | if(mbc1.mode) { 368 | rom_bank = mbc1.bank1 & 0x1F; 369 | } else { 370 | rom_bank = ((mbc1.bank2 << 5) & 3) | (mbc1.bank1 & 0x1F); 371 | } 372 | 373 | //rom_bank &= 374 | 375 | if(rom_bank) { 376 | rom_bank &= (rom_size_banks-1); 377 | } else { 378 | rom_bank++; 379 | } 380 | 381 | //rom_bank &= (rom_size_banks-1); 382 | //if(!rom_bank) rom_bank++; 383 | 384 | addr -= 0x4000; 385 | return rom_bytes[(rom_bank * 16384) + addr]; 386 | } else if(addr >= 0xA000 && addr <= 0xBFFF) { 387 | if(!mbc1.ram_enable) { 388 | write_log("[mbc] warning: attempt to read from address 0x%04X when external RAM is disabled, returning ones\n", addr); 389 | return 0xFF; 390 | } 391 | 392 | if(mbc1.mode) ram_bank = mbc1.bank2 & 3; 393 | else ram_bank = 0; 394 | 395 | return ex_ram[(ram_bank * 8192) + (addr - 0xA000)]; 396 | } else { 397 | write_log("[mbc] unimplemented read at address 0x%04X in MBC%d\n", addr, mbc_type); 398 | die(-1, NULL); 399 | return 0xFF; 400 | } 401 | } 402 | 403 | 404 | // MBC5 functions here 405 | static inline void mbc5_write(uint16_t addr, uint8_t byte) { 406 | if(addr >= 0x2000 && addr <= 0x2FFF) { 407 | mbc5.rom_bank &= 0x100; 408 | mbc5.rom_bank |= byte; // low 8 bits of ROM bank select 409 | 410 | #ifdef MBC_LOG 411 | write_log("[mbc] selecting ROM bank %d\n", mbc5.rom_bank); 412 | #endif 413 | } else if(addr >= 0x3000 && addr <= 0x3FFF) { 414 | mbc5.rom_bank &= 0xFF; 415 | mbc5.rom_bank |= ((byte & 1) << 8); // high bit of ROM bank select 416 | 417 | #ifdef MBC_LOG 418 | write_log("[mbc] selecting ROM bank %d\n", mbc5.rom_bank); 419 | #endif 420 | } else if(addr >= 0x4000 && addr <= 0x5FFF) { 421 | byte &= 0x0F; 422 | mbc5.ram_bank = byte; 423 | 424 | #ifdef MBC_LOG 425 | write_log("[mbc] selecting RAM bank %d\n", byte); 426 | #endif 427 | } else if(addr >= 0x0000 && addr <= 0x1FFF) { 428 | if(byte == 0x0A) { 429 | mbc5.ram_enable = 1; 430 | ex_ram_modified = 0; 431 | #ifdef MBC_LOG 432 | write_log("[mbc] enabled access to external RAM\n"); 433 | #endif 434 | } else { 435 | mbc5.ram_enable = 0; 436 | #ifdef MBC_LOG 437 | write_log("[mbc] disabled access to external RAM\n"); 438 | #endif 439 | 440 | write_ramfile(); 441 | } 442 | } else if(addr >= 0xA000 && addr <= 0xBFFF) { 443 | if(!mbc5.ram_enable) { 444 | write_log("[mbc] warning: attempt to write to address 0x%04X value 0x%02X when external RAM is disabled\n", addr, byte); 445 | return; 446 | } 447 | 448 | ex_ram[(mbc5.ram_bank * 8192) + (addr - 0xA000)] = byte; 449 | ex_ram_modified = 1; 450 | } else if(addr <= 0x6000 && addr <= 0x7FFF) { 451 | // i can't find any info on what this does but apparently pokemon yellow does this? 452 | write_log("[mbc] warning: undefined write at address 0x%04X value 0x%02X in MBC5, ignoring\n", addr, byte); 453 | return; 454 | } else { 455 | write_log("[mbc] unimplemented write at address 0x%04X value 0x%02X in MBC%d\n", addr, byte, mbc_type); 456 | die(-1, NULL); 457 | } 458 | } 459 | 460 | static inline uint8_t mbc5_read(uint16_t addr) { 461 | uint8_t *rom_bytes = (uint8_t *)rom; 462 | if(addr >= 0x4000 && addr <= 0x7FFF) { 463 | addr -= 0x4000; 464 | return rom_bytes[((mbc5.rom_bank & (rom_size_banks-1)) * 16384) + addr]; 465 | } else if(addr >= 0xA000 && addr <= 0xBFFF) { 466 | if(!mbc5.ram_enable) { 467 | write_log("[mbc] warning: attempt to read from address 0x%04X when external RAM is disabled, returning ones\n", addr); 468 | return 0xFF; 469 | } 470 | 471 | return ex_ram[(mbc5.ram_bank * 8192) + (addr - 0xA000)]; 472 | } else { 473 | write_log("[mbc] unimplemented read at address 0x%04X in MBC%d\n", addr, mbc_type); 474 | die(-1, NULL); 475 | return 0xFF; 476 | } 477 | } 478 | 479 | // general fucntions called from memory.c 480 | uint8_t mbc_read(uint16_t addr) { 481 | switch(mbc_type) { 482 | case 1: 483 | return mbc1_read(addr); 484 | case 3: 485 | return mbc3_read(addr); 486 | case 5: 487 | return mbc5_read(addr); 488 | default: 489 | write_log("[mbc] unimplemented read at address 0x%04X in MBC%d\n", addr, mbc_type); 490 | die(-1, NULL); 491 | return 0xFF; // unreachable 492 | } 493 | } 494 | 495 | void mbc_write(uint16_t addr, uint8_t byte) { 496 | switch(mbc_type) { 497 | case 0: 498 | write_log("[mbc] undefined write to read-only region 0x%04X value 0x%02X in MBC%d, ignoring...\n", addr, byte, mbc_type); 499 | return; 500 | case 1: 501 | return mbc1_write(addr, byte); 502 | case 3: 503 | return mbc3_write(addr, byte); 504 | case 5: 505 | return mbc5_write(addr, byte); 506 | default: 507 | write_log("[mbc] unimplemented write at address 0x%04X value 0x%02X in MBC%d\n", addr, byte, mbc_type); 508 | die(-1, NULL); 509 | } 510 | } -------------------------------------------------------------------------------- /src/core/display.c: -------------------------------------------------------------------------------- 1 | 2 | /* tinygb - a tiny gameboy emulator 3 | (c) 2022 by jewel */ 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | //#define DISPLAY_LOG 11 | 12 | /* 13 | 14 | Notes to self regarding how the display works: 15 | - Horizontal line starts at mode 2 (reading OAM) 16 | - Next mode is mode 3 (reading both OAM and VRAM) 17 | - Next mode is mode 0 (H-blank, not reading anything) 18 | - After 144 lines are completed, enter mode 1 (V-blank) 19 | - V-blank lasts for 10 "lines" in which nothing is being read 20 | 21 | - The program does not write direct pixels to the screen, instead it has tile 22 | data stored in VRAM. The background is drawn as a map of tiles, and the 23 | window is drawn on top of the background also as a map of tiles, and finally 24 | the sprites (OAM) are drawn as a final map. 25 | 26 | - Mode (2 --> 3 --> 0) 144 times 27 | - Mode (1) 10 times 28 | 29 | */ 30 | 31 | #define HDMA_GENERAL 0 32 | #define HDMA_HBLANK 1 33 | 34 | display_t display; 35 | int display_cycles = 0; 36 | 37 | void *vram; 38 | uint32_t *framebuffer, *scaled_framebuffer, *temp_framebuffer; 39 | uint32_t *background_buffer; 40 | uint8_t oam[OAM_SIZE]; 41 | 42 | int scaled_w, scaled_h; 43 | 44 | int framecount = 0; 45 | int hdma_active = 0; 46 | int hdma_type; 47 | 48 | static int line_rendered = 0; 49 | 50 | int hdma_hblank_next_line; 51 | int hdma_hblank_cycles = 0; 52 | 53 | int drawn_frames = 0; 54 | 55 | uint32_t bw_palette[4] = { 56 | 0xC4CFA1, 0x8B956D, 0x4D533C, 0x1F1F1F 57 | }; 58 | 59 | uint32_t preset_palettes[10][4] = { 60 | {0xC4CFA1, 0x8B956D, 0x4D533C, 0x1F1F1F}, // 0 61 | {0x9BEBEB, 0x6DA1DF, 0x6653CB, 0x501A68}, // 1 62 | {0xFFF5DE, 0xFD9785, 0xF60983, 0x15017A}, // 2 63 | {0xDCEDEB, 0x90ADBB, 0x56689D, 0x262338}, // 3 64 | {0xF7FFB7, 0xA5D145, 0x2A8037, 0x001B27}, // 4 65 | {0xFBDFB7, 0xFFB037, 0xEE316B, 0x842D72}, // 5 66 | {0xFE7BBF, 0x974EC3, 0x504099, 0x313866}, // 6 67 | {0x58CCED, 0x3895D3, 0x1261A0, 0x072F5F}, // 7 68 | {0xFEFDDF, 0xFDD037, 0xFAB22C, 0xDA791A}, // 8 69 | {0xFFFFFF, 0xAAAAAA, 0x555555, 0x000000}, // 9 70 | }; 71 | 72 | uint32_t cgb_palette[4]; 73 | 74 | int monochrome_palette; 75 | 76 | // dummy debug function 77 | void cgb_dump_bgpd() { 78 | uint32_t color32; 79 | uint16_t color16; 80 | uint8_t r, g, b; 81 | 82 | for(int i = 0; i < 8; i++) { 83 | for(int j = 0; j < 4; j++) { 84 | color16 = display.bgpd[(i*8)+(j*2)]; 85 | color16 |= (display.bgpd[(i*8)+(j*2)+1] << 8); 86 | 87 | color32 = truecolor(color16); 88 | r = (color32 >> 16) & 0xFF; 89 | g = (color32 >> 8) & 0xFF; 90 | b = color32 & 0xFF; 91 | 92 | write_log("[display] CGB bg palette %d color %d = \e[38;2;%d;%d;%dm#%06X\e[0m\n", i, j, r, g, b, color32); 93 | } 94 | } 95 | } 96 | 97 | void cgb_dump_obpd() { 98 | uint32_t color32; 99 | uint16_t color16; 100 | uint8_t r, g, b; 101 | 102 | for(int i = 0; i < 8; i++) { 103 | for(int j = 0; j < 4; j++) { 104 | color16 = display.obpd[(i*8)+(j*2)]; 105 | color16 |= (display.obpd[(i*8)+(j*2)+1] << 8); 106 | 107 | color32 = truecolor(color16); 108 | r = (color32 >> 16) & 0xFF; 109 | g = (color32 >> 8) & 0xFF; 110 | b = color32 & 0xFF; 111 | 112 | write_log("[display] CGB obj palette %d color %d = \e[38;2;%d;%d;%dm#%06X\e[0m\n", i, j, r, g, b, color32); 113 | } 114 | } 115 | } 116 | 117 | void load_bw_palette() { 118 | if(monochrome_palette > 9) monochrome_palette = 0; 119 | 120 | write_log("[display] loaded monochrome palette %d\n", monochrome_palette); 121 | 122 | bw_palette[0] = preset_palettes[monochrome_palette][0]; 123 | bw_palette[1] = preset_palettes[monochrome_palette][1]; 124 | bw_palette[2] = preset_palettes[monochrome_palette][2]; 125 | bw_palette[3] = preset_palettes[monochrome_palette][3]; 126 | } 127 | 128 | void next_palette() { 129 | if(monochrome_palette >= 9) monochrome_palette = 0; 130 | else monochrome_palette++; 131 | 132 | load_bw_palette(); 133 | } 134 | 135 | void prev_palette() { 136 | if(monochrome_palette == 0) monochrome_palette = 9; 137 | else monochrome_palette--; 138 | 139 | load_bw_palette(); 140 | } 141 | 142 | void display_start() { 143 | memset(&display, 0, sizeof(display_t)); 144 | display.lcdc = 0x91; 145 | display.scy = 0; 146 | display.scx = 0; 147 | display.ly = 0; 148 | display.lyc = 0; 149 | display.bgp = 0xFC; 150 | display.obp0 = 0xFF; 151 | display.obp1 = 0xFF; 152 | display.wy = 0; 153 | display.wx = 0; 154 | 155 | load_bw_palette(); 156 | 157 | if(is_cgb) { 158 | for(int i = 0; i < 32; i++) { 159 | // bg palette is initialized to white in CGB 160 | display.bgpd[i*2] = 0xFF; 161 | display.bgpd[(i*2)+1] = 0x7F; 162 | } 163 | } 164 | 165 | scaled_w = scaling*GB_WIDTH; 166 | scaled_h = scaling*GB_HEIGHT; 167 | 168 | vram = calloc(1, 16384); // 8 KB for original gb, 2x8 KB for CGB 169 | if(!vram) { 170 | die(-1, "unable to allocate memory for VRAM\n"); 171 | } 172 | 173 | framebuffer = calloc(GB_WIDTH*GB_HEIGHT, 4); 174 | temp_framebuffer = calloc(GB_WIDTH*GB_HEIGHT, 4); 175 | background_buffer = calloc(256*256, 4); 176 | if(scaling != 1) scaled_framebuffer = calloc(GB_WIDTH*GB_HEIGHT, 4*scaling*scaling*4); 177 | else scaled_framebuffer = framebuffer; 178 | 179 | if(!framebuffer || !scaled_framebuffer || !temp_framebuffer || !background_buffer) { 180 | die(-1, "unable to allocate memory for framebuffer\n"); 181 | } 182 | 183 | write_log("[display] initialized display\n"); 184 | } 185 | 186 | void handle_general_hdma() { 187 | uint16_t src = (display.hdma1 << 8) | (display.hdma2 & 0xF0); 188 | uint16_t dst = ((display.hdma3 & 0x1F) << 8) | (display.hdma4 & 0xF0); 189 | dst += 0x8000; 190 | 191 | int count = (display.hdma5 + 1) << 4; 192 | 193 | #ifdef DISPLAY_LOG 194 | write_log("[display] handle general HDMA transfer from 0x%04X to 0x%04X, %d bytes\n", src, dst, count); 195 | #endif 196 | 197 | for(int i = 0; i < count; i++) { 198 | write_byte(dst+i, read_byte(src+i)); 199 | } 200 | 201 | display.hdma5 = 0xFF; 202 | } 203 | 204 | void handle_hblank_hdma() { 205 | uint16_t src = (display.hdma1 << 8) | (display.hdma2 & 0xF0); 206 | uint16_t dst = ((display.hdma3 & 0x1F) << 8) | (display.hdma4 & 0xF0); 207 | dst += 0x8000; 208 | 209 | #ifdef DISPLAY_LOG 210 | write_log("[display] handle H-blank HDMA transfer from 0x%04X to 0x%04X, 16 bytes at LY=%d\n", src, dst, display.ly); 211 | #endif 212 | 213 | for(int i = 0; i < 16; i++) { 214 | write_byte(dst+i, read_byte(src+i)); 215 | } 216 | 217 | src += 16; 218 | dst += 16; 219 | dst -= 0x8000; 220 | 221 | display.hdma1 = (src >> 8) & 0xFF; 222 | display.hdma2 = src & 0xF0; 223 | display.hdma3 = (dst >> 8) & 0x1F; 224 | display.hdma4 = dst & 0xF0; 225 | 226 | display.hdma5--; 227 | if(display.hdma5 == 0x7F) { 228 | // done 229 | #ifdef DISPLAY_LOG 230 | write_log("[display] completed H-blank transfer\n"); 231 | #endif 232 | display.hdma5 = 0xFF; 233 | } 234 | } 235 | 236 | void display_write(uint16_t addr, uint8_t byte) { 237 | switch(addr) { 238 | case LCDC: 239 | #ifdef DISPLAY_LOG 240 | write_log("[display] write to LCDC register value 0x%02X\n", byte); 241 | #endif 242 | display.lcdc = byte; 243 | return; 244 | case STAT: 245 | #ifdef DISPLAY_LOG 246 | write_log("[display] write to STAT register value 0x%02X, ignoring lowest 3 bits\n", byte); 247 | #endif 248 | byte &= 0xF8; 249 | display.stat &= 7; 250 | display.stat |= byte; 251 | return; 252 | case SCX: 253 | #ifdef DISPLAY_LOG 254 | write_log("[display] write to SCX register value 0x%02X\n", byte); 255 | #endif 256 | display.scx = byte; 257 | return; 258 | case SCY: 259 | #ifdef DISPLAY_LOG 260 | write_log("[display] write to SCY register value 0x%02X\n", byte); 261 | #endif 262 | display.scy = byte; 263 | return; 264 | case LY: 265 | #ifdef DISPLAY_LOG 266 | write_log("[display] write to LY register, resetting...\n"); 267 | #endif 268 | display.ly = 0; 269 | return; 270 | case LYC: 271 | #ifdef DISPLAY_LOG 272 | write_log("[display] write to LYC register value 0x%02X\n", byte); 273 | #endif 274 | display.lyc = byte; 275 | return; 276 | case BGP: 277 | #ifdef DISPLAY_LOG 278 | write_log("[display] write to BGP register value 0x%02X\n", byte); 279 | #endif 280 | display.bgp = byte; 281 | return; 282 | case OBP0: 283 | #ifdef DISPLAY_LOG 284 | write_log("[display] write to OBP0 register value 0x%02X\n", byte); 285 | #endif 286 | display.obp0 = byte; 287 | return; 288 | case OBP1: 289 | #ifdef DISPLAY_LOG 290 | write_log("[display] write to OBP1 register value 0x%02X\n", byte); 291 | #endif 292 | display.obp1 = byte; 293 | return; 294 | case WX: 295 | #ifdef DISPLAY_LOG 296 | write_log("[display] write to WX register value 0x%02X\n", byte); 297 | #endif 298 | display.wx = byte; 299 | return; 300 | case WY: 301 | #ifdef DISPLAY_LOG 302 | write_log("[display] write to WY register value 0x%02X\n", byte); 303 | #endif 304 | display.wy = byte; 305 | return; 306 | case DMA: 307 | #ifdef DISPLAY_LOG 308 | write_log("[display] write to DMA register value 0x%02X\n", byte); 309 | #endif 310 | display.dma = byte; 311 | return; 312 | case VBK: 313 | if(is_cgb) { 314 | #ifdef DISPLAY_LOG 315 | write_log("[display] write to VBK register value 0x%02X, ignoring upper 7 bits...\n", byte); 316 | #endif 317 | 318 | byte &= 1; // only lowest bit matters 319 | display.vbk = byte; 320 | } else { 321 | //write_log("[display] write to VBK register value 0x%02X in non-CGB mode, ignoring...\n", byte); 322 | } 323 | case HDMA1: 324 | if(is_cgb) { 325 | #ifdef DISPLAY_LOG 326 | write_log("[display] write to HDMA1 register value 0x%02X\n", byte); 327 | #endif 328 | display.hdma1 = byte; 329 | } else { 330 | //write_log("[display] write to HDMA1 register value 0x%02X in non-CGB mode, ignoring...\n", byte); 331 | } 332 | return; 333 | case HDMA2: 334 | if(is_cgb) { 335 | #ifdef DISPLAY_LOG 336 | write_log("[display] write to HDMA2 register value 0x%02X\n", byte); 337 | #endif 338 | display.hdma2 = byte; 339 | } else { 340 | //write_log("[display] write to HDMA2 register value 0x%02X in non-CGB mode, ignoring...\n", byte); 341 | } 342 | return; 343 | case HDMA3: 344 | if(is_cgb) { 345 | #ifdef DISPLAY_LOG 346 | write_log("[display] write to HDMA3 register value 0x%02X\n", byte); 347 | #endif 348 | display.hdma3 = byte; 349 | } else { 350 | //write_log("[display] write to HDMA3 register value 0x%02X in non-CGB mode, ignoring...\n", byte); 351 | } 352 | return; 353 | case HDMA4: 354 | if(is_cgb) { 355 | #ifdef DISPLAY_LOG 356 | write_log("[display] write to HDMA4 register value 0x%02X\n", byte); 357 | #endif 358 | display.hdma4 = byte; 359 | } else { 360 | //write_log("[display] write to HDMA4 register value 0x%02X in non-CGB mode, ignoring...\n", byte); 361 | } 362 | return; 363 | case HDMA5: 364 | if(is_cgb) { 365 | #ifdef DISPLAY_LOG 366 | write_log("[display] write to HDMA5 register value 0x%02X\n", byte); 367 | #endif 368 | 369 | if(byte & 0x80) { 370 | // H-blank DMA 371 | #ifdef DISPLAY_LOG 372 | write_log("[display] H-blank DMA %d bytes from 0x%02X%02X to VRAM 0x%02X%02X\n", ((byte & 0x7F) + 1)*16, display.hdma1, display.hdma2, (display.hdma3 & 0x1F) + 0x80, display.hdma4); 373 | #endif 374 | display.hdma5 = byte; // display_cycle() will handle the rest from here 375 | if(!(display.stat & 3)) { // already in mode 0 (H-blank) 376 | handle_hblank_hdma(); 377 | } 378 | } else { 379 | // differentiate between general purpose DMA and cancelling H-blank DMA 380 | if(display.hdma5 == 0xFF || !(display.hdma5 & 0x80)) { 381 | display.hdma5 = byte; 382 | handle_general_hdma(); 383 | } else { 384 | #ifdef DISPLAY_LOG 385 | write_log("[display] cancelled H-blank DMA transfer\n"); 386 | #endif 387 | display.hdma5 &= 0x7F; 388 | } 389 | } 390 | 391 | } else { 392 | //write_log("[display] write to HDMA5 register value 0x%02X in non-CGB mode, ignoring...\n", byte); 393 | } 394 | return; 395 | case BGPI: 396 | if(!is_cgb) { 397 | //write_log("[display] write to BGPI register value 0x%02X in non-CGB mode, ignoring...\n", byte); 398 | } else { 399 | display.bgpi = byte; 400 | } 401 | return; 402 | case OBPI: 403 | if(!is_cgb) { 404 | //write_log("[display] write to OBPI register value 0x%02X in non-CGB mode, ignoring...\n", byte); 405 | } else { 406 | display.obpi = byte; 407 | } 408 | return; 409 | case BGPD: 410 | if(!is_cgb) { 411 | //write_log("[display] write to BGPD register value 0x%02X in non-CGB mode, ignoring...\n", byte); 412 | } else { 413 | int index = display.bgpi & 0x3F; 414 | display.bgpd[index] = byte; 415 | 416 | if(display.bgpi & 0x80) { // auto increment 417 | index++; 418 | display.bgpi = (index & 0x3F) | 0x80; 419 | } 420 | } 421 | return; 422 | case OBPD: 423 | if(!is_cgb) { 424 | //write_log("[display] write to OBPD register value 0x%02X in non-CGB mode, ignoring...\n", byte); 425 | } else { 426 | int index = display.obpi & 0x3F; 427 | display.obpd[index] = byte; 428 | 429 | if(display.obpi & 0x80) { // auto increment 430 | index++; 431 | display.obpi = (index & 0x3F) | 0x80; 432 | } 433 | } 434 | return; 435 | default: 436 | write_log("[memory] unimplemented write to I/O port 0x%04X value 0x%02X\n", addr, byte); 437 | die(-1, NULL); 438 | } 439 | } 440 | 441 | uint8_t display_read(uint16_t addr) { 442 | switch(addr) { 443 | case LCDC: 444 | return display.lcdc; 445 | case STAT: 446 | return display.stat; 447 | case SCY: 448 | return display.scy; 449 | case SCX: 450 | return display.scx; 451 | case LY: 452 | return display.ly; 453 | case LYC: 454 | return display.lyc; 455 | case DMA: 456 | write_log("[display] undefined read from write-only DMA register, returning ones\n"); 457 | return 0xFF; 458 | case BGP: 459 | return display.bgp; 460 | case OBP0: 461 | return display.obp0; 462 | case OBP1: 463 | return display.obp1; 464 | case WX: 465 | return display.wx; 466 | case WY: 467 | return display.wy; 468 | case VBK: 469 | if(is_cgb) { 470 | return display.vbk; 471 | } else { 472 | //write_log("[display] undefined read from VBK in non-CGB mode, returning ones\n"); 473 | return 0xFF; 474 | } 475 | case HDMA1: 476 | if(is_cgb) return display.hdma1; 477 | else return 0xFF; 478 | case HDMA2: 479 | if(is_cgb) return display.hdma2; 480 | else return 0xFF; 481 | case HDMA3: 482 | if(is_cgb) return display.hdma3; 483 | else return 0xFF; 484 | case HDMA4: 485 | if(is_cgb) return display.hdma4; 486 | else return 0xFF; 487 | case HDMA5: 488 | if(is_cgb) { 489 | if(display.hdma5 == 0xFF) return 0xFF; 490 | else return display.hdma5 ^ 0x80; // 0 = active, 1 = inactive, contrary to common sense 491 | } 492 | else return 0xFF; 493 | default: 494 | write_log("[memory] unimplemented read from IO port 0x%04X\n", addr); 495 | die(-1, NULL); 496 | } 497 | 498 | return 0xFF; // unreachable 499 | } 500 | 501 | void scale_xline(uint32_t *new, uint32_t *old, int scaled_width) { 502 | for(int i = 0; i < scaled_width; i++) { 503 | //printf("copy new X %d, old X %d\n", i, i/scaling); 504 | new[i] = old[(i/scaling)]; 505 | //new[i] = 0xFFFFFF; 506 | 507 | //printf("old x = %d, old y = %d\n", i/scaling, y); 508 | } 509 | } 510 | 511 | void update_framebuffer() { 512 | // scale up the buffer 513 | if(scaling != 1) { 514 | for(int y = 0; y < scaled_h; y++) { 515 | uint32_t *dst = scaled_framebuffer + (y * scaled_w); 516 | uint32_t *src = framebuffer + ((y / scaling) * GB_WIDTH); 517 | 518 | scale_xline(dst, src, scaled_w); 519 | } 520 | } 521 | 522 | // write it to the screen 523 | /*if(surface->format->BytesPerPixel == 4) { 524 | // 32-bpp 525 | for(int i = 0; i < scaled_h; i++) { 526 | //void *src = (void *)(scaled_framebuffer + (i * GB_WIDTH * 4)); 527 | //void *src = (void *)(scaled_framebuffer + (i * scaled_w)); 528 | void *src = (void *)(scaled_framebuffer + (i * scaled_w)); 529 | void *dst = (void *)(surface->pixels + (i * surface->pitch)); 530 | memcpy(dst, src, scaled_w*4); 531 | } 532 | } else { 533 | die(-1, "unimplemented non 32-bpp surfaces\n"); 534 | } 535 | 536 | //framecount++; 537 | if(framecount > frameskip) { 538 | SDL_UpdateWindowSurface(window); 539 | framecount = 0; 540 | drawn_frames++; 541 | }*/ 542 | 543 | update_window(scaled_framebuffer); 544 | } 545 | 546 | void cgb_bg_palette(int palette) { // dump the palette into cgb_palette[] 547 | uint16_t color16; 548 | uint32_t color32; 549 | 550 | for(int i = 0; i < 4; i++) { 551 | color16 = display.bgpd[(palette<<3)+(i<<1)] & 0xFF; 552 | color16 |= (display.bgpd[(palette<<3)+(i<<1)+1] & 0xFF) << 8; 553 | color32 = truecolor(color16); 554 | 555 | cgb_palette[i] = color32; 556 | } 557 | } 558 | 559 | void cgb_obj_palette(int palette) { // dump the palette into cgb_palette[] 560 | uint16_t color16; 561 | uint32_t color32; 562 | 563 | for(int i = 0; i < 4; i++) { 564 | color16 = display.obpd[(palette<<3)+(i<<1)] & 0xFF; 565 | color16 |= (display.obpd[(palette<<3)+(i<<1)+1] & 0xFF) << 8; 566 | color32 = truecolor(color16); 567 | 568 | cgb_palette[i] = color32; 569 | } 570 | } 571 | 572 | void hflip_tile(uint32_t *buffer, int x, int y) { 573 | // flips an 8x8 tile within a 256x256 buffer 574 | // to be used in backgrounds, windows, and SGB borders 575 | //write_log("flipping tile at x,y %d,%d\n", x, y); 576 | 577 | uint32_t temp_color; 578 | 579 | uint32_t *ptr = (uint32_t *)((void *)buffer + (y * 256*4) + (x * 4)); 580 | 581 | for(int i = 0; i < 8; i++) { 582 | for(int j = 0; j < 4; j++) { 583 | temp_color = ptr[7-j]; 584 | ptr[7-j] = ptr[j]; 585 | ptr[j] = temp_color; 586 | } 587 | 588 | ptr += 256; 589 | } 590 | } 591 | 592 | void vflip_tile(uint32_t *buffer, int x, int y) { 593 | // flips an 8x8 tile within a 256x256 buffer 594 | uint32_t temp_color; 595 | 596 | uint32_t *ptr1 = (uint32_t *)((void *)buffer + (y * 256*4) + (x * 4)); 597 | uint32_t *ptr2 = (uint32_t *)((void *)buffer + ((y+7) * 256*4) + (x * 4)); 598 | 599 | for(int i = 0; i < 4; i++) { 600 | for(int j = 0; j < 8; j++) { 601 | temp_color = ptr1[j]; 602 | ptr1[j] = ptr2[j]; 603 | ptr2[j] = temp_color; 604 | } 605 | 606 | ptr1 += 256; 607 | ptr2 -= 256; 608 | } 609 | } 610 | 611 | void plot_bg_tile(int is_window, int x, int y, uint8_t tile, uint8_t *tile_data, uint8_t cgb_flags) { 612 | // x and y are in tiles, not pixels 613 | int xp = x << 3; // x8 614 | int yp = y << 3; 615 | 616 | int visible_row; // only draw one row, save 8x performance 617 | 618 | if(!is_window) { 619 | if(display.scy >= 113) { // 255 minus 143 620 | // a wraparound will inevitably occur 621 | int bg_line = display.scy + display.ly; 622 | 623 | if(bg_line >= 256) { 624 | // wrap occured 625 | bg_line -= 256; 626 | 627 | int wrapped_ly = display.ly - (256 - display.scy); 628 | if(!((wrapped_ly) >= yp && (wrapped_ly) <= (yp+7))) { 629 | return; 630 | } 631 | 632 | visible_row = wrapped_ly - yp; 633 | } else { 634 | // no wrap 635 | if(!((display.ly+display.scy) >= yp && (display.ly+display.scy) <= (yp+7))) { 636 | return; // save a fuckton of performance 637 | } 638 | 639 | visible_row = (display.ly+display.scy) - yp; 640 | } 641 | } else { 642 | if(!((display.ly+display.scy) >= yp && (display.ly+display.scy) <= (yp+7))) { 643 | return; // save a fuckton of performance 644 | } 645 | 646 | visible_row = (display.ly+display.scy) - yp; 647 | } 648 | } else { 649 | if(xp >= GB_WIDTH || yp >= GB_HEIGHT) return; 650 | if(!(display.ly >= (yp+display.wy) && display.ly <= (yp+display.wy+7))) return; 651 | 652 | visible_row = display.ly - (yp+display.wy); 653 | } 654 | 655 | //if(!is_window) { 656 | //write_log("tile xp %d yp %d LY %d visible row %d\n", xp, yp, display.ly, visible_row); 657 | //} 658 | 659 | uint32_t color; 660 | uint8_t data, color_index; 661 | uint8_t data_lo, data_hi; 662 | uint8_t *ptr; 663 | uint8_t positive_tile; 664 | 665 | /*write_log("[display] rendering bg tile %d, data bytes ", tile); 666 | 667 | for(int i = 0; i < 16; i++) { 668 | write_log("%02X ", tile_data[(tile * 16) + i]); 669 | } 670 | 671 | write_log("\n");*/ 672 | 673 | int cgb_palette_number; 674 | 675 | if(display.lcdc & 0x10) ptr = tile_data + (tile * 16); // normal positive 676 | else { 677 | tile_data += 0x800; // to 0x9000 678 | 679 | if(tile & 0x80) { 680 | // negative 681 | positive_tile = ~tile; 682 | positive_tile++; 683 | 684 | ptr = tile_data - (positive_tile * 16); 685 | } else { 686 | // positive 687 | ptr = tile_data + (tile * 16); 688 | } 689 | } 690 | 691 | if(is_cgb && (cgb_flags & 0x08)) { 692 | // tile is in bank 1 693 | ptr += 8192; 694 | } 695 | 696 | if(is_cgb) { 697 | cgb_palette_number = cgb_flags & 7; 698 | cgb_bg_palette(cgb_palette_number); 699 | } 700 | 701 | // 8x8 tiles 702 | for(int i = 0; i < 8; i++) { 703 | //printf("data for row %d is %02X %02X\n", i, ptr[0], ptr[1]); 704 | 705 | if(i == visible_row) { 706 | for(int j = 7; j >= 0; j--) { 707 | 708 | /*int s = 6 - ((j % 3) * 2); 709 | data = *ptr >> s; 710 | data &= 3;*/ 711 | 712 | /*data = (ptr[1] >> (7 - j)); 713 | data <<= 1; 714 | data &= 2; // keep only bit 1 715 | data |= ptr[0] >> (7 - j) & 1;*/ 716 | 717 | data_hi = (ptr[1] >> (j)) & 1; 718 | data_hi <<= 1; 719 | 720 | data_lo = (ptr[0] >> (j)); 721 | data_lo &= 1; 722 | 723 | data = data_hi | data_lo; 724 | 725 | //printf("data for x/y %d/%d is %d\n", i, j, data); 726 | 727 | if(!is_cgb) { 728 | color_index = (display.bgp >> (data * 2)) & 3; 729 | color = bw_palette[color_index]; 730 | } else { 731 | color = cgb_palette[data]; 732 | } 733 | 734 | background_buffer[(yp * 256) + xp] = color; 735 | 736 | /*if(color != 0xFFFFFF) { 737 | printf("h"); 738 | }*/ 739 | 740 | xp++; 741 | } 742 | } 743 | 744 | yp++; 745 | xp = x << 3; // x8 746 | ptr += 2; 747 | } 748 | 749 | if(is_cgb) { 750 | if(cgb_flags & 0x20) hflip_tile(background_buffer, x << 3, y << 3); 751 | if(cgb_flags & 0x40) vflip_tile(background_buffer, x << 3, y << 3); 752 | } 753 | } 754 | 755 | inline void hflip_sprite(uint32_t *sprite_colors, uint8_t *sprite_data) { 756 | // horizontal flip 757 | uint32_t temp_color; 758 | uint8_t temp_data; 759 | 760 | for(int y = 0; y < 8; y++) { 761 | for(int x = 0; x < 4; x++) { 762 | temp_color = sprite_colors[(y*8)+7-x]; 763 | temp_data = sprite_data[(y*8)+7-x]; 764 | 765 | sprite_colors[(y*8)+7-x] = sprite_colors[(y*8)+x]; 766 | sprite_data[(y*8)+7-x] = sprite_data[(y*8)+x]; 767 | 768 | sprite_colors[(y*8)+x] = temp_color; 769 | sprite_data[(y*8)+x] = temp_data; 770 | } 771 | } 772 | } 773 | 774 | inline void vflip_sprite(uint32_t *sprite_colors, uint8_t *sprite_data) { 775 | // vertical flip 776 | uint32_t temp_color; 777 | uint8_t temp_data; 778 | 779 | uint32_t *ptr2_colors = sprite_colors + 56; 780 | uint8_t *ptr2_data = sprite_data + 56; 781 | 782 | for(int y = 0; y < 4; y++) { 783 | for(int x = 0; x < 8; x++) { 784 | temp_color = sprite_colors[x]; 785 | temp_data = sprite_data[x]; 786 | 787 | sprite_colors[x] = ptr2_colors[x]; 788 | sprite_data[x] = ptr2_data[x]; 789 | 790 | ptr2_colors[x] = temp_color; 791 | ptr2_data[x] = temp_data; 792 | } 793 | 794 | sprite_colors += 8; 795 | sprite_data += 8; 796 | ptr2_colors -= 8; 797 | ptr2_data -= 8; 798 | } 799 | } 800 | 801 | void plot_small_sprite(int n) { 802 | // n max 40 803 | /*if(n >= 40) { 804 | write_log("[display] warning: attempt to draw non-existent sprite number %d, ignoring...\n", n); 805 | return; 806 | }*/ 807 | 808 | uint8_t *oam_data = oam + (n * 4); 809 | 810 | uint8_t x, y, tile, flags; 811 | y = oam_data[0]; 812 | x = oam_data[1]; 813 | tile = oam_data[2]; 814 | flags = oam_data[3]; 815 | 816 | uint8_t data, data_lo, data_hi, color_index; 817 | uint32_t color, bg_color, bg_color_zero; 818 | 819 | if(!y || y >= 152 || !x || x >= 168) return; // invisible sprite 820 | 821 | x -= 8; 822 | y -= 16; 823 | 824 | if(!(display.ly >= y && display.ly <= y+8)) return; // performance 825 | 826 | //write_log("[display] plotting tile %d at x/y %d/%d\n", tile, x, y); 827 | 828 | // 8x8 tiles 829 | uint8_t *tile_data = vram + 0x0000; // always starts at 0x8000, unlike bg/window 830 | uint8_t *ptr = tile_data + (tile * 16); 831 | 832 | uint32_t sprite_colors[64]; // 8x8 833 | uint8_t sprite_data[64]; 834 | int sprite_data_index = 0; 835 | int cgb_palette_number; 836 | 837 | if(!is_cgb) { 838 | // get bg color zero for layering 839 | bg_color_zero = bw_palette[display.bgp & 3]; 840 | } else { 841 | cgb_bg_palette(0); 842 | bg_color_zero = cgb_palette[0]; 843 | 844 | // prepare cgb palette 845 | cgb_palette_number = flags & 7; 846 | cgb_obj_palette(cgb_palette_number); 847 | 848 | if(flags & 0x08) ptr += 8192; // bank 1 849 | } 850 | 851 | sprite_data_index = 0; 852 | for(int i = 0; i < 8; i++) { 853 | for(int j = 7; j >= 0; j--) { 854 | data_hi = (ptr[1] >> (j)) & 1; 855 | data_hi <<= 1; 856 | 857 | data_lo = (ptr[0] >> (j)); 858 | data_lo &= 1; 859 | 860 | data = data_hi | data_lo; 861 | 862 | if(!is_cgb) { 863 | // monochrome palettes 864 | if(flags & 0x10) color_index = (display.obp1 >> (data * 2)) & 3; // palette 1 865 | else color_index = (display.obp0 >> (data * 2)) & 3; // palette 0 866 | color = bw_palette[color_index]; 867 | } else { 868 | // cgb palettes 869 | color = cgb_palette[data]; 870 | } 871 | 872 | sprite_colors[sprite_data_index] = color; 873 | sprite_data[sprite_data_index] = data; 874 | 875 | sprite_data_index++; 876 | } 877 | 878 | ptr += 2; 879 | } 880 | 881 | // check if we need to flip this sprite 882 | if(flags & 0x20) hflip_sprite(sprite_colors, sprite_data); // horizontal flip 883 | if(flags & 0x40) vflip_sprite(sprite_colors, sprite_data); // vertical flip 884 | 885 | // now plot the actual sprite 886 | sprite_data_index = 0; 887 | for(int i = 0; i < 8; i++) { 888 | for(int j = 0; j < 8; j++) { 889 | if(flags & 0x80) { 890 | // sprite is behind bg colors 1-3, on top of bg color 0 891 | 892 | // get bg color 893 | bg_color = temp_framebuffer[((i + y) * GB_WIDTH) + (j + x)]; 894 | if((bg_color == bg_color_zero) && sprite_data[sprite_data_index]) temp_framebuffer[((i + y) * GB_WIDTH) + (j + x)] = sprite_colors[sprite_data_index]; 895 | } else { 896 | // sprite is on top of bg, normal scenario 897 | // sprite color value zero means transparent, so only plot non-zero values 898 | if(sprite_data[sprite_data_index]) temp_framebuffer[((i + y) * GB_WIDTH) + (j + x)] = sprite_colors[sprite_data_index]; 899 | } 900 | 901 | sprite_data_index++; 902 | } 903 | } 904 | 905 | return; 906 | } 907 | 908 | void render_line() { 909 | uint32_t *src = temp_framebuffer + (display.ly * GB_WIDTH); 910 | uint32_t *dst = framebuffer + (display.ly * GB_WIDTH); 911 | 912 | if(is_sgb && sgb_screen_mask) { 913 | uint32_t sgb_blank_color; 914 | switch(sgb_screen_mask) { 915 | case 1: // freeze at current frame 916 | return; 917 | case 2: // freeze black 918 | sgb_blank_color = bw_palette[3]; 919 | break; 920 | case 3: // freeze color zero 921 | default: 922 | sgb_blank_color = bw_palette[0]; 923 | } 924 | 925 | for(int i = 0; i < GB_WIDTH; i++) { 926 | dst[i] = sgb_blank_color; 927 | } 928 | 929 | return; 930 | } 931 | 932 | // renders a single horizontal line 933 | copy_oam(oam); 934 | 935 | uint8_t *bg_win_tiles; 936 | if(display.lcdc & 0x10) bg_win_tiles = vram + 0; // 0x8000-0x8FFF 937 | else bg_win_tiles = vram + 0x800; // 0x8800-0x97FF 938 | 939 | // test if background is enabled 940 | if(display.lcdc & 0x01) { 941 | uint8_t *bg_map; 942 | uint8_t *bg_cgb_flags; 943 | if(display.lcdc & 0x08) bg_map = vram + 0x1C00; // 0x9C00-0x9FFF 944 | else bg_map = vram + 0x1800; // 0x9800-0x9BFF 945 | 946 | bg_cgb_flags = bg_map + 8192; // next bank 947 | 948 | for(int y = 0; y < 32; y++) { 949 | for(int x = 0; x < 32; x++) { 950 | plot_bg_tile(0, x, y, *bg_map, bg_win_tiles, *bg_cgb_flags); 951 | bg_map++; 952 | bg_cgb_flags++; 953 | } 954 | } 955 | 956 | // here the background has been drawn, copy the visible part of it 957 | //write_log("[display] rendering background, SCY = %d, SCX = %d, LY = %d\n", display.scy, display.scx, display.ly); 958 | int temp_index = 0; 959 | unsigned int bg_index = display.scy * 256; 960 | unsigned int bg_x = display.scx, bg_y = display.scy; 961 | 962 | for(int y = 0; y < GB_HEIGHT; y++) { 963 | if(bg_y > 255) { 964 | bg_y = 0; 965 | } 966 | 967 | bg_x = display.scx; 968 | bg_index = (bg_y * 256) + bg_x; 969 | 970 | for(int x = 0; x < GB_WIDTH; x++) { 971 | if(bg_x > 255) { 972 | bg_index -= 256; 973 | bg_x = 0; 974 | } 975 | 976 | //temp_framebuffer[(y * GB_WIDTH) + x] = background_buffer[((y + display.scy) * 256) + (x + display.scx)]; 977 | temp_framebuffer[temp_index+x] = background_buffer[bg_index+x]; 978 | 979 | bg_x++; 980 | } 981 | 982 | temp_index += GB_WIDTH; 983 | bg_y++; 984 | } 985 | 986 | } else { 987 | // no background, clear to white 988 | for(int i = 0; i < GB_WIDTH*GB_HEIGHT; i++) { 989 | temp_framebuffer[i] = bw_palette[0]; 990 | } 991 | } 992 | 993 | // window layer on top of the background 994 | if(display.lcdc & 0x20) { // && display.wx >= 7 && display.wx <= 166 && display.wy <= 143) { 995 | // window enabled 996 | uint8_t *win_map; 997 | uint8_t *win_cgb_flags; 998 | if(display.lcdc & 0x40) win_map = vram + 0x1C00; // 0x9C00-0x9FFF 999 | else win_map = vram + 0x1800; // 0x9800-0x9BFF 1000 | 1001 | win_cgb_flags = win_map + 8192; // next bank 1002 | 1003 | // windows have the same format as backgrounds 1004 | for(int y = 0; y < 32; y++) { 1005 | for(int x = 0; x < 32; x++) { 1006 | plot_bg_tile(1, x, y, *win_map, bg_win_tiles, *win_cgb_flags); 1007 | win_map++; 1008 | win_cgb_flags++; 1009 | } 1010 | } 1011 | 1012 | // draw the window 1013 | int wx; 1014 | if(display.wx <= 7) wx = 0; 1015 | else wx = display.wx - 7; 1016 | 1017 | int wy = display.wy; 1018 | int temp_index = (wy * GB_WIDTH) + (wx); 1019 | int bg_index = 0; 1020 | 1021 | for(int y = 0; y < GB_HEIGHT - wy; y++) { 1022 | for(int x = 0; x < GB_WIDTH - wx; x++) { 1023 | temp_framebuffer[temp_index + x] = background_buffer[bg_index + x]; 1024 | } 1025 | 1026 | temp_index += GB_WIDTH; 1027 | bg_index += 256; 1028 | } 1029 | } 1030 | 1031 | // object layer 1032 | if(display.lcdc & 0x02) { 1033 | // sprites are enabled 1034 | if(display.lcdc & 0x04) { 1035 | // 8x16 sprites 1036 | uint8_t *oam_data = oam; 1037 | uint8_t tile_store; 1038 | 1039 | for(int i = 0; i < 40; i++) { 1040 | tile_store = oam_data[2]; 1041 | 1042 | oam_data[2] &= 0xFE; // upper tile 1043 | plot_small_sprite(i); 1044 | oam_data[2] |= 0x01; // lower tile 1045 | oam_data[0] += 8; // y - lower tile 1046 | plot_small_sprite(i); 1047 | 1048 | oam_data[2] = tile_store; 1049 | oam_data[0] -= 8; // back to what it was 1050 | 1051 | oam_data += 4; 1052 | } 1053 | } else { 1054 | for(int i = 0; i < 40; i++) { // 40 sprites 1055 | plot_small_sprite(i); 1056 | } 1057 | } 1058 | } 1059 | 1060 | line_rendered = 1; 1061 | 1062 | // done, copy the singular line we were at 1063 | if(using_sgb_palette) { 1064 | return sgb_recolor(dst, src, display.ly, bw_palette); 1065 | } 1066 | 1067 | for(int i = 0; i < GB_WIDTH; i++) { 1068 | dst[i] = src[i]; 1069 | } 1070 | } 1071 | 1072 | void display_cycle() { 1073 | if(!(display.lcdc & LCDC_ENABLE)) return; 1074 | display_cycles += timing.last_instruction_cycles; 1075 | 1076 | // handle OAM DMA transfers if ongoing 1077 | if(display.dma) { 1078 | uint16_t dma_src = display.dma << 8; 1079 | 1080 | #ifdef DISPLAY_LOG 1081 | //write_log("[display] DMA transfer from 0x%04X to sprite OAM region\n", dma_src); 1082 | #endif 1083 | 1084 | for(int i = 0; i < OAM_SIZE; i++) { 1085 | write_byte(0xFE00+i, read_byte(dma_src+i)); 1086 | } 1087 | 1088 | display.dma = 0; 1089 | } 1090 | 1091 | // mode 2 = 0 -> 79 1092 | // mode 3 = 80 -> 251 1093 | // mode 0 = 252 -> 455 1094 | 1095 | // mode 1 is a special case where it goes through all of these cycles 10 times 1096 | uint8_t mode = display.stat & 3; 1097 | 1098 | //write_log("[display] cycles = %d, mode = %d, LY = %d, STAT = 0x%02X\n", display_cycles, mode, display.ly, display.stat); 1099 | if(mode == 1) { // vblank is a special case 1100 | //write_log("[display] in vblank, io_if = 0x%02X\n", io_if); 1101 | if(display_cycles >= 456) { 1102 | display_cycles -= 456; // dont lose any cycles 1103 | 1104 | display.ly++; 1105 | line_rendered = 0; 1106 | if(display.ly >= 154) { 1107 | // vblank is now over 1108 | display.stat &= 0xFC; 1109 | display.stat |= 2; // enter mode 2 1110 | display.ly = 0; 1111 | 1112 | if(display.stat & 0x20) { 1113 | send_interrupt(1); 1114 | } 1115 | } 1116 | 1117 | if(display.ly == display.lyc) { 1118 | // TODO: send STAT interrupt 1119 | display.stat |= 0x04; // coincidence flag 1120 | if(display.stat & 0x40) { 1121 | //write_log("[display] sending STAT interrupt at LY=LYC=%d\n", display.ly); 1122 | send_interrupt(1); 1123 | } 1124 | } else { 1125 | display.stat &= 0xFB; 1126 | } 1127 | } 1128 | } else { 1129 | // all other modes 1130 | if(display_cycles <= 79) { 1131 | // mode 2 -- reading OAM 1132 | display.stat &= 0xFC; 1133 | display.stat |= 2; 1134 | 1135 | if(mode != 2 && display.stat & 0x20) { 1136 | // just entered mode 2 1137 | //write_log("entered mode 2 on line %d\n", display.ly); 1138 | send_interrupt(1); 1139 | } 1140 | } else if(display_cycles <= 251) { 1141 | // mode 3 -- reading OAM and VRAM 1142 | display.stat &= 0xFC; 1143 | display.stat |= 3; 1144 | 1145 | // complete one line 1146 | if((framecount > frameskip) && !line_rendered) render_line(); 1147 | } else if(display_cycles <= 455) { 1148 | // mode 0 1149 | display.stat &= 0xFC; 1150 | 1151 | if(mode != 0 && display.stat & 0x08) { 1152 | // just entered mode 0 1153 | //die(-1, "entered mode 0 STAT\n"); 1154 | send_interrupt(1); 1155 | 1156 | // handle CGB HDMA transfer 1157 | if(is_cgb && display.hdma5 & 0x80 && display.hdma5 != 0xFF) { 1158 | handle_hblank_hdma(); 1159 | } 1160 | } 1161 | 1162 | } else if(display_cycles >= 456) { 1163 | // a horizontal line has been completed 1164 | display_cycles -= 456; // dont lose any cycles 1165 | 1166 | display.ly++; 1167 | line_rendered = 0; 1168 | if(display.ly >= 144) { 1169 | // begin vblank (mode 1) 1170 | display.stat &= 0xFC; 1171 | display.stat |= 1; 1172 | 1173 | //write_log("[display] entering vblank state, STAT = 0x%02X\n", display.stat); 1174 | 1175 | send_interrupt(0); 1176 | 1177 | // update the actual screen 1178 | update_framebuffer(); 1179 | framecount++; 1180 | } else { 1181 | /* // return to mode zero -- what? 1182 | display.stat &= 0xFC; */ 1183 | 1184 | // mode TWO not zero 1185 | display.stat &= 0xFC; 1186 | display.stat |= 2; 1187 | 1188 | if(mode != 2 && display.stat & 0x20) { 1189 | // just entered mode 2 1190 | //write_log("entered mode 2 on line %d\n", display.ly); 1191 | send_interrupt(1); 1192 | } 1193 | } 1194 | 1195 | if(display.ly == display.lyc) { 1196 | // TODO: send STAT interrupt 1197 | display.stat |= 0x04; // coincidence flag 1198 | if(display.stat & 0x40) { 1199 | //write_log("[display] sending STAT interrupt at LY=LYC=%d\n", display.ly); 1200 | send_interrupt(1); 1201 | } 1202 | } else { 1203 | display.stat &= 0xFB; 1204 | } 1205 | } 1206 | } 1207 | } 1208 | 1209 | void vram_write(uint16_t addr, uint8_t byte) { 1210 | //write_log("[display] write to VRAM 0x%04X value 0x%02X\n", addr, byte); 1211 | addr -= 0x8000; 1212 | 1213 | uint8_t *ptr = (uint8_t *)vram + addr; 1214 | ptr += (8192 * display.vbk); // for CGB banking 1215 | 1216 | *ptr = byte; 1217 | } 1218 | 1219 | uint8_t vram_read(uint16_t addr) { 1220 | addr -= 0x8000; 1221 | 1222 | uint8_t *ptr = (uint8_t *)vram + addr; 1223 | ptr += (8192 * display.vbk); 1224 | 1225 | return *ptr; 1226 | } --------------------------------------------------------------------------------