瀏覽代碼

Retain preferences; add fullscreen toggle control

master
Nathaniel Walizer 11 月之前
父節點
當前提交
d19e256dae
共有 11 個文件被更改,包括 459 次插入49 次删除
  1. +1
    -0
      Makefile
  2. +194
    -0
      src/ini.c
  3. +44
    -0
      src/ini.h
  4. +2
    -0
      src/input.h
  5. +9
    -2
      src/menu.c
  6. +50
    -25
      src/nese.c
  7. +6
    -0
      src/render.h
  8. +6
    -0
      src/sdl_input.c
  9. +31
    -22
      src/sdl_render.c
  10. +79
    -0
      src/state.c
  11. +37
    -0
      src/state.h

+ 1
- 0
Makefile 查看文件

@@ -50,6 +50,7 @@ SRC_SRCS_1 += cart.c mapper.c
SRC_SRCS_1 += apu.c audio.c
SRC_SRCS_1 += file.c save.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_overlay.c



+ 194
- 0
src/ini.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;
}

+ 44
- 0
src/ini.h 查看文件

@@ -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_

+ 2
- 0
src/input.h 查看文件

@@ -16,6 +16,8 @@ typedef enum {
input_Result_Save,
input_Result_Load,
input_Result_Cancel,
input_Result_View,
input_Result_Refresh,
} nes_Input_Result;

#define nes_controller_num_buttons (8U)


+ 9
- 2
src/menu.c 查看文件

@@ -187,7 +187,8 @@ static int run_menu(menu_state* state, const file_list* files,
int buttons = wait_for_input(input, &sys->input);
int special = (buttons >> 8);

if (input_Result_Quit == special) {
if ( input_Result_Quit == special ||
input_Result_Refresh == special) {
status = 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;
}

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);
}

if (input_Result_Quit == status) {
cart_filename = (char*)-1;
@@ -283,6 +288,7 @@ int run_game_menu(menu_state* state, nes_Renderer* rend,
"Restore",
"Reset",
"Select ROM",
"Toggle Fullscreen",
"Exit",
};
static int choices[] = {
@@ -291,6 +297,7 @@ int run_game_menu(menu_state* state, nes_Renderer* rend,
input_Result_Load,
input_Result_Reset,
input_Result_Menu,
input_Result_View,
input_Result_Quit,
};
static const file_list options = {


+ 50
- 25
src/nese.c 查看文件

@@ -10,6 +10,7 @@
#include "mapper.h"
#include "save.h"
#include "menu.h"
#include "state.h"


#define audio_freq (44100U)
@@ -106,16 +107,6 @@ static int loadsave_tick(loadsave_state* loadsave) {
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,
nes_Input_Reader* input, nes* sys,
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);

nes_cart_done(&sys->cart);
cleanup_cart_info(cur_cart);
cart_info_done(cur_cart);

sys->cart = new_cart;
*cur_cart = cart;
@@ -173,18 +164,35 @@ static int select_rom(menu_state* menu, nes_Renderer* rend,
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,
cart_info* cart) {
nese_State* state) {
int status = 0;
menu_state rom_menu = {0};

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,
sys, cart);
sys, &state->cart);

if (input_Result_Cancel == status) {
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 status = 0;

nese_State state = {0};
load_prefs_filename(&state, "nese.prefs");

nes_Renderer* rend = &sdl_renderer;
if (status == 0) {
status = nes_render_init(rend);
if (0 == status) {
nes_render_fullscreen(
rend,
(state.flags & (1 << State_Bit_Fullscreen))
);
}
}

nes_Input_Reader* input = &sdl_input;
@@ -222,18 +239,24 @@ int main(int argc, char* argv[]) {
status = nes_init(&sys, audio_freq);
}

cart_info cart = {0};
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);
if (NULL != cart.file) {
cart_info_done(&state.cart);
state.cart = cart;
nes_setup_cart(&sys);
} else {
cart_info_done(&cart);
}
}

// 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) {
@@ -261,9 +284,9 @@ int main(int argc, char* argv[]) {
// Load/Save Operations
int action = loadsave_tick(&loadsave);
if (input_Result_Load == action) {
load_state(&sys, cart.filename);
load_state(&sys, state.cart.filename);
} else if (input_Result_Save == action) {
save_state(&sys, cart.filename);
save_state(&sys, state.cart.filename);
}

// Sleep to catch up to master clock
@@ -291,7 +314,7 @@ int main(int argc, char* argv[]) {
if (input_Result_Menu == status) {
status = do_game_menu(
&game_menu, rend,
input, &sys, &cart
input, &sys, &state
);
if ( input_Result_Load == status ||
input_Result_Save == status) {
@@ -344,11 +367,13 @@ int main(int argc, char* argv[]) {
status == 0 ? "OK" : "Halted");

// 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);
cleanup_cart_info(&cart);

nes_audio_done(audio);
nes_input_done(input);


+ 6
- 0
src/render.h 查看文件

@@ -9,6 +9,7 @@ typedef struct nes_Renderer_t {
int (*init)(struct nes_Renderer_t*);
void (*done)(struct nes_Renderer_t*);
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_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);
}

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,
int dim) {
rend->draw_last_frame(rend, dim);


+ 6
- 0
src/sdl_input.c 查看文件

@@ -98,6 +98,12 @@ static int sdl_input_update(nes_Input_Reader* reader,
if (SDL_QUIT == event.type) {
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 ||
SDL_KEYUP == event.type) &&
0 == event.key.repeat


+ 31
- 22
src/sdl_render.c 查看文件

@@ -77,6 +77,9 @@ static int sdl_render_init(nes_Renderer* rend) {
data->win_w = (nes_ppu_scan_w * 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(
"NESe",
SDL_WINDOWPOS_UNDEFINED,
@@ -87,28 +90,6 @@ static int sdl_render_init(nes_Renderer* rend) {
fprintf(stderr, "SDL: Failed to create window\n");
SDL_Quit();
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();
}

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(
const nes_ppu* ppu, int index, int y, const uint8_t* pal,
uint8_t* dst, int start, int end, const uint8_t* back) {
@@ -644,6 +652,7 @@ nes_Renderer sdl_renderer = {
.init = sdl_render_init,
.done = sdl_render_done,
.render = sdl_render,
.fullscreen = sdl_render_fullscreen,
.draw_last_frame = sdl_redraw_frame,
.draw_text = sdl_draw_text,
.text_size = sdl_text_size,


+ 79
- 0
src/state.c 查看文件

@@ -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;
}

+ 37
- 0
src/state.h 查看文件

@@ -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_

Loading…
取消
儲存