#define ENABLE_SOUND 0 #include "peanut_gb.h" #include #include #include #include #include #include #include #include struct priv_t { uint8_t *rom; uint8_t *cart_ram; uint8_t fb[LCD_WIDTH * LCD_HEIGHT * 4]; size_t save_size; char *save_file_name; MwWidget *win; MwWidget *fb_widget; MwLLPixmap *px; }; static struct gb_s gb; static struct priv_t priv; static void key_pressed(MwWidget handle, void *client, void *user); static void key_released(MwWidget handle, void *client, void *user); int mfb_open(const char *title, int width, int height) { int depth, i, formatCount, convDepth = -1; MwLibraryInit(); priv.win = MwCreateWidget(MwWindowClass, title, NULL, MwDEFAULT, MwDEFAULT, width * 2, height * 2); priv.fb_widget = MwCreateWidget(MwImageClass, "fb", priv.win, 0, 0, width * 2, height * 2); MwAddUserHandler(priv.win, MwNkeyHandler, key_pressed, NULL); MwAddUserHandler(priv.win, MwNkeyReleaseHandler, key_released, NULL); priv.px = NULL; return 1; } int mfb_update(void *buffer) { MwPending(priv.win); MwStep(priv.win); if (priv.px == NULL) { priv.px = MwLoadRaw(priv.win, buffer, LCD_WIDTH, LCD_HEIGHT); } else { MwPixmapReloadRaw(priv.px, buffer); } MwVaApply(priv.fb_widget, MwNpixmap, priv.px, NULL); return 0; } void mfb_close(void) {} static void key_pressed(MwWidget handle, void *user, void *client) { (void)handle; (void)user; int k = *(int *)client; switch (k) { case MwLLKeyEnter: gb.direct.joypad &= ~JOYPAD_START; break; case MwLLKeyBackSpace: gb.direct.joypad &= ~JOYPAD_SELECT; break; case 'z': gb.direct.joypad &= ~JOYPAD_A; break; case 'x': gb.direct.joypad &= ~JOYPAD_B; break; case MwLLKeyUp: gb.direct.joypad &= ~JOYPAD_UP; break; case MwLLKeyRight: gb.direct.joypad &= ~JOYPAD_RIGHT; break; case MwLLKeyDown: gb.direct.joypad &= ~JOYPAD_DOWN; break; case MwLLKeyLeft: gb.direct.joypad &= ~JOYPAD_LEFT; break; } } static void key_released(MwWidget handle, void *user, void *client) { (void)handle; (void)user; int k = *(int *)client; switch (k) { case MwLLKeyEnter: gb.direct.joypad |= JOYPAD_START; break; case MwLLKeyBackSpace: gb.direct.joypad |= JOYPAD_SELECT; break; case 'z': gb.direct.joypad |= JOYPAD_A; break; case 'x': gb.direct.joypad |= JOYPAD_B; break; case MwLLKeyUp: gb.direct.joypad |= JOYPAD_UP; break; case MwLLKeyRight: gb.direct.joypad |= JOYPAD_RIGHT; break; case MwLLKeyDown: gb.direct.joypad |= JOYPAD_DOWN; break; case MwLLKeyLeft: gb.direct.joypad |= JOYPAD_LEFT; break; } } /** * Returns a byte from the ROM file at the given address. */ uint8_t gb_rom_read(struct gb_s *gb, const uint_fast32_t addr) { const struct priv_t *const p = gb->direct.priv; return p->rom[addr]; } /** * Returns a byte from the cartridge RAM at the given address. */ uint8_t gb_cart_ram_read(struct gb_s *gb, const uint_fast32_t addr) { const struct priv_t *const p = gb->direct.priv; return p->cart_ram[addr]; } void read_cart_ram_file(const char *save_file_name, uint8_t **dest, const size_t len) { FILE *f; /* If save file not required. */ if (len == 0) { *dest = NULL; return; } /* Allocate enough memory to hold save file. */ *dest = malloc(len); printf("%s\n", save_file_name); if ((f = fopen(save_file_name, "rb")) == NULL) { printf("Unable to open save file for reading: %s\n", strerror(errno)); return; } /* Read save file to allocated memory. */ fread(*dest, sizeof(uint8_t), len, f); fclose(f); } void write_cart_ram_file(const char *save_file_name, uint8_t **dest, const size_t len) { FILE *f; if (len == 0 || *dest == NULL) return; if ((f = fopen(save_file_name, "wb")) == NULL) { printf("Unable to open save file for writing: %s\n", strerror(errno)); return; } /* Record save file. */ fwrite(*dest, sizeof(uint8_t), len, f); fclose(f); return; } /** * Writes a given byte to the cartridge RAM at the given address. */ void gb_cart_ram_write(struct gb_s *gb, const uint_fast32_t addr, const uint8_t val) { const struct priv_t *const p = gb->direct.priv; p->cart_ram[addr] = val; write_cart_ram_file(priv.save_file_name, &priv.cart_ram, priv.save_size); } /** * Returns a pointer to the allocated space containing the ROM. Must be freed. */ uint8_t *read_rom_to_ram(const char *file_name) { FILE *rom_file = fopen(file_name, "rb"); size_t rom_size; uint8_t *rom = NULL; if (rom_file == NULL) return NULL; fseek(rom_file, 0, SEEK_END); rom_size = ftell(rom_file); rewind(rom_file); rom = malloc(rom_size); if (fread(rom, sizeof(uint8_t), rom_size, rom_file) != rom_size) { free(rom); fclose(rom_file); return NULL; } fclose(rom_file); return rom; } /** * Ignore all errors. */ void gb_error(struct gb_s *gb, const enum gb_error_e gb_err, const uint16_t val) { const char *gb_err_str[GB_INVALID_MAX] = {"UNKNOWN", "INVALID OPCODE", "INVALID READ", "INVALID WRITE", "HALT FOREVER"}; struct priv_t *priv = gb->direct.priv; fprintf(stderr, "Error %d occurred: %s at %04X\n. Exiting.\n", gb_err, gb_err_str[gb_err], val); /* Free memory and then exit. */ free(priv->cart_ram); free(priv->rom); exit(EXIT_FAILURE); } /** * Draws scanline into framebuffer. */ void lcd_draw_line(struct gb_s *gb, const uint8_t pixels[160], const uint_fast8_t line) { struct priv_t *priv = gb->direct.priv; const uint16_t palette[3][4] = {{0xFF, 0xA5, 0x52, 0x00}, {0xFF, 0xA5, 0x52, 0x00}, {0xFF, 0xA5, 0x52, 0x00}}; for (unsigned int x = 0; x < LCD_WIDTH; x++) { MwU8 p = palette[(pixels[x] & LCD_PALETTE_ALL) >> 4][pixels[x] & 3]; int i = ((line * LCD_WIDTH) + x) * 4; priv->fb[i] = p; priv->fb[i + 1] = p; priv->fb[i + 2] = p; priv->fb[i + 3] = 255; } } int main(int argc, char **argv) { int save_timer = 0; char *rom_file_name = NULL; enum gb_init_error_e ret; char *str_replace; const char extension[] = ".sav"; switch (argc) { case 2: rom_file_name = argv[1]; break; default: fprintf(stderr, "%s ROM\n", argv[0]); exit(EXIT_FAILURE); } /* Copy input ROM file to allocated memory. */ if ((priv.rom = read_rom_to_ram(rom_file_name)) == NULL) { printf("%d: %s\n", __LINE__, strerror(errno)); exit(EXIT_FAILURE); } /* Initialise context. */ ret = gb_init(&gb, &gb_rom_read, &gb_cart_ram_read, &gb_cart_ram_write, &gb_error, &priv); if (ret != GB_INIT_NO_ERROR) { printf("Error: %d\n", ret); exit(EXIT_FAILURE); } /* Load Save File. */ gb_get_save_size_s(&gb, &priv.save_size); printf("%ld\n", priv.save_size); priv.cart_ram = malloc(priv.save_size); /* rom save name */ priv.save_file_name = malloc(strlen(rom_file_name) + strlen(extension) + 1); strcpy(priv.save_file_name, rom_file_name); if ((str_replace = strrchr(priv.save_file_name, '.')) == NULL || str_replace == priv.save_file_name) str_replace = priv.save_file_name + strlen(priv.save_file_name); for (unsigned int i = 0; i <= strlen(extension); i++) *(str_replace++) = extension[i]; /* Only attempt to load a save file if the ROM actually supports saves.*/ if (priv.save_size > 0) read_cart_ram_file(priv.save_file_name, &priv.cart_ram, priv.save_size); gb_init_lcd(&gb, &lcd_draw_line); if (!mfb_open("Peanut-minifb", LCD_WIDTH, LCD_HEIGHT)) return EXIT_FAILURE; while (1) { const double target_speed_us = 1000000.0 / VERTICAL_SYNC; int_fast16_t delay; unsigned long start, end; struct timeval timecheck; int state; gettimeofday(&timecheck, NULL); start = (long)timecheck.tv_sec * 1000000 + (long)timecheck.tv_usec; /* Execute CPU cycles until the screen has to be redrawn. */ gb_run_frame(&gb); state = mfb_update(priv.fb); /* ESC pressed */ if (state < 0) break; gettimeofday(&timecheck, NULL); end = (long)timecheck.tv_sec * 1000000 + (long)timecheck.tv_usec; delay = target_speed_us - (end - start); /* If it took more than the maximum allowed time to draw frame, * do not delay. * Interlaced mode could be enabled here to help speed up * drawing. */ if (delay < 0) continue; usleep(delay); if (save_timer++ >= 60) { } save_timer++; } mfb_close(); free(priv.cart_ram); free(priv.rom); return EXIT_SUCCESS; }