Bläddra i källkod

Add in-game menu

master
Nathaniel Walizer 11 månader sedan
förälder
incheckning
dc05a32636
11 ändrade filer med 331 tillägg och 161 borttagningar
  1. +0
    -27
      src/cart.c
  2. +0
    -2
      src/cart.h
  3. +7
    -6
      src/input.h
  4. +12
    -1
      src/nes.c
  5. +4
    -0
      src/nes.h
  6. +152
    -47
      src/nese.c
  7. +4
    -3
      src/render.h
  8. +12
    -10
      src/save.c
  9. +3
    -3
      src/sdl_input.c
  10. +122
    -60
      src/sdl_menu.c
  11. +15
    -2
      src/sdl_render.c

+ 0
- 27
src/cart.c Visa fil

@@ -108,30 +108,3 @@ int nes_cart_init_file(nes_cart* cart, FILE* file) {

return status;
}


FILE* nes_load_cart(nes_cart* cart, const char* cart_filename) {
int status = 0;

FILE* cart_file = fopen(cart_filename, "rb");
if (NULL == cart_file) {
status = -1;
fprintf(stderr, "Could not open %s\n", cart_filename);
}

if (status == 0) {
status = nes_cart_init_file(cart, cart_file);
}

if (status == 0) {
// Failure might mean there's nothing to load
load_sram(cart, cart_filename);
}

if (status != 0 && NULL != cart_file) {
fclose(cart_file);
cart_file = NULL;
}

return cart_file;
}

+ 0
- 2
src/cart.h Visa fil

@@ -28,7 +28,5 @@ int nes_cart_init_file(nes_cart*, FILE* file);
int nes_cart_init_mem(nes_cart*, void*, int len);
void nes_cart_done(nes_cart*);

FILE* nes_load_cart(nes_cart* cart, const char* cart_filename);


#endif // NES_CART_H_

+ 7
- 6
src/input.h Visa fil

@@ -9,12 +9,13 @@

typedef enum {
input_Result_Error = -1,
input_Result_OK = 0,
input_Result_Quit = 1,
input_Result_Reset = 2,
input_Result_Save = 3,
input_Result_Load = 4,
input_Result_Cancel = 5,
input_Result_OK,
input_Result_Quit,
input_Result_Menu,
input_Result_Reset,
input_Result_Save,
input_Result_Load,
input_Result_Cancel,
} nes_Input_Result;

#define nes_controller_num_buttons (8U)


+ 12
- 1
src/nes.c Visa fil

