aboutsummaryrefslogtreecommitdiff
path: root/core.c
diff options
context:
space:
mode:
authorneonloop2021-08-04 15:09:12 +0000
committerneonloop2021-08-04 15:09:12 +0000
commit99632f66e74fc57c463072be312d634aeb67bc61 (patch)
treee4ccaf52b93d04c69865d82556e2ce4cd3a6c599 /core.c
downloadpicoarch-99632f66e74fc57c463072be312d634aeb67bc61.tar.gz
picoarch-99632f66e74fc57c463072be312d634aeb67bc61.tar.bz2
picoarch-99632f66e74fc57c463072be312d634aeb67bc61.zip
Initial commit
Diffstat (limited to 'core.c')
-rw-r--r--core.c593
1 files changed, 593 insertions, 0 deletions
diff --git a/core.c b/core.c
new file mode 100644
index 0000000..f6b112d
--- /dev/null
+++ b/core.c
@@ -0,0 +1,593 @@
+#include <dlfcn.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include "core.h"
+#include "libpicofe/input.h"
+#include "main.h"
+#include "options.h"
+#include "plat.h"
+#include "unzip.h"
+
+#define PATH_SEPARATOR_CHAR '/'
+
+struct core_cbs current_core;
+
+double sample_rate;
+double frame_rate;
+double aspect_ratio;
+int audio_buffer_size_override;
+int state_slot;
+
+static char config_dir[MAX_PATH];
+static char save_dir[MAX_PATH];
+static char system_dir[MAX_PATH];
+static char temp_rom[MAX_PATH];
+static struct retro_game_info game_info;
+
+static uint32_t buttons = 0;
+
+static void gamepak_related_name(char *buf, size_t len, const char *new_extension)
+{
+ char filename[MAX_PATH];
+ char *dot;
+
+ strncpy(filename, basename(content_path), sizeof(filename));
+ filename[sizeof(filename) - 1] = 0;
+
+ dot = strrchr(filename, '.');
+ if (dot)
+ *dot = 0;
+
+ snprintf(buf, len, "%s%s%s", save_dir, filename, new_extension);
+}
+
+void config_file_name(char *buf, size_t len, int is_game)
+{
+ if (is_game && content_path) {
+ gamepak_related_name(buf, len, ".cfg");
+ } else {
+ snprintf(buf, len, "%s%s", config_dir, "picoarch.cfg");
+ }
+}
+
+void sram_write(void) {
+ char filename[MAX_PATH];
+ FILE *sram_file = NULL;
+ void *sram;
+
+ size_t sram_size = current_core.retro_get_memory_size(RETRO_MEMORY_SAVE_RAM);
+ if (!sram_size) {
+ return;
+ }
+
+ gamepak_related_name(filename, MAX_PATH, ".sav");
+
+ sram_file = fopen(filename, "w");
+ if (!sram_file) {
+ PA_ERROR("Error opening SRAM file: %s\n", strerror(errno));
+ return;
+ }
+
+ sram = current_core.retro_get_memory_data(RETRO_MEMORY_SAVE_RAM);
+
+ if (!sram || sram_size != fwrite(sram, 1, sram_size, sram_file)) {
+ PA_ERROR("Error writing SRAM data to file\n");
+ }
+
+ fclose(sram_file);
+
+ sync();
+}
+
+void sram_read(void) {
+ char filename[MAX_PATH];
+ FILE *sram_file = NULL;
+ void *sram;
+
+ size_t sram_size = current_core.retro_get_memory_size(RETRO_MEMORY_SAVE_RAM);
+ if (!sram_size) {
+ return;
+ }
+
+ gamepak_related_name(filename, MAX_PATH, ".sav");
+
+ sram_file = fopen(filename, "r");
+ if (!sram_file) {
+ return;
+ }
+
+ sram = current_core.retro_get_memory_data(RETRO_MEMORY_SAVE_RAM);
+
+ if (!sram || !fread(sram, 1, sram_size, sram_file)) {
+ PA_ERROR("Error reading SRAM data\n");
+ }
+
+ fclose(sram_file);
+}
+
+void state_file_name(char *name, size_t size, int slot) {
+ char extension[5] = {0};
+
+ snprintf(extension, 5, ".st%d", slot);
+ gamepak_related_name(name, MAX_PATH, extension);
+}
+
+int state_read(void) {
+ char filename[MAX_PATH];
+ FILE *state_file = NULL;
+ void *state = NULL;
+ int ret = -1;
+
+ size_t state_size = current_core.retro_serialize_size();
+ if (!state_size) {
+ return 0;
+ }
+
+ state = calloc(1, state_size);
+ if (!state) {
+ PA_ERROR("Couldn't allocate memory for state\n");
+ goto error;
+ }
+
+ state_file_name(filename, MAX_PATH, state_slot);
+
+ state_file = fopen(filename, "r");
+ if (!state_file) {
+ PA_ERROR("Error opening state file: %s\n", strerror(errno));
+ goto error;
+ }
+
+ if (state_size != fread(state, 1, state_size, state_file)) {
+ PA_ERROR("Error reading state data from file\n");
+ goto error;
+ }
+
+ if (!current_core.retro_unserialize(state, state_size)) {
+ PA_ERROR("Error restoring save state\n", strerror(errno));
+ goto error;
+ }
+
+ ret = 0;
+error:
+ if (state)
+ free(state);
+ if (state_file)
+ fclose(state_file);
+ return ret;
+}
+
+int state_write(void) {
+ char filename[MAX_PATH];
+ FILE *state_file = NULL;
+ void *state = NULL;
+ int ret = -1;
+
+ size_t state_size = current_core.retro_serialize_size();
+ if (!state_size) {
+ return false;
+ }
+
+ state = calloc(1, state_size);
+ if (!state) {
+ PA_ERROR("Couldn't allocate memory for state\n");
+ goto error;
+ }
+
+ state_file_name(filename, MAX_PATH, state_slot);
+
+ state_file = fopen(filename, "w");
+ if (!state_file) {
+ PA_ERROR("Error opening state file: %s\n", strerror(errno));
+ goto error;
+ }
+
+ if (!current_core.retro_serialize(state, state_size)) {
+ PA_ERROR("Error creating save state\n", strerror(errno));
+ goto error;
+ }
+
+ if (state_size != fwrite(state, 1, state_size, state_file)) {
+ PA_ERROR("Error writing state data to file\n");
+ goto error;
+ }
+ ret = 0;
+error:
+ if (state)
+ free(state);
+ if (state_file)
+ fclose(state_file);
+
+ sync();
+ return ret;
+}
+
+static void set_directories(void) {
+ const char *home = getenv("HOME");
+ char *dst = save_dir;
+ int len = MAX_PATH;
+
+ if (home != NULL) {
+ snprintf(dst, len, "%s/.picoarch-%s/", home, core_name);
+ mkdir(dst, 0755);
+ }
+
+ strncpy(config_dir, save_dir, MAX_PATH-1);
+
+#ifdef MINUI
+ strncpy(system_dir, save_dir, MAX_PATH-1);
+#else
+ if (!getcwd(system_dir, MAX_PATH)) {
+ PA_FATAL("Can't find system directory");
+ }
+#endif
+}
+
+static bool pa_environment(unsigned cmd, void *data) {
+ switch(cmd) {
+ case RETRO_ENVIRONMENT_GET_OVERSCAN: { /* 2 */
+ bool *out = (bool *)data;
+ if (out)
+ *out = true;
+ break;
+ }
+ case RETRO_ENVIRONMENT_GET_CAN_DUPE: { /* 3 */
+ bool *out = (bool *)data;
+ if (out)
+ *out = true;
+ break;
+ }
+ case RETRO_ENVIRONMENT_SET_MESSAGE: { /* 6 */
+ /* Just warn for now. TODO: Display on the screen */
+ const struct retro_message *message = (const struct retro_message*)data;
+ PA_WARN(message->msg);
+ break;
+ }
+ case RETRO_ENVIRONMENT_GET_SYSTEM_DIRECTORY: { /* 9 */
+ const char **out = (const char **)data;
+ if (out)
+ *out = system_dir;
+ break;
+ }
+ case RETRO_ENVIRONMENT_SET_PIXEL_FORMAT: { /* 10 */
+ const enum retro_pixel_format *format = (enum retro_pixel_format *)data;
+
+ if (*format != RETRO_PIXEL_FORMAT_RGB565) {
+ /* 565 is only supported format */
+ return false;
+ }
+ break;
+ }
+ case RETRO_ENVIRONMENT_GET_VARIABLE: { /* 15 */
+ struct retro_variable *var = (struct retro_variable *)data;
+ if (var && var->key) {
+ var->value = options_get_value(var->key);
+ }
+ break;
+ }
+ case RETRO_ENVIRONMENT_SET_VARIABLES: { /* 16 */
+ const struct retro_variable *vars = (const struct retro_variable *)data;
+ options_free();
+ if (vars) {
+ options_init_variables(vars);
+ load_config();
+ }
+ break;
+ }
+ case RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE: { /* 17 */
+ bool *out = (bool *)data;
+ if (out)
+ *out = options_changed();
+ break;
+ }
+ case RETRO_ENVIRONMENT_GET_LOG_INTERFACE: { /* 27 */
+ struct retro_log_callback *log_cb = (struct retro_log_callback *)data;
+ if (log_cb)
+ log_cb->log = pa_log;
+ break;
+ }
+ case RETRO_ENVIRONMENT_GET_SAVE_DIRECTORY: { /* 31 */
+ const char **out = (const char **)data;
+ if (out)
+ *out = save_dir;
+ break;
+ }
+ case RETRO_ENVIRONMENT_GET_INPUT_BITMASKS: { /* 52 */
+ bool *out = (bool *)data;
+ if (out)
+ *out = true;
+ break;
+ }
+ case RETRO_ENVIRONMENT_GET_CORE_OPTIONS_VERSION: { /* 52 */
+ unsigned *out = (unsigned *)data;
+ if (out)
+ *out = 1;
+ break;
+ }
+ case RETRO_ENVIRONMENT_SET_CORE_OPTIONS: { /* 53 */
+ options_free();
+ if (data) {
+ options_init(*(const struct retro_core_option_definition **)data);
+ load_config();
+ }
+ break;
+ }
+ case RETRO_ENVIRONMENT_SET_CORE_OPTIONS_INTL: { /* 54 */
+ const struct retro_core_options_intl *options = (const struct retro_core_options_intl *)data;
+ if (options && options->us) {
+ options_free();
+ options_init(options->us);
+ load_config();
+ }
+ break;
+ }
+ case RETRO_ENVIRONMENT_SET_AUDIO_BUFFER_STATUS_CALLBACK: { /* 62 */
+ const struct retro_audio_buffer_status_callback *cb =
+ (const struct retro_audio_buffer_status_callback *)data;
+ if (cb) {
+ current_core.retro_audio_buffer_status = cb->callback;
+ } else {
+ current_core.retro_audio_buffer_status = NULL;
+ }
+ break;
+ }
+ case RETRO_ENVIRONMENT_SET_MINIMUM_AUDIO_LATENCY: { /* 63 */
+ const unsigned *latency_ms = (const unsigned *)data;
+ if (latency_ms) {
+ unsigned frames = *latency_ms * frame_rate / 1000;
+ if (frames > audio_buffer_size && frames < 30)
+ audio_buffer_size_override = frames;
+ else
+ PA_WARN("Audio buffer change out of range (%d), ignored\n", frames);
+ }
+ break;
+ }
+ default:
+ PA_DEBUG("Unsupported environment cmd: %u\n", cmd);
+ return false;
+ }
+
+ return true;
+}
+
+static void pa_video_refresh(const void *data, unsigned width, unsigned height, size_t pitch) {
+ if (data && !should_quit) {
+ pa_track_render();
+ plat_video_process(data, width, height, pitch);
+ }
+}
+
+static void pa_audio_sample(int16_t left, int16_t right) {
+ const struct audio_frame frame = { .left = left, .right = right };
+ if (!should_quit && enable_audio)
+ plat_sound_write(&frame, 1);
+}
+
+static size_t pa_audio_sample_batch(const int16_t *data, size_t frames) {
+ if (!should_quit && enable_audio)
+ plat_sound_write((const struct audio_frame *)data, frames);
+ return frames;
+}
+
+static void pa_input_poll(void) {
+ int actions[IN_BINDTYPE_COUNT] = { 0, };
+ unsigned int emu_act;
+ int which = EACTION_NONE;
+
+ in_update(actions);
+ emu_act = actions[IN_BINDTYPE_EMU];
+ if (emu_act) {
+ for (; !(emu_act & 1); emu_act >>= 1, which++)
+ ;
+ emu_act = which;
+ }
+ handle_emu_action(which);
+
+ buttons = actions[IN_BINDTYPE_PLAYER12];
+}
+
+static int16_t pa_input_state(unsigned port, unsigned device, unsigned index, unsigned id) {
+ if (port == 0 && device == RETRO_DEVICE_JOYPAD && index == 0) {
+ if (id == RETRO_DEVICE_ID_JOYPAD_MASK)
+ return buttons;
+
+ return (buttons >> id) & 1;
+ }
+
+ return 0;
+}
+
+int core_load(const char *corefile) {
+ void (*set_environment)(retro_environment_t) = NULL;
+ void (*set_video_refresh)(retro_video_refresh_t) = NULL;
+ void (*set_audio_sample)(retro_audio_sample_t) = NULL;
+ void (*set_audio_sample_batch)(retro_audio_sample_batch_t) = NULL;
+ void (*set_input_poll)(retro_input_poll_t) = NULL;
+ void (*set_input_state)(retro_input_state_t) = NULL;
+
+ PA_INFO("Loading core %s\n", corefile);
+
+ memset(&current_core, 0, sizeof(current_core));
+ current_core.handle = dlopen(corefile, RTLD_LAZY);
+
+ if (!current_core.handle) {
+ PA_ERROR("Couldn't load core: %s\n", dlerror());
+ return -1;
+ }
+
+ set_directories();
+
+ current_core.retro_init = dlsym(current_core.handle, "retro_init");
+ current_core.retro_deinit = dlsym(current_core.handle, "retro_deinit");
+ current_core.retro_get_system_info = dlsym(current_core.handle, "retro_get_system_info");
+ current_core.retro_get_system_av_info = dlsym(current_core.handle, "retro_get_system_av_info");
+ current_core.retro_set_controller_port_device = dlsym(current_core.handle, "retro_set_controller_port_device");
+ current_core.retro_reset = dlsym(current_core.handle, "retro_reset");
+ current_core.retro_run = dlsym(current_core.handle, "retro_run");
+ current_core.retro_serialize_size = dlsym(current_core.handle, "retro_serialize_size");
+ current_core.retro_serialize = dlsym(current_core.handle, "retro_serialize");
+ current_core.retro_unserialize = dlsym(current_core.handle, "retro_unserialize");
+ current_core.retro_cheat_reset = dlsym(current_core.handle, "retro_cheat_reset");
+ current_core.retro_cheat_set = dlsym(current_core.handle, "retro_cheat_set");
+ current_core.retro_load_game = dlsym(current_core.handle, "retro_load_game");
+ current_core.retro_load_game_special = dlsym(current_core.handle, "retro_load_game_special");
+ current_core.retro_unload_game = dlsym(current_core.handle, "retro_unload_game");
+ current_core.retro_get_region = dlsym(current_core.handle, "retro_get_region");
+ current_core.retro_get_memory_data = dlsym(current_core.handle, "retro_get_memory_data");
+ current_core.retro_get_memory_size = dlsym(current_core.handle, "retro_get_memory_size");
+
+ set_environment = dlsym(current_core.handle, "retro_set_environment");
+ set_video_refresh = dlsym(current_core.handle, "retro_set_video_refresh");
+ set_audio_sample = dlsym(current_core.handle, "retro_set_audio_sample");
+ set_audio_sample_batch = dlsym(current_core.handle, "retro_set_audio_sample_batch");
+ set_input_poll = dlsym(current_core.handle, "retro_set_input_poll");
+ set_input_state = dlsym(current_core.handle, "retro_set_input_state");
+
+ set_environment(pa_environment);
+ set_video_refresh(pa_video_refresh);
+ set_audio_sample(pa_audio_sample);
+ set_audio_sample_batch(pa_audio_sample_batch);
+ set_input_poll(pa_input_poll);
+ set_input_state(pa_input_state);
+
+ current_core.retro_init();
+ current_core.initialized = true;
+ PA_INFO("Finished loading core\n");
+ return 0;
+}
+
+#define MAX_EXTENSIONS 24
+
+static void core_handle_zip(const char *path, struct retro_system_info *info, struct retro_game_info *game_info, FILE** file) {
+ const char *extensions[MAX_EXTENSIONS] = {0};
+ char *extensionstr = strdup(info->valid_extensions);
+ char *ext = NULL;
+ char *saveptr = NULL;
+ int index = 0;
+ bool haszip = false;
+ FILE *dest = NULL;
+
+ if (info->valid_extensions && has_suffix_i(path, ".zip")) {
+ while((ext = strtok_r(index == 0 ? extensionstr : NULL, "|", &saveptr))) {
+ if (!strcmp(ext, "zip")) {
+ haszip = true;
+ break;
+ }
+ extensions[index++] = ext;
+ if (index > MAX_EXTENSIONS - 1) break;
+ }
+
+ if (!haszip) {
+ if (!unzip_tmp(*file, extensions, temp_rom, MAX_PATH)) {
+ game_info->path = temp_rom;
+ dest = fopen(temp_rom, "r");
+ if (dest) {
+ fclose(*file);
+ *file = dest;
+ }
+ }
+ }
+ }
+ free(extensionstr);
+}
+
+int core_load_content(const char *path) {
+ struct retro_system_info info = {0};
+ struct retro_system_av_info av_info = {0};
+ FILE *file = fopen(path, "rb");
+ int ret = -1;
+
+ if (!file) {
+ PA_ERROR("Couldn't load content: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ memset(&game_info, 0, sizeof(struct retro_game_info));
+
+ PA_INFO("Loading %s\n", path);
+ game_info.path = path;
+
+ current_core.retro_get_system_info(&info);
+
+ core_handle_zip(path, &info, &game_info, &file);
+
+ fseek(file, 0, SEEK_END);
+ game_info.size = ftell(file);
+ rewind(file);
+
+ if (!info.need_fullpath) {
+ void *game_data = malloc(game_info.size);
+
+ if (!game_data) {
+ PA_ERROR("Couldn't allocate memory for content\n");
+ goto finish;
+ }
+
+ if (fread(game_data, 1, game_info.size, file) != game_info.size) {
+ PA_ERROR("Couldn't read file: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ game_info.data = game_data;
+ }
+
+ if (!current_core.retro_load_game(&game_info)) {
+ PA_ERROR("Couldn't load content\n");
+ goto finish;
+ }
+
+ sram_read();
+
+ current_core.retro_get_system_av_info(&av_info);
+
+ PA_INFO("Screen: %dx%d\n", av_info.geometry.base_width, av_info.geometry.base_height);
+ PA_INFO("Audio sample rate: %f\n", av_info.timing.sample_rate);
+ PA_INFO("Frame rate: %f\n", av_info.timing.fps);
+
+ sample_rate = av_info.timing.sample_rate;
+ frame_rate = av_info.timing.fps;
+ aspect_ratio = av_info.geometry.aspect_ratio;
+
+ if (!aspect_ratio)
+ aspect_ratio = (float)av_info.geometry.base_width / (float)av_info.geometry.base_height;
+
+#ifdef MMENU
+ gamepak_related_name(save_template_path, MAX_PATH, ".st%i");
+#endif
+
+ ret = 0;
+finish:
+ if (file)
+ fclose(file);
+
+ return ret;
+}
+
+void core_unload(void) {
+ PA_INFO("Unloading core...\n");
+
+ if (current_core.initialized) {
+ current_core.retro_deinit();
+ current_core.initialized = false;
+ }
+
+ if (game_info.data) {
+ free((void *)game_info.data);
+ game_info.data = NULL;
+ }
+
+ if (current_core.handle) {
+ dlclose(current_core.handle);
+ current_core.handle = NULL;
+ }
+
+ if (temp_rom[0]) {
+ remove(temp_rom);
+ temp_rom[0] = '\0';
+ }
+ options_free();
+}