Compare commits

...

4 Commits

Author SHA1 Message Date
Matthew Laux b2a6890524 save game support, and don't update the timer for now, it breaks things 2022-08-04 15:03:58 -05:00
Matthew Laux 03dab9524b bad timer implementation 2022-08-04 01:57:14 -05:00
Matthew Laux af18bb9a39 lcd window support 2022-08-04 01:31:59 -05:00
Matthew Laux d6b6988742 use color palettes. yay, another layer of lookup 2022-08-03 19:34:56 -05:00
7 changed files with 164 additions and 49 deletions

View File

@ -13,6 +13,7 @@ extern "C" {
#include "cpu.h"
#include "rom.h"
#include "lcd.h"
#include "mbc.h"
#include "instructions.h"
}
@ -62,7 +63,16 @@ GLuint make_output_texture() {
return image_texture;
}
unsigned char default_palette[] = { 0xff, 0xaa, 0x55, 0x00 };
unsigned int default_palette[] = { 0x9ff4e5, 0x00b9be, 0x005f8c, 0x002b59 };
// ugly evenly spaced: { 0xffffff, 0xaaaaaa, 0x555555, 0x000000 };
// bgb default: {0xe0f8d0, 0x88c070, 0x346856, 0x081820};
// https://lospec.com/palette-list/blk-aqu4
// { 0x9ff4e5, 0x00b9be, 0x005f8c, 0x002b59 };
// https://lospec.com/palette-list/velvet-cherry-gb
// { 0x9775a6, 0x683a68, 0x412752, 0x2d162c };
void convert_output(struct lcd *lcd) {
int x, y;
@ -72,9 +82,9 @@ void convert_output(struct lcd *lcd) {
int val = lcd->buf[y * 256 + x];
int fill = default_palette[val];
//int fill = val ? 255 : 0;
output_image[out_index++] = fill;
output_image[out_index++] = fill;
output_image[out_index++] = fill;
output_image[out_index++] = (fill >> 16) & 0xff;
output_image[out_index++] = (fill >> 8) & 0xff;
output_image[out_index++] = fill & 0xff;
output_image[out_index++] = 255;
}
}
@ -83,9 +93,9 @@ void convert_output(struct lcd *lcd) {
for (x = 0; x < 160; x++) {
int val = lcd->pixels[y * 160 + x];
int fill = default_palette[val];
visible_pixels[out_index++] = fill;
visible_pixels[out_index++] = fill;
visible_pixels[out_index++] = fill;
visible_pixels[out_index++] = (fill >> 16) & 0xff;
visible_pixels[out_index++] = (fill >> 8) & 0xff;
visible_pixels[out_index++] = fill & 0xff;
visible_pixels[out_index++] = 255;
}
}
@ -154,6 +164,7 @@ int main(int argc, char *argv[])
dmg_new(&dmg, &cpu, &rom, &lcd);
cpu.dmg = &dmg;
mbc_load_ram(dmg.rom->mbc, "save.sav");
// cpu_bind_mem_model(&cpu, &dmg, dmg_read, dmg_write);
cpu.pc = 0x100;
@ -373,6 +384,7 @@ int main(int argc, char *argv[])
SDL_DestroyWindow(window);
SDL_Quit();
mbc_save_ram(dmg.rom->mbc, "save.sav");
rom_free(&rom);
return 0;

104
src/dmg.c
View File

