#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_update (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]; 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)) && yx[1]) { gb->ppu->ost.data[gb->ppu->ost.n_entries] = (gb->ppu->ost.addr << 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 (GB *gb, ut8 *tile) { const bool use_window = !!(gb->ppu->fifo.shift_out & GB_PIXEL_FIFO_FETCH_WINDOW); ut64 addr; if (use_window) { const ut8 x = ((gb->ppu->fifo.x + ((!!gb->ppu->fifo.n_fpixel) << 3)) - gb->ppu->buf[GB_PPU_WX]) & 0xf8; //maybe store this at begin of line const ut8 y = (gb->ppu->buf[GB_PPU_LY] - gb->ppu->buf[GB_PPU_WY]) & 0xf8; addr = ((gb->ppu->buf[GB_PPU_LCDC] & GB_PPU_LCDC_WIN_TILE_MAP)? 0x1800: 0x1c00) + y * 32 + x; } else { const ut8 x = (gb->ppu->fifo.x + gb->ppu->buf[GB_PPU_SCX] + ((!!gb->ppu->fifo.n_fpixel) << 3)) & 0xf8; const ut8 y = (gb->ppu->buf[GB_PPU_LY] + gb->ppu->buf[GB_PPU_SCY]) & 0xf8; addr = ((gb->ppu->buf[GB_PPU_LCDC] & GB_PPU_LCDC_BG_TILE_MAP)? 0x1800: 0x1c00) + y * 32 + x; } r_io_fd_read_at (gb->io, gb->ppu->vram_fd, addr, tile, 1); } static void gb_ppu_pixel_fifo_merge_opixels (PixelFifo *pxf, bool priority) { 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->fetch[1].fetched & (0xf << ((i - 1) << 2))) >> ((i - 1) << 2)); pixels = pixels << 4; continue; } } } static void gb_pixel_fifo_fetch_continue (GB *gb) { PixelFifo *fifo = &gb->ppu->fifo; ut8 fetcher = !!(fifo->shift_out & GB_PIXEL_FIFO_FETCH_SELECT); ut8 tile; switch (fifo->fetch[fetcher].state_ctr) { case 0: read_tile_data (gb, &tile); if (gb->ppu->buf[GB_PPU_LCDC] & GB_PPU_LCDC_TILE_BASE) { fifo->fetch[fetcher].addr = tile * 16; } else { st8 stile = (st8)tile; fifo->fetch[fetcher].addr = 0x1000 + stile * 16; } case 1: break; case 2: case 4: break; case 3: fifo->fetch[fetcher].addr++; case 5: } } static ut32 gb_ppu_render_update (GB *gb, ut32 cycles) { return 0; } static ut32 gb_ppu_hblank_update (GB *gb, ut32 cycles) { return 0; } static ut32 gb_ppu_vblank_update (GB *gb, ut32 cycles) { return 0; } void gb_ppu_update (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_update (gb, cycles); break; case GB_PPU_STAT_MODE_RENDER: cycles = gb_ppu_render_update (gb, cycles); break; case GB_PPU_STAT_MODE_HBLANK: cycles = gb_ppu_hblank_update (gb, cycles); break; case GB_PPU_STAT_MODE_VBLANK: cycles = gb_ppu_vblank_update (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); }