386 lines
8.7 KiB
C
386 lines
8.7 KiB
C
#define ENABLE_SOUND 0
|
|
|
|
#include "peanut_gb.h"
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/time.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include <Mw/Milsko.h>
|
|
|
|
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;
|
|
}
|