(3DS) Add bottom screen menu (#12470)

* (3DS) Add bottom screen menu
 -> User can save/load state on botom screen with thumbnail.
 -> Call a save_state_to_file() when RAM state has data to write a disk.
 -> If the bottom screen needs updating, swap the bottom framebuffers.

Add: SAVE/LODE STATE TO RAM
 -> This is useful for devices with slow I/O
 -> 3DS bottom save state use CMD_EVENT_SAVE_STATE_TO_RAM
 -> 3DS bottom load state use CMD_EVENT_LOAD_STATE when RAM state has no data
 -> 3DS bottom load state use CMD_EVENT_LOAD_STATE_FROM_RAM when RAM sate has data

* Rewrite path_get_state to retroarch_get_current_savestate_path

* Fix unterminated state_path
This commit is contained in:
bulzipke 2021-09-04 01:14:03 +09:00 committed by GitHub
parent 51dcad6cb3
commit 8adc24ecbc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 1051 additions and 119 deletions

View file

@ -2,6 +2,7 @@ TARGET := retroarch_3ds
LIBRETRO =
DEBUG = 0
CONSOLE_LOG = 0
GRIFFIN_BUILD = 1
WHOLE_ARCHIVE_LINK = 0
BUILD_3DSX = 1
@ -154,6 +155,10 @@ else
CFLAGS += -O3
endif
ifeq ($(CONSOLE_LOG), 1)
CFLAGS += -DCONSOLE_LOG
endif
ifeq ($(LIBCTRU_NO_DEPRECATION), 1)
CFLAGS += -DLIBCTRU_NO_DEPRECATION
endif

View file

@ -2,6 +2,7 @@ TARGET := retroarch_3ds_salamander
LIBRETRO =
DEBUG = 0
CONSOLE_LOG = 0
BUILD_3DSX = 1
BUILD_3DS = 0
BUILD_CIA = 1
@ -95,6 +96,10 @@ else
CFLAGS += -O3
endif
ifeq ($(CONSOLE_LOG), 1)
CFLAGS += -DCONSOLE_LOG
endif
CFLAGS += -I. -Ideps/7zip -Ideps/stb -Ilibretro-common/include -Ilibretro-common/include/compat/zlib
#CFLAGS += -DRARCH_INTERNAL

View file

@ -79,13 +79,16 @@ enum event_command
CMD_EVENT_UNLOAD_CORE,
CMD_EVENT_CLOSE_CONTENT,
CMD_EVENT_LOAD_STATE,
CMD_EVENT_LOAD_STATE_FROM_RAM,
/* Swaps the current state with what's on the undo load buffer */
CMD_EVENT_UNDO_LOAD_STATE,
/* Rewrites a savestate on disk */
CMD_EVENT_UNDO_SAVE_STATE,
CMD_EVENT_SAVE_STATE,
CMD_EVENT_SAVE_STATE_TO_RAM,
CMD_EVENT_SAVE_STATE_DECREMENT,
CMD_EVENT_SAVE_STATE_INCREMENT,
CMD_EVENT_RAM_STATE_TO_FILE,
/* Takes screenshot. */
CMD_EVENT_TAKE_SCREENSHOT,
/* Quits RetroArch. */

View file

@ -45,6 +45,15 @@ bool content_load_ram_file(unsigned slot);
/* Save a RAM state from memory to disk. */
bool content_save_ram_file(unsigned slot, bool compress);
/* Load a state from memory. */
bool content_load_state_from_ram(void);
/* Save a state to memory. */
bool content_save_state_to_ram(void);
/* Save a ram state from memory to disk. */
bool content_ram_state_to_file(const char *path);
/* Load a state from disk to memory. */
bool content_load_state(const char* path, bool load_to_backup_buffer, bool autoload);

View file

@ -162,8 +162,10 @@ static void frontend_ctr_deinit(void* data)
verbosity_enable();
retro_main_log_file_init(NULL, false);
#ifdef CONSOLE_LOG
if (ctr_bottom_screen_enabled && (ctr_fork_mode == FRONTEND_FORK_NONE))
wait_for_input();
#endif
CFGU_GetModelNintendo2DS(&not_2DS);
@ -419,13 +421,15 @@ void gfxSetFramebufferInfo(gfxScreen_t screen, u8 id)
id,
(u32*)gfxBottomFramebuffers[id],
(u32*)gfxBottomFramebuffers[id],
240 * 2,
GSP_RGB565_OES);
240 * 3,
GSP_BGR8_OES);
}
}
#endif
#ifdef CONSOLE_LOG
PrintConsole* ctrConsole;
#endif
static void frontend_ctr_init(void* data)
{
@ -436,10 +440,10 @@ static void frontend_ctr_init(void* data)
verbosity_enable();
gfxInit(GSP_BGR8_OES, GSP_RGB565_OES, false);
gfxInit(GSP_BGR8_OES, GSP_BGR8_OES, false);
u32 topSize = 400 * 240 * 3;
u32 bottomSize = 320 * 240 * 2;
u32 bottomSize = 320 * 240 * 3;
#ifdef USE_CTRULIB_2
linearFree(gfxGetFramebuffer(GFX_TOP, GFX_LEFT, NULL, NULL));
@ -473,7 +477,9 @@ static void frontend_ctr_init(void* data)
gfxSetFramebufferInfo(GFX_BOTTOM, 0);
gfxSet3D(true);
#ifdef CONSOLE_LOG
ctrConsole = consoleInit(GFX_BOTTOM, NULL);
#endif
/* enable access to all service calls when possible. */
if (svchax_init)

View file

@ -21,8 +21,11 @@
#define COLOR_ABGR(r, g, b, a) (((unsigned)(a) << 24) | ((b) << 16) | ((g) << 8) | ((r) << 0))
#define CTR_TOP_FRAMEBUFFER_WIDTH 400
#define CTR_TOP_FRAMEBUFFER_HEIGHT 240
#define CTR_TOP_FRAMEBUFFER_WIDTH 400
#define CTR_TOP_FRAMEBUFFER_HEIGHT 240
#define CTR_BOTTOM_FRAMEBUFFER_WIDTH 320
#define CTR_BOTTOM_FRAMEBUFFER_HEIGHT 240
#define CTR_STATE_DATE_SIZE 11
#ifdef USE_CTRULIB_2
extern u8* gfxTopLeftFramebuffers[2];
@ -30,7 +33,9 @@ extern u8* gfxTopRightFramebuffers[2];
extern u8* gfxBottomFramebuffers[2];
#endif
#ifdef CONSOLE_LOG
extern PrintConsole* ctrConsole;
#endif
extern const u8 ctr_sprite_shbin[];
extern const u32 ctr_sprite_shbin_size;
@ -58,6 +63,13 @@ typedef enum
CTR_VIDEO_MODE_LAST
} ctr_video_mode_enum;
typedef enum
{
CTR_BOTTOM_MENU_NOT_AVAILABLE = 0,
CTR_BOTTOM_MENU_DEFAULT,
CTR_BOTTOM_MENU_SELECT,
} ctr_bottom_menu;
typedef struct ctr_video
{
struct
@ -67,6 +79,7 @@ typedef struct ctr_video
void* left;
void* right;
}top;
void* bottom;
}drawbuffers;
void* depthbuffer;
@ -116,11 +129,10 @@ typedef struct ctr_video
bool overlay_full_screen;
#endif
void* empty_framebuffer;
aptHookCookie lcd_aptHook;
ctr_video_mode_enum video_mode;
int current_buffer_top;
int current_buffer_bottom;
bool p3d_event_pending;
bool ppf_event_pending;
@ -133,6 +145,17 @@ typedef struct ctr_video
int size;
}vertex_cache;
bool init_bottom_menu;
bool refresh_bottom_menu;
bool render_font_bottom;
bool render_state_from_png_file;
bool state_data_on_ram;
bool state_data_exist;
char state_date[CTR_STATE_DATE_SIZE];
int state_slot;
ctr_bottom_menu bottom_menu;
ctr_bottom_menu prev_bottom_menu;
struct ctr_bottom_texture_data *bottom_textures;
} ctr_video_t;
typedef struct ctr_texture
@ -156,6 +179,13 @@ struct ctr_overlay_data
};
#endif
struct ctr_bottom_texture_data
{
uintptr_t texture;
ctr_vertex_t* frame_coords;
ctr_scale_vector_t scale_vector;
};
static INLINE void ctr_set_scale_vector(ctr_scale_vector_t* vec,
int viewport_width, int viewport_height,
int texture_width, int texture_height)

