mirror of https://github.com/mlaux/gb6.git
Compare commits
4 Commits
b0147a60d4
...
b2a6890524
Author | SHA1 | Date |
---|---|---|
Matthew Laux | b2a6890524 | |
Matthew Laux | 03dab9524b | |
Matthew Laux | af18bb9a39 | |
Matthew Laux | d6b6988742 |
|
@ -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
104
src/dmg.c
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
19
src/lcd.c
19
src/lcd.c
|
@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
45
src/mbc.c
45
src/mbc.c
|
@ -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;
|
||||
}
|
|
@ -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
|
Loading…
Reference in New Issue