| @@ -50,6 +50,7 @@ SRC_SRCS_1 += cart.c mapper.c | |||||
| SRC_SRCS_1 += apu.c audio.c | SRC_SRCS_1 += apu.c audio.c | ||||
| SRC_SRCS_1 += file.c save.c | SRC_SRCS_1 += file.c save.c | ||||
| SRC_SRCS_1 += overlay.c menu.c | SRC_SRCS_1 += overlay.c menu.c | ||||
| SRC_SRCS_1 += state.c ini.c | |||||
| SRC_SRCS_1 += sdl_render.c sdl_input.c sdl_audio.c sdl_timer.c | SRC_SRCS_1 += sdl_render.c sdl_input.c sdl_audio.c sdl_timer.c | ||||
| SRC_SRCS_1 += sdl_overlay.c | SRC_SRCS_1 += sdl_overlay.c | ||||
| @@ -0,0 +1,194 @@ | |||||
| #include <ctype.h> | |||||
| #include <stdint.h> | |||||
| #include <stdio.h> | |||||
| #include <stdlib.h> | |||||
| #include <string.h> | |||||
| #include "ini.h" | |||||
| static inline int stracmp(const char* zstr, | |||||
| const char* lstr, size_t len) { | |||||
| int diff = 0; | |||||
| while (0 == diff && *zstr && len > 0) { | |||||
| --len; | |||||
| diff = (*zstr - *lstr); | |||||
| ++zstr; | |||||
| ++lstr; | |||||
| } | |||||
| if (0 == diff) { | |||||
| if (0 == len && !*zstr) diff = *zstr; | |||||
| else if (0 != len && *zstr) diff = *lstr; | |||||
| } | |||||
| return diff; | |||||
| } | |||||
| static const ini_datum* find_name(const ini_datum* schema, | |||||
| const char* name, int len, | |||||
| const ini_data_type type) { | |||||
| if (0 == stracmp(schema->name, name, len)) { | |||||
| return schema; | |||||
| } | |||||
| for (int i = 0; i < schema->count; ++i) { | |||||
| if ( ( ini_none == type || | |||||
| type == schema->data[i].type) && | |||||
| 0 == stracmp(schema->data[i].name, name, len)) { | |||||
| return &schema->data[i]; | |||||
| } | |||||
| } | |||||
| return NULL; | |||||
| } | |||||
| int write_ini_file(FILE* file, const ini_datum* schema, | |||||
| const void* data) { | |||||
| int status = 0; | |||||
| const void* ptr = (data + schema->offset); | |||||
| if (ini_section == schema->type) { | |||||
| fprintf(file, "[%s]\n", schema->name); | |||||
| for (int i = 0; i < schema->count; ++i) { | |||||
| write_ini_file(file, &schema->data[i], ptr); | |||||
| } | |||||
| } else if (ini_comment == schema->type) { | |||||
| fprintf(file, "; %s", *(char**)ptr); | |||||
| } else { | |||||
| fprintf(file, "%s = ", schema->name); | |||||
| if (ini_string == schema->type) { | |||||
| fprintf(file, "\"%s\"", *(char**)ptr); | |||||
| } else if (ini_integer == schema->type) { | |||||
| fprintf(file, "%d", *(uint32_t*)ptr); | |||||
| } else if (ini_flag == schema->type) { | |||||
| fprintf(file, "%d", !!( *(uint32_t*)ptr & | |||||
| (1 << schema->shift))); | |||||
| } | |||||
| } | |||||
| fputc('\n', file); | |||||
| return status; | |||||
| } | |||||
| static inline const char* first_char(const char* str) { | |||||
| while (*str && isspace(*str)) ++str; | |||||
| return str; | |||||
| } | |||||
| static inline const char* first_space(const char* str) { | |||||
| while (*str && !isspace(*str)) ++str; | |||||
| return str; | |||||
| } | |||||
| static inline const char* end_key(const char* str) { | |||||
| while (*str && '=' != *str && !isspace(*str)) ++str; | |||||
| return str; | |||||
| } | |||||
| static inline const char* last_char(const char* str) { | |||||
| int len = strlen(str); | |||||
| const char* end = str + len - 1; | |||||
| while (end > str && isspace(*end)) --end; | |||||
| return end; | |||||
| } | |||||
| static inline char* parse_string(const char* str) { | |||||
| const char* last = last_char(str); | |||||
| if ('"' == str[0] && '"' == *last) { | |||||
| return strndup(&str[1], last - str - 1); | |||||
| } | |||||
| return strndup(str, last - str + 1); | |||||
| } | |||||
| static inline int parse_key_value(const char* key_start, | |||||
| const ini_datum* section, | |||||
| void* data) { | |||||
| const char* key_end = end_key(key_start + 1); | |||||
| if (NULL == key_end) return -1; | |||||
| const ini_datum* def = find_name(section, key_start, | |||||
| (key_end - key_start), | |||||
| ini_none); | |||||
| if (NULL == def || def->type <= ini_section) return -1; | |||||
| const char* equal = first_char(key_end); | |||||
| if (NULL == equal || '=' != *equal) return -1; | |||||
| const char* val = first_char(equal + 1); | |||||
| if (NULL == val) return -1; | |||||
| const char* ptr = data + def->offset; | |||||
| if (ini_string == def->type) { | |||||
| *(char**)ptr = parse_string(val); | |||||
| } else if ( ini_integer == def->type || | |||||
| ini_flag == def->type) { | |||||
| int intval = 0; | |||||
| if (0 >= sscanf(val, "%d", &intval)) return -1; | |||||
| if (ini_integer == def->type) { | |||||
| *(int32_t*)ptr = intval; | |||||
| } else { | |||||
| int32_t mask = (1 << def->shift); | |||||
| if (intval) *(uint32_t*)ptr |= mask; | |||||
| else *(uint32_t*)ptr &= ~mask; | |||||
| } | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| int read_ini_file(FILE* file, const ini_datum* schema, | |||||
| void* data) { | |||||
| int status = 0; | |||||
| const ini_datum* section = NULL; | |||||
| const ini_datum* subsection = NULL; | |||||
| void* ptr = data; | |||||
| char* line = NULL; | |||||
| size_t sz_line = 0; | |||||
| while (0 == status && 0 <= getline(&line, &sz_line, file)) { | |||||
| const char* str = first_char(line); | |||||
| if ('[' == str[0]) { | |||||
| const char* start = &str[1]; | |||||
| const char* end = strchr(start, ']'); | |||||
| if (NULL != end) { | |||||
| ptr = data; | |||||
| int len = (end - start); | |||||
| if ('.' == start[0]) { | |||||
| if (NULL != section) { | |||||
| subsection = find_name( | |||||
| section, start, len, ini_section | |||||
| ); | |||||
| ptr += section->offset; | |||||
| if (NULL != subsection) { | |||||
| ptr += subsection->offset; | |||||
| } | |||||
| } | |||||
| } else { | |||||
| section = find_name( | |||||
| schema, start, len, ini_section | |||||
| ); | |||||
| subsection = section; | |||||
| if (NULL != section) ptr += section->offset; | |||||
| } | |||||
| } | |||||
| } else if (';' == str[0]) { | |||||
| // Ignore comments | |||||
| } else if (isalnum(str[0])) { | |||||
| // Key-value | |||||
| // Ignoring return value: | |||||
| // - Unknown sections or keys are ignored | |||||
| // - Just ignore malformed files, I guess | |||||
| parse_key_value(str, subsection, ptr); | |||||
| } | |||||
| } | |||||
| free(line); | |||||
| return status; | |||||
| } | |||||
| @@ -0,0 +1,44 @@ | |||||
| #ifndef NESE_INI_H_ | |||||
| #define NESE_INI_H_ | |||||
| #define DBG_LOG(...) fprintf(stderr, __VA_ARGS__) | |||||
| #define ERR_LOG(...) DBG_LOG(__VA_ARGS__) | |||||
| #define INI_ERR(...) ERR_LOG(__VA_ARGS__) | |||||
| typedef enum { | |||||
| ini_invalid = -1, | |||||
| ini_none = 0, | |||||
| ini_comment, | |||||
| ini_section, | |||||
| ini_string, | |||||
| ini_integer, | |||||
| ini_flag, | |||||
| } ini_data_type; | |||||
| typedef struct ini_datum { | |||||
| ini_data_type type; | |||||
| int offset; // offset into associated struct | |||||
| char* name; // key, section, or comment | |||||
| union { | |||||
| char* string; // string, comment | |||||
| struct { | |||||
| int32_t value; // int, flag | |||||
| uint32_t shift; // flag | |||||
| }; | |||||
| struct { | |||||
| int count; | |||||
| struct ini_datum* data; // section | |||||
| }; | |||||
| }; | |||||
| } ini_datum; | |||||
| int write_ini_file(FILE* file, const ini_datum* schema, | |||||
| const void* data); | |||||
| int read_ini_file(FILE* file, const ini_datum* schema, | |||||
| void* data); | |||||
| #endif // NESE_INI_H_ | |||||
| @@ -16,6 +16,8 @@ typedef enum { | |||||
| input_Result_Save, | input_Result_Save, | ||||
| input_Result_Load, | input_Result_Load, | ||||
| input_Result_Cancel, | input_Result_Cancel, | ||||
| input_Result_View, | |||||
| input_Result_Refresh, | |||||
| } nes_Input_Result; | } nes_Input_Result; | ||||
| #define nes_controller_num_buttons (8U) | #define nes_controller_num_buttons (8U) | ||||
| @@ -187,7 +187,8 @@ static int run_menu(menu_state* state, const file_list* files, | |||||
| int buttons = wait_for_input(input, &sys->input); | int buttons = wait_for_input(input, &sys->input); | ||||
| int special = (buttons >> 8); | int special = (buttons >> 8); | ||||
| if (input_Result_Quit == special) { | |||||
| if ( input_Result_Quit == special || | |||||
| input_Result_Refresh == special) { | |||||
| status = special; | status = special; | ||||
| } else if ( input_Result_Menu == special || | } else if ( input_Result_Menu == special || | ||||
| @@ -250,8 +251,12 @@ char* run_main_menu(menu_state* state, nes_Renderer* rend, | |||||
| if (current >= 0) menu.cursor = current; | if (current >= 0) menu.cursor = current; | ||||
| } | } | ||||
| int status = run_menu(&menu, &files, 20, | |||||
| // Don't let window refreshes interrupt us. | |||||
| int status = input_Result_Refresh; | |||||
| while (input_Result_Refresh == status) { | |||||
| status = run_menu(&menu, &files, 20, | |||||
| rend, input, sys); | rend, input, sys); | ||||
| } | |||||
| if (input_Result_Quit == status) { | if (input_Result_Quit == status) { | ||||
| cart_filename = (char*)-1; | cart_filename = (char*)-1; | ||||
| @@ -283,6 +288,7 @@ int run_game_menu(menu_state* state, nes_Renderer* rend, | |||||
| "Restore", | "Restore", | ||||
| "Reset", | "Reset", | ||||
| "Select ROM", | "Select ROM", | ||||
| "Toggle Fullscreen", | |||||
| "Exit", | "Exit", | ||||
| }; | }; | ||||
| static int choices[] = { | static int choices[] = { | ||||
| @@ -291,6 +297,7 @@ int run_game_menu(menu_state* state, nes_Renderer* rend, | |||||
| input_Result_Load, | input_Result_Load, | ||||
| input_Result_Reset, | input_Result_Reset, | ||||
| input_Result_Menu, | input_Result_Menu, | ||||
| input_Result_View, | |||||
| input_Result_Quit, | input_Result_Quit, | ||||
| }; | }; | ||||
| static const file_list options = { | static const file_list options = { | ||||
| @@ -10,6 +10,7 @@ | |||||
| #include "mapper.h" | #include "mapper.h" | ||||
| #include "save.h" | #include "save.h" | ||||
| #include "menu.h" | #include "menu.h" | ||||
| #include "state.h" | |||||
| #define audio_freq (44100U) | #define audio_freq (44100U) | ||||
| @@ -106,16 +107,6 @@ static int loadsave_tick(loadsave_state* loadsave) { | |||||
| return action; | return action; | ||||
| } | } | ||||
| typedef struct { | |||||
| char* filename; | |||||
| FILE* file; | |||||
| } cart_info; | |||||
| static void cleanup_cart_info(cart_info* cart) { | |||||
| if (cart->file) fclose(cart->file); | |||||
| free(cart->filename); | |||||
| } | |||||
| static int select_rom(menu_state* menu, nes_Renderer* rend, | static int select_rom(menu_state* menu, nes_Renderer* rend, | ||||
| nes_Input_Reader* input, nes* sys, | nes_Input_Reader* input, nes* sys, | ||||
| cart_info* cur_cart) { | cart_info* cur_cart) { | ||||
| @@ -162,7 +153,7 @@ static int select_rom(menu_state* menu, nes_Renderer* rend, | |||||
| save_sram(&sys->cart, cur_cart->filename); | save_sram(&sys->cart, cur_cart->filename); | ||||
| nes_cart_done(&sys->cart); | nes_cart_done(&sys->cart); | ||||
| cleanup_cart_info(cur_cart); | |||||
| cart_info_done(cur_cart); | |||||
| sys->cart = new_cart; | sys->cart = new_cart; | ||||
| *cur_cart = cart; | *cur_cart = cart; | ||||
| @@ -173,18 +164,35 @@ static int select_rom(menu_state* menu, nes_Renderer* rend, | |||||
| return status; | return status; | ||||
| } | } | ||||
| static int do_game_menu(menu_state* state, nes_Renderer* rend, | |||||
| static int do_game_menu(menu_state* menu, nes_Renderer* rend, | |||||
| nes_Input_Reader* input, nes* sys, | nes_Input_Reader* input, nes* sys, | ||||
| cart_info* cart) { | |||||
| nese_State* state) { | |||||
| int status = 0; | int status = 0; | ||||
| menu_state rom_menu = {0}; | menu_state rom_menu = {0}; | ||||
| while (1) { | while (1) { | ||||
| status = run_game_menu(state, rend, input, sys); | |||||
| status = run_game_menu(menu, rend, input, sys); | |||||
| if ( input_Result_View == status || | |||||
| input_Result_Refresh == status) { | |||||
| if (input_Result_View == status) { | |||||
| state->flags ^= (1 << State_Bit_Fullscreen); | |||||
| } else { | |||||
| // We need to do this to flush both buffers | |||||
| nes_draw_last_frame(rend, 1); | |||||
| nes_draw_done(rend); | |||||
| } | |||||
| // We call this both times since it does both | |||||
| // the toggle and the recalculation. | |||||
| nes_render_fullscreen( | |||||
| rend, | |||||
| state->flags & (1 << State_Bit_Fullscreen) | |||||
| ); | |||||
| continue; | |||||
| if (input_Result_Menu == status) { | |||||
| } else if (input_Result_Menu == status) { | |||||
| status = select_rom(&rom_menu, rend, input, | status = select_rom(&rom_menu, rend, input, | ||||
| sys, cart); | |||||
| sys, &state->cart); | |||||
| if (input_Result_Cancel == status) { | if (input_Result_Cancel == status) { | ||||
| status = input_Result_OK; | status = input_Result_OK; | ||||
| @@ -202,9 +210,18 @@ static int do_game_menu(menu_state* state, nes_Renderer* rend, | |||||
| int main(int argc, char* argv[]) { | int main(int argc, char* argv[]) { | ||||
| int status = 0; | int status = 0; | ||||
| nese_State state = {0}; | |||||
| load_prefs_filename(&state, "nese.prefs"); | |||||
| nes_Renderer* rend = &sdl_renderer; | nes_Renderer* rend = &sdl_renderer; | ||||
| if (status == 0) { | if (status == 0) { | ||||
| status = nes_render_init(rend); | status = nes_render_init(rend); | ||||
| if (0 == status) { | |||||
| nes_render_fullscreen( | |||||
| rend, | |||||
| (state.flags & (1 << State_Bit_Fullscreen)) | |||||
| ); | |||||
| } | |||||
| } | } | ||||
| nes_Input_Reader* input = &sdl_input; | nes_Input_Reader* input = &sdl_input; | ||||
| @@ -222,18 +239,24 @@ int main(int argc, char* argv[]) { | |||||
| status = nes_init(&sys, audio_freq); | status = nes_init(&sys, audio_freq); | ||||
| } | } | ||||
| cart_info cart = {0}; | |||||
| if (0 == status && argc > 1) { | if (0 == status && argc > 1) { | ||||
| cart.filename = strdup(argv[1]); | |||||
| cart_info cart = { | |||||
| .filename = strdup(argv[1]), | |||||
| }; | |||||
| cart.file = nes_load_cart(&sys.cart, cart.filename); | cart.file = nes_load_cart(&sys.cart, cart.filename); | ||||
| if (NULL != cart.file) { | if (NULL != cart.file) { | ||||
| cart_info_done(&state.cart); | |||||
| state.cart = cart; | |||||
| nes_setup_cart(&sys); | nes_setup_cart(&sys); | ||||
| } else { | |||||
| cart_info_done(&cart); | |||||
| } | } | ||||
| } | } | ||||
| // If we didn't launch with a file, run the loader | // If we didn't launch with a file, run the loader | ||||
| if (0 == status && NULL == cart.file) { | |||||
| status = select_rom(NULL, rend, input, &sys, &cart); | |||||
| if (0 == status && NULL == state.cart.file) { | |||||
| status = select_rom(NULL, rend, input, | |||||
| &sys, &state.cart); | |||||
| } | } | ||||
| if (status == 0) { | if (status == 0) { | ||||
| @@ -261,9 +284,9 @@ int main(int argc, char* argv[]) { | |||||
| // Load/Save Operations | // Load/Save Operations | ||||
| int action = loadsave_tick(&loadsave); | int action = loadsave_tick(&loadsave); | ||||
| if (input_Result_Load == action) { | if (input_Result_Load == action) { | ||||
| load_state(&sys, cart.filename); | |||||
| load_state(&sys, state.cart.filename); | |||||
| } else if (input_Result_Save == action) { | } else if (input_Result_Save == action) { | ||||
| save_state(&sys, cart.filename); | |||||
| save_state(&sys, state.cart.filename); | |||||
| } | } | ||||
| // Sleep to catch up to master clock | // Sleep to catch up to master clock | ||||
| @@ -291,7 +314,7 @@ int main(int argc, char* argv[]) { | |||||
| if (input_Result_Menu == status) { | if (input_Result_Menu == status) { | ||||
| status = do_game_menu( | status = do_game_menu( | ||||
| &game_menu, rend, | &game_menu, rend, | ||||
| input, &sys, &cart | |||||
| input, &sys, &state | |||||
| ); | ); | ||||
| if ( input_Result_Load == status || | if ( input_Result_Load == status || | ||||
| input_Result_Save == status) { | input_Result_Save == status) { | ||||
| @@ -344,11 +367,13 @@ int main(int argc, char* argv[]) { | |||||
| status == 0 ? "OK" : "Halted"); | status == 0 ? "OK" : "Halted"); | ||||
| // Failure might mean there's nothing to save | // Failure might mean there's nothing to save | ||||
| save_sram(&sys.cart, cart.filename); | |||||
| save_sram(&sys.cart, state.cart.filename); | |||||
| } | } | ||||
| save_prefs_filename(&state, "nese.prefs"); | |||||
| nese_state_done(&state); | |||||
| nes_done(&sys); | nes_done(&sys); | ||||
| cleanup_cart_info(&cart); | |||||
| nes_audio_done(audio); | nes_audio_done(audio); | ||||
| nes_input_done(input); | nes_input_done(input); | ||||
| @@ -9,6 +9,7 @@ typedef struct nes_Renderer_t { | |||||
| int (*init)(struct nes_Renderer_t*); | int (*init)(struct nes_Renderer_t*); | ||||
| void (*done)(struct nes_Renderer_t*); | void (*done)(struct nes_Renderer_t*); | ||||
| int (*render)(struct nes_Renderer_t*, nes_ppu*); | int (*render)(struct nes_Renderer_t*, nes_ppu*); | ||||
| void (*fullscreen)(struct nes_Renderer_t*, int enable); | |||||
| void (*draw_last_frame)(struct nes_Renderer_t*, int dim); | void (*draw_last_frame)(struct nes_Renderer_t*, int dim); | ||||
| void (*draw_text)(struct nes_Renderer_t*, const char*, int x, int y, uint32_t color); | void (*draw_text)(struct nes_Renderer_t*, const char*, int x, int y, uint32_t color); | ||||
| @@ -31,6 +32,11 @@ static inline int nes_render(nes_Renderer* rend, nes_ppu* ppu) { | |||||
| return rend->render(rend, ppu); | return rend->render(rend, ppu); | ||||
| } | } | ||||
| static inline void nes_render_fullscreen(nes_Renderer* rend, | |||||
| int enable) { | |||||
| rend->fullscreen(rend, enable); | |||||
| } | |||||
| static inline void nes_draw_last_frame(nes_Renderer* rend, | static inline void nes_draw_last_frame(nes_Renderer* rend, | ||||
| int dim) { | int dim) { | ||||
| rend->draw_last_frame(rend, dim); | rend->draw_last_frame(rend, dim); | ||||
| @@ -98,6 +98,12 @@ static int sdl_input_update(nes_Input_Reader* reader, | |||||
| if (SDL_QUIT == event.type) { | if (SDL_QUIT == event.type) { | ||||
| status = input_Result_Quit; | status = input_Result_Quit; | ||||
| } else if (SDL_WINDOWEVENT == event.type) { | |||||
| if ( SDL_WINDOWEVENT_EXPOSED == | |||||
| event.window.event) { | |||||
| status = input_Result_Refresh; | |||||
| } | |||||
| } else if ( ( SDL_KEYDOWN == event.type || | } else if ( ( SDL_KEYDOWN == event.type || | ||||
| SDL_KEYUP == event.type) && | SDL_KEYUP == event.type) && | ||||
| 0 == event.key.repeat | 0 == event.key.repeat | ||||
| @@ -77,6 +77,9 @@ static int sdl_render_init(nes_Renderer* rend) { | |||||
| data->win_w = (nes_ppu_scan_w * scale); | data->win_w = (nes_ppu_scan_w * scale); | ||||
| data->win_h = (nes_ppu_scan_h * scale); | data->win_h = (nes_ppu_scan_h * scale); | ||||
| data->view.w = data->win_w; | |||||
| data->view.h = data->win_h; | |||||
| data->window = SDL_CreateWindow( | data->window = SDL_CreateWindow( | ||||
| "NESe", | "NESe", | ||||
| SDL_WINDOWPOS_UNDEFINED, | SDL_WINDOWPOS_UNDEFINED, | ||||
| @@ -87,28 +90,6 @@ static int sdl_render_init(nes_Renderer* rend) { | |||||
| fprintf(stderr, "SDL: Failed to create window\n"); | fprintf(stderr, "SDL: Failed to create window\n"); | ||||
| SDL_Quit(); | SDL_Quit(); | ||||
| status = -1; | status = -1; | ||||
| } else { | |||||
| // TODO: Hide behind flag | |||||
| SDL_SetWindowFullscreen(data->window, | |||||
| SDL_WINDOW_FULLSCREEN); | |||||
| SDL_GetWindowSize(data->window, | |||||
| &data->win_w, &data->win_h); | |||||
| // Determine the viewport within the screen | |||||
| int w = data->win_w; | |||||
| int h = data->win_h; | |||||
| if ((w * nes_ppu_scan_h) > (h * nes_ppu_scan_w)) { | |||||
| w = (h * nes_ppu_scan_w) / nes_ppu_scan_h; | |||||
| } else { | |||||
| h = (w * nes_ppu_scan_h) / nes_ppu_scan_w; | |||||
| } | |||||
| data->view.x = (data->win_w - w) / 2; | |||||
| data->view.y = (data->win_h - h) / 2; | |||||
| data->view.w = w; | |||||
| data->view.h = h; | |||||
| } | } | ||||
| } | } | ||||
| @@ -209,6 +190,33 @@ static void sdl_render_done(nes_Renderer* rend) { | |||||
| SDL_Quit(); | SDL_Quit(); | ||||
| } | } | ||||
| static void sdl_render_fullscreen(nes_Renderer* rend, int on) { | |||||
| sdl_render_data* data = (sdl_render_data*)rend->data; | |||||
| SDL_SetWindowFullscreen(data->window, | |||||
| on ? SDL_WINDOW_FULLSCREEN : 0); | |||||
| SDL_GetWindowSize(data->window, | |||||
| &data->win_w, &data->win_h); | |||||
| // Determine the viewport within the screen | |||||
| int w = data->win_w; | |||||
| int h = data->win_h; | |||||
| if ((w * nes_ppu_scan_h) > (h * nes_ppu_scan_w)) { | |||||
| w = (h * nes_ppu_scan_w) / nes_ppu_scan_h; | |||||
| } else { | |||||
| h = (w * nes_ppu_scan_h) / nes_ppu_scan_w; | |||||
| } | |||||
| data->view.x = (data->win_w - w) / 2; | |||||
| data->view.y = (data->win_h - h) / 2; | |||||
| data->view.w = w; | |||||
| data->view.h = h; | |||||
| SDL_RenderSetClipRect(data->renderer, &data->view); | |||||
| } | |||||
| static inline void render_sprite_line( | static inline void render_sprite_line( | ||||
| const nes_ppu* ppu, int index, int y, const uint8_t* pal, | const nes_ppu* ppu, int index, int y, const uint8_t* pal, | ||||
| uint8_t* dst, int start, int end, const uint8_t* back) { | uint8_t* dst, int start, int end, const uint8_t* back) { | ||||
| @@ -644,6 +652,7 @@ nes_Renderer sdl_renderer = { | |||||
| .init = sdl_render_init, | .init = sdl_render_init, | ||||
| .done = sdl_render_done, | .done = sdl_render_done, | ||||
| .render = sdl_render, | .render = sdl_render, | ||||
| .fullscreen = sdl_render_fullscreen, | |||||
| .draw_last_frame = sdl_redraw_frame, | .draw_last_frame = sdl_redraw_frame, | ||||
| .draw_text = sdl_draw_text, | .draw_text = sdl_draw_text, | ||||
| .text_size = sdl_text_size, | .text_size = sdl_text_size, | ||||
| @@ -0,0 +1,79 @@ | |||||
| #include <stddef.h> | |||||
| #include "state.h" | |||||
| #include "ini.h" | |||||
| void cart_info_done(cart_info* cart) { | |||||
| if (cart->file) fclose(cart->file); | |||||
| free(cart->filename); | |||||
| } | |||||
| void nese_state_done(nese_State* state) { | |||||
| cart_info_done(&state->cart); | |||||
| } | |||||
| static const ini_datum prefs_schema = { | |||||
| .type = ini_section, | |||||
| .name = "nese", | |||||
| .count = 2, | |||||
| .data = (ini_datum[]){ | |||||
| { | |||||
| .type = ini_section, | |||||
| .name = ".cart", | |||||
| .offset = offsetof(nese_State, cart), | |||||
| .count = 1, | |||||
| .data = (ini_datum[]){ | |||||
| { | |||||
| .type = ini_string, | |||||
| .name = "filename", | |||||
| .offset = offsetof(cart_info, filename), | |||||
| }, | |||||
| } | |||||
| }, { | |||||
| .type = ini_section, | |||||
| .name = ".flags", | |||||
| .offset = offsetof(nese_State, flags), | |||||
| .count = 1, | |||||
| .data = (ini_datum[]){ | |||||
| { | |||||
| .type = ini_flag, | |||||
| .name = "fullscreen", | |||||
| .shift = State_Bit_Fullscreen, | |||||
| }, | |||||
| } | |||||
| }, | |||||
| }, | |||||
| }; | |||||
| int save_prefs_file(const nese_State* state, FILE* file) { | |||||
| return write_ini_file(file, &prefs_schema, state); | |||||
| } | |||||
| int load_prefs_file(nese_State* state, FILE* file) { | |||||
| return read_ini_file(file, &prefs_schema, state); | |||||
| } | |||||
| int save_prefs_filename(const nese_State* state, | |||||
| const char* filename) { | |||||
| int status = -1; | |||||
| FILE* file = fopen(filename, "w"); | |||||
| if (NULL != file) { | |||||
| status = write_ini_file(file, &prefs_schema, state); | |||||
| fclose(file); | |||||
| } | |||||
| return status; | |||||
| } | |||||
| int load_prefs_filename(nese_State* state, | |||||
| const char* filename) { | |||||
| int status = -1; | |||||
| FILE* file = fopen(filename, "r"); | |||||
| if (NULL != file) { | |||||
| status = read_ini_file(file, &prefs_schema, state); | |||||
| fclose(file); | |||||
| } | |||||
| return status; | |||||
| } | |||||
| @@ -0,0 +1,37 @@ | |||||
| #ifndef NESE_STATE_H_ | |||||
| #define NESE_STATE_H_ | |||||
| #include <stdint.h> | |||||
| #include <stdio.h> | |||||
| #include <stdlib.h> | |||||
| typedef struct { | |||||
| char* filename; | |||||
| FILE* file; | |||||
| } cart_info; | |||||
| void cart_info_done(cart_info*); | |||||
| typedef enum { | |||||
| State_Bit_Fullscreen = 0, | |||||
| } nese_State_Flags; | |||||
| typedef struct { | |||||
| cart_info cart; | |||||
| nese_State_Flags flags; | |||||
| } nese_State; | |||||
| void nese_state_done(nese_State*); | |||||
| int load_prefs_file(nese_State*, FILE*); | |||||
| int save_prefs_file(const nese_State*, FILE*); | |||||
| int load_prefs_filename(nese_State*, const char*); | |||||
| int save_prefs_filename(const nese_State*, const char*); | |||||
| #endif // NESE_STATE_H_ | |||||