commit eed8e2bed2d0d7dfe51834d6c27714472017cb6e Author: Themaister Date: Wed May 26 21:27:37 2010 +0200 SSNES diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..cacab9b620 --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +TARGET = ssnes + +SOURCE = ssnes.c +CFLAGS = -O3 -march=native -Wall + +OBJ = ssnes.o +SOBJ = libsnes.so + +LIBS = -lrsound -lglfw + +all: + $(CC) $(CFLAGS) -o $(TARGET) $(SOURCE) $(SOBJ) $(LIBS) + +ssnes.o: + $(CC) -c -o ssnes.o ssnes.c $(CFLAGS) + + +clean: + rm -rf $(OBJ) + rm -rf $(TARGET) diff --git a/config.h b/config.h new file mode 100644 index 0000000000..2a0d1b99b7 --- /dev/null +++ b/config.h @@ -0,0 +1,51 @@ +/// Config header for SSNES +// +// + +#ifndef __CONFIG_H +#define __CONFIG_H + +#include +#include "libsnes.hpp" +#include + +struct snes_keybind +{ + int id; + int key; + int joykey; +}; + +static const bool force_aspect = true; // On resize and fullscreen, rendering area will stay 8:7 + +// Windowed +static const float xscale = 5.0; // Real x res = 256 * xscale +static const float yscale = 5.0; // Real y res = 224 * yscale + +// Fullscreen +static const bool fullscreen = false; +static const unsigned fullscreen_x = 1920; +static const unsigned fullscreen_y = 1200; + +// Video VSYNC (recommended) +static const bool vsync = true; + +// Keybinds +static const struct snes_keybind snes_keybinds[] = { + { SNES_DEVICE_ID_JOYPAD_A, 'X', 1 }, + { SNES_DEVICE_ID_JOYPAD_B, 'Z', 0 }, + { SNES_DEVICE_ID_JOYPAD_X, 'S', 3 }, + { SNES_DEVICE_ID_JOYPAD_Y, 'A', 2 }, + { SNES_DEVICE_ID_JOYPAD_L, 'Q', 4 }, + { SNES_DEVICE_ID_JOYPAD_R, 'W', 5 }, + { SNES_DEVICE_ID_JOYPAD_LEFT, GLFW_KEY_LEFT, 12 }, + { SNES_DEVICE_ID_JOYPAD_RIGHT, GLFW_KEY_RIGHT, 13 }, + { SNES_DEVICE_ID_JOYPAD_UP, GLFW_KEY_UP, 10 }, + { SNES_DEVICE_ID_JOYPAD_DOWN, GLFW_KEY_DOWN, 11 }, + { SNES_DEVICE_ID_JOYPAD_START, GLFW_KEY_ENTER, 6 }, + { SNES_DEVICE_ID_JOYPAD_SELECT, GLFW_KEY_RSHIFT, 14 }, + { -1 } +}; + +#endif + diff --git a/ssnes.c b/ssnes.c new file mode 100644 index 0000000000..e6b6ec24af --- /dev/null +++ b/ssnes.c @@ -0,0 +1,336 @@ +#include +#include "libsnes.hpp" +#include +#include +#include +#include +#include "config.h" + +///// RSound +static rsound_t *rd; + + +///// GL +static GLuint texture; +static uint8_t* gl_buffer; +static void GLFWCALL resize(int width, int height); +static void init_gl(void); +static void uninit_gl(void); + +static void uninit_gl(void) +{ + free(gl_buffer); + glfwTerminate(); +} + +static void init_gl(void) +{ + glfwInit(); + + int res; + if ( fullscreen ) + res = glfwOpenWindow(fullscreen_x, fullscreen_y, 0, 0, 0, 0, 0, 0, GLFW_FULLSCREEN); + + else + res = glfwOpenWindow(256 * xscale, 224 * yscale, 0, 0, 0, 0, 0, 0, GLFW_WINDOW); + + if ( !res ) + { + glfwTerminate(); + exit(1); + } + + glfwSetWindowSizeCallback(resize); + + if ( vsync ) + glfwSwapInterval(1); // Force vsync + else + glfwSwapInterval(0); + + gl_buffer = malloc(256 * 256 * 4); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_DITHER); + glEnable(GL_DEPTH_TEST); + + glfwSetWindowTitle("SSNES"); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glGenTextures(1, &texture); + glBindTexture(GL_TEXTURE_2D, texture); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 256); + glTexImage2D(GL_TEXTURE_2D, + 0, GL_RGB, 256, 256, 0, GL_RGBA, + GL_UNSIGNED_INT_8_8_8_8, gl_buffer); +} + +static void GLFWCALL resize(int width, int height) +{ + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + + if ( force_aspect ) + { + float desired_aspect = 256.0/224.0; + float in_aspect = (float)width / height; + + if ( (int)(in_aspect*100) > (int)(desired_aspect*100) ) + { + float delta = (in_aspect / desired_aspect - 1.0) / 2.0 + 0.5; + glOrtho(0.5 - delta, 0.5 + delta, 0, 1, -1, 1); + } + + else if ( (int)(in_aspect*100) < (int)(desired_aspect*100) ) + { + float delta = (desired_aspect / in_aspect - 1.0) / 2.0 + 0.5; + glOrtho(0, 1, 0.5 - delta, 0.5 + delta, -1, 1); + } + else + glOrtho(0, 1, 0, 1, -1, 1); + + } + else + glOrtho(0, 1, 0, 1, -1, 1); + + glViewport(0, 0, width, height); + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); +} + +static void init_audio(void) +{ + rsd_init(&rd); + int channels = 2; + int rate = 32000; + int format = RSD_S16_LE; + int latency = 64; + rsd_set_param(rd, RSD_CHANNELS, &channels); + rsd_set_param(rd, RSD_SAMPLERATE, &rate); + rsd_set_param(rd, RSD_FORMAT, &format); + rsd_set_param(rd, RSD_LATENCY, &latency); + if ( rsd_start(rd) < 0 ) + { + fprintf(stderr, "FAILED TO START RSD\n"); + exit(1); + } +} + +static void video_refresh_GL(const uint16_t* data, unsigned width, unsigned height) +{ + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glPixelStorei(GL_UNPACK_ROW_LENGTH, width); + + uint32_t output[width*height]; + int y; + for ( y = 0; y < height; y++ ) + { + const uint16_t *src = data + y * 1024; + uint32_t *dst = output + y * width; + + int x; + for ( x = 0; x < width; x++ ) + { + uint16_t pixel = *src++; + int r = (pixel & 0x001f) << 3; + int g = (pixel & 0x03e0) >> 2; + int b = (pixel & 0x7c00) >> 7; + *dst++ = (r << 24) | (g << 16) | (b << 8); + } + } + + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, output); + + glLoadIdentity(); + glColor3f(1,1,1); + + glBegin(GL_QUADS); + + float h = 224.0/256.0; + + glTexCoord2f(0, h); glVertex3i(0, 0, 0); + glTexCoord2f(0, 0); glVertex3i(0, 1, 0); + glTexCoord2f(1, 0); glVertex3i(1, 1, 0); + glTexCoord2f(1, h); glVertex3i(1, 0, 0); + + glEnd(); + + glfwSwapBuffers(); +} + +static void audio_refresh(uint16_t left, uint16_t right) +{ + uint16_t samples[2] = {left, right}; + + rsd_delay_wait(rd); + if ( rsd_write(rd, samples, 4) < 4 ) + fprintf(stderr, "WTF!!\n"); + + if ( rsd_delay_ms(rd) < 32 ) + { + int ms = 32; + size_t size = (ms * 32000 * 4) / 1000; + void *temp = calloc(1, size); + rsd_write(rd, temp, size); + free(temp); + } +} + +static void snes_input_poll(void) +{ + glfwPollEvents(); +} + +static int16_t input_state(bool port, unsigned device, unsigned index, unsigned id) +{ + + if ( port != 0 || device != SNES_DEVICE_JOYPAD ) + return 0; + + int i; + int joypad_id = -1; + int joypad_buttons = -1; + + // Finds the first joypad that's alive + for ( i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; i++ ) + { + if ( glfwGetJoystickParam(i, GLFW_PRESENT) == GL_TRUE ) + { + joypad_id = i; + joypad_buttons = glfwGetJoystickParam(i, GLFW_BUTTONS); + break; + } + } + + unsigned char buttons[128]; + if ( joypad_id != -1 ) + { + glfwGetJoystickButtons(joypad_id, buttons, joypad_buttons); + } + + for ( i = 0; snes_keybinds[i].id != -1; i++ ) + { + if ( snes_keybinds[i].id == (int)id ) + { + if ( glfwGetKey(snes_keybinds[i].key )) + return 1; + + if ( snes_keybinds[i].joykey < joypad_buttons && buttons[snes_keybinds[i].joykey] == GLFW_PRESS ) + return 1; + } + } + + return 0; +} + + +int main(int argc, char *argv[]) +{ + if ( argc != 2 ) + { + fprintf(stderr, "Usage: %s file\n", argv[0]); + exit(1); + } + char savefile_name[strlen(argv[1]+5)]; + strcpy(savefile_name, argv[1]); + strcat(savefile_name, ".sav"); + + init_gl(); + + snes_init(); + + snes_set_video_refresh(video_refresh_GL); + snes_set_audio_sample(audio_refresh); + snes_set_input_poll(snes_input_poll); + snes_set_input_state(input_state); + + init_audio(); + + FILE *file = fopen(argv[1], "rb"); + if ( file == NULL ) + exit(1); + + fseek(file, 0, SEEK_END); + long length = ftell(file); + rewind(file); + if ((length & 0x7fff) == 512) + { + length -= 512; + fseek(file, 512, SEEK_SET); + } + + void *rom_buf = malloc(length); + if ( rom_buf == NULL ) + { + fprintf(stderr, "Couldn't allocate memory!\n"); + exit(1); + } + + if ( fread(rom_buf, 1, length, file) < length ) + { + fprintf(stderr, "Didn't read whole file.\n"); + exit(1); + } + + fclose(file); + + snes_load_cartridge_normal(NULL, rom_buf, length); + + free(rom_buf); + + unsigned serial_size = snes_serialize_size(); + uint8_t *serial_data = malloc(serial_size); + snes_serialize(serial_data, serial_size); + + file = fopen(savefile_name, "rb"); + if ( file != NULL ) + { + fread(serial_data, 1, serial_size, file); + fclose(file); + snes_unserialize(serial_data, serial_size); + snes_reset(); + } + + for(;;) + { + int quitting = glfwGetKey(GLFW_KEY_ESC) || !glfwGetWindowParam(GLFW_OPENED); + + if ( quitting ) + break; + + if ( glfwGetKey( GLFW_KEY_F2 )) + snes_serialize(serial_data, serial_size); + else if ( glfwGetKey(GLFW_KEY_F4) ) + snes_unserialize(serial_data, serial_size); + + snes_run(); + } + + file = fopen(savefile_name, "wb"); + if ( file != NULL ) + { + snes_serialize(serial_data, serial_size); + fwrite(serial_data, 1, serial_size, file); + fclose(file); + } + + snes_unload(); + snes_term(); + + uninit_gl(); + + rsd_stop(rd); + rsd_free(rd); + + return 0; +}