@ -81,7 +81,7 @@ u8 dmg_read(void *_dmg, u16 address)
return dmg->zero_page[address - 0xff80];
} else if (address == 0xff00) {
return get_button_state(dmg);
} else if (address == 0xff04) {
} else if (address == REG_TIMER_DIV) {
counter++;
return counter;
} else if (address == 0xff0f) {
@ -145,34 +145,37 @@ void dmg_request_interrupt(struct dmg *dmg, int nr)
}
// TODO move to lcd.c, it needs to be able to access dmg_read though
static void render_background(struct dmg *dmg, int lcdc)
static void render_background(struct dmg *dmg, int lcdc, int is_window)
{
int bg_base = (lcdc & LCDC_BG_TILE_MAP) ? 0x9c00 : 0x9800;
int window_base = (lcdc & LCDC_WINDOW_TILE_MAP) ? 0x9c00 : 0x9800;
int use_unsigned = lcdc & LCDC_BG_TILE_DATA;
int tilebase = use_unsigned ? 0x8000 : 0x9000;
int bg_map = (lcdc & LCDC_BG_TILE_MAP) ? 0x9c00 : 0x9800;
int window_map = (lcdc & LCDC_WINDOW_TILE_MAP) ? 0x9c00 : 0x9800;
int map_base_addr = is_window ? window_map : bg_map;
int unsigned_mode = lcdc & LCDC_BG_TILE_DATA;
int tile_base_addr = unsigned_mode ? 0x8000 : 0x9000;
int palette = lcd_read(dmg->lcd, REG_BGP);
u8 *dest = is_window ? dmg->lcd->window : dmg->lcd->buf;
//printf("%04x %04x %04x\n", bg_base, window_base, tilebase);
int k = 0, off = 0;
int tile_y = 0, tile_x = 0;
int tile_y, tile_x;
for (tile_y = 0; tile_y < 32; tile_y++) {
for (tile_x = 0; tile_x < 32; tile_x++) {
off = 256 * 8 * tile_y + 8 * tile_x;
int tile = dmg_read(dmg, bg_base + (tile_y * 32 + tile_x));
int eff_addr;
if (use_unsigned) {
eff_addr = tilebase + 16 * tile;
int eff_addr, b, i;
int off = 256 * 8 * tile_y + 8 * tile_x;
int tile_index = dmg_read(dmg, map_base_addr + (tile_y * 32 + tile_x));
if (unsigned_mode) {
eff_addr = tile_base_addr + 16 * tile_index;
} else {
eff_addr = tilebase + 16 * (signed char) tile;
eff_addr = tile_base_addr + 16 * (signed char) tile_index;
}
int b, i;
for (b = 0; b < 16; b += 2) {
int data1 = dmg_read(dmg, eff_addr + b);
int data2 = dmg_read(dmg, eff_addr + b + 1);
for (i = 7; i >= 0; i--) {
dmg->lcd->buf[off] = ((data1 & (1 << i)) ? 1 : 0) << 1;
dmg->lcd->buf[off] |= (data2 & (1 << i)) ? 1 : 0;
int col_index = (data1 & (1 << i)) ? 1 : 0;
col_index |= ((data2 & (1 << i)) ? 1 : 0) << 1;
dest[off] = (palette >> (col_index << 1)) & 3;
off++;
}
off += 248;
@ -191,8 +194,8 @@ struct oam_entry {
// TODO: only ten per scanline, priority, attributes, move to lcd.c
static void render_objs(struct dmg *dmg)
{
struct oam_entry *oam = (struct oam_entry *) dmg->lcd->oam;
int k, lcd_x, lcd_y, off;
struct oam_entry *oam = (struct oam_entry *) dmg->lcd->oam;
int tall = lcd_isset(dmg->lcd, REG_LCDC, LCDC_OBJ_SIZE);
for (k = 0; k < 40; k++, oam++) {
@ -202,23 +205,26 @@ static void render_objs(struct dmg *dmg)
if (oam->pos_x == 0 || oam->pos_x >= 168) {
continue;
}
int first_tile = 0x8000 + 16 * oam->tile;
int palette = oam->attrs & OAM_ATTR_PALETTE
? lcd_read(dmg->lcd, REG_OBP1)
: lcd_read(dmg->lcd, REG_OBP0);
lcd_x = oam->pos_x - 8;
lcd_y = oam->pos_y - 16;
off = 160 * lcd_y + lcd_x;
int eff_addr = 0x8000 + 16 * oam->tile;
int b, i, limit = 16;
int b, i, tile_bytes = 16;
if (tall) {
limit = 32;
tile_bytes = 32;
}
for (b = 0; b < limit; b += 2) {
for (b = 0; b < tile_bytes; b += 2) {
int use_tile = b;
if (oam->attrs & OAM_ATTR_MIRROR_Y) {
use_tile = (limit - 2) - b;
use_tile = (tile_bytes - 2) - b;
}
int data1 = dmg_read(dmg, eff_addr + use_tile);
int data2 = dmg_read(dmg, eff_addr + use_tile + 1);
int data1 = dmg_read(dmg, first_tile + use_tile);
int data2 = dmg_read(dmg, first_tile + use_tile + 1);
for (i = 7; i >= 0; i--) {
if (off < 0 || off >= 160 * 144) {
// terrible clipping. need to not have an if per-pixel
@ -228,10 +234,10 @@ static void render_objs(struct dmg *dmg)
if (oam->attrs & OAM_ATTR_MIRROR_X) {
use_index = 7 - i;
}
int color = ((data1 & (1 << use_index)) ? 1 : 0) << 1
| ((data2 & (1 << use_index)) ? 1 : 0);
if (color) {
dmg->lcd->pixels[off] = color;
int col_index = ((data1 & (1 << use_index)) ? 1 : 0)
| ((data2 & (1 << use_index)) ? 1 : 0) << 1;
if (col_index) {
dmg->lcd->pixels[off] = (palette >> (col_index << 1)) & 3;
}
off++;
}
@ -240,6 +246,31 @@ static void render_objs(struct dmg *dmg)
}
}
static void timer_step(struct dmg *dmg)
{
if (!(dmg_read(dmg, REG_TIMER_CONTROL) & TIMER_CONTROL_ENABLED)) {
return;
}
int passed = dmg->cpu->cycle_count - dmg->last_timer_update;
// TODO
if (passed < 10000) {
return;
}
u8 counter = dmg_read(dmg, REG_TIMER_COUNT);
u8 modulo = dmg_read(dmg, REG_TIMER_MOD);
counter++;
if (!counter) {
counter = modulo;
dmg_request_interrupt(dmg, INT_TIMER);
}
dmg_write(dmg, REG_TIMER_COUNT, counter);
dmg->last_timer_update = dmg->cpu->cycle_count;
}
void dmg_step(void *_dmg)
{
struct dmg *dmg = (struct dmg *) _dmg;
@ -247,9 +278,9 @@ void dmg_step(void *_dmg)
// order of dependencies? i think cpu needs to step first then update
// all other hw
cpu_step(dmg->cpu);
// timer_step(dmg);
// each line takes 456 cycles
int cycle_diff = dmg->cpu->cycle_count - dmg->last_lcd_update;
if (cycle_diff >= 456) {
@ -280,14 +311,15 @@ void dmg_step(void *_dmg)
int lcdc = lcd_read(dmg->lcd, REG_LCDC);
if (lcdc & LCDC_ENABLE_BG) {
render_background(dmg, lcdc);
render_background(dmg, lcdc, 0);
}
if (lcdc & LCDC_ENABLE_WINDOW) {
// printf("window\n");
int window_enabled = lcdc & LCDC_ENABLE_WINDOW;
if (window_enabled) {
render_background(dmg, lcdc, 1);
}
lcd_apply_scroll(dmg->lcd);
lcd_apply_scroll(dmg->lcd, window_enabled);
if (lcdc & LCDC_ENABLE_OBJ) {
render_objs(dmg);

View File

@ -15,6 +15,13 @@
#define BUTTON_SELECT (1 << 2)
#define BUTTON_START (1 << 3)
#define REG_TIMER_DIV 0xFF04
#define REG_TIMER_COUNT 0xFF05
#define REG_TIMER_MOD 0xFF06
#define REG_TIMER_CONTROL 0xFF07
#define TIMER_CONTROL_ENABLED (1 << 2)
struct cpu;
struct rom;
struct lcd;
@ -27,6 +34,7 @@ struct dmg {
u8 video_ram[0x2000];
u8 zero_page[0x80];
u32 last_lcd_update;
u32 last_timer_update;
int joypad_selected;
int action_selected; // non-0 if A/B/start/select selected, 0 for directions
u8 interrupt_enabled;

View File

@ -29,8 +29,11 @@ void lcd_set_mode(struct lcd *lcd, int mode)
void lcd_new(struct lcd *lcd)
{
lcd->buf = malloc(256 * 256);
memset(lcd->buf, 0, 256 * 256);
const size_t size = 256 * 256;
lcd->buf = malloc(size);
lcd->window = malloc(size);
memset(lcd->buf, 0, size);
memset(lcd->window, 0, size);
// todo < 8 bpp
lcd->pixels = malloc(LCD_WIDTH * LCD_HEIGHT);
}
@ -66,18 +69,26 @@ void lcd_put_pixel(struct lcd *lcd, u8 x, u8 y, u8 value)
lcd->pixels[y * LCD_WIDTH + x] = value;
}
void lcd_apply_scroll(struct lcd *lcd)
void lcd_apply_scroll(struct lcd *lcd, int use_window)
{
int scroll_y = lcd_read(lcd, REG_SCY);
int scroll_x = lcd_read(lcd, REG_SCX);
int win_x = lcd_read(lcd, REG_WX) - 7;
int win_y = lcd_read(lcd, REG_WY);
int lines;
for (lines = 0; lines < 144; lines++) {
int src_y = (scroll_y + lines) & 0xff;
int cols;
for (cols = 0; cols < 160; cols++) {
int src_off = (src_y << 8) + ((scroll_x + cols) & 0xff);
lcd->pixels[lines * 160 + cols] = lcd->buf[src_off];
u8 *src = lcd->buf;
if (use_window && cols >= win_x && lines >= win_y) {
src_off = (((lines - win_y) & 0xff) << 8) + ((cols - win_x) & 0xff);
src = lcd->window;
}
lcd->pixels[lines * 160 + cols] = src[src_off];
}
}
}

View File

@ -48,6 +48,7 @@ struct lcd {
u8 oam[0xa0];
u8 regs[0x0c];
u8 *buf; // 256x256
u8 *window;
u8 *pixels; // the actual 160x144 visible area
};
@ -64,7 +65,7 @@ int lcd_isset(struct lcd *lcd, u16 addr, u8 bit);
void lcd_set_mode(struct lcd *lcd, int mode);
int lcd_step(struct lcd *lcd);
void lcd_apply_scroll(struct lcd *lcd);
void lcd_apply_scroll(struct lcd *lcd, int use_window);
// output the pixels to the screen
void lcd_draw(struct lcd *lcd);

View File

@ -57,6 +57,7 @@ struct mbc *mbc_new(int type)
return NULL;
}
mbc.type = type;
mbc.has_battery = type == 3;
return &mbc;
}
@ -76,3 +77,47 @@ int mbc_write(struct mbc *mbc, struct dmg *dmg, u16 addr, u8 data)
}
return mbc1_write(mbc, dmg, addr, data);
}
int mbc_save_ram(struct mbc *mbc, const char *filename)
{
FILE *fp;
if (!mbc->has_battery) {
return 0;
}
fp = fopen(filename, "w");
if (!fp) {
return 0;
}
if (fwrite(mbc->ram, 1, RAM_SIZE, fp) < RAM_SIZE) {
fclose(fp);
return 0;
}
fclose(fp);
return 1;
}
int mbc_load_ram(struct mbc *mbc, const char *filename)
{
FILE *fp;
if (!mbc->has_battery) {
return 0;
}
fp = fopen(filename, "r");
if (!fp) {
return 0;
}
if (fread(mbc->ram, 1, RAM_SIZE, fp) < RAM_SIZE) {
fclose(fp);
return 0;
}
fclose(fp);
return 1;
}

View File

@ -3,14 +3,17 @@
#include "types.h"
#define RAM_SIZE 0x8000
struct dmg;
struct mbc {
int type;
int has_battery;
int rom_bank;
int ram_bank;
int ram_enabled;
u8 ram[0x8000];
u8 ram[RAM_SIZE];
};
struct mbc *mbc_new(int type);
@ -19,5 +22,8 @@ struct mbc *mbc_new(int type);
int mbc_read(struct mbc *mbc, struct dmg *dmg, u16 addr, u8 *out_data);
// return 1 if handled, return 0 for base dmg behavior
int mbc_write(struct mbc *mbc, struct dmg *dmg, u16 addr, u8 data);
// 1 if success, 0 if error
int mbc_save_ram(struct mbc *mbc, const char *filename);
int mbc_load_ram(struct mbc *mbc, const char *filename);
#endif