Skip to content

Instantly share code, notes, and snippets.

@ITotalJustice
Created September 16, 2023 16:49
Show Gist options
  • Save ITotalJustice/e0a646aaaa43c7ebd8c6a265cf6acdd9 to your computer and use it in GitHub Desktop.
Save ITotalJustice/e0a646aaaa43c7ebd8c6a265cf6acdd9 to your computer and use it in GitHub Desktop.
n64 main.c, will commit eventually when im done with changes
#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