From 073d9aa7d12e8e71f15fd489af9c384eca87b943 Mon Sep 17 00:00:00 2001 From: Andre Leiradella Date: Sat, 3 Sep 2016 17:50:04 +0100 Subject: [PATCH] added /info rest service to the embedded http server --- cheevos.c | 5 + cheevos.h | 2 + core.h | 9 + core_impl.c | 22 +- httpserver/httpserver.c | 383 ++++++++++++++++++++++++++++++--- managers/core_option_manager.c | 18 -- managers/core_option_manager.h | 19 ++ 7 files changed, 411 insertions(+), 47 deletions(-) diff --git a/cheevos.c b/cheevos.c index 41d1529d3c..ed3d5b13a1 100644 --- a/cheevos.c +++ b/cheevos.c @@ -2365,3 +2365,8 @@ void cheevos_set_support_cheevos(bool state) { cheevos_locals.core_supports = state; } + +bool cheevos_get_support_cheevos(void) +{ + return cheevos_locals.core_supports; +} diff --git a/cheevos.h b/cheevos.h index 0a2d0dc21d..71ae2ac961 100644 --- a/cheevos.h +++ b/cheevos.h @@ -57,6 +57,8 @@ bool cheevos_set_cheats(void); void cheevos_set_support_cheevos(bool state); +bool cheevos_get_support_cheevos(void); + void cheevos_parse_guest_addr(cheevos_var_t *var, unsigned value); uint8_t *cheevos_get_memory(const cheevos_var_t *var); diff --git a/core.h b/core.h index 1ea430aa8c..9d9d598f1c 100644 --- a/core.h +++ b/core.h @@ -57,6 +57,11 @@ typedef struct retro_ctx_api_info unsigned version; } retro_ctx_api_info_t; +typedef struct retro_ctx_region_info +{ + unsigned region; +} retro_ctx_region_info_t; + typedef struct retro_ctx_controller_info { unsigned port; @@ -160,6 +165,8 @@ bool core_api_version(retro_ctx_api_info_t *api); */ bool core_verify_api_version(void); +bool core_get_region(retro_ctx_region_info_t *info); + bool core_get_memory(retro_ctx_memory_info_t *info); /* Initialize system A/V information. */ @@ -184,6 +191,8 @@ void core_uninit_symbols(void); void core_set_input_state(retro_ctx_input_state_info_t *info); +bool core_is_game_loaded(void); + RETRO_END_DECLS #endif diff --git a/core_impl.c b/core_impl.c index 4ad1881f06..3cf84dd8d4 100644 --- a/core_impl.c +++ b/core_impl.c @@ -40,6 +40,7 @@ #endif static struct retro_core_t core; +static bool core_game_loaded; static unsigned core_poll_type; static bool core_input_polled; static bool core_has_set_input_descriptors = false; @@ -247,8 +248,11 @@ bool core_load_game(retro_ctx_load_content_info_t *load_info) return false; if (load_info->special) - return core.retro_load_game_special(load_info->special->id, load_info->info, load_info->content->size); - return core.retro_load_game(*load_info->content->elems[0].data ? load_info->info : NULL); + core_game_loaded = core.retro_load_game_special(load_info->special->id, load_info->info, load_info->content->size); + else + core_game_loaded = core.retro_load_game(*load_info->content->elems[0].data ? load_info->info : NULL); + + return core_game_loaded; } bool core_get_system_info(struct retro_system_info *system) @@ -343,6 +347,7 @@ bool core_unload_game(void) video_driver_deinit_hw_context(); audio_driver_stop(); core.retro_unload_game(); + core_game_loaded = false; return true; } @@ -398,6 +403,14 @@ bool core_verify_api_version(void) return true; } +bool core_get_region(retro_ctx_region_info_t *info) +{ + if (!info) + return false; + info->region = core.retro_get_region(); + return true; +} + bool core_has_set_input_descriptor(void) { return core_has_set_input_descriptors; @@ -412,3 +425,8 @@ void core_unset_input_descriptors(void) { core_has_set_input_descriptors = false; } + +bool core_is_game_loaded(void) +{ + return core_game_loaded; +} diff --git a/httpserver/httpserver.c b/httpserver/httpserver.c index a7a811bdca..26558763fe 100644 --- a/httpserver/httpserver.c +++ b/httpserver/httpserver.c @@ -1,12 +1,20 @@ #include #include "system.h" #include "runloop.h" +#include "core.h" +#include "gfx/video_driver.h" +#include "managers/core_option_manager.h" +#include "cheevos.h" +#include "string/stdstring.h" #include "compat/zlib.h" #include "civetweb.h" #include #include +#define BASIC_INFO "info" +#define MEMORY_MAP "memoryMap" + static struct mg_callbacks s_httpserver_callbacks; static struct mg_context* s_httpserver_ctx; @@ -56,6 +64,38 @@ static void httpserver_z85_encode_inplace(Bytef* data, size_t size) } } +static void json_string_encode(char* output, size_t size, const char* input) +{ + /* Don't use with UTF-8 strings. */ + char k; + + if (*input != 0 && size != 0) + { + do + { + switch (k = *input++) + { + case '"': /* fall through */ + case '\\': /* fall through */ + case '/': if (size >= 3) { *output++ = '\\'; *output++ = k; size -= 2; } break; + case '\b': if (size >= 3) { *output++ = '\\'; *output++ = 'b'; size -= 2; } break; + case '\f': if (size >= 3) { *output++ = '\\'; *output++ = 'f'; size -= 2; } break; + case '\n': if (size >= 3) { *output++ = '\\'; *output++ = 'n'; size -= 2; } break; + case '\r': if (size >= 3) { *output++ = '\\'; *output++ = 'r'; size -= 2; } break; + case '\t': if (size >= 3) { *output++ = '\\'; *output++ = 't'; size -= 2; } break; + default: if (size >= 2) { *output++ = k; } size--; break; + } + } + while (*input != 0); + } + + *output = 0; +} + +/*============================================================ +HTTP ERRORS +============================================================ */ + static int httpserver_error(struct mg_connection* conn, unsigned code, const char* fmt, ...) { const char* reason; @@ -84,11 +124,266 @@ static int httpserver_error(struct mg_connection* conn, unsigned code, const cha va_end(args); buffer[sizeof(buffer) - 1] = 0; - mg_printf(conn, "HTTP/1.1 %u %s\r\nContent-Type: text/plain\r\n\r\n", code, reason); - mg_printf(conn, "%u %s\r\n\r\n%s", code, reason, buffer); + mg_printf(conn, "HTTP/1.1 %u %s\r\nContent-Type: text/html\r\n\r\n", code, reason); + mg_printf(conn, "

%u %s

%s

", code, reason, buffer); return 1; } +/*============================================================ +INFO +============================================================ */ + +static int httpserver_handle_basic_info(struct mg_connection* conn, void* cbdata) +{ + static const char *libretro_btn_desc[] = { + "B (bottom)", "Y (left)", "Select", "Start", + "D-Pad Up", "D-Pad Down", "D-Pad Left", "D-Pad Right", + "A (right)", "X (up)", + "L", "R", "L2", "R2", "L3", "R3", + }; + + const struct mg_request_info* req = mg_get_request_info(conn); + const settings_t* settings = config_get_ptr(); + const rarch_system_info_t* system; + char core_path[PATH_MAX_LENGTH]; + retro_ctx_api_info_t api; + retro_ctx_region_info_t region; + const char* pixel_format; + retro_ctx_memory_info_t sram; + retro_ctx_memory_info_t rtc; + retro_ctx_memory_info_t sysram; + retro_ctx_memory_info_t vram; + const struct retro_subsystem_info* subsys; + const struct retro_subsystem_rom_info* rom; + const struct retro_subsystem_memory_info* mem; + const struct retro_controller_description* ctrl; + const struct retro_system_av_info* av_info; + const core_option_manager_t* core_opts; + const struct core_option* opts; + unsigned p, q, r; + const char* comma; + + if (!runloop_ctl(RUNLOOP_CTL_SYSTEM_INFO_GET, &system)) + { + return httpserver_error(conn, 500, "Could not get system information in %s", __FUNCTION__); + } + + if (string_is_empty(system->info.library_name)) + { + return httpserver_error(conn, 500, "Core not initialized in %s", __FUNCTION__); + } + + if (!core_is_game_loaded()) + { + return httpserver_error(conn, 500, "Game not loaded in %s", __FUNCTION__); + } + + json_string_encode(core_path, sizeof(core_path), config_get_active_core_path()); + + core_api_version(&api); + core_get_region(®ion); + + switch (video_driver_get_pixel_format()) + { + case RETRO_PIXEL_FORMAT_0RGB1555: pixel_format = "RETRO_PIXEL_FORMAT_0RGB1555"; break; + case RETRO_PIXEL_FORMAT_XRGB8888: pixel_format = "RETRO_PIXEL_FORMAT_XRGB8888"; break; + case RETRO_PIXEL_FORMAT_RGB565: pixel_format = "RETRO_PIXEL_FORMAT_RGB565"; break; + default: pixel_format = "?"; break; + } + + sram.id = RETRO_MEMORY_SAVE_RAM; + core_get_memory(&sram); + + rtc.id = RETRO_MEMORY_RTC; + core_get_memory(&rtc); + + sysram.id = RETRO_MEMORY_SYSTEM_RAM; + core_get_memory(&sysram); + + vram.id = RETRO_MEMORY_VIDEO_RAM; + core_get_memory(&vram); + + mg_printf(conn, + "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n" + "{" + "\"corePath\":\"%s\"," + "\"api\":%u," + "\"systemInfo\":" + "{" + "\"libraryName\":\"%s\"," + "\"libraryVersion\":\"%s\"," + "\"validExtensions\":\"%s\"," + "\"needsFullpath\":%s," + "\"blockExtract\":%s" + "}," + "\"region\":\"%s\"," + "\"pixelFormat\":\"%s\"," + "\"rotation\":%u," + "\"performaceLevel\":%u," +#ifdef HAVE_CHEEVOS + "\"frontendSupportsAchievements\":true," + "\"coreSupportsAchievements\":%s," +#else + "\"frontendSupportsAchievements\":false," + "\"coreSupportsAchievements\":null," +#endif + "\"saveRam\":{\"ptr\":\"%p\",\"size\":" STRING_REP_UINT64 "}," + "\"rtcRam\":{\"ptr\":\"%p\",\"size\":" STRING_REP_UINT64 "}," + "\"systemRam\":{\"ptr\":\"%p\",\"size\":" STRING_REP_UINT64 "}," + "\"videoRam\":{\"ptr\":\"%p\",\"size\":" STRING_REP_UINT64 "},", + core_path, + api.version, + system->info.library_name, + system->info.library_version, + system->info.valid_extensions, + system->info.need_fullpath ? "true" : "false", + system->info.block_extract ? "true" : "false", + region.region ? "RETRO_REGION_PAL" : "RETRO_REGION_NTSC", + pixel_format, + system->rotation, + system->performance_level, +#ifdef HAVE_CHEEVOS + cheevos_get_support_cheevos() ? "true" : "false", +#endif + sram.data, sram.size, + rtc.data, rtc.size, + sysram.data, sysram.size, + vram.data, vram.size + ); + + mg_printf(conn, "\"subSystems\":["); + subsys = system->subsystem.data; + + for (p = 0; p < system->subsystem.size; p++, subsys++) + { + mg_printf(conn, "%s{\"id\":%u,\"description\":\"%s\",\"identifier\":\"%s\",\"roms\":[", p == 0 ? "" : ",", subsys->id, subsys->desc, subsys->ident); + rom = subsys->roms; + + for (q = 0; q < subsys->num_roms; q++, rom++) + { + mg_printf(conn, + "%s{" + "\"description\":\"%s\"," + "\"extensions\":\"%s\"," + "\"needsFullpath\":%s," + "\"blockExtract\":%s," + "\"required\":%s," + "\"memory\":[", + q == 0 ? "" : ",", + rom->desc, + rom->valid_extensions, + rom->need_fullpath ? "true" : "false", + rom->block_extract ? "true" : "false", + rom->required ? "true" : "false" + ); + + mem = rom->memory; + comma = ""; + + for (r = 0; r < rom->num_memory; r++, mem++) + { + mg_printf(conn, "%s{\"extension\":\"%s\",\"type\":%u}", comma, mem->extension, mem->type); + comma = ","; + } + + mg_printf(conn, "]}"); + } + + mg_printf(conn, "]}"); + } + + av_info = video_viewport_get_system_av_info(); + + mg_printf(conn, + "],\"avInfo\":{" + "\"geometry\":{" + "\"baseWidth\":%u," + "\"baseHeight\":%u," + "\"maxWidth\":%u," + "\"maxHeight\":%u," + "\"aspectRatio\":%f" + "}," + "\"timing\":{" + "\"fps\":%f," + "\"sampleRate\":%f" + "}" + "},", + av_info->geometry.base_width, + av_info->geometry.base_height, + av_info->geometry.max_width, + av_info->geometry.max_height, + av_info->geometry.aspect_ratio, + av_info->timing.fps, + av_info->timing.sample_rate + ); + + mg_printf(conn, "\"ports\":["); + comma = ""; + + for (p = 0; p < system->ports.size; p++) + { + ctrl = system->ports.data[p].types; + + for (q = 0; q < system->ports.data[p].num_types; q++, ctrl++) + { + mg_printf(conn, "%s{\"index\":%u,\"id\":%u,\"desc\":\"%s\"}", comma, p, ctrl->id, ctrl->desc); + comma = ","; + } + } + + mg_printf(conn, "],\"inputDescriptors\":["); + comma = ""; + + if (core_has_set_input_descriptor()) + { + for (p = 0; p < settings->input.max_users; p++) + { + for (q = 0; q < RARCH_FIRST_CUSTOM_BIND; q++) + { + const char* description = system->input_desc_btn[p][q]; + + if (description) + { + mg_printf(conn, + "%s{\"player\":%u,\"button\":\"%s\",\"description\":\"%s\"}", + comma, + p + 1, + libretro_btn_desc[q], + description + ); + + comma = ","; + } + } + } + } + + mg_printf(conn, "],\"coreOptions\":["); + runloop_ctl(RUNLOOP_CTL_CORE_OPTIONS_LIST_GET, (void*)&core_opts); + opts = core_opts->opts; + + for (p = 0; p < core_opts->size; p++, opts++) + { + mg_printf(conn, "%s{\"key\":\"%s\",\"description\":\"%s\",\"values\":[", p == 0 ? "" : ",", opts->key, opts->desc); + comma = ""; + + for (q = 0; q < opts->vals->size; q++) + { + mg_printf(conn, "%s\"%s\"", comma, opts->vals->elems[q].data); + comma = ","; + } + + mg_printf(conn, "]}"); + } + + mg_printf(conn, "]}"); + return 1; +} + +/*============================================================ +MMAPS +============================================================ */ + static int httpserver_handle_get_mmaps(struct mg_connection* conn, void* cbdata) { const struct mg_request_info* req = mg_get_request_info(conn); @@ -114,12 +409,10 @@ static int httpserver_handle_get_mmaps(struct mg_connection* conn, void* cbdata) mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"); mg_printf(conn, "["); - for (id = 0; id < mmaps->num_descriptors; id++, mmap++, comma = ",") + for (id = 0; id < mmaps->num_descriptors; id++, mmap++) { - mg_printf(conn, "%s", comma); - mg_printf(conn, - "{" + "%s{" "\"id\":%u," "\"flags\":" STRING_REP_UINT64 "," "\"ptr\":\"%p\"," @@ -128,7 +421,9 @@ static int httpserver_handle_get_mmaps(struct mg_connection* conn, void* cbdata) "\"select\":" STRING_REP_UINT64 "," "\"disconnect\":" STRING_REP_UINT64 "," "\"len\":" STRING_REP_UINT64 "," - "\"addrspace\":", + "\"addrspace\":\"%s\"" + "}", + comma, id, mmap->flags, mmap->ptr, @@ -136,19 +431,11 @@ static int httpserver_handle_get_mmaps(struct mg_connection* conn, void* cbdata) mmap->start, mmap->select, mmap->disconnect, - mmap->len - ); + mmap->len, + mmap->addrspace ? mmap->addrspace : "" + ); - if (mmap->addrspace) - { - mg_printf(conn, "\"%s\"", mmap->addrspace); - } - else - { - mg_printf(conn, "null"); - } - - mg_printf(conn, "}"); + comma = ","; } mg_printf(conn, "]"); @@ -162,6 +449,8 @@ static int httpserver_handle_get_mmap(struct mg_connection* conn, void* cbdata) rarch_system_info_t* system; const struct retro_memory_map* mmaps; const struct retro_memory_descriptor* mmap; + size_t start, length; + const char* param; unsigned id; uLong buflen; Bytef* buffer; @@ -171,7 +460,7 @@ static int httpserver_handle_get_mmap(struct mg_connection* conn, void* cbdata) return httpserver_error(conn, 405, "Unimplemented method in %s: %s", __FUNCTION__, req->request_method); } - if (sscanf(req->request_uri, "/mmaps/%u", &id) != 1) + if (sscanf(req->request_uri, "/" MEMORY_MAP "/%u", &id) != 1) { return httpserver_error(conn, 500, "Malformed request in %s: %s", __FUNCTION__, req->request_uri); } @@ -189,7 +478,38 @@ static int httpserver_handle_get_mmap(struct mg_connection* conn, void* cbdata) } mmap = mmaps->descriptors + id; - buflen = compressBound(mmap->len); + + start = 0; + length = mmap->len; + + if (req->query_string != NULL) + { + param = strstr(req->query_string, "start="); + + if (param != NULL) + { + start = atoll(param + 6); + } + + param = strstr(req->query_string, "length="); + + if (param != NULL) + { + length = atoll(param + 7); + } + } + + if (start >= mmap->len) + { + start = mmap->len - 1; + } + + if (length > mmap->len - start) + { + length = mmap->len - start; + } + + buflen = compressBound(length); buffer = (Bytef*)malloc(((buflen + 3) / 4) * 5); if (buffer == NULL) @@ -197,7 +517,7 @@ static int httpserver_handle_get_mmap(struct mg_connection* conn, void* cbdata) return httpserver_error(conn, 500, "Out of memory in %s", __FUNCTION__); } - if (compress2(buffer, &buflen, (Bytef*)mmap->ptr, mmap->len, Z_BEST_COMPRESSION) != Z_OK) + if (compress2(buffer, &buflen, (Bytef*)mmap->ptr + start, length, Z_BEST_COMPRESSION) != Z_OK) { free((void*)buffer); return httpserver_error(conn, 500, "Error during compression in %s", __FUNCTION__); @@ -211,13 +531,15 @@ static int httpserver_handle_get_mmap(struct mg_connection* conn, void* cbdata) mg_printf(conn, "HTTP/1.1 200 OK\r\nContent-Type: application/json\r\n\r\n"); mg_printf(conn, "{" - "\"length\":" STRING_REP_UINT64 "," + "\"start\":" STRING_REP_ULONG "," + "\"length\":" STRING_REP_ULONG "," "\"compression\":\"deflate\"," "\"compressedLength\":" STRING_REP_ULONG "," "\"encoding\":\"Z85\"," "\"data\":\"%s\"" "}", - mmap->len, + start, + length, (size_t)buflen, (char*)buffer ); @@ -231,7 +553,7 @@ static int httpserver_handle_mmaps(struct mg_connection* conn, void* cbdata) const struct mg_request_info* req = mg_get_request_info(conn); unsigned id; - if (sscanf(req->request_uri, "/mmaps/%u", &id) == 1) + if (sscanf(req->request_uri, "/" MEMORY_MAP "/%u", &id) == 1) { return httpserver_handle_get_mmap(conn, cbdata); } @@ -241,6 +563,10 @@ static int httpserver_handle_mmaps(struct mg_connection* conn, void* cbdata) } } +/*============================================================ +HTTP SERVER +============================================================ */ + int httpserver_init(unsigned port) { char str[16]; @@ -261,8 +587,11 @@ int httpserver_init(unsigned port) return -1; } - mg_set_request_handler(s_httpserver_ctx, "/mmaps", httpserver_handle_mmaps, NULL); - mg_set_request_handler(s_httpserver_ctx, "/mmaps/", httpserver_handle_mmaps, NULL); + mg_set_request_handler(s_httpserver_ctx, "/" BASIC_INFO, httpserver_handle_basic_info, NULL); + + mg_set_request_handler(s_httpserver_ctx, "/" MEMORY_MAP, httpserver_handle_mmaps, NULL); + mg_set_request_handler(s_httpserver_ctx, "/" MEMORY_MAP "/", httpserver_handle_mmaps, NULL); + return 0; } diff --git a/managers/core_option_manager.c b/managers/core_option_manager.c index fbcc56bc30..df6575adde 100644 --- a/managers/core_option_manager.c +++ b/managers/core_option_manager.c @@ -28,24 +28,6 @@ #include "core_option_manager.h" -struct core_option -{ - char *desc; - char *key; - struct string_list *vals; - size_t index; -}; - -struct core_option_manager -{ - config_file_t *conf; - char conf_path[PATH_MAX_LENGTH]; - - struct core_option *opts; - size_t size; - bool updated; -}; - static bool core_option_manager_parse_variable( core_option_manager_t *opt, size_t idx, const struct retro_variable *var) diff --git a/managers/core_option_manager.h b/managers/core_option_manager.h index 4e78c585fd..2ee2db3354 100644 --- a/managers/core_option_manager.h +++ b/managers/core_option_manager.h @@ -21,9 +21,28 @@ #include #include +#include "lists/string_list.h" RETRO_BEGIN_DECLS +struct core_option +{ + char *desc; + char *key; + struct string_list *vals; + size_t index; +}; + +struct core_option_manager +{ + config_file_t *conf; + char conf_path[PATH_MAX_LENGTH]; + + struct core_option *opts; + size_t size; + bool updated; +}; + typedef struct core_option_manager core_option_manager_t; /**