Created
September 16, 2023 16:49
-
-
Save ITotalJustice/e0a646aaaa43c7ebd8c6a265cf6acdd9 to your computer and use it in GitHub Desktop.
n64 main.c, will commit eventually when im done with changes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdint.h> | |
#include <string.h> | |
#include <stdlib.h> | |
#include <libdragon.h> | |
#include <sms.h> | |
// todo: use timer_ticks() for benchmarking | |
// add timer_ticks() after functions and log the diff | |
// then sort the logs that way. | |
// todo: add menu (press Z to enter) | |
// - dithering | |
// - savestate | |
// - auto load state | |
// - frame blending | |
// todo: add python to cmake for rom creating | |
// - pass z64 argv0 and rom folder as argv1 | |
// todo: add frame blending (should i even emulate ghosting?) | |
// - allow for up to 4 frames blended | |
#define FRAMEBUFFERS (3) | |
#define FPS_SKIP_MAX (4) | |
#define AUDIO_FREQ (22050) | |
#define AUDIO_START (0) | |
#define AUDIO_BUFFERS (4) | |
#define AUDIO_ENABLED (1) | |
enum Menu | |
{ | |
Menu_MAIN, | |
Menu_ROM, | |
}; | |
enum ScaleMode | |
{ | |
ScaleMode_NONE, | |
ScaleMode_FILL, | |
}; | |
struct Colour { uint8_t r,g,b; }; | |
// https://www.smspower.org/uploads/Development/sg1000.txt | |
const struct Colour SG_COLOUR_TABLE[] = | |
{ | |
{0x00, 0x00, 0x00}, // 0: transparent | |
{0x00, 0x00, 0x00}, // 1: black | |
{0x20, 0xC0, 0x20}, // 2: green | |
{0x60, 0xE0, 0x60}, // 3: bright green | |
{0x20, 0x20, 0xE0}, // 4: blue | |
{0x40, 0x60, 0xE0}, // 5: bright blue | |
{0xA0, 0x20, 0x20}, // 6: dark red | |
{0x40, 0xC0, 0xE0}, // 7: cyan (?) | |
{0xE0, 0x20, 0x20}, // 8: red | |
{0xE0, 0x60, 0x60}, // 9: bright red | |
{0xC0, 0xC0, 0x20}, // 10: yellow | |
{0xC0, 0xC0, 0x80}, // 11: bright yellow | |
{0x20, 0x80, 0x20}, // 12: dark green | |
{0xC0, 0x40, 0xA0}, // 13: pink | |
{0xA0, 0xA0, 0xA0}, // 14: gray | |
{0xE0, 0xE0, 0xE0}, // 15: white | |
}; | |
static uint8_t rom_data[SMS_ROM_SIZE_MAX]; | |
// todo: | |
static const char* scale_mode_str[] = { "None", "Stretch" }; | |
static const char* filter_mode_str[] = { "Nearest", "Bilinear", "Median" }; | |
static const char* dithering_mode_str[] = { "None", "Square", "Bayer", "Noise" }; | |
static const char* audio_mode_str[] = { "Disabled", "Enabled" }; | |
static const char* audio_freq_str[] = { "11025", "22050", "44100" }; | |
static enum Menu menu = Menu_MAIN; | |
static enum ScaleMode scale_mode = ScaleMode_NONE; | |
static bool loadrom_once = false; | |
static int fps_skip = 0; | |
static volatile int fps = 0; | |
static volatile int previous_fps = 0; | |
static short* audio_buffer = NULL; | |
static struct SMS_ApuSample* sms_audio_samples = NULL; | |
static size_t audio_samples = 0; | |
static struct SMS_Core sms = {0}; | |
static surface_t* surface; | |
static surface_t surface_emu; | |
static const int FONT_PACIFICO = 1; | |
static rdpq_font_t *fnt1; | |
_Noreturn static void display_message_error(const char* msg) | |
{ | |
console_init(); | |
console_set_render_mode(RENDER_MANUAL); | |
for (;;) | |
{ | |
console_clear(); | |
printf("%s", msg); | |
console_render(); | |
} | |
} | |
static void core_audio_callback(void* user, struct SMS_ApuSample* samples, uint32_t size) | |
{ | |
#if AUDIO_START | |
audio_buffer = audio_write_begin(); | |
SMS_apu_mixer_s16(samples, audio_buffer, size); | |
audio_write_end(); | |
#else | |
if (!audio_buffer) | |
{ | |
return; | |
} | |
SMS_apu_mixer_s16(samples, audio_buffer, size); | |
audio_write(audio_buffer); | |
#endif | |
} | |
static void timer_callback(int ovfl) | |
{ | |
// called every 1s | |
previous_fps = fps; | |
fps = 0; | |
} | |
static uint32_t core_colour_callback(void* user, uint8_t r, uint8_t g, uint8_t b) | |
{ | |
if (SMS_is_system_type_gg(&sms)) | |
{ | |
r = (r << 4) | r; | |
g = (g << 4) | g; | |
b = (b << 4) | b; | |
return graphics_make_color(r, g, b, 0xFF); | |
} | |
else if (SMS_is_system_type_sms(&sms)) | |
{ | |
r = (r << 6) | (r << 4) | (r << 2) | r; | |
g = (g << 6) | (g << 4) | (g << 2) | g; | |
b = (b << 6) | (b << 4) | (b << 2) | b; | |
return graphics_make_color(r, g, b, 0xFF); | |
} | |
else | |
{ | |
return graphics_make_color(r, g, b, 0xFF); | |
} | |
} | |
static void change_menu(enum Menu new_menu) | |
{ | |
menu = new_menu; | |
} | |
static void core_vblank_callback(void* user) | |
{ | |
static int fps_skip_counter = 0; | |
if (fps_skip_counter > 0) | |
{ | |
fps_skip_counter--; | |
SMS_skip_frame(&sms, true); | |
} | |
else | |
{ | |
fps_skip_counter = fps_skip; | |
SMS_skip_frame(&sms, false); | |
} | |
surface = display_get(); | |
rdpq_attach_clear(surface, NULL); | |
rdpq_mode_begin(); | |
rdpq_set_mode_standard(); | |
rdpq_mode_filter(FILTER_POINT); | |
rdpq_mode_dithering(DITHER_BAYER_NONE); | |
rdpq_mode_end(); | |
int x,y,w,h; | |
SMS_get_pixel_region(&sms, &x, &y, &w, &h); | |
float scale_w = 1; | |
float scale_h = 1; | |
switch (scale_mode) | |
{ | |
case ScaleMode_NONE: | |
if (w == 160 && h == 144) // still scale gg games by 1.5 | |
{ | |
scale_w = 1.5; | |
scale_h = 1.5; | |
} | |
break; | |
case ScaleMode_FILL: | |
scale_w = (float)surface->width / (float)w; | |
scale_h = (float)surface->height / (float)h; | |
break; | |
} | |
const int center_x = (surface->width - w * scale_w) / 2; | |
const int center_y = (surface->height - h * scale_h) / 2; | |
const rdpq_blitparms_t p = { | |
.s0 = x, | |
.t0 = y, | |
.width = w, | |
.height = h, | |
.cx = 0, | |
.cy = 0, | |
.scale_x = scale_w, | |
.scale_y = scale_h, | |
}; | |
// display emulation screen (with free scaling / filtering / dithering :)) | |
rdpq_tex_blit(&surface_emu, center_x, center_y, &p); | |
// debug stuff | |
rdpq_text_printf(NULL, FONT_PACIFICO, 10, 10, "[FPS: %d] [skip: %d]", previous_fps, fps_skip); | |
rdpq_text_print(NULL, FONT_PACIFICO, 10, 230, "[Z = Menu] [L/R = Frame skip]"); | |
// flip frame | |
rdpq_detach_show(); | |
} | |
static void display_rom(struct controller_data* kdown, struct controller_data* kheld) | |
{ | |
if (kdown->c[0].Z) | |
{ | |
// change_menu(Menu_MAIN); | |
// return; | |
} | |
else if (kdown->c[0].L) | |
{ | |
fps_skip = fps_skip > 0 ? fps_skip - 1 : 0; | |
} | |
else if (kdown->c[0].R) | |
{ | |
fps_skip = fps_skip < FPS_SKIP_MAX ? fps_skip + 1 : FPS_SKIP_MAX; | |
} | |
SMS_set_port_a(&sms, JOY1_UP_BUTTON, kheld->c[0].up); | |
SMS_set_port_a(&sms, JOY1_RIGHT_BUTTON, kheld->c[0].right); | |
SMS_set_port_a(&sms, JOY1_DOWN_BUTTON, kheld->c[0].down); | |
SMS_set_port_a(&sms, JOY1_LEFT_BUTTON, kheld->c[0].left); | |
SMS_set_port_a(&sms, JOY1_A_BUTTON, kheld->c[0].A); | |
SMS_set_port_a(&sms, JOY1_B_BUTTON, kheld->c[0].B); | |
// don't handle start button on non gg games as it was handled | |
// as the console pause button, which is fiq which i don't | |
// emulate correctly. | |
// for one, it should be handled shortley after vblank, and two, | |
// my emulation of it sucks | |
if (SMS_is_system_type_gg(&sms)) | |
{ | |
SMS_set_port_b(&sms, PAUSE_BUTTON, kheld->c[0].start); | |
} | |
SMS_run(&sms, SMS_CYCLES_PER_FRAME); | |
} | |
static void update_joystick_directions(struct controller_data* keys) | |
{ | |
// can't remember where i got this value from | |
#define JOYSTICK_DEAD_ZONE 32 | |
if (keys->c[0].x < -JOYSTICK_DEAD_ZONE) | |
{ | |
keys->c[0].left = true; | |
} | |
else if (keys->c[0].x > +JOYSTICK_DEAD_ZONE) | |
{ | |
keys->c[0].right = true; | |
} | |
if (keys->c[0].y > +JOYSTICK_DEAD_ZONE) | |
{ | |
keys->c[0].up = true; | |
} | |
else if(keys->c[0].y < -JOYSTICK_DEAD_ZONE) | |
{ | |
keys->c[0].down = true; | |
} | |
} | |
int main(void) | |
{ | |
// everyone uses 320x240 so i'll do the same | |
display_init(RESOLUTION_320x240, DEPTH_16_BPP, FRAMEBUFFERS, GAMMA_NONE, FILTERS_RESAMPLE); | |
rdpq_init(); | |
controller_init(); | |
timer_init(); | |
new_timer(TIMER_TICKS(1000000), TF_CONTINUOUS, timer_callback); | |
#if AUDIO_ENABLED | |
audio_init(AUDIO_FREQ, AUDIO_BUFFERS); | |
audio_pause(0); | |
audio_samples = audio_get_buffer_length(); | |
sms_audio_samples = malloc(audio_samples * sizeof(struct SMS_ApuSample)); | |
#if AUDIO_START | |
// audio_buffer = audio_write_begin(); | |
#else | |
audio_buffer = malloc(audio_samples * 2 * sizeof(short)); | |
#endif | |
#endif | |
if (!SMS_init(&sms)) | |
{ | |
display_message_error("failed to init sms"); | |
} | |
SMS_set_colour_callback(&sms, core_colour_callback); | |
SMS_set_vblank_callback(&sms, core_vblank_callback); | |
#if AUDIO_ENABLED | |
SMS_set_apu_callback(&sms, core_audio_callback, sms_audio_samples, audio_samples, audio_get_frequency()); | |
#endif | |
// set the palette for sg1000 games | |
uint32_t palette[16]; | |
for (int i = 0; i < 16; i++) | |
{ | |
const struct Colour c = SG_COLOUR_TABLE[i]; | |
palette[i] = graphics_make_color(c.r, c.g, c.b, 0xFF); | |
} | |
SMS_set_builtin_palette(&sms, palette); | |
// this is the surface the emulator draws to (in software) | |
surface_emu = surface_alloc(FMT_RGBA16, RESOLUTION_320x240.width, RESOLUTION_320x240.height); | |
SMS_set_pixels(&sms, surface_emu.buffer, surface_emu.width, 16); | |
if (dfs_init(DFS_DEFAULT_LOCATION) != DFS_ESUCCESS) | |
{ | |
display_message_error("Filesystem failed to start!\n"); | |
} | |
fnt1 = rdpq_font_load("rom:/trim.font64"); | |
// rdpq_font_style(fnt1, 0, &(rdpq_fontstyle_t){ | |
// .color = RGBA32(0xFF, 0xFF, 0xFF, 0xFF), | |
// }); | |
rdpq_text_register_font(FONT_PACIFICO, fnt1); | |
dma_read(rom_data, 0x10200000, SMS_ROM_SIZE_MAX); | |
if (!SMS_loadrom(&sms, rom_data, SMS_ROM_SIZE_MAX, -1)) | |
{ | |
display_message_error("failed to load rom"); | |
} | |
else | |
{ | |
loadrom_once = true; | |
change_menu(Menu_ROM); | |
} | |
for (;;) | |
{ | |
controller_scan(); | |
struct controller_data kheld = get_keys_pressed(); | |
struct controller_data kdown = get_keys_down(); | |
update_joystick_directions(&kheld); | |
update_joystick_directions(&kdown); | |
switch(menu) | |
{ | |
case Menu_MAIN: | |
// display_menu(&kdown, &kheld); | |
break; | |
case Menu_ROM: | |
display_rom(&kdown, &kheld); | |
break; | |
} | |
fps++; | |
} | |
// unreachable | |
timer_close(); | |
audio_close(); | |
rspq_close(); | |
display_close(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment