#include #include #include #include #ifndef GB_H RIOPlugin r_io_plugin_gb_mbc1; #endif typedef struct gb_mbc1_data { RIO *io; ut32 *rombank_to_io_map; ut32 *rambank_to_io_map; ut64 seek; int file_fd; int mem_fd; ut32 rombank0_map; ut8 n_rombanks; ut8 n_rambanks; ut8 rombank_mask; ut8 rombank_high; ut8 rombank_low; union { ut8 high_rombank; ut8 rambank; }; bool ram_enable; bool mode; bool small_ram; } MBC1; static void disable_ram (MBC1 *mbc) { ut32 i; for (i = 0; i < mbc->n_rambanks; i++) { RIOMap *map = r_io_map_get (mbc->io, mbc->rambank_to_io_map[i]); map->perm = 0; } mbc->ram_enable = false; } static void enable_ram (MBC1 *mbc) { ut32 i; for (i = 0; i < mbc->n_rambanks; i++) { RIOMap *map = r_io_map_get (mbc->io, mbc->rambank_to_io_map[i]); map->perm = R_PERM_RW; } mbc->ram_enable = true; } static bool __check(RIO *io, const char *pathname, bool many) { return r_str_startswith (pathname, "gb_mbc1://"); } ut8 mbc1_mask[] = { 0x1, 0x3, 0x7, 0xf, 0x1f, 0x3f, 0x7f }; static RIODesc *__open(RIO *io, const char *pathname, int rw, int mode) { if (!r_str_startswith (pathname, "gb_mbc1://")) { return NULL; } if (!r_file_exists (&pathname[10]) || !r_file_is_regular (&pathname[10])) { return NULL; } RIODesc *desc = r_io_desc_open (io, &pathname[10], R_PERM_R, 0); if (!desc) { return NULL; } ut8 mbc_info[3] = {0xfb, 0xff, 0xff}; r_io_desc_read_at (desc, 0x147, mbc_info, 3); r_io_desc_close (desc); if (R_UNLIKELY (!mbc_info[0] || (mbc_info[0] > 3) || (mbc_info[1] > 6) || (mbc_info[2] > 2))) { //not a mbc1 game return NULL; } MBC1 *mbc = R_NEW0 (MBC1); if (!mbc) { return NULL; } mbc->n_rombanks = 0x1 << (mbc_info[1] + 1); mbc->rombank_to_io_map = R_NEWS0 (ut32, mbc->n_rombanks); if (!mbc->rombank_to_io_map) { free (mbc); return NULL; } eprintf ("%d rombanks\n", mbc->n_rombanks); mbc->rombank_mask = mbc1_mask[mbc_info[1]]; if (mbc_info[0] != 1) { switch (mbc_info[2]) { case 0x1: mbc->small_ram = true; case 0x3: mbc->n_rambanks = 4; break; case 0x2: mbc->n_rambanks = 1; break; } mbc->rambank_to_io_map = R_NEWS0 (ut32, mbc->n_rambanks); if (!mbc->rambank_to_io_map) { free (mbc->rombank_to_io_map); free (mbc); return NULL; } eprintf ("%d rambanks\n", mbc->n_rambanks); } else { eprintf ("no external ram\n", mbc->n_rombanks); } mbc->io = r_io_new (); if (!mbc->io) { free (mbc->rombank_to_io_map); free (mbc->rambank_to_io_map); free (mbc); return NULL; } mbc->io->va = true; mbc->file_fd = r_io_fd_open (mbc->io, &pathname[10], R_PERM_R, 0); if (mbc->file_fd < 0) { goto fail; } if (mbc->n_rambanks) { char *malloc_uri = r_str_newf ("malloc://0x%"PFMT64x, mbc->small_ram? 0x800: (0x2000 * mbc->n_rambanks)); mbc->mem_fd = r_io_fd_open (mbc->io, malloc_uri, R_PERM_RW, 0); free (malloc_uri); if (mbc->mem_fd < 0) { goto fail; } } RIOMap *map; ut32 i; for (i = 0; i < mbc->n_rombanks; i++) { map = r_io_map_add_bottom (mbc->io, mbc->file_fd, R_PERM_R, 0x4000 * i, (i & 0x1f)? 0x4000: 0ULL, 0x4000); if (!map) { goto fail; } mbc->rombank_to_io_map[i] = map->id; } if (mbc->n_rombanks < 32) { map = r_io_map_add_bottom (mbc->io, mbc->file_fd, R_PERM_R, 0ULL, 0x4000, 0x4000); if (!map) { goto fail; } mbc->rombank0_map = map->id; } if (mbc->small_ram) { for (i = 0; i < 4; i++) { map = r_io_map_add (mbc->io, mbc->mem_fd, R_PERM_RW, 0ULL, 0xa000 + 0x800 * i, 0x800); if (!map) { goto fail; } mbc->rambank_to_io_map[i] = map->id; } } else if (mbc->n_rambanks) { for (i = 0; i < 4; i++) { map = r_io_map_add_bottom (mbc->io, mbc->mem_fd, R_PERM_RW, 0x2000 * i, 0xa000, 0x2000); if (!map) { goto fail; } mbc->rambank_to_io_map[i] = map->id; } } mbc->rombank_high = 1; mbc->io->cache.mode = 0; desc = r_io_desc_new (io, &r_io_plugin_gb_mbc1, pathname, R_PERM_RWX, mode, mbc); mbc->io->va = true; if (!desc) { goto fail; } disable_ram (mbc); return desc; fail: r_io_free (mbc->io); free (mbc->rombank_to_io_map); free (mbc->rambank_to_io_map); free (mbc); return NULL; } static bool __close(RIODesc *desc) { MBC1 *mbc = (MBC1 *)desc->data; r_io_free (mbc->io); free (mbc->rombank_to_io_map); free (mbc->rambank_to_io_map); R_FREE (desc->data); return true; } static ut64 __lseek(RIO* io, RIODesc *desc, ut64 offset, int whence) { MBC1 *mbc = (MBC1 *)desc->data; switch (whence) { case R_IO_SEEK_SET: return mbc->seek = R_MIN (0xc000, offset); case R_IO_SEEK_CUR: return mbc->seek = R_MIN (0xc000, mbc->seek + offset); case R_IO_SEEK_END: return mbc->seek = 0xc000; } return mbc->seek; } static int __write(RIO *io, RIODesc *desc, const ut8 *buf, int len) { MBC1 *mbc = (MBC1 *)desc->data; len = R_MIN (len, 0xc000 - mbc->seek); r_io_write_at (mbc->io, mbc->seek, buf, len); ut64 endseek = mbc->seek + len; ut8 rombank; while ((mbc->seek < 0x8000) && (mbc->seek < endseek)) { const ut32 i = mbc->seek - (endseek - len); switch (mbc->seek & 0x6000) { case 0ULL: if ((buf[i] & 0x0f) == 0x0a) { eprintf ("enabling external ram\n"); enable_ram (mbc); } else { eprintf ("disabling external ram\n"); disable_ram (mbc); } break; case 0x2000: eprintf ("switching banks (low bits)\n"); rombank = buf[i] & 0x1f; if (!rombank) { rombank++; } rombank |= (mbc->high_rombank & 0x3) << 5; rombank &= mbc->rombank_mask; if (!rombank) { r_io_map_priorize (mbc->io, mbc->rombank0_map); mbc->rombank_high = 0; } else if (R_LIKELY (rombank < mbc->n_rombanks)) { r_io_map_priorize (mbc->io, mbc->rombank_to_io_map[rombank]); mbc->rombank_high = rombank; } break; case 0x4000: //rambank switch/high rombank bits switch eprintf ("switching banks (high bits)\n"); mbc->rambank = buf[i] & 0x3; rombank = mbc->rombank_high & 0x1f; rombank |= (mbc->high_rombank & 0x3) << 5; rombank &= mbc->rombank_mask; if (!rombank) { r_io_map_priorize (mbc->io, mbc->rombank0_map); mbc->rombank_high = 0; } else if (R_LIKELY (rombank < mbc->n_rombanks)) { r_io_map_priorize (mbc->io, mbc->rombank_to_io_map[rombank]); mbc->rombank_high = rombank; } if (mbc->mode) { if (!mbc->small_ram && mbc->n_rambanks > 1) { r_io_map_priorize (mbc->io, mbc->rambank_to_io_map[mbc->rambank]); } rombank = (mbc->high_rombank << 5) & mbc->rombank_mask; r_io_map_priorize (mbc->io, mbc->rombank_to_io_map[rombank]); mbc->rombank_low = rombank; } break; case 0x6000: eprintf ("switching mode\n"); mbc->mode = !!(buf[i] & 0x1); if (!mbc->mode) { r_io_map_priorize (mbc->io, mbc->rombank_to_io_map[0]); mbc->rombank_low = 0; if (!mbc->small_ram && mbc->n_rambanks > 1) { r_io_map_priorize (mbc->io, mbc->rambank_to_io_map[0]); } } else { if (!mbc->small_ram && mbc->n_rambanks > 1) { r_io_map_priorize (mbc->io, mbc->rambank_to_io_map[mbc->rambank]); } rombank = (mbc->high_rombank << 5) & mbc->rombank_mask; r_io_map_priorize (mbc->io, mbc->rombank_to_io_map[rombank]); mbc->rombank_low = rombank; } break; } mbc->seek++; } mbc->seek = endseek; return len; } static int __read(RIO *io, RIODesc *desc, ut8 *buf, int len) { MBC1 *mbc = (MBC1 *)desc->data; len = R_MIN (len, 0xc000 - mbc->seek); if (!r_io_read_at (mbc->io, mbc->seek, buf, len)) { eprintf ("r_io_read_at failed\n"); return -1; } eprintf ("read @ 0x%"PFMT64x" %d bytes\n", mbc->seek, len); mbc->seek += len; return len; } RIOPlugin r_io_plugin_gb_mbc1 = { .meta = { .name = "gb_mbc1", .desc = "gb_mbc1", .license = "LGPL", }, .uris = "gb_mbc1://", .open = __open, .close = __close, .read = __read, .check = __check, .seek = __lseek, .write = __write, }; #ifndef R2_PLUGIN_INCORE R_API RLibStruct radare_plugin = { .type = R_LIB_TYPE_IO, .data = &r_io_plugin_gb_mbc1, .version = R2_VERSION }; #endif