View file

@ -50,6 +50,16 @@
#include "../../tasks/tasks_internal.h"
#endif
enum
{
CTR_TEXTURE_BOTTOM_MENU,
CTR_TEXTURE_STATE_THUMBNAIL,
CTR_TEXTURE_LAST
};
/* TODO/FIXME - global referenced outside */
extern uint64_t lifecycle_state;
/* An annoyance...
* Have to keep track of bottom screen enable state
* externally, otherwise cannot detect current state
@ -244,6 +254,551 @@ static void ctr_update_viewport(
}
static const char *ctr_texture_path(unsigned id)
{
switch (id)
{
case CTR_TEXTURE_BOTTOM_MENU:
return "ctr/bottom_menu.png";
case CTR_TEXTURE_STATE_THUMBNAIL:
{
static char texture_path[PATH_MAX_LENGTH];
char state_path[PATH_MAX_LENGTH];
if (!retroarch_get_current_savestate_path(state_path,
sizeof(state_path)))
return NULL;
snprintf(texture_path, sizeof(texture_path),
"%s.png", state_path);
return path_basename(texture_path);
}
}
return NULL;
}
static void ctr_update_state_date(void *data)
{
ctr_video_t *ctr = (ctr_video_t*)data;
time_t now = time(NULL);
struct tm *t = localtime(&now);
sprintf(ctr->state_date, "%02d/%02d/%d",
t->tm_mon + 1, t->tm_mday, t->tm_year + 1900);
}
static bool ctr_update_state_date_from_file(void *data)
{
char state_path[PATH_MAX_LENGTH];
ctr_video_t *ctr = (ctr_video_t*)data;
if (!retroarch_get_current_savestate_path(state_path, sizeof(state_path)))
return false;
#ifdef USE_CTRULIB_2
time_t mtime;
bool file_exists = archive_getmtime(state_path + 5, &mtime) == 0;
#else
u64 mtime;
bool file_exists = sdmc_getmtime(state_path + 5, &mtime) == 0;
#endif
if (!file_exists)
{
ctr->state_data_exist = false;
snprintf(ctr->state_date, sizeof(ctr->state_date), "00/00/0000");
return false;
}
ctr->state_data_exist = true;
#ifdef USE_CTRULIB_2
struct tm *t = localtime(&mtime);
#else
time_t ft = mtime;
struct tm *t = localtime(&ft);
#endif
sprintf(ctr->state_date, "%02d/%02d/%d",
t->tm_mon + 1, t->tm_mday, t->tm_year + 1900);
return true;
}
static void ctr_state_thumbnail_geom(void *data)
{
ctr_video_t *ctr = (ctr_video_t *) data;
struct ctr_bottom_texture_data *o = NULL;
const int target_width = 120;
const int target_height = 90;
unsigned width, height;
if (ctr)
o = &ctr->bottom_textures[CTR_TEXTURE_STATE_THUMBNAIL];
if (!o)
return;
ctr_texture_t *texture = (ctr_texture_t *) o->texture;
if (!texture)
return;
float scale = (float) target_width / texture->active_width;
if (target_width > texture->active_width * scale) {
scale = (float) (target_width + 1) / texture->active_width;
}
o->frame_coords->u0 = 0;
o->frame_coords->v0 = 0;
o->frame_coords->u1 = texture->active_width;
o->frame_coords->v1 = texture->active_height;
int x_offset = 184;
int y_offset = 46 + (target_height - texture->active_height * scale) / 2;
o->frame_coords->x0 = x_offset;
o->frame_coords->y0 = y_offset;
o->frame_coords->x1 = o->frame_coords->x0 + texture->active_width * scale;
o->frame_coords->y1 = o->frame_coords->y0 + texture->active_height * scale;
ctr_set_scale_vector(&o->scale_vector,
CTR_BOTTOM_FRAMEBUFFER_WIDTH,
CTR_BOTTOM_FRAMEBUFFER_HEIGHT,
texture->width,
texture->height);
}
static bool ctr_load_bottom_texture(void *data)
{
unsigned i;
const char *dir_assets;
ctr_video_t *ctr = (ctr_video_t *)data;
settings_t *settings = config_get_ptr();
for (i = 0; i < CTR_TEXTURE_LAST; i++)
{
if (i == CTR_TEXTURE_STATE_THUMBNAIL)
dir_assets = dir_get_ptr(RARCH_DIR_SAVESTATE);
else
dir_assets = settings->paths.directory_assets;
if (gfx_display_reset_textures_list(
ctr_texture_path(i), dir_assets,
&ctr->bottom_textures[i].texture,
TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL))
{
struct ctr_bottom_texture_data *o = &ctr->bottom_textures[i];
o->frame_coords = linearAlloc(sizeof(ctr_vertex_t));
if (i == CTR_TEXTURE_STATE_THUMBNAIL)
{
ctr_state_thumbnail_geom(ctr);
ctr->render_state_from_png_file = true;
}
else
{
ctr_texture_t *texture = (ctr_texture_t *) o->texture;
o->frame_coords->u0 = 0;
o->frame_coords->v0 = 0;
o->frame_coords->u1 = texture->width;
o->frame_coords->v1 = texture->height;
o->frame_coords->x0 = 0;
o->frame_coords->y0 = 0;
o->frame_coords->x1 = o->frame_coords->x0 + texture->width;
o->frame_coords->y1 = o->frame_coords->y0 + texture->height;
ctr_set_scale_vector(&o->scale_vector,
CTR_BOTTOM_FRAMEBUFFER_WIDTH,
CTR_BOTTOM_FRAMEBUFFER_HEIGHT,
texture->width,
texture->height);
}
}
else if (i == CTR_TEXTURE_BOTTOM_MENU)
return false;
}
return true;
}
static void save_state_to_file(void *data)
{
ctr_video_t *ctr = (ctr_video_t*)data;
char state_path[PATH_MAX_LENGTH];
global_t *global = global_get_ptr();
const char *name_savestate = global->name.savestate;
if (ctr->state_slot > 0)
snprintf(state_path, sizeof(state_path), "%s%d", name_savestate,
ctr->state_slot);
else if (ctr->state_slot < 0)
fill_pathname_join_delim(state_path,
name_savestate, "auto", '.', sizeof(state_path));
else
strlcpy(state_path, name_savestate, sizeof(state_path));
command_event(CMD_EVENT_RAM_STATE_TO_FILE, state_path);
}
static void bottom_menu_control(void* data, bool lcd_bottom)
{
touchPosition state_tmp_touch;
uint32_t state_tmp = 0;
ctr_video_t *ctr = (ctr_video_t*)data;
settings_t *settings = config_get_ptr();
int config_slot = settings->ints.state_slot;
if (!ctr->init_bottom_menu)
{
if (ctr_load_bottom_texture(ctr))
{
ctr_update_state_date_from_file(ctr);
ctr->bottom_menu = CTR_BOTTOM_MENU_DEFAULT;
}
ctr->init_bottom_menu = true;
}
BIT64_CLEAR(lifecycle_state, RARCH_MENU_TOGGLE);
state_tmp = hidKeysDown();
hidTouchRead(&state_tmp_touch);
if (state_tmp & KEY_TOUCH)
{
#ifdef CONSOLE_LOG
BIT64_SET(lifecycle_state, RARCH_MENU_TOGGLE);
return;
#endif
if (!rarch_ctl(RARCH_CTL_CORE_IS_RUNNING, NULL))
return;
if (!lcd_bottom ||
ctr->bottom_menu == CTR_BOTTOM_MENU_NOT_AVAILABLE)
{
BIT64_SET(lifecycle_state, RARCH_MENU_TOGGLE);
return;
}
switch (ctr->bottom_menu)
{
case CTR_BOTTOM_MENU_DEFAULT:
BIT64_SET(lifecycle_state, RARCH_MENU_TOGGLE);
break;
case CTR_BOTTOM_MENU_SELECT:
if (state_tmp_touch.px > 8 &&
state_tmp_touch.px < 164 &&
state_tmp_touch.py > 9 &&
state_tmp_touch.py < 86)
{
BIT64_SET(lifecycle_state, RARCH_MENU_TOGGLE);
}
else if (state_tmp_touch.px > 8 &&
state_tmp_touch.px < 164 &&
state_tmp_touch.py > 99 &&
state_tmp_touch.py < 230)
{
char screenshot_full_path[PATH_MAX_LENGTH];
struct ctr_bottom_texture_data *o =
&ctr->bottom_textures[CTR_TEXTURE_STATE_THUMBNAIL];
ctr_texture_t *texture = (ctr_texture_t *) o->texture;
if (texture)
linearFree(texture->data);
else
{
o->texture = (uintptr_t) calloc(1, sizeof(ctr_texture_t));
o->frame_coords = linearAlloc(sizeof(ctr_vertex_t));
texture = (ctr_texture_t *) o->texture;
}
texture->width = ctr->texture_width;
texture->height = ctr->texture_width;
texture->active_width = ctr->frame_coords->u1;
texture->active_height = ctr->frame_coords->v1;
texture->data = linearAlloc(
ctr->texture_width * ctr->texture_height *
(ctr->rgb32? 4:2));
memcpy(texture->data, ctr->texture_swizzled,
ctr->texture_width * ctr->texture_height *
(ctr->rgb32? 4:2));
ctr_state_thumbnail_geom(ctr);
ctr->state_data_exist = true;
ctr->state_data_on_ram = true;
ctr->render_state_from_png_file = false;
ctr_update_state_date(ctr);
command_event(CMD_EVENT_SAVE_STATE_TO_RAM, NULL);
if (settings->bools.savestate_thumbnail_enable)
{
sprintf(screenshot_full_path, "%s/%s",
dir_get_ptr(RARCH_DIR_SAVESTATE),
ctr_texture_path(CTR_TEXTURE_STATE_THUMBNAIL));
take_screenshot(NULL, screenshot_full_path, true,
video_driver_cached_frame_has_valid_framebuffer(),
true, true);
}
BIT64_SET(lifecycle_state, RARCH_MENU_TOGGLE);
}
else if (state_tmp_touch.px > 176 &&
state_tmp_touch.px < 311 &&
state_tmp_touch.py > 9 &&
state_tmp_touch.py < 230 &&
ctr->state_data_exist)
{
if (ctr->state_data_on_ram)
command_event(CMD_EVENT_LOAD_STATE_FROM_RAM, NULL);
else
command_event(CMD_EVENT_LOAD_STATE, NULL);
BIT64_SET(lifecycle_state, RARCH_MENU_TOGGLE);
}
break;
}
ctr->refresh_bottom_menu = true;
}
if (ctr->bottom_menu == CTR_BOTTOM_MENU_NOT_AVAILABLE ||
!rarch_ctl(RARCH_CTL_CORE_IS_RUNNING, NULL))
return;
if (ctr->state_slot != config_slot)
{
if (ctr->state_data_on_ram)
{
save_state_to_file(ctr);
ctr->state_data_on_ram = false;
}
ctr->state_slot = config_slot;
struct ctr_bottom_texture_data *o = &ctr->bottom_textures[CTR_TEXTURE_STATE_THUMBNAIL];
ctr_texture_t *texture = (ctr_texture_t *) o->texture;
if (texture)
{
linearFree(texture->data);
linearFree(o->frame_coords);
o->texture = 0;
}
if (ctr_update_state_date_from_file(ctr))
{
if(gfx_display_reset_textures_list(
ctr_texture_path(CTR_TEXTURE_STATE_THUMBNAIL),
dir_get_ptr(RARCH_DIR_SAVESTATE),
&o->texture,
TEXTURE_FILTER_MIPMAP_LINEAR, NULL, NULL))
{
o->frame_coords = linearAlloc(sizeof(ctr_vertex_t));
ctr_state_thumbnail_geom(ctr);
ctr->render_state_from_png_file = true;
}
}
ctr->refresh_bottom_menu = true;
}
if (menu_driver_is_alive())
ctr->bottom_menu = CTR_BOTTOM_MENU_SELECT;
else
ctr->bottom_menu = CTR_BOTTOM_MENU_DEFAULT;
if (ctr->prev_bottom_menu != ctr->bottom_menu)
{
ctr->prev_bottom_menu = ctr->bottom_menu;
ctr->refresh_bottom_menu = true;
}
}
static void font_driver_render_msg_bottom(
void *data,
const char *msg,
const void *_params,
void *font_data)
{
ctr_video_t *ctr = (ctr_video_t*)data;
ctr->render_font_bottom = true;
font_driver_render_msg(ctr, msg, _params, font_data);
ctr->render_font_bottom = false;
}
static void ctr_render_bottom_screen(void *data)
{
ctr_video_t *ctr = (ctr_video_t*)data;
if (!ctr)
return;
if (!ctr->refresh_bottom_menu)
return;
struct font_params params = { 0, };
params.text_align = TEXT_ALIGN_CENTER;
params.color = COLOR_ABGR(255, 255, 255, 255);
switch (ctr->bottom_menu)
{
case CTR_BOTTOM_MENU_NOT_AVAILABLE:
{
params.scale = 1.6f;
params.x = 0.0f;
params.y = 0.5f;
font_driver_render_msg_bottom(ctr,
msg_hash_to_str(MSG_3DS_BOTTOM_MENU_ASSET_NOT_FOUND),
&params, NULL);
}
break;
case CTR_BOTTOM_MENU_DEFAULT:
{
params.scale = 1.6f;
params.x = 0.0f;
params.y = 0.5f;
font_driver_render_msg_bottom(ctr,
msg_hash_to_str(MSG_3DS_BOTTOM_MENU_DEFAULT),
&params, NULL);
}
break;
case CTR_BOTTOM_MENU_SELECT:
{
params.scale = 1.48f;
params.color = COLOR_ABGR(255, 255, 255, 255);
/* draw state thumbnail */
if (ctr->state_data_exist) {
struct ctr_bottom_texture_data *o = (struct ctr_bottom_texture_data*)
&ctr->bottom_textures[CTR_TEXTURE_STATE_THUMBNAIL];
ctr_texture_t *texture = (ctr_texture_t *) o->texture;
if (texture)
{
GPU_TEXCOLOR colorType = GPU_RGBA8;
if (!ctr->render_state_from_png_file && !ctr->rgb32)
colorType = GPU_RGB565;
ctrGuSetTexture(GPU_TEXUNIT0, VIRT_TO_PHYS(texture->data),
texture->width, texture->height,
GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) |
GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_EDGE) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_EDGE),
colorType);
GPUCMD_AddWrite(GPUREG_GSH_BOOLUNIFORM, 0);
ctrGuSetVertexShaderFloatUniform(0, (float*)&o->scale_vector, 1);
ctrGuSetAttributeBuffersAddress(VIRT_TO_PHYS(o->frame_coords));
GPU_SetViewport(NULL,
VIRT_TO_PHYS(ctr->drawbuffers.bottom),
0, 0, CTR_BOTTOM_FRAMEBUFFER_HEIGHT,
CTR_BOTTOM_FRAMEBUFFER_WIDTH);
GPU_DrawArray(GPU_GEOMETRY_PRIM, 0, 1);
}
else
{
params.x = 0.266f;
params.y = 0.64f;
font_driver_render_msg_bottom(ctr,
msg_hash_to_str(MSG_3DS_BOTTOM_MENU_NO_STATE_THUMBNAIL),
&params, NULL);
}
}
else
{
params.x = 0.266f;
params.y = 0.64f;
font_driver_render_msg_bottom(ctr,
msg_hash_to_str(MSG_3DS_BOTTOM_MENU_NO_STATE_DATA),
&params, NULL);
}
/* draw bottom menu */
struct ctr_bottom_texture_data *o = &ctr->bottom_textures[CTR_TEXTURE_BOTTOM_MENU];
ctr_texture_t *texture = (ctr_texture_t *) o->texture;
ctrGuSetTexture(GPU_TEXUNIT0, VIRT_TO_PHYS(texture->data),
texture->width, texture->height,
GPU_TEXTURE_MAG_FILTER(GPU_LINEAR) | GPU_TEXTURE_MIN_FILTER(GPU_LINEAR) |
GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_EDGE) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_EDGE),
GPU_RGBA8);
GPUCMD_AddWrite(GPUREG_GSH_BOOLUNIFORM, 0);
ctrGuSetVertexShaderFloatUniform(0, (float*)&o->scale_vector, 1);
ctrGuSetAttributeBuffersAddress(VIRT_TO_PHYS(o->frame_coords));
GPU_SetViewport(NULL,
VIRT_TO_PHYS(ctr->drawbuffers.bottom),
0, 0, CTR_BOTTOM_FRAMEBUFFER_HEIGHT,
CTR_BOTTOM_FRAMEBUFFER_WIDTH);
GPU_DrawArray(GPU_GEOMETRY_PRIM, 0, 1);
/* draw resume game */
params.x = -0.178f;
params.y = 0.78f;
font_driver_render_msg_bottom(ctr,
msg_hash_to_str(MSG_3DS_BOTTOM_MENU_RESUME),
&params, NULL);
/* draw create restore point */
params.x = -0.178f;
params.y = 0.33f;
font_driver_render_msg_bottom(ctr,
msg_hash_to_str(MSG_3DS_BOTTOM_MENU_SAVE_STATE),
&params, NULL);
/* draw load restore point */
params.x = 0.266f;
params.y = 0.24f;
font_driver_render_msg_bottom(ctr,
msg_hash_to_str(MSG_3DS_BOTTOM_MENU_LOAD_STATE),
&params, NULL);
/* draw date */
params.x = 0.266f;
params.y = 0.87f;
font_driver_render_msg_bottom(ctr, ctr->state_date,
&params, NULL);
}
break;
}
}
static void ctr_set_bottom_screen_enable(bool enabled)
{
Handle lcd_handle;
u8 not_2DS;
CFGU_GetModelNintendo2DS(&not_2DS);
if(not_2DS && srvGetServiceHandle(&lcd_handle, "gsp::Lcd") >= 0)
{
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = enabled? 0x00110040: 0x00120040;
cmdbuf[1] = 2;
svcSendSyncRequest(lcd_handle);
svcCloseHandle(lcd_handle);
}
ctr_bottom_screen_enabled = enabled;
}
static void ctr_lcd_aptHook(APT_HookType hook, void* param)
{
ctr_video_t *ctr = (ctr_video_t*)param;
@ -303,16 +858,12 @@ static void ctr_lcd_aptHook(APT_HookType hook, void* param)
if ((hook == APTHOOK_ONSUSPEND) || (hook == APTHOOK_ONRESTORE) || (hook == APTHOOK_ONWAKEUP))
{
Handle lcd_handle;
u8 not_2DS;
CFGU_GetModelNintendo2DS(&not_2DS);
if(not_2DS && srvGetServiceHandle(&lcd_handle, "gsp::Lcd") >= 0)
ctr_set_bottom_screen_enable(hook == APTHOOK_ONSUSPEND);
if (ctr->state_data_on_ram)
{
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = ((hook == APTHOOK_ONSUSPEND) || ctr_bottom_screen_enabled)? 0x00110040: 0x00120040;
cmdbuf[1] = 2;
svcSendSyncRequest(lcd_handle);
svcCloseHandle(lcd_handle);
save_state_to_file(ctr);
ctr->state_data_on_ram = false;
}
}
@ -342,34 +893,11 @@ static bool ctr_tasks_finder(retro_task_t *task,void *userdata)
task_finder_data_t ctr_tasks_finder_data = {ctr_tasks_finder, NULL};
#endif
static void ctr_set_bottom_screen_enable(void* data, bool enabled)
{
Handle lcd_handle;
u8 not_2DS;
ctr_video_t *ctr = (ctr_video_t*)data;
if (!ctr)
return;
gfxBottomFramebuffers[0] = enabled ? (u8*)ctrConsole->frameBuffer:
(u8*)ctr->empty_framebuffer;
CFGU_GetModelNintendo2DS(&not_2DS);
if(not_2DS && srvGetServiceHandle(&lcd_handle, "gsp::Lcd") >= 0)
{
u32 *cmdbuf = getThreadCommandBuffer();
cmdbuf[0] = enabled? 0x00110040: 0x00120040;
cmdbuf[1] = 2;
svcSendSyncRequest(lcd_handle);
svcCloseHandle(lcd_handle);
}
ctr_bottom_screen_enabled = enabled;
}
static void* ctr_init(const video_info_t* video,
input_driver_t** input, void** input_data)
{
size_t i;
float refresh_rate;
u8 device_model = 0xFF;
void* ctrinput = NULL;
@ -392,6 +920,7 @@ static void* ctr_init(const video_info_t* video,
ctr->drawbuffers.top.left = vramAlloc(CTR_TOP_FRAMEBUFFER_WIDTH * CTR_TOP_FRAMEBUFFER_HEIGHT * 2 * sizeof(uint32_t));
ctr->drawbuffers.top.right = (void*)((uint32_t*)ctr->drawbuffers.top.left + CTR_TOP_FRAMEBUFFER_WIDTH * CTR_TOP_FRAMEBUFFER_HEIGHT);
ctr->drawbuffers.bottom = vramAlloc(CTR_BOTTOM_FRAMEBUFFER_WIDTH * CTR_BOTTOM_FRAMEBUFFER_HEIGHT * 2 * sizeof(uint32_t));
ctr->display_list_size = 0x4000;
ctr->display_list = linearAlloc(ctr->display_list_size * sizeof(uint32_t));
@ -401,6 +930,22 @@ static void* ctr_init(const video_info_t* video,
ctr->vertex_cache.buffer = linearAlloc(ctr->vertex_cache.size * sizeof(ctr_vertex_t));
ctr->vertex_cache.current = ctr->vertex_cache.buffer;
ctr->bottom_textures = (struct ctr_bottom_texture_data *)calloc(CTR_TEXTURE_LAST,
sizeof(*ctr->bottom_textures));
ctr->init_bottom_menu = false;
ctr->state_data_exist = false;
ctr->state_data_on_ram = false;
ctr->render_font_bottom = false;
ctr->refresh_bottom_menu = true;
ctr->render_state_from_png_file = false;
ctr->bottom_menu = CTR_BOTTOM_MENU_NOT_AVAILABLE;
ctr->prev_bottom_menu = CTR_BOTTOM_MENU_NOT_AVAILABLE;
ctr->state_slot = settings->ints.state_slot;
snprintf(ctr->state_date, sizeof(ctr->state_date), "%s", "00/00/0000");
ctr->state_date[CTR_STATE_DATE_SIZE - 1] = '\0';
ctr->rgb32 = video->rgb32;
ctr->texture_width = video->input_scale * RARCH_SCALE_BASE;
ctr->texture_height = video->input_scale * RARCH_SCALE_BASE;
@ -512,6 +1057,7 @@ static void* ctr_init(const video_info_t* video,
ctr->smooth = video->smooth;
ctr->vsync = video->vsync;
ctr->current_buffer_top = 0;
ctr->current_buffer_bottom = 0;
/* Only O3DS and O3DSXL support running in 'dual-framebuffer'
* mode with the parallax barrier disabled
@ -520,9 +1066,6 @@ static void* ctr_init(const video_info_t* video,
CFGU_GetSystemModel(&device_model); /* (0 = O3DS, 1 = O3DSXL, 2 = N3DS, 3 = 2DS, 4 = N3DSXL, 5 = N2DSXL) */
ctr->supports_parallax_disable = (device_model == 0) || (device_model == 1);
ctr->empty_framebuffer = linearAlloc(320 * 240 * 2);
memset(ctr->empty_framebuffer, 0, 320 * 240 * 2);
refresh_rate = (32730.0 * 8192.0) / 4481134.0;
driver_ctl(RARCH_DRIVER_CTL_SET_REFRESH_RATE, &refresh_rate);
@ -539,7 +1082,7 @@ static void* ctr_init(const video_info_t* video,
/* Set bottom screen enable state, if required */
if (lcd_bottom != ctr_bottom_screen_enabled)
ctr_set_bottom_screen_enable(ctr, lcd_bottom);
ctr_set_bottom_screen_enable(lcd_bottom);
gspSetEventCallback(GSPGPU_EVENT_VBlank0,
(ThreadFunc)ctr_vsync_hook, ctr, false);
@ -557,12 +1100,10 @@ static bool ctr_frame(void* data, const void* frame,
unsigned pitch, const char* msg, video_frame_info_t *video_info)
{
static uint64_t currentTick,lastTick;
touchPosition state_tmp_touch;
extern GSPGPU_FramebufferInfo topFramebufferInfo;
extern GSPGPU_FramebufferInfo topFramebufferInfo, bottomFramebufferInfo;
extern u8* gfxSharedMemory;
extern u8 gfxThreadID;
uint32_t diff;
uint32_t state_tmp = 0;
ctr_video_t *ctr = (ctr_video_t*)data;
static float fps = 0.0;
static int total_frames = 0;
@ -584,6 +1125,7 @@ static bool ctr_frame(void* data, const void* frame,
#ifdef HAVE_GFX_WIDGETS
bool widgets_active = video_info->widgets_active;
#endif
bool lcd_bottom = false;
if (!width || !height || !settings)
{
@ -591,12 +1133,10 @@ static bool ctr_frame(void* data, const void* frame,
return true;
}
state_tmp = hidKeysDown();
hidTouchRead(&state_tmp_touch);
if((state_tmp & KEY_TOUCH) && (state_tmp_touch.py < 120))
{
ctr_set_bottom_screen_enable(ctr, !ctr_bottom_screen_enabled);
}
lcd_bottom = settings->bools.video_3ds_lcd_bottom;
if (lcd_bottom != ctr_bottom_screen_enabled)
ctr_set_bottom_screen_enable(lcd_bottom);
bottom_menu_control(data, lcd_bottom);
if (ctr->p3d_event_pending)
{
@ -742,11 +1282,23 @@ static bool ctr_frame(void* data, const void* frame,
custom_vp_height
);
ctrGuSetMemoryFill(true, (u32*)ctr->drawbuffers.top.left, 0x00000000,
(u32*)ctr->drawbuffers.top.left + 2 * CTR_TOP_FRAMEBUFFER_WIDTH * CTR_TOP_FRAMEBUFFER_HEIGHT,
0x201, NULL, 0x00000000,
0,
0x201);
if (ctr->refresh_bottom_menu)
{
ctrGuSetMemoryFill(true, (u32*)ctr->drawbuffers.top.left, 0x00000000,
(u32*)ctr->drawbuffers.top.left + 2 * CTR_TOP_FRAMEBUFFER_WIDTH * CTR_TOP_FRAMEBUFFER_HEIGHT,
0x201,
(u32*)ctr->drawbuffers.bottom, 0x00000000,
(u32*)ctr->drawbuffers.bottom + 2 * CTR_BOTTOM_FRAMEBUFFER_WIDTH * CTR_BOTTOM_FRAMEBUFFER_HEIGHT,
0x201);
}
else
{
ctrGuSetMemoryFill(true, (u32*)ctr->drawbuffers.top.left, 0x00000000,
(u32*)ctr->drawbuffers.top.left + 2 * CTR_TOP_FRAMEBUFFER_WIDTH * CTR_TOP_FRAMEBUFFER_HEIGHT,
0x201, NULL, 0x00000000,
0,
0x201);
}
GPUCMD_SetBufferOffset(0);
@ -923,6 +1475,12 @@ static bool ctr_frame(void* data, const void* frame,
gfx_widgets_frame(video_info);
#endif
#ifndef CONSOLE_LOG
if (ctr_bottom_screen_enabled &&
rarch_ctl(RARCH_CTL_CORE_IS_RUNNING, NULL))
ctr_render_bottom_screen(ctr);
#endif
if (msg)
font_driver_render_msg(ctr, msg, NULL, NULL);
@ -943,10 +1501,21 @@ static bool ctr_frame(void* data, const void* frame,
CTRGU_RGBA8,
gfxTopRightFramebuffers[ctr->current_buffer_top], 240, CTRGU_RGB8, CTRGU_MULTISAMPLE_NONE);
#ifndef CONSOLE_LOG
ctrGuDisplayTransfer(true,
ctr->drawbuffers.bottom,
240,
320,
CTRGU_RGBA8,
gfxBottomFramebuffers[ctr->current_buffer_bottom],
240,
CTRGU_RGB8,
CTRGU_MULTISAMPLE_NONE);
#endif
/* Swap buffers : */
#ifdef USE_CTRULIB_2
u32 *buf0, *buf1;
u32 *buf0, *buf1, *bottom;
u32 stride;
buf0 = (u32*)gfxTopLeftFramebuffers[ctr->current_buffer_top];
@ -970,6 +1539,16 @@ static bool ctr_frame(void* data, const void* frame,
gspPresentBuffer(GFX_TOP, ctr->current_buffer_top, buf0, buf1,
stride, (1<<8)|((1^bit5)<<6)|((bit5)<<5)|GSP_BGR8_OES);
#ifndef CONSOLE_LOG
if (ctr->refresh_bottom_menu)
{
bottom = (u32*)gfxBottomFramebuffers[ctr->current_buffer_bottom];
stride = 240 * 3;
gspPresentBuffer(GFX_BOTTOM, ctr->current_buffer_bottom, bottom, bottom,
stride, GSP_BGR8_OES);
}
#endif
#else
topFramebufferInfo.
active_framebuf = ctr->current_buffer_top;
@ -990,7 +1569,7 @@ static bool ctr_frame(void* data, const void* frame,
framebuf1_vaddr = (u32*)gfxTopRightFramebuffers[ctr->current_buffer_top];
else
topFramebufferInfo.
framebuf1_vaddr = topFramebufferInfo.framebuf0_vaddr;
framebuf1_vaddr = topFramebufferInfo.framebuf0_vaddr;
topFramebufferInfo.
framebuf_widthbytesize = 240 * 3;
@ -1008,11 +1587,43 @@ static bool ctr_frame(void* data, const void* frame,
framebufferInfoHeader[0x0] ^= 1;
framebufferInfo[framebufferInfoHeader[0x0]] = topFramebufferInfo;
framebufferInfoHeader[0x1] = 1;
#ifndef CONSOLE_LOG
if (ctr->refresh_bottom_menu)
{
bottomFramebufferInfo.
active_framebuf = ctr->current_buffer_bottom;
bottomFramebufferInfo.
framebuf0_vaddr = (u32*)gfxBottomFramebuffers[
ctr->current_buffer_bottom];
bottomFramebufferInfo.
framebuf1_vaddr = (u32*)(gfxBottomFramebuffers[
ctr->current_buffer_bottom] + 240 * 3);
bottomFramebufferInfo.
framebuf_widthbytesize = 240 * 3;
bottomFramebufferInfo.format = GSP_BGR8_OES;
bottomFramebufferInfo.
framebuf_dispselect = ctr->current_buffer_bottom;
bottomFramebufferInfo.unk = 0x00000000;
u8* framebufferInfoHeader2 = gfxSharedMemory+0x200+gfxThreadID*0x80+0x40;
GSPGPU_FramebufferInfo*
framebufferInfo2 =
(GSPGPU_FramebufferInfo*)&framebufferInfoHeader2[0x4];
framebufferInfoHeader2[0x0] ^= 1;
framebufferInfo2[framebufferInfoHeader2[0x0]] = bottomFramebufferInfo;
framebufferInfoHeader2[0x1] = 1;
}
#endif
#endif
ctr->current_buffer_top ^= 1;
ctr->current_buffer_bottom ^= 1;
ctr->p3d_event_pending = true;
ctr->ppf_event_pending = true;
ctr->refresh_bottom_menu = false;
return true;
}
@ -1047,16 +1658,33 @@ static bool ctr_suppress_screensaver(void* data, bool enable)
static void ctr_free(void* data)
{
unsigned i;
ctr_video_t* ctr = (ctr_video_t*)data;
if (!ctr)
return;
if (ctr->state_data_on_ram)
save_state_to_file(ctr);
aptUnhook(&ctr->lcd_aptHook);
gspSetEventCallback(GSPGPU_EVENT_VBlank0, NULL, NULL, true);
shaderProgramFree(&ctr->shader);
DVLB_Free(ctr->dvlb);
vramFree(ctr->drawbuffers.top.left);
vramFree(ctr->drawbuffers.bottom);
for (i = 0; i < CTR_TEXTURE_LAST; i++)
{
struct ctr_bottom_texture_data *o = &ctr->bottom_textures[i];
ctr_texture_t *texture = (ctr_texture_t *) o->texture;
if (texture)
{
linearFree(texture->data);
linearFree(o->frame_coords);
o->texture = 0;
}
}
free(ctr->bottom_textures);
linearFree(ctr->display_list);
linearFree(ctr->texture_linear);
linearFree(ctr->texture_swizzled);
@ -1064,7 +1692,6 @@ static void ctr_free(void* data)
linearFree(ctr->menu.texture_linear);
linearFree(ctr->menu.texture_swizzled);
linearFree(ctr->menu.frame_coords);
linearFree(ctr->empty_framebuffer);
linearFree(ctr->vertex_cache.buffer);
linearFree(ctr);
#if 0

View file

@ -42,7 +42,8 @@
typedef struct
{
ctr_texture_t texture;
ctr_scale_vector_t scale_vector;
ctr_scale_vector_t scale_vector_top;
ctr_scale_vector_t scale_vector_bottom;
const font_renderer_driver_t* font_driver;
void* font_data;
} ctr_font_t;
@ -95,7 +96,15 @@ static void* ctr_font_init_font(void* data, const char* font_path,
linearFree(tmp);
#endif
ctr_set_scale_vector(&font->scale_vector, 400, 240, font->texture.width, font->texture.height);
ctr_set_scale_vector(&font->scale_vector_top,
CTR_TOP_FRAMEBUFFER_WIDTH,
CTR_TOP_FRAMEBUFFER_HEIGHT,
font->texture.width, font->texture.height);
ctr_set_scale_vector(&font->scale_vector_bottom,
CTR_BOTTOM_FRAMEBUFFER_WIDTH,
CTR_BOTTOM_FRAMEBUFFER_HEIGHT,
font->texture.width, font->texture.height);
return font;
}
@ -174,11 +183,12 @@ static void ctr_font_render_line(
switch (text_align)
{
case TEXT_ALIGN_RIGHT:
x -= ctr_font_get_message_width(font, msg, msg_len, scale);
x += width - ctr_font_get_message_width(font, msg, msg_len, scale);
break;
case TEXT_ALIGN_CENTER:
x -= ctr_font_get_message_width(font, msg, msg_len, scale) / 2;
x += width / 2 -
ctr_font_get_message_width(font, msg, msg_len, scale) / 2;
break;
}
@ -231,7 +241,11 @@ static void ctr_font_render_line(
return;
GPUCMD_AddWrite(GPUREG_GSH_BOOLUNIFORM, 0);
ctrGuSetVertexShaderFloatUniform(0, (float*)&font->scale_vector, 1);
if (!ctr->render_font_bottom)
ctrGuSetVertexShaderFloatUniform(0, (float*)&font->scale_vector_top, 1);
else
ctrGuSetVertexShaderFloatUniform(0, (float*)&font->scale_vector_bottom, 1);
GSPGPU_FlushDataCache(ctr->vertex_cache.current,
(v - ctr->vertex_cache.current) * sizeof(ctr_vertex_t));
ctrGuSetAttributeBuffers(2,
@ -261,20 +275,31 @@ static void ctr_font_render_line(
GPU_TEXTURE_WRAP_S(GPU_CLAMP_TO_EDGE) | GPU_TEXTURE_WRAP_T(GPU_CLAMP_TO_EDGE),
GPU_L8);
GPU_SetViewport(NULL,
VIRT_TO_PHYS(ctr->drawbuffers.top.left),
0, 0, CTR_TOP_FRAMEBUFFER_HEIGHT,
ctr->video_mode == CTR_VIDEO_MODE_2D_800X240
? CTR_TOP_FRAMEBUFFER_WIDTH * 2 : CTR_TOP_FRAMEBUFFER_WIDTH);
GPU_DrawArray(GPU_GEOMETRY_PRIM, 0, v - ctr->vertex_cache.current);
if (ctr->video_mode == CTR_VIDEO_MODE_3D)
if (!ctr->render_font_bottom)
{
GPU_SetViewport(NULL,
VIRT_TO_PHYS(ctr->drawbuffers.top.right),
0, 0, CTR_TOP_FRAMEBUFFER_HEIGHT,
CTR_TOP_FRAMEBUFFER_WIDTH);
VIRT_TO_PHYS(ctr->drawbuffers.top.left),
0, 0, CTR_TOP_FRAMEBUFFER_HEIGHT,
ctr->video_mode == CTR_VIDEO_MODE_2D_800X240
? CTR_TOP_FRAMEBUFFER_WIDTH * 2 : CTR_TOP_FRAMEBUFFER_WIDTH);
GPU_DrawArray(GPU_GEOMETRY_PRIM, 0, v - ctr->vertex_cache.current);
if (ctr->video_mode == CTR_VIDEO_MODE_3D)
{
GPU_SetViewport(NULL,
VIRT_TO_PHYS(ctr->drawbuffers.top.right),
0, 0, CTR_TOP_FRAMEBUFFER_HEIGHT,
CTR_TOP_FRAMEBUFFER_WIDTH);
GPU_DrawArray(GPU_GEOMETRY_PRIM, 0, v - ctr->vertex_cache.current);
}
}
else
{
GPU_SetViewport(NULL,
VIRT_TO_PHYS(ctr->drawbuffers.bottom),
0, 0, CTR_BOTTOM_FRAMEBUFFER_HEIGHT,
CTR_BOTTOM_FRAMEBUFFER_WIDTH);
GPU_DrawArray(GPU_GEOMETRY_PRIM, 0, v - ctr->vertex_cache.current);
}
@ -363,8 +388,10 @@ static void ctr_font_render_msg(
alpha, r_dark, g_dark, b_dark, alpha_dark;
ctr_font_t * font = (ctr_font_t*)data;
ctr_video_t *ctr = (ctr_video_t*)userdata;
unsigned width = ctr->vp.full_width;
unsigned height = ctr->vp.full_height;
unsigned width = ctr->render_font_bottom ?
CTR_BOTTOM_FRAMEBUFFER_WIDTH : CTR_TOP_FRAMEBUFFER_WIDTH;
unsigned height = ctr->render_font_bottom ?
CTR_BOTTOM_FRAMEBUFFER_HEIGHT : CTR_TOP_FRAMEBUFFER_HEIGHT;
settings_t *settings = config_get_ptr();
float video_msg_pos_x = settings->floats.video_msg_pos_x;
float video_msg_pos_y = settings->floats.video_msg_pos_y;

View file

@ -32,9 +32,6 @@
static uint32_t pad_state;
static int16_t analog_state[DEFAULT_MAX_PADS][2][2];
/* TODO/FIXME - global referenced outside */
extern uint64_t lifecycle_state;
static const char *ctr_joypad_name(unsigned pad)
{
return "3DS Controller";
@ -193,11 +190,6 @@ static void ctr_joypad_poll(void)
analog_state[0][RETRO_DEVICE_INDEX_ANALOG_RIGHT] [RETRO_DEVICE_ID_ANALOG_X] = ctr_joypad_fix_range(state_tmp_right_analog.dx);
analog_state[0][RETRO_DEVICE_INDEX_ANALOG_RIGHT] [RETRO_DEVICE_ID_ANALOG_Y] = -ctr_joypad_fix_range(state_tmp_right_analog.dy);
BIT64_CLEAR(lifecycle_state, RARCH_MENU_TOGGLE);
if((state_tmp & KEY_TOUCH) && (state_tmp_touch.py > 120))
BIT64_SET(lifecycle_state, RARCH_MENU_TOGGLE);
/* panic button */
if((state_tmp & KEY_START) &&
(state_tmp & KEY_SELECT) &&

View file

@ -11255,6 +11255,10 @@ MSG_HASH(
MSG_FAILED_TO_SAVE_SRAM,
"Failed to save SRAM"
)
MSG_HASH(
MSG_FAILED_TO_LOAD_SRAM,
"Failed to load SRAM"
)
MSG_HASH(
MSG_FAILED_TO_SAVE_STATE_TO,
"Failed to save state to"
@ -12674,6 +12678,34 @@ MSG_HASH(
MENU_ENUM_LABEL_VALUE_CTR_VIDEO_MODE_2D_800X240,
"2D (High Resolution)"
)
MSG_HASH(
MSG_3DS_BOTTOM_MENU_DEFAULT,
"Tap the Touch Screen to go\nto the Retroarch menu"
)
MSG_HASH(
MSG_3DS_BOTTOM_MENU_ASSET_NOT_FOUND,
"bottom_menu.png not found\nin the assets/ctr folder"
)
MSG_HASH(
MSG_3DS_BOTTOM_MENU_NO_STATE_DATA,
"No\nData"
)
MSG_HASH(
MSG_3DS_BOTTOM_MENU_NO_STATE_THUMBNAIL,
"No\nScreenshot"
)
MSG_HASH(
MSG_3DS_BOTTOM_MENU_RESUME,
"Resume Game"
)
MSG_HASH(
MSG_3DS_BOTTOM_MENU_SAVE_STATE,
"Create\nRestore Point"
)
MSG_HASH(
MSG_3DS_BOTTOM_MENU_LOAD_STATE,
"Load\nRestore Point"
)
#endif
#ifdef HAVE_QT
MSG_HASH(

View file

@ -17382,8 +17382,10 @@ static bool setting_append_list(
general_write_handler,
general_read_handler,
SD_FLAG_CMD_APPLY_AUTO);
#ifdef CONSOLE_LOG
MENU_SETTINGS_LIST_CURRENT_ADD_CMD(list, list_info, CMD_EVENT_REINIT_FROM_TOGGLE);
#endif
#endif
#ifdef HAVE_NETWORKING
CONFIG_BOOL(

View file

@ -399,6 +399,7 @@ enum msg_hash_enums
MSG_LOADING_STATE,
MSG_FAILED_TO_SAVE_STATE_TO,
MSG_FAILED_TO_SAVE_SRAM,
MSG_FAILED_TO_LOAD_SRAM,
MSG_STATE_SIZE,
MSG_FAILED_TO_LOAD_CONTENT,
MSG_COULD_NOT_READ_CONTENT_FILE,
@ -3222,6 +3223,14 @@ enum msg_hash_enums
MSG_MANUAL_CONTENT_SCAN_M3U_CLEANUP,
MSG_MANUAL_CONTENT_SCAN_END,
MSG_3DS_BOTTOM_MENU_DEFAULT,
MSG_3DS_BOTTOM_MENU_ASSET_NOT_FOUND,
MSG_3DS_BOTTOM_MENU_NO_STATE_DATA,
MSG_3DS_BOTTOM_MENU_NO_STATE_THUMBNAIL,
MSG_3DS_BOTTOM_MENU_RESUME,
MSG_3DS_BOTTOM_MENU_SAVE_STATE,
MSG_3DS_BOTTOM_MENU_LOAD_STATE,
MSG_LAST,
/* Ensure sizeof(enum) == sizeof(int) */

View file

@ -7980,6 +7980,31 @@ static void path_clear_all(void)
path_clear(RARCH_PATH_BASENAME);
}
bool retroarch_get_current_savestate_path(char *path, size_t len)
{
struct rarch_state *p_rarch = &rarch_st;
const global_t *global = &p_rarch->g_extern;
settings_t *settings = p_rarch->configuration_settings;
int state_slot = settings ? settings->ints.state_slot : 0;
const char *name_savestate = NULL;
if (!path || !global)
return false;
name_savestate = global->name.savestate;
if (string_is_empty(name_savestate))
return false;
if (state_slot > 0)
snprintf(path, len, "%s%d", name_savestate, state_slot);
else if (state_slot < 0)
fill_pathname_join_delim(path, name_savestate, "auto", '.', len);
else
strlcpy(path, name_savestate, len);
return true;
}
enum rarch_content_type path_is_media_type(const char *path)
{
char ext_lower[128];
@ -11451,20 +11476,7 @@ static bool command_event_main_state(
state_path[0] = msg[0] = '\0';
if (global)
{
int state_slot = settings->ints.state_slot;
const char *name_savestate = global->name.savestate;
if (state_slot > 0)
snprintf(state_path, sizeof(state_path), "%s%d",
name_savestate, state_slot);
else if (state_slot < 0)
fill_pathname_join_delim(state_path,
name_savestate, "auto", '.', sizeof(state_path));
else
strlcpy(state_path, name_savestate, sizeof(state_path));
}
retroarch_get_current_savestate_path(state_path, sizeof(state_path));
core_serialize_size(&info);
@ -11473,6 +11485,7 @@ static bool command_event_main_state(
switch (cmd)
{
case CMD_EVENT_SAVE_STATE:
case CMD_EVENT_SAVE_STATE_TO_RAM:
{
bool savestate_auto_index =
settings->bools.savestate_auto_index;
@ -11481,7 +11494,10 @@ static bool command_event_main_state(
bool frame_time_counter_reset_after_save_state =
settings->bools.frame_time_counter_reset_after_save_state;
content_save_state(state_path, true, false);
if (cmd == CMD_EVENT_SAVE_STATE)
content_save_state(state_path, true, false);
else
content_save_state_to_ram();
/* Clean up excess savestates if necessary */
if (savestate_auto_index && (savestate_max_keep > 0))
@ -11499,24 +11515,33 @@ static bool command_event_main_state(
}
break;
case CMD_EVENT_LOAD_STATE:
if (content_load_state(state_path, false, false))
case CMD_EVENT_LOAD_STATE_FROM_RAM:
{
bool res = false;
if (cmd == CMD_EVENT_LOAD_STATE)
res = content_load_state(state_path, false, false);
else
res = content_load_state_from_ram();
if (res)
{
#ifdef HAVE_CHEEVOS
if (rcheevos_hardcore_active())
{
rcheevos_pause_hardcore();
runloop_msg_queue_push(msg_hash_to_str(MSG_CHEEVOS_HARDCORE_MODE_DISABLED), 0, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
if (rcheevos_hardcore_active())
{
rcheevos_pause_hardcore();
runloop_msg_queue_push(msg_hash_to_str(MSG_CHEEVOS_HARDCORE_MODE_DISABLED), 0, 180, true, NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
}
#endif
ret = true;
ret = true;
#ifdef HAVE_NETWORKING
netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, NULL);
netplay_driver_ctl(RARCH_NETPLAY_CTL_LOAD_SAVESTATE, NULL);
#endif
{
bool frame_time_counter_reset_after_load_state =
settings->bools.frame_time_counter_reset_after_load_state;
if (frame_time_counter_reset_after_load_state)
p_rarch->video_driver_frame_time_count = 0;
{
bool frame_time_counter_reset_after_load_state =
settings->bools.frame_time_counter_reset_after_load_state;
if (frame_time_counter_reset_after_load_state)
p_rarch->video_driver_frame_time_count = 0;
}
}
}
push_msg = false;
@ -12238,11 +12263,13 @@ bool command_event(enum event_command cmd, void *data)
return false;
break;
case CMD_EVENT_UNDO_LOAD_STATE:
case CMD_EVENT_UNDO_SAVE_STATE:
case CMD_EVENT_LOAD_STATE_FROM_RAM:
if (!command_event_main_state(p_rarch, cmd))
return false;
break;
case CMD_EVENT_UNDO_SAVE_STATE:
if (!command_event_main_state(p_rarch, cmd))
case CMD_EVENT_RAM_STATE_TO_FILE:
if (!content_ram_state_to_file((char *) data))
return false;
break;
case CMD_EVENT_RESIZE_WINDOWED_SCALE:
@ -12274,6 +12301,7 @@ bool command_event(enum event_command cmd, void *data)
#endif
return false;
case CMD_EVENT_SAVE_STATE:
case CMD_EVENT_SAVE_STATE_TO_RAM:
{
bool savestate_auto_index = settings->bools.savestate_auto_index;
int state_slot = settings->ints.state_slot;

View file

@ -2114,6 +2114,8 @@ typedef enum apple_view_type
APPLE_VIEW_TYPE_METAL
} apple_view_type_t;
bool retroarch_get_current_savestate_path(char *path, size_t len);
RETRO_END_DECLS
#endif

View file

@ -151,6 +151,10 @@ static struct save_state_buf undo_save_buf;
* Can be restored with undo_load_state(). */
static struct save_state_buf undo_load_buf;
/* Buffer that stores state instead of file.
* This is useful for devices with slow I/O. */
static struct save_state_buf ram_buf;
#ifdef HAVE_THREADS
/* TODO/FIXME - global state - perhaps move outside this file */
static struct autosave_st autosave_state;
@ -1669,6 +1673,15 @@ bool content_reset_savestate_backups(void)
undo_load_buf.path[0] = '\0';
undo_load_buf.size = 0;
if (ram_buf.data)
{
free(ram_buf.data);
ram_buf.data = NULL;
}
ram_buf.path[0] = '\0';
ram_buf.size = 0;
return true;
}
@ -1808,6 +1821,148 @@ static bool dump_to_file_desperate(const void *data,
return true;
}
/**
* content_load_state_from_ram:
* Load a state from ram.
*
* Returns: true if successful, false otherwise.
**/
bool content_load_state_from_ram(void)
{
size_t temp_data_size;
bool ret = false;
void* temp_data = NULL;
RARCH_LOG("[State]: %s, %u %s.\n",
msg_hash_to_str(MSG_LOADING_STATE),
(unsigned)ram_buf.size,
msg_hash_to_str(MSG_BYTES));
/* We need to make a temporary copy of the buffer, to allow the swap below */
temp_data = malloc(ram_buf.size);
temp_data_size = ram_buf.size;
memcpy(temp_data, ram_buf.data, ram_buf.size);
/* Swap the current state with the backup state. This way, we can undo
what we're undoing */
content_save_state("RAM", false, false);
ret = content_deserialize_state(temp_data, temp_data_size);
/* Clean up the temporary copy */
free(temp_data);
temp_data = NULL;
if (!ret)
{
RARCH_ERR("[State]: %s.\n",
msg_hash_to_str(MSG_FAILED_TO_LOAD_SRAM));
}
return ret;
}
/**
* content_save_state_from_ram:
* Save a state to ram.
*
* Returns: true if successful, false otherwise.
**/
bool content_save_state_to_ram(void)
{
retro_ctx_size_info_t info;
void *data = NULL;
size_t serial_size;
core_serialize_size(&info);
if (info.size == 0)
return false;
serial_size = info.size;
if (!save_state_in_background)
{
data = content_get_serialized_data(&serial_size);
if (!data)
{
RARCH_ERR("[State]: %s.\n",
msg_hash_to_str(MSG_FAILED_TO_SAVE_SRAM));
return false;
}
RARCH_LOG("[State]: %s, %u %s.\n",
msg_hash_to_str(MSG_SAVING_STATE),
(unsigned)serial_size,
msg_hash_to_str(MSG_BYTES));
}
if (!data)
data = content_get_serialized_data(&serial_size);
if (!data)
{
RARCH_ERR("[State]: %s.\n",
msg_hash_to_str(MSG_FAILED_TO_SAVE_SRAM));
return false;
}
/* If we were holding onto an old state already, clean it up first */
if (ram_buf.data)
{
free(ram_buf.data);
ram_buf.data = NULL;
}
ram_buf.data = malloc(serial_size);
if (!ram_buf.data)
{
free(data);
return false;
}
memcpy(ram_buf.data, data, serial_size);
free(data);
ram_buf.size = serial_size;
return true;
}
/**
* content_ram_state_to_file:
* @path : path of ram state that shall be written to.
* Save a ram state from memory to disk.
*
* Returns: true if successful, false otherwise.
**/
bool content_ram_state_to_file(const char *path)
{
settings_t *settings = config_get_ptr();
#if defined(HAVE_ZLIB)
bool compress_files = settings->bools.save_file_compression;
#else
bool compress_files = false;
#endif
bool write_success;
if (!path)
return false;
if (!ram_buf.data)
return false;
#if defined(HAVE_ZLIB)
if (compress_files)
write_success = rzipstream_write_file(
path, ram_buf.data, ram_buf.size);
else
#endif
write_success = filestream_write_file(
path, ram_buf.data, ram_buf.size);
return write_success;
}
/**
* content_save_ram_file:
* @path : path of RAM state that shall be written to.