Compare commits

...

2 commits

Author SHA1 Message Date
7b0bb0ad5c update readme, use only Mw/Milsko.h 2026-03-10 20:52:35 -07:00
631450e88a proper inputs, and saving 2026-03-10 20:46:43 -07:00
4 changed files with 93 additions and 36 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
*.zip *.zip
*.gb *.gb
milskoboy milskoboy
*.sav

3
README.md Normal file
View file

@ -0,0 +1,3 @@
# milskoboy
Basic Gameboy emulator written using PeanutGB and [Milsko](https://gitea.nishi.boats/pyrite-dev/milsko). Not meant to be used seriously, instead meant to be portable enough to be usable as a test across any platforms Milsko supports.

115
main.c
View file

@ -2,20 +2,15 @@
#include "peanut_gb.h" #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> #include <Mw/Milsko.h>
struct priv_t { struct priv_t {
uint8_t *rom; MwU8 *rom;
uint8_t *cart_ram; MwU8 *cart_ram;
uint8_t fb[LCD_WIDTH * LCD_HEIGHT * 4]; MwU8 fb[LCD_WIDTH * LCD_HEIGHT * 4];
MwU64 save_size;
char *save_file_name;
MwWidget *win; MwWidget *win;
MwWidget *fb_widget; MwWidget *fb_widget;
@ -33,10 +28,10 @@ int mfb_open(const char *title, int width, int height) {
MwLibraryInit(); MwLibraryInit();
priv.win = MwCreateWidget(MwWindowClass, title, NULL, MwDEFAULT, MwDEFAULT, priv.win = MwCreateWidget(MwWindowClass, title, NULL, MwDEFAULT, MwDEFAULT,
width, height); width * 2, height * 2);
priv.fb_widget = priv.fb_widget =
MwCreateWidget(MwImageClass, "fb", priv.win, 0, 0, width, height); MwCreateWidget(MwImageClass, "fb", priv.win, 0, 0, width * 2, height * 2);
MwAddUserHandler(priv.win, MwNkeyHandler, key_pressed, NULL); MwAddUserHandler(priv.win, MwNkeyHandler, key_pressed, NULL);
MwAddUserHandler(priv.win, MwNkeyReleaseHandler, key_released, NULL); MwAddUserHandler(priv.win, MwNkeyReleaseHandler, key_released, NULL);
@ -148,7 +143,7 @@ static void key_released(MwWidget handle, void *user, void *client) {
/** /**
* Returns a byte from the ROM file at the given address. * 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) { MwU8 gb_rom_read(struct gb_s *gb, const uint_fast32_t addr) {
const struct priv_t *const p = gb->direct.priv; const struct priv_t *const p = gb->direct.priv;
return p->rom[addr]; return p->rom[addr];
} }
@ -156,27 +151,73 @@ uint8_t gb_rom_read(struct gb_s *gb, const uint_fast32_t addr) {
/** /**
* Returns a byte from the cartridge RAM at the given address. * 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) { MwU8 gb_cart_ram_read(struct gb_s *gb, const uint_fast32_t addr) {
const struct priv_t *const p = gb->direct.priv; const struct priv_t *const p = gb->direct.priv;
return p->cart_ram[addr]; return p->cart_ram[addr];
} }
void read_cart_ram_file(const char *save_file_name, MwU8 **dest,
const MwU64 len) {
FILE *f;
/* If save file not required. */
if (len == 0) {
*dest = NULL;
return;
}
/* Allocate enough memory to hold save file. */
*dest = malloc(len);
if ((f = fopen(save_file_name, "rb")) == NULL) {
printf("Unable to open save file (%s) for reading: %s\n", save_file_name,
strerror(errno));
return;
}
/* Read save file to allocated memory. */
fread(*dest, sizeof(MwU8), len, f);
fclose(f);
}
void write_cart_ram_file(const char *save_file_name, MwU8 **dest,
const MwU64 len) {
FILE *f;
if (len == 0 || *dest == NULL)
return;
if ((f = fopen(save_file_name, "wb")) == NULL) {
printf("Unable to open save file (%s) for writing: %s\n", save_file_name,
strerror(errno));
return;
}
/* Record save file. */
fwrite(*dest, sizeof(MwU8), len, f);
fclose(f);
return;
}
/** /**
* Writes a given byte to the cartridge RAM at the given address. * 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, void gb_cart_ram_write(struct gb_s *gb, const uint_fast32_t addr,
const uint8_t val) { const MwU8 val) {
const struct priv_t *const p = gb->direct.priv; const struct priv_t *const p = gb->direct.priv;
p->cart_ram[addr] = val; 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. * Returns a pointer to the allocated space containing the ROM. Must be freed.
*/ */
uint8_t *read_rom_to_ram(const char *file_name) { MwU8 *read_rom_to_ram(const char *file_name) {
FILE *rom_file = fopen(file_name, "rb"); FILE *rom_file = fopen(file_name, "rb");
size_t rom_size; MwU64 rom_size;
uint8_t *rom = NULL; MwU8 *rom = NULL;
if (rom_file == NULL) if (rom_file == NULL)
return NULL; return NULL;
@ -186,7 +227,7 @@ uint8_t *read_rom_to_ram(const char *file_name) {
rewind(rom_file); rewind(rom_file);
rom = malloc(rom_size); rom = malloc(rom_size);
if (fread(rom, sizeof(uint8_t), rom_size, rom_file) != rom_size) { if (fread(rom, sizeof(MwU8), rom_size, rom_file) != rom_size) {
free(rom); free(rom);
fclose(rom_file); fclose(rom_file);
return NULL; return NULL;
@ -218,7 +259,7 @@ void gb_error(struct gb_s *gb, const enum gb_error_e gb_err,
/** /**
* Draws scanline into framebuffer. * Draws scanline into framebuffer.
*/ */
void lcd_draw_line(struct gb_s *gb, const uint8_t pixels[160], void lcd_draw_line(struct gb_s *gb, const MwU8 pixels[160],
const uint_fast8_t line) { const uint_fast8_t line) {
struct priv_t *priv = gb->direct.priv; struct priv_t *priv = gb->direct.priv;
const uint16_t palette[3][4] = {{0xFF, 0xA5, 0x52, 0x00}, const uint16_t palette[3][4] = {{0xFF, 0xA5, 0x52, 0x00},
@ -237,9 +278,11 @@ void lcd_draw_line(struct gb_s *gb, const uint8_t pixels[160],
} }
int main(int argc, char **argv) { int main(int argc, char **argv) {
/* Must be freed */ int save_timer = 0;
char *rom_file_name = NULL; char *rom_file_name = NULL;
enum gb_init_error_e ret; enum gb_init_error_e ret;
char *str_replace;
const char extension[] = ".sav";
switch (argc) { switch (argc) {
case 2: case 2:
@ -265,11 +308,25 @@ int main(int argc, char **argv) {
printf("Error: %d\n", ret); printf("Error: %d\n", ret);
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
/* Load Save File. */
gb_get_save_size_s(&gb, &priv.save_size);
priv.cart_ram = malloc(gb_get_save_size(&gb)); 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); gb_init_lcd(&gb, &lcd_draw_line);
// gb.direct.interlace = true;
if (!mfb_open("Peanut-minifb", LCD_WIDTH, LCD_HEIGHT)) if (!mfb_open("Peanut-minifb", LCD_WIDTH, LCD_HEIGHT))
return EXIT_FAILURE; return EXIT_FAILURE;
@ -281,9 +338,7 @@ int main(int argc, char **argv) {
struct timeval timecheck; struct timeval timecheck;
int state; int state;
gettimeofday(&timecheck, NULL); start = MwTimeGetTick();
start = (long)timecheck.tv_sec * 1000000 + (long)timecheck.tv_usec;
/* Execute CPU cycles until the screen has to be redrawn. */ /* Execute CPU cycles until the screen has to be redrawn. */
gb_run_frame(&gb); gb_run_frame(&gb);
@ -293,8 +348,7 @@ int main(int argc, char **argv) {
if (state < 0) if (state < 0)
break; break;
gettimeofday(&timecheck, NULL); end = MwTimeGetTick();
end = (long)timecheck.tv_sec * 1000000 + (long)timecheck.tv_usec;
delay = target_speed_us - (end - start); delay = target_speed_us - (end - start);
@ -307,6 +361,9 @@ int main(int argc, char **argv) {
continue; continue;
usleep(delay); usleep(delay);
if (save_timer++ >= 60) {
}
save_timer++;
} }
mfb_close(); mfb_close();

View file

@ -41,11 +41,7 @@
#define __has_include(x) 0 #define __has_include(x) 0
#endif #endif
#include <stdbool.h> /* Required for bool types */ #include <Mw/Milsko.h>
#include <stdint.h> /* Required for int types */
#include <stdlib.h> /* Required for abort */
#include <string.h> /* Required for memset */
#include <time.h> /* Required for tm struct */
/** /**
* If PEANUT_GB_IS_LITTLE_ENDIAN is positive, then Peanut-GB will be configured * If PEANUT_GB_IS_LITTLE_ENDIAN is positive, then Peanut-GB will be configured
@ -3364,7 +3360,7 @@ void gb_run_frame(struct gb_s *gb) {
__gb_step_cpu(gb); __gb_step_cpu(gb);
} }
int gb_get_save_size_s(struct gb_s *gb, size_t *ram_size) { int gb_get_save_size_s(struct gb_s *gb, MwU64 *ram_size) {
const uint_fast16_t ram_size_location = 0x0149; const uint_fast16_t ram_size_location = 0x0149;
const uint_fast32_t ram_sizes[] = { const uint_fast32_t ram_sizes[] = {
/* 0, 2KiB, 8KiB, 32KiB, 128KiB, 64KiB */ /* 0, 2KiB, 8KiB, 32KiB, 128KiB, 64KiB */
@ -3769,7 +3765,7 @@ void gb_init_serial(struct gb_s *gb,
* set. * set.
* \returns 0 on success, or -1 if the RAM size is invalid or unknown. * \returns 0 on success, or -1 if the RAM size is invalid or unknown.
*/ */
int gb_get_save_size_s(struct gb_s *gb, size_t *ram_size); int gb_get_save_size_s(struct gb_s *gb, MwU64 *ram_size);
/** /**
* Deprecated. Use gb_get_save_size_s() instead. * Deprecated. Use gb_get_save_size_s() instead.