@@ -66,7 +66,6 @@ static void nes_irq(void* sys, int active) {
int nes_init(nes* sys, int audio_freq) {
e6502_init(&sys->cpu, (e6502_Read*)nes_mem_read,
(e6502_Write*)nes_mem_write, sys);
nes_map_set_irq(sys->cart.mapper, nes_irq, sys);
nes_ppu_init(&sys->ppu, &sys->cart);
return nes_apu_init(
&sys->apu,
@@ -77,6 +76,18 @@ int nes_init(nes* sys, int audio_freq) {
);
}

void nes_done(nes* sys) {
nes_cart_done(&sys->cart);
nes_apu_done(&sys->apu);
}

int nes_setup_cart(nes* sys) {
nes_map_set_irq(sys->cart.mapper, nes_irq, sys);
sys->ppu.mapper = sys->cart.mapper;
nes_reset(sys);
return 0;
}

void nes_reset(nes* sys) {
e6502_reset(&sys->cpu);
nes_ppu_reset(&sys->ppu);


+ 4
- 0
src/nes.h Visa fil

@@ -58,6 +58,10 @@ void nes_mem_write(nes*, uint16_t addr, uint8_t);

int nes_init(nes*, int audio_freq);

void nes_done(nes*);

int nes_setup_cart(nes*);

void nes_reset(nes*);

nes_ppu_Result nes_step(nes*, int* run);


+ 152
- 47
src/nese.c Visa fil

@@ -22,7 +22,32 @@ extern nes_Input_Reader sdl_input;
extern nes_Audio_Stream sdl_audio;


static nes sys = {0};
static FILE* nes_load_cart(nes_cart* cart,
const char* cart_filename) {
int status = 0;

FILE* cart_file = fopen(cart_filename, "rb");
if (NULL == cart_file) {
status = -1;
fprintf(stderr, "Could not open %s\n", cart_filename);
}

if (status == 0) {
status = nes_cart_init_file(cart, cart_file);
}

if (status == 0) {
// Failure might mean there's nothing to load
load_sram(cart, cart_filename);
}

if (status != 0 && NULL != cart_file) {
fclose(cart_file);
cart_file = NULL;
}

return cart_file;
}


typedef struct {
@@ -32,7 +57,7 @@ typedef struct {
int mode;
} loadsave_state;

void loadsave_start(loadsave_state* loadsave, int mode) {
static void loadsave_start(loadsave_state* loadsave, int mode) {
if (0 != loadsave->mode) {
// We can't do two things at once.
overlay_clear_message(loadsave->overlay, loadsave->id);
@@ -54,18 +79,15 @@ void loadsave_start(loadsave_state* loadsave, int mode) {
}
}

int loadsave_tick(loadsave_state* loadsave) {
static int loadsave_tick(loadsave_state* loadsave) {
int action = 0;

if (loadsave->mode != 0 && --loadsave->timer <= 0) {
const char *op = NULL;

if (input_Result_Load == loadsave->mode) {
//load_state(&sys, cart_filename);
op = "restored";

} else if (input_Result_Save == loadsave->mode) {
//save_state(&sys, cart_filename);
op = "saved";
}

@@ -84,73 +106,138 @@ int loadsave_tick(loadsave_state* loadsave) {
return action;
}

typedef struct {
char* filename;
FILE* file;
} cart_info;

int main(int argc, char* argv[]) {
int status = 0;

nes_Renderer* rend = &sdl_renderer;
if (status == 0) {
status = nes_render_init(rend);
}

nes_Input_Reader* input = &sdl_input;
if (status == 0) {
status = nes_input_init(input);
}

nes_Audio_Stream* audio = &sdl_audio;
if (status == 0) {
status = nes_audio_init(audio, audio_freq);
}
static void cleanup_cart_info(cart_info* cart) {
if (cart->file) fclose(cart->file);
free(cart->filename);
}

char* cart_filename = NULL;
FILE* cart_file = NULL;
if (0 == status && argc > 1) {
cart_filename = strdup(argv[1]);
cart_file = nes_load_cart(&sys.cart, argv[1]);
}
static int select_rom(menu_state* menu, nes_Renderer* rend,
nes_Input_Reader* input, nes* sys,
cart_info* cur_cart) {
int status = 0;
cart_info cart = {0};
nes_cart new_cart = {0};

menu_state menu = {0};
while (0 == status && NULL == cart_file) {
while (0 == status && NULL == cart.file) {
// Display a load failure message?
if (NULL != cart_filename) {
if (NULL != cart.filename) {
char message[1024];
snprintf(message, sizeof(message) - 1,
"Could not load\n%s", cart_filename);
int button = modal_popup(message, rend, input, &sys);
"Could not load\n%s", cart.filename);
int button = modal_popup(message, rend, input, sys);
if (input_Result_Quit == (button >> 8)) {
// Program closed inside modal
status = -1;
status = input_Result_Quit;
}
}

if (0 == status) {
// If we didn't launch with a file, run the loader
cart_filename = run_main_menu(&menu, rend,
input, &sys);
cart.filename = run_main_menu(menu, rend,
input, sys);

if (NULL == cart_filename) {
if ( NULL == cart.filename ||
(char*)-1 == cart.filename ) {
// This means that we dumped out of the loader
cart.filename = NULL;
status = -1;

} else {
cart_file = nes_load_cart(&sys.cart,
cart_filename);
cart.file = nes_load_cart(&new_cart,
cart.filename);
}
}
}

if (0 == status && NULL != cart.file) {
save_sram(&sys->cart, cur_cart->filename);

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

sys->cart = new_cart;
*cur_cart = cart;

nes_setup_cart(sys);
}

return status;
}

static int do_game_menu(menu_state* state, nes_Renderer* rend,
nes_Input_Reader* input, nes* sys,
cart_info* cart) {
int status = 0;
menu_state rom_menu = {0};

while (1) {
status = run_game_menu(state, rend, input, sys);

if (input_Result_Menu == status) {
status = select_rom(&rom_menu, rend, input,
sys, cart);

if (input_Result_Cancel == status) {
continue;
}
}

break;
}

return status;
}


int main(int argc, char* argv[]) {
int status = 0;

nes_Renderer* rend = &sdl_renderer;
if (status == 0) {
status = nes_render_init(rend);
}

nes_Input_Reader* input = &sdl_input;
if (status == 0) {
status = nes_input_init(input);
}

nes_Audio_Stream* audio = &sdl_audio;
if (status == 0) {
status = nes_audio_init(audio, audio_freq);
}

nes sys = {0};
if (status == 0) {
status = nes_init(&sys, audio_freq);
}

cart_info cart = {0};
if (0 == status && argc > 1) {
cart.filename = strdup(argv[1]);
cart.file = nes_load_cart(&sys.cart, cart.filename);
if (NULL != cart.file) {
nes_setup_cart(&sys);
}
}

menu_state main_menu = {0};
if (0 == status && NULL == cart.file) {
status = select_rom(&main_menu, rend, input,
&sys, &cart);
}

if (status == 0) {
menu_state game_menu = {0};
loadsave_state loadsave = {
.overlay = &rend->overlay,
};

nes_reset(&sys);

nes_render(rend, &sys.ppu);

time_us t_target = time_now();
@@ -170,9 +257,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, cart.filename);
} else if (input_Result_Save == action) {
save_state(&sys, cart_filename);
save_state(&sys, cart.filename);
}

// Sleep to catch up to master clock
@@ -197,6 +284,20 @@ int main(int argc, char* argv[]) {
// Update button states every rendered frame
status = nes_input_update(input, &sys.input);

if (input_Result_Menu == status) {
status = do_game_menu(
&game_menu, rend,
input, &sys, &cart
);
if ( input_Result_Load == status ||
input_Result_Save == status) {
loadsave.mode = status;
loadsave.timer = 0;
status = 0;
}
// Allow other options to fall through
}

if (input_Result_Reset == status) {
overlay_add_message(
&rend->overlay,
@@ -239,11 +340,15 @@ 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, cart.filename);
}

if (NULL != cart_file) fclose(cart_file);
free(cart_filename);
nes_done(&sys);
cleanup_cart_info(&cart);

nes_audio_done(audio);
nes_input_done(input);
nes_render_done(rend);

return status;
}

+ 4
- 3
src/render.h Visa fil

@@ -10,7 +10,7 @@ typedef struct nes_Renderer_t {
void (*done)(struct nes_Renderer_t*);
int (*render)(struct nes_Renderer_t*, nes_ppu*);

void (*draw_last_frame)(struct nes_Renderer_t*);
void (*draw_last_frame)(struct nes_Renderer_t*, int dim);
void (*draw_text)(struct nes_Renderer_t*, const char*, int x, int y);
void (*text_size)(struct nes_Renderer_t*, const char*, int* w, int* h);
void (*draw_done)(struct nes_Renderer_t*);
@@ -31,8 +31,9 @@ static inline int nes_render(nes_Renderer* rend, nes_ppu* ppu) {
return rend->render(rend, ppu);
}

static inline void nes_draw_last_frame(nes_Renderer* rend) {
rend->draw_last_frame(rend);
static inline void nes_draw_last_frame(nes_Renderer* rend,
int dim) {
rend->draw_last_frame(rend, dim);
}

static inline void nes_text_size(nes_Renderer* rend,


+ 12
- 10
src/save.c Visa fil

@@ -37,16 +37,18 @@ int load_sram(nes_cart* cart, const char* cart_filename) {
int save_sram(const nes_cart* cart, const char* cart_filename) {
int status = -1;

int sram_size = cart->mapper->sram_size ?
cart->mapper->sram_size(cart->mapper) : 0;
const void* sram = cart->mapper->sram ?
cart->mapper->sram(cart->mapper) : NULL;

if (sram_size > 0 && NULL != sram) {
char sram_filename[FILENAME_MAX] = {0};
make_sram_filename(sram_filename, FILENAME_MAX - 1,
cart_filename);
status = write_file(sram_filename, sram, sram_size);
if (NULL != cart->mapper) {
int sram_size = cart->mapper->sram_size ?
cart->mapper->sram_size(cart->mapper) : 0;
const void* sram = cart->mapper->sram ?
cart->mapper->sram(cart->mapper) : NULL;

if (sram_size > 0 && NULL != sram) {
char sram_filename[FILENAME_MAX] = {0};
make_sram_filename(sram_filename, FILENAME_MAX - 1,
cart_filename);
status = write_file(sram_filename, sram, sram_size);
}
}

return status;


+ 3
- 3
src/sdl_input.c Visa fil

@@ -53,7 +53,7 @@ static void sdl_input_done(nes_Input_Reader* input) {
sdl_lose_gamepad(input->data);
}

static const int sdl_reset_key = SDLK_ESCAPE;
static const int sdl_menu_key = SDLK_ESCAPE;
static const int sdl_save_key = SDLK_F1;
static const int sdl_load_key = SDLK_F2;

@@ -111,9 +111,9 @@ static int sdl_input_update(nes_Input_Reader* reader,
input->controllers[0].buttons &= ~mask;
}

} else if ( sdl_reset_key == event.key.keysym.sym &&
} else if ( sdl_menu_key == event.key.keysym.sym &&
SDL_KEYDOWN == event.type) {
status = input_Result_Reset;
status = input_Result_Menu;

} else if (sdl_save_key == event.key.keysym.sym) {
if (SDL_KEYDOWN == event.type) {


+ 122
- 60
src/sdl_menu.c Visa fil

@@ -22,7 +22,7 @@ static int get_input(nes_Input_Reader* reader,
}

static int wait_for_input(nes_Input_Reader* reader,
nes_input* input) {
nes_input* input) {
int buttons = input->controllers[0].buttons;
int status = 0;
for ( ;
@@ -95,16 +95,21 @@ static void fix_filename(char* dst, int n, const char* src) {
}

static inline int n_visible(void) {
return (((nes_ppu_render_h - 30) - 1) / 11) - 1;
return ((nes_ppu_render_h - 20) / 11);
}

static void show_menu(const menu_state* menu,
nes_Renderer* rend, file_list* files) {
nes_draw_last_frame(rend);
static void show_menu(const menu_state* menu, int dim, int x,
nes_Renderer* rend,
const file_list* files) {
nes_draw_last_frame(rend, dim);

int bottom = menu->top + n_visible();
int bottom = menu->top + n_visible() - 1;

for ( int n = menu->top, y = 10;
int max = n_visible();
if (max > files->count) max = files->count;
int y = (nes_ppu_render_h - (max * 11)) / 2;

for ( int n = menu->top;
n < files->count && n <= bottom;
++n, y += 11 ) {
char filename[100];
@@ -115,13 +120,73 @@ static void show_menu(const menu_state* menu,
( (menu->top == n && 0 != menu->top) ||
(bottom == n && files->count - 1 > bottom) ) ?
"..." : filename,
20, y);
if (menu->cursor == n) nes_draw_text(rend, ">", 10, y);
x, y);
if (menu->cursor == n) {
nes_draw_text(rend, ">", x - 10, y);
}
}

nes_draw_done(rend);
}

static int run_menu(menu_state* state, const file_list* files,
int x, nes_Renderer* rend,
nes_Input_Reader* input, nes* sys) {
menu_state menu = {0};
if (NULL != state) {
menu = *state;
if (menu.cursor < 0) {
menu.cursor = 0;
} else if (menu.cursor >= files->count) {
menu.cursor = files->count - 1;
}
}

int status = 0;
while (0 == status) {
// Scrolling (do this first to ensure menu is valid)
const int visible = n_visible() - 1;
menu.top = menu.cursor - (visible / 2);
if (menu.top <= 0) {
// We use <= so we don't readjust the top from 0
menu.top = 0;
} else if (menu.top + visible >= files->count) {
menu.top = (files->count - 1) - visible;
}

show_menu(&menu, NULL != sys->cart.mapper,
x, rend, files);

int buttons = wait_for_input(input, &sys->input);
int special = (buttons >> 8);

if (input_Result_Quit == special) {
status = special;

} else if (buttons & (1 << Button_B)) {
status = input_Result_Cancel;

} else if (buttons & ( (1 << Button_A) |
(1 << Button_Start) )) {
// Select
break;

} else if (buttons & (1 << Button_Up)) {
if (menu.cursor > 0) --menu.cursor;

} else if (buttons & ( (1 << Button_Down) |
(1 << Button_Select) )) {
if (menu.cursor < (files->count - 1)) {
++menu.cursor;
}
}
}

if (NULL != state) *state = menu;

return status;
}


char* run_main_menu(menu_state* state, nes_Renderer* rend,
nes_Input_Reader* input, nes* sys) {
@@ -130,7 +195,7 @@ char* run_main_menu(menu_state* state, nes_Renderer* rend,
DIR* dir = opendir("rom");

if (NULL == dir) {
nes_draw_last_frame(rend);
nes_draw_last_frame(rend, NULL != sys->cart.mapper);
nes_draw_text(rend, "No ROMS found!", 10, 10);
nes_draw_text(rend, "Press any key to exit", 10, 21);
nes_draw_done(rend);
@@ -142,55 +207,19 @@ char* run_main_menu(menu_state* state, nes_Renderer* rend,
closedir(dir);

menu_state menu = {0};
if (NULL != state) {
menu = *state;
if (menu.cursor < 0) {
menu.cursor = 0;
} else if (menu.cursor >= files.count) {
menu.cursor = files.count - 1;
}
}

while (1) {
// Scrolling (do this first to ensure menu is valid)
const int visible = n_visible();
menu.top = menu.cursor - (visible / 2);
if (menu.top <= 0) {
// We use <= so we don't readjust the top from 0
menu.top = 0;
} else if (menu.top + visible >= files.count) {
menu.top = (files.count - 1) - visible;
}

show_menu(&menu, rend, &files);
if (NULL != state) menu = *state;

int buttons = wait_for_input(input, &sys->input);
int special = (buttons >> 8);
int status = run_menu(&menu, &files, 20,
rend, input, sys);

if ( input_Result_Quit == special ||
(buttons & (1 << Button_B))) {
// Cancel
menu.cursor = -1;
break;
if (input_Result_Quit == status) {
cart_filename = (char*)-1;

} else if (buttons & ( (1 << Button_A) |
(1 << Button_Start) )) {
// Select
break;
} else if (input_Result_Cancel == status) {
cart_filename = NULL;

} else if (buttons & (1 << Button_Up)) {
if (menu.cursor > 0) --menu.cursor;

} else if (buttons & ( (1 << Button_Down) |
(1 << Button_Select) )) {
if (menu.cursor < (files.count - 1)) {
++menu.cursor;
}
}
}

// Selection has been made (or cancelled)
if (menu.cursor >= 0 && menu.cursor < files.count) {
} else if ( menu.cursor >= 0 &&
menu.cursor < files.count) {
char filename[1024];
snprintf(filename, sizeof(filename) - 1,
"%s/%s", "rom", files.files[menu.cursor]);
@@ -199,15 +228,48 @@ char* run_main_menu(menu_state* state, nes_Renderer* rend,

free_file_list(&files);

if (NULL != state) {
*state = menu;
if (menu.cursor < 0) state->cursor = 0;
}
if (NULL != state) *state = menu;
}

return cart_filename;
}

int run_game_menu(menu_state* state, nes_Renderer* rend,
nes_Input_Reader* input, nes* sys) {
static char* items[] = {
"Resume",
"Save",
"Load",
"Reset",
"Select ROM",
"Exit",
};
static int choices[] = {
input_Result_OK,
input_Result_Save,
input_Result_Load,
input_Result_Reset,
input_Result_Menu,
input_Result_Quit,
};
static const file_list options = {
.files = items,
.count = (sizeof(items) / sizeof(*items)),
};

menu_state menu = {0};
if (NULL != state) menu = *state;

int status = run_menu(&menu, &options, 100,
rend, input, sys);

if (NULL != state) *state = menu;

if (0 == status) status = choices[menu.cursor];

return status;
}

int modal_popup(const char* message, nes_Renderer* rend,
nes_Input_Reader* input, nes* sys) {
int w = 0;
@@ -218,7 +280,7 @@ int modal_popup(const char* message, nes_Renderer* rend,
int x = (nes_ppu_render_w - w) / 2;
int y = (nes_ppu_render_h - h) / 2;

nes_draw_last_frame(rend);
nes_draw_last_frame(rend, NULL != sys->cart.mapper);
nes_draw_text(rend, message, x, y);
nes_draw_done(rend);



+ 15
- 2
src/sdl_render.c Visa fil

@@ -148,7 +148,7 @@ static int sdl_render_init(nes_Renderer* rend) {
} else {
SDL_LockTextureToSurface(data->texture, NULL,
&data->target);
SDL_FillRect(data->target, NULL, 0x556677FF);
SDL_FillRect(data->target, NULL, 0x006677FF);
}
}

@@ -560,11 +560,24 @@ static int sdl_render(nes_Renderer* rend, nes_ppu* ppu) {
}


static void sdl_redraw_frame(nes_Renderer* rend) {
static void sdl_redraw_frame(nes_Renderer* rend, int dim) {
sdl_render_data* data = (sdl_render_data*)rend->data;

if (dim) {
SDL_SetTextureAlphaMod(data->texture, 0x7F);
SDL_SetTextureBlendMode(data->texture,
SDL_BLENDMODE_BLEND);
SDL_RenderClear(data->renderer);
}

SDL_UnlockTexture(data->texture);
SDL_RenderCopy(data->renderer, data->texture, NULL, NULL);
SDL_LockTextureToSurface(data->texture, NULL, &data->target);

if (dim) {
SDL_SetTextureBlendMode(data->texture,
SDL_BLENDMODE_NONE);
}
}

static void sdl_draw_present(nes_Renderer* rend) {


Laddar…
Avbryt
Spara