#include #include #include #include #include #include RIOPlugin r_io_plugin_gb_ppu; static ut64 __lseek(RIO* io, RIODesc *desc, ut64 offset, int whence) { GBPPU *ppu = desc->data; ut64 seek = ppu->seek & 0xffff; switch (whence) { case R_IO_SEEK_SET: seek = R_MIN (GB_PPU_N_REGS, offset); break; case R_IO_SEEK_CUR: seek = R_MIN (GB_PPU_N_REGS, seek + offset); break; case R_IO_SEEK_END: seek = GB_PPU_N_REGS; break; } ppu->seek = (ppu->seek & (~0xffff)) | seek; return seek; } static bool __check(RIO *io, const char *pathname, bool many) { return r_str_startswith (pathname, "gb_ppu://"); } static int __read(RIO *io, RIODesc *desc, ut8 *buf, int len) { GBPPU *ppu = desc->data; ut64 seek = ppu->seek & 0xffff; if (ppu->seek >= GB_PPU_N_REGS || len < 1) { return 0; } len = R_MIN (len, GB_PPU_N_REGS - seek); memcpy (buf, &ppu->buf[ppu->seek], len); seek += len; ppu->seek = (ppu->seek & (~0xffff)) | seek; return len; } static int __write(RIO *io, RIODesc *desc, const ut8 *buf, int len) { GBPPU *ppu = desc->data; ut64 seek = ppu->seek & 0xffff; if (ppu->seek >= GB_PPU_N_REGS || len < 1) { return 0; } len = R_MIN (len, GB_PPU_N_REGS - seek); ut32 i; for (i = 0; i < len; i++) { switch (seek) { case GB_PPU_STAT: ppu->buf[GB_PPU_STAT] = (buf[i] & 0xf8) | (ppu->buf[GB_PPU_STAT] & 0x7); case GB_PPU_LY: break; default: ppu->buf[seek] = buf[i]; break; } seek++; } return len; } static bool __close(RIODesc *desc) { return true; } static RIODesc *__open(RIO *io, const char *pathname, int rw, int mode) { if (!r_str_startswith (pathname, "gb_ppu://")) { return NULL; } GBPPU *ppu = NULL; sscanf (pathname, "gb_ppu://%p", &ppu); RIODesc *desc = r_io_desc_new (io, &r_io_plugin_gb_ppu, pathname, R_PERM_RWX, mode, ppu); return desc; } RIOPlugin r_io_plugin_gb_ppu = { .meta = { .name = "gb_ppu", .desc = "gb_ppu", .license = "LGPL", }, .uris = "gb_ppu://", .open = __open, .close = __close, .read = __read, .check = __check, .seek = __lseek, .write = __write, }; GBPPU *gb_ppu_open (RIO *io, SDL_Renderer *renderer) { GBPPU *ppu = R_NEW0 (GBPPU); if (!ppu) { return NULL; } ppu->vram_fd = r_io_fd_open (io, "malloc//:0x2000", R_PERM_RWX, 0); if (ppu->vram_fd < 0) { free (ppu); return NULL; } char uri[64]; sprintf (uri, "gb_ppu://%p", ppu); RIODesc *desc = r_io_desc_open_plugin (io, &r_io_plugin_gb_ppu, uri, R_PERM_RWX, 0); if (!desc) { r_io_fd_close (io, ppu->vram_fd); free (ppu); return NULL; } ppu->reg_fd = desc->fd; ppu->pixbuf = gb_pix_buf_new (renderer, 160, 144); if (!ppu->pixbuf) { r_io_desc_close (desc); r_io_fd_close (io, ppu->vram_fd); free (ppu); return NULL; } return ppu; } static ut32 gb_ppu_oam_scan_continue (GB *gb, ut32 cycles) { if (gb->ppu->ost.addr >= 0xa0) { gb->ppu->ost.addr = 0; gb->ppu->ost.n_entries = 0; } if (cycles & 0x1) { R_LOG_WARN ("Odd amount of cycles"); } if (gb->dma->seek & GB_DMA_ACTIVE) { const ut8 running_cycles = R_MIN (cycles, (0xa0 - gb->ppu->ost.addr) >> 1); //every oam entry costs 2 cycles gb->ppu->ost.addr += running_cycles << 1; cycles -= running_cycles; goto beach; } const ut8 height = (gb->ppu->buf[GB_PPU_LCDC] & GB_PPU_LCDC_BIG_OBJ)? 16: 8; const ut8 ly = gb->ppu->buf[GB_PPU_LY] + 16; while (cycles && gb->ppu->ost.addr <= 0xa0) { if (gb->ppu->ost.n_entries < 10) { ut8 yx[2]; //this is probably inaccurate r_io_fd_read_at (gb->io, gb->dma->oam_fd, (ut64)gb->ppu->ost.addr, yx, 2); if ((yx[0] <= ly) && (ly < (yx[0] + height))) { gb->ppu->ost.data[gb->ppu->ost.n_entries] = (gb->ppu->ost.addr << 16) | (yx[0] << 8) | yx[1]; gb->ppu->ost.n_entries++; } } gb->ppu->ost.addr += 4; cycles -= 2; } beach: if (gb->ppu->ost.addr == 0xa0) { //indicate next mode gb->ppu->buf[GB_PPU_STAT] |= GB_PPU_STAT_MODE_RENDER; RIOMap *vram = r_io_map_get (gb->io, gb->ppu->vram_mapid); vram->perm = 0; //disable vram access for rendering } return cycles; } static void read_tile_data (GBPPU *ppu, RIO *io, ut8 *tile) { const bool use_window = !!(ppu->fifo.flags & GB_PIXEL_FIFO_FETCH_WINDOW); ut64 addr; if (use_window) { const ut8 x = ((ppu->fifo.x + ((!!ppu->fifo.n_fpixel) << 3)) - ppu->buf[GB_PPU_WX]) & 0xf8; //maybe store this at begin of line #if 0 const ut8 y = (ppu->buf[GB_PPU_LY] - ppu->buf[GB_PPU_WY]) & 0xf8; #else const ut8 y = (ppu->buf[GB_PPU_LY] - ppu->fifo.wy) & 0xf8; #endif addr = ((ppu->buf[GB_PPU_LCDC] & GB_PPU_LCDC_WIN_TILE_MAP)? 0x1800: 0x1c00) + y * 32 + x; } else { const ut8 x = (ppu->fifo.x + ppu->buf[GB_PPU_SCX] + ((!!ppu->fifo.n_fpixel) << 3)) & 0xf8; const ut8 y = (ppu->buf[GB_PPU_LY] + ppu->buf[GB_PPU_SCY]) & 0xf8; addr = ((ppu->buf[GB_PPU_LCDC] & GB_PPU_LCDC_BG_TILE_MAP)? 0x1800: 0x1c00) + y * 32 + x; } r_io_fd_read_at (io, ppu->vram_fd, addr, tile, 1); } static void read_obj_data (GBPPU *ppu, RIO *io, ut8 *tile, int oam_fd) { ut8 addr = (ppu->fifo.obj >> 16) & 0xff; r_io_fd_read_at (io, oam_fd, (ut64)(addr + 2), tile, 1); if (ppu->buf[GB_PPU_LCDC] & GB_PPU_LCDC_BIG_OBJ) { tile[0] &= 0xfe; } } static void gb_ppu_pixel_fifo_merge_opixels (PixelFifo *pxf) { ut32 pixels = pxf->data >> 32; ut32 newpixels = 0; ut32 i; for (i = 0; i < 8; i++) { if (!(pixels & (0x3 << 28))) { //check for transparency newpixels = (newpixels << 4) | ((pxf->fetcher[1].fetched & (0xf << (i << 2))) >> (i << 2)); pixels = pixels << 4; continue; } } } static void gb_pixel_fifo_fetch_continue (GB *gb) { PixelFifo *fifo = &gb->ppu->fifo; ut8 fetch_obj = !!(fifo->flags & GB_PIXEL_FIFO_FETCH_OBJECT); ut8 tile; switch (fifo->fetcher[fetch_obj].state_ctr) { case 0: if (!fetch_obj) { read_tile_data (gb->ppu, gb->io, &tile); if (gb->ppu->buf[GB_PPU_LCDC] & GB_PPU_LCDC_TILE_BASE) { fifo->fetcher[0].addr = tile * 16; } else { st8 s_tile = (st8)tile; fifo->fetcher[0].addr = 0x1000 + s_tile * 16; } if (fifo->flags & GB_PIXEL_FIFO_FETCH_WINDOW) { fifo->fetcher[0].addr += ((gb->ppu->buf[GB_PPU_LY] - fifo->wy) & 0x7) * 2; } else { fifo->fetcher[0].addr += (gb->ppu->buf[GB_PPU_LY] & 0x7) * 2; } } else { read_obj_data (gb->ppu, gb->io, &tile, gb->dma->oam_fd); fifo->fetcher[1].addr = tile; } case 1: break; case 2: if (fetch_obj) { ut8 addr = (fifo->obj >> 16) & 0xff; //read object attributes r_io_fd_read_at (gb->io, gb->dma->oam_fd, (ut64)(addr + 3), &tile, 1); fifo->obj = (fifo->obj & 0xffffff) | (tile << 24); //tile idx tile = fifo->fetcher[1].addr & 0xff; //object_y - line_y addr = ((fifo->obj >> 8) & 0xff) - gb->ppu->buf[GB_PPU_LY]; fifo->fetcher[1].addr = tile * 16; if (fifo->obj & GB_FIFO_OAM_FLIP_Y) { //perform y flip addr ^= (gb->ppu->buf[GB_PPU_LCDC] & GB_PPU_LCDC_BIG_OBJ)? 0xf: 0x7; } fifo->fetcher[1].addr += addr * 2; } else { r_io_fd_read_at (gb->io, gb->ppu->vram_fd, fifo->fetcher[0].addr, &fifo->fetcher[0].data[0], 1); fifo->fetcher[0].addr++; } case 3: break; case 4: r_io_fd_read_at (gb->io, gb->ppu->vram_fd, fifo->fetcher[fetch_obj].addr, &fifo->fetcher[fetch_obj].data[!fetch_obj], 1); fifo->fetcher[fetch_obj].addr++; if (!fetch_obj) { ut8 *p = &fifo->fetcher[0].data[0]; fifo->fetcher[0].fetched = ((p[1] & 0x80) << 22) | ((p[0] & 0x80) << 21) | ((p[1] & 0x40) << 19) | ((p[0] & 0x40) << 18) | ((p[1] & 0x20) << 16) | ((p[0] & 0x20) << 15) | ((p[1] & 0x10) << 13) | ((p[0] & 0x10) << 12) | ((p[1] & 0x8) << 10) | ((p[0] & 0x8) << 9) | ((p[1] & 0x4) << 7) | ((p[0] & 0x4) << 6) | ((p[1] & 0x2) << 4) | ((p[0] & 0x2) << 3) | ((p[1] & 0x1) << 1) | (p[0] & 0x1); if (fifo->flags & GB_PIXEL_FIFO_FETCH_WINDOW) { fifo->fetcher[0].fetched |= 0x44444444; } } case 5: if (!fetch_obj) { fifo->flags |= GB_PIXEL_FIFO_FETCH_READY; } break; case 6: if (fetch_obj) { r_io_fd_read_at (gb->io, gb->ppu->vram_fd, fifo->fetcher[1].addr, &fifo->fetcher[1].data[1], 1); ut8 *p = &fifo->fetcher[1].data[0]; if (fifo->obj & GB_FIFO_OAM_FLIP_X) { fifo->fetcher[1].fetched = ((p[1] & 0x80) >> 6) | ((p[0] & 0x80) >> 7) | ((p[1] & 0x40) >> 1) | ((p[0] & 0x40) >> 2) | ((p[1] & 0x20) << 4) | ((p[0] & 0x20) << 3) | ((p[1] & 0x10) << 9) | ((p[0] & 0x10) << 8) | ((p[1] & 0x8) << 14) | ((p[0] & 0x8) << 13) | ((p[1] & 0x4) << 19) | ((p[0] & 0x4) << 18) | ((p[1] & 0x2) << 24) | ((p[0] & 0x2) << 23) | ((p[1] & 0x1) << 29) | ((p[0] & 0x1) << 28); } else { fifo->fetcher[1].fetched = ((p[1] & 0x80) << 22) | ((p[0] & 0x80) << 21) | ((p[1] & 0x40) << 19) | ((p[0] & 0x40) << 18) | ((p[1] & 0x20) << 16) | ((p[0] & 0x20) << 15) | ((p[1] & 0x10) << 13) | ((p[0] & 0x10) << 12) | ((p[1] & 0x8) << 10) | ((p[0] & 0x8) << 9) | ((p[1] & 0x4) << 7) | ((p[0] & 0x4) << 6) | ((p[1] & 0x2) << 4) | ((p[0] & 0x2) << 3) | ((p[1] & 0x1) << 1) | (p[0] & 0x1); } fifo->fetcher[1].fetched |= (fifo->obj & GB_FIFO_OAM_PALLET)? 0xcccccccc: 0x88888888; if ((fifo->obj & 0xff) < 8) { //is this correct? fifo->fetcher[1].fetched <<= ((8 - (fifo->obj & 0xff)) << 2); } } break; case 7: if (fetch_obj) { fifo->flags |= GB_PIXEL_FIFO_FETCH_READY; } } fifo->fetcher[fetch_obj].state_ctr = (fifo->fetcher[fetch_obj].state_ctr + 1) & 0x7; } static bool check_objects (GBPPU *ppu) { if (!ppu->fifo.x) { return false; } ut32 i; for (i = 0; i < ppu->ost.n_entries; i++) { const ut8 x = ppu->ost.data[i] & 0xff; //x cannot be 0, because oam entries with x = 0 don't end in this array //because of this setting x to 0 can be used to invalidate entries if (x && (x == ppu->fifo.x)) { ppu->fifo.obj = ppu->ost.data[i]; #if 0 ppu->ost.data[i] &= 0xffffff00; #else ppu->ost.data[i] = 0; #endif return true; } } return false; } static void shit_out_pixel (GBPPU *ppu) { ppu->fifo.shift_out = (ppu->fifo.shift_out & 0xf0) | ppu->fifo.data >> 60; ppu->fifo.data = ppu->fifo.data << 4; ppu->fifo.n_fpixel--; if (ppu->fifo.dx) { //discard pixel ppu->fifo.dx--; return; } ut8 color; if (!(ppu->fifo.shift_out & 0x08)) { //bg pallet color = (ppu->buf[GB_PPU_BGP] & (0x3 << ((ppu->fifo.shift_out & 0x3) << 1))) >> ((ppu->fifo.shift_out & 0x3) << 1); } else { const ut8 pal = !!(ppu->fifo.shift_out & 0x04); color = (ppu->buf[GB_PPU_OBP0 + pal] & (0x3 << ((ppu->fifo.shift_out & 0x3) << 1))) >> ((ppu->fifo.shift_out & 0x3) << 1); } #if 0 if (ppu->fifo.x > 7) { //is this correct? gb_pix_buf_set_pixel (ppu->pixbuf, ppu->fifo.x, ppu->buf[GB_PPU_LY], color); } #endif gb_pix_buf_set_pixel (ppu->pixbuf, ppu->fifo.x, ppu->buf[GB_PPU_LY], color); ppu->fifo.x++; } static void gb_ppu_render_launch (GB *gb) { RIOMap *vram = r_io_map_get (gb->io, gb->ppu->vram_mapid); vram->perm = 0; //disable vram access for rendering gb->ppu->fifo = (const PixelFifo){0}; gb->ppu->fifo.remaining_cycles = 376; } static ut32 gb_ppu_render_continue (GB *gb, ut32 cycles) { PixelFifo *fifo = &gb->ppu->fifo; if (R_UNLIKELY (!(fifo->flags & GB_PIXEL_FIFO_REG_DATA_LOADED))) { fifo->dx = gb->ppu->buf[GB_PPU_SCX] & 0x7; fifo->wy = gb->ppu->buf[GB_PPU_WY]; fifo->flags |= GB_PIXEL_FIFO_REG_DATA_LOADED; } while (cycles) { if (fifo->n_fpixel < 9) { //initial phase on each line //also when window starts? gb_pixel_fifo_fetch_continue (gb); if (fifo->flags & GB_PIXEL_FIFO_FETCH_READY) { if (!fifo->n_fpixel) { fifo->data = ((ut64)fifo->fetcher[0].fetched) << 32; } else { fifo->data |= fifo->fetcher[0].fetched; } fifo->n_fpixel += 8; //I suspect the initial 16 pixels fetch on each line only takes 12 cycles instead of 16, //because the pixel fifo is empty fifo->flags ^= GB_PIXEL_FIFO_FETCH_READY; fifo->fetcher[0].state_ctr = 0; } } else { if (!(fifo->flags & GB_PIXEL_FIFO_FETCH_OBJECT)) { if (check_objects (gb->ppu)) { fifo->flags |= GB_PIXEL_FIFO_FETCH_OBJECT; } else { shit_out_pixel (gb->ppu); } gb_pixel_fifo_fetch_continue (gb); if (fifo->n_fpixel < 9 && (fifo->flags & GB_PIXEL_FIFO_FETCH_READY)) { fifo->data |= fifo->fetcher[0].fetched; fifo->n_fpixel += 8; fifo->flags ^= GB_PIXEL_FIFO_FETCH_READY; } } else { gb_pixel_fifo_fetch_continue (gb); if (fifo->flags & GB_PIXEL_FIFO_FETCH_READY) { gb_ppu_pixel_fifo_merge_opixels (fifo); fifo->flags ^= GB_PIXEL_FIFO_FETCH_OBJECT | GB_PIXEL_FIFO_FETCH_READY; } } } fifo->remaining_cycles--; cycles--; } return 0; } static ut32 gb_ppu_hblank_continue (GB *gb, ut32 cycles) { return 0; } static ut32 gb_ppu_vblank_continue (GB *gb, ut32 cycles) { return 0; } void gb_ppu_continue (GB *gb, ut32 cycles) { while (cycles) { switch (gb->ppu->buf[GB_PPU_STAT] & GB_PPU_STAT_MODE_MASK) { case GB_PPU_STAT_MODE_OAM_SCAN: cycles = gb_ppu_oam_scan_continue (gb, cycles); if ((gb->ppu->buf[GB_PPU_STAT] & GB_PPU_STAT_MODE_MASK) == GB_PPU_STAT_MODE_RENDER) { gb_ppu_render_launch (gb); } break; case GB_PPU_STAT_MODE_RENDER: cycles = gb_ppu_render_continue (gb, cycles); break; case GB_PPU_STAT_MODE_HBLANK: cycles = gb_ppu_hblank_continue (gb, cycles); break; case GB_PPU_STAT_MODE_VBLANK: cycles = gb_ppu_vblank_continue (gb, cycles); break; } } //TODO } void gb_ppu_close (GBPPU *ppu, RIO *io) { r_io_fd_close (io, ppu->vram_fd); r_io_fd_close (io, ppu->reg_fd); gb_pix_buf_free (ppu->pixbuf); free (ppu); }