From 5e17accad0b1f929de609131be1a61265ffd9bfe Mon Sep 17 00:00:00 2001 From: Bobby Smiles Date: Wed, 4 Oct 2017 00:40:11 +0200 Subject: [PATCH] Basic support for GB cart loader command. Definition of GB carts loaded inside transferpak is done through the mupen64plus.cfg file as follow : [TransferPak] GB-rom-1 = "path/to/gb_rom.gb" GB-ram-1 = "path/to/gb_rom.sav" GB-rom-2 ... GB-ram-2 ... Setting an empty GB ROM acts as if GB cart was removed form TransferPak. Setting an empty GB RAM let the Core generate a blank save file. These parameters are modifiable during emulator execution. You just need to trigger the GB cart change button, and these parameters will be reloaded. You can also specify these at program startup using command-line arguments --gb-{rom,ram}-{1,2,3,4} --- README | 2 + src/core_interface.c | 12 +++ src/core_interface.h | 4 + src/main.c | 192 ++++++++++++++++++++++++++++++++++++++++++- src/osal_files.h | 20 +++++ 5 files changed, 227 insertions(+), 3 deletions(-) diff --git a/README b/README index e7fff35..316cd90 100644 --- a/README +++ b/README @@ -42,6 +42,8 @@ Parameters: --emumode (mode) : set emu mode to: 0=Pure Interpreter 1=Interpreter 2=DynaRec --testshots (list) : take screenshots at frames given in comma-separated (list), then quit --set (param-spec) : set a configuration variable, format: ParamSection[ParamName]=Value + --gb-rom-{1,2,3,4} : define GB cart rom to load inside transferpak {1,2,3,4}" + --gb-ram-{1,2,3,4} : define GB cart ram to load inside transferpak {1,2,3,4}" --core-compare-send : use the Core Comparison debugging feature, in data sending mode --core-compare-recv : use the Core Comparison debugging feature, in data receiving mode --nosaveoptions : do not save the given command-line options in configuration file diff --git a/src/core_interface.c b/src/core_interface.c index e189f9d..780ed73 100644 --- a/src/core_interface.c +++ b/src/core_interface.c @@ -73,6 +73,10 @@ ptr_ConfigGetParamFloat ConfigGetParamFloat = NULL; ptr_ConfigGetParamBool ConfigGetParamBool = NULL; ptr_ConfigGetParamString ConfigGetParamString = NULL; +ptr_ConfigExternalOpen ConfigExternalOpen = NULL; +ptr_ConfigExternalClose ConfigExternalClose = NULL; +ptr_ConfigExternalGetParameter ConfigExternalGetParameter = NULL; + ptr_ConfigGetSharedDataFilepath ConfigGetSharedDataFilepath = NULL; ptr_ConfigGetUserConfigPath ConfigGetUserConfigPath = NULL; ptr_ConfigGetUserDataPath ConfigGetUserDataPath = NULL; @@ -239,6 +243,10 @@ m64p_error AttachCoreLib(const char *CoreLibFilepath) ConfigGetParamBool = (ptr_ConfigGetParamBool) osal_dynlib_getproc(CoreHandle, "ConfigGetParamBool"); ConfigGetParamString = (ptr_ConfigGetParamString) osal_dynlib_getproc(CoreHandle, "ConfigGetParamString"); + ConfigExternalOpen = (ptr_ConfigExternalOpen) osal_dynlib_getproc(CoreHandle, "ConfigExternalOpen"); + ConfigExternalClose = (ptr_ConfigExternalClose) osal_dynlib_getproc(CoreHandle, "ConfigExternalClose"); + ConfigExternalGetParameter = (ptr_ConfigExternalGetParameter) osal_dynlib_getproc(CoreHandle, "ConfigExternalGetParameter"); + ConfigGetSharedDataFilepath = (ptr_ConfigGetSharedDataFilepath) osal_dynlib_getproc(CoreHandle, "ConfigGetSharedDataFilepath"); ConfigGetUserConfigPath = (ptr_ConfigGetUserConfigPath) osal_dynlib_getproc(CoreHandle, "ConfigGetUserConfigPath"); ConfigGetUserDataPath = (ptr_ConfigGetUserDataPath) osal_dynlib_getproc(CoreHandle, "ConfigGetUserDataPath"); @@ -304,6 +312,10 @@ m64p_error DetachCoreLib(void) ConfigGetParamBool = NULL; ConfigGetParamString = NULL; + ConfigExternalOpen = NULL; + ConfigExternalClose = NULL; + ConfigExternalGetParameter = NULL; + ConfigGetSharedDataFilepath = NULL; ConfigGetUserDataPath = NULL; ConfigGetUserCachePath = NULL; diff --git a/src/core_interface.h b/src/core_interface.h index f28a642..9ad91cb 100644 --- a/src/core_interface.h +++ b/src/core_interface.h @@ -70,6 +70,10 @@ extern ptr_ConfigGetParamFloat ConfigGetParamFloat; extern ptr_ConfigGetParamBool ConfigGetParamBool; extern ptr_ConfigGetParamString ConfigGetParamString; +extern ptr_ConfigExternalOpen ConfigExternalOpen; +extern ptr_ConfigExternalClose ConfigExternalClose; +extern ptr_ConfigExternalGetParameter ConfigExternalGetParameter; + extern ptr_ConfigGetSharedDataFilepath ConfigGetSharedDataFilepath; extern ptr_ConfigGetUserConfigPath ConfigGetUserConfigPath; extern ptr_ConfigGetUserDataPath ConfigGetUserDataPath; diff --git a/src/main.c b/src/main.c index d4d003b..64d6e80 100644 --- a/src/main.c +++ b/src/main.c @@ -22,9 +22,9 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* This is the main application entry point for the console-only front-end - * for Mupen64Plus v2.0. + * for Mupen64Plus v2.0. */ - + #include #include #include @@ -37,6 +37,7 @@ #include "m64p_types.h" #include "main.h" #include "osal_preproc.h" +#include "osal_files.h" #include "plugin.h" #include "version.h" @@ -56,6 +57,7 @@ int g_Verbose = 0; static m64p_handle l_ConfigCore = NULL; static m64p_handle l_ConfigVideo = NULL; static m64p_handle l_ConfigUI = NULL; +static m64p_handle l_ConfigTransferPak = NULL; static const char *l_CoreLibPath = NULL; static const char *l_ConfigDirPath = NULL; @@ -151,6 +153,69 @@ static void FrameCallback(unsigned int FrameIndex) } } + +static char *formatstr(const char *fmt, ...) +{ + int size = 128, ret; + char *str = (char *)malloc(size), *newstr; + va_list args; + + /* There are two implementations of vsnprintf we have to deal with: + * C99 version: Returns the number of characters which would have been written + * if the buffer had been large enough, and -1 on failure. + * Windows version: Returns the number of characters actually written, + * and -1 on failure or truncation. + * NOTE: An implementation equivalent to the Windows one appears in glibc <2.1. + */ + while (str != NULL) + { + va_start(args, fmt); + ret = vsnprintf(str, size, fmt, args); + va_end(args); + + // Successful result? + if (ret >= 0 && ret < size) + return str; + + // Increment the capacity of the buffer + if (ret >= size) + size = ret + 1; // C99 version: We got the needed buffer size + else + size *= 2; // Windows version: Keep guessing + + newstr = (char *)realloc(str, size); + if (newstr == NULL) + free(str); + str = newstr; + } + + return NULL; +} + +static int is_path_separator(char c) +{ + return strchr(OSAL_DIR_SEPARATORS, c) != NULL; +} + +char* combinepath(const char* first, const char *second) +{ + size_t len_first, off_second = 0; + + if (first == NULL || second == NULL) + return NULL; + + len_first = strlen(first); + + while (is_path_separator(first[len_first-1])) + len_first--; + + while (is_path_separator(second[off_second])) + off_second++; + + return formatstr("%.*s%c%s", (int) len_first, first, OSAL_DIR_SEPARATORS[0], second + off_second); +} + + /********************************************************************************************************* * Configuration handling */ @@ -160,6 +225,7 @@ static m64p_error OpenConfigurationHandles(void) float fConfigParamsVersion; int bSaveConfig = 0; m64p_error rval; + unsigned int i; /* Open Configuration sections for core library and console User Interface */ rval = (*ConfigOpenSection)("Core", &l_ConfigCore); @@ -176,6 +242,13 @@ static m64p_error OpenConfigurationHandles(void) return rval; } + rval = (*ConfigOpenSection)("Transferpak", &l_ConfigTransferPak); + if (rval != M64ERR_SUCCESS) + { + DebugMessage(M64MSG_ERROR, "failed to open 'Transferpak' configuration section"); + return rval; + } + rval = (*ConfigOpenSection)("UI-Console", &l_ConfigUI); if (rval != M64ERR_SUCCESS) { @@ -214,8 +287,25 @@ static m64p_error OpenConfigurationHandles(void) (*ConfigSetDefaultString)(l_ConfigUI, "InputPlugin", "mupen64plus-input-sdl" OSAL_DLL_EXTENSION, "Filename of input plugin"); (*ConfigSetDefaultString)(l_ConfigUI, "RspPlugin", "mupen64plus-rsp-hle" OSAL_DLL_EXTENSION, "Filename of RSP plugin"); - if (bSaveConfig && ConfigSaveSection != NULL) /* ConfigSaveSection was added in Config API v2.1.0 */ + for(i = 1; i < 5; ++i) { + char key[64]; + char desc[2048]; +#define SET_DEFAULT_STRING(key_fmt, default_value, desc_fmt) \ + do { \ + snprintf(key, sizeof(key), key_fmt, i); \ + snprintf(desc, sizeof(desc), desc_fmt, i); \ + (*ConfigSetDefaultString)(l_ConfigTransferPak, key, default_value, desc); \ + } while(0) + + SET_DEFAULT_STRING("GB-rom-%u", "", "Filename of the GB ROM to load into transferpak %u"); + SET_DEFAULT_STRING("GB-ram-%u", "", "Filename of the GB RAM to load into transferpak %u"); +#undef SET_DEFAULT_STRING + } + + if (bSaveConfig && ConfigSaveSection != NULL) { /* ConfigSaveSection was added in Config API v2.1.0 */ (*ConfigSaveSection)("UI-Console"); + (*ConfigSaveSection)("Transferpak"); + } return M64ERR_SUCCESS; } @@ -270,6 +360,8 @@ static void printUsage(const char *progname) " --savestate (filepath) : savestate loaded at startup\n" " --testshots (list) : take screenshots at frames given in comma-separated (list), then quit\n" " --set (param-spec) : set a configuration variable, format: ParamSection[ParamName]=Value\n" + " --gb-rom-{1,2,3,4} : define GB cart rom to load inside transferpak {1,2,3,4}\n" + " --gb-ram-{1,2,3,4} : define GB cart ram to load inside transferpak {1,2,3,4}\n" " --core-compare-send : use the Core Comparison debugging feature, in data sending mode\n" " --core-compare-recv : use the Core Comparison debugging feature, in data receiving mode\n" " --nosaveoptions : do not save the given command-line options in configuration file\n" @@ -584,6 +676,21 @@ static m64p_error ParseCommandLineFinal(int argc, const char **argv) { l_SaveOptions = 0; } +#define PARSE_GB_CART_PARAM(param, key) \ + else if (strcmp(argv[i], param) == 0) \ + { \ + ConfigSetParameter(l_ConfigTransferPak, key, M64TYPE_STRING, argv[i+1]); \ + i++; \ + } + PARSE_GB_CART_PARAM("--gb-rom-1", "GB-rom-1") + PARSE_GB_CART_PARAM("--gb-ram-1", "GB-ram-1") + PARSE_GB_CART_PARAM("--gb-rom-2", "GB-rom-2") + PARSE_GB_CART_PARAM("--gb-ram-2", "GB-ram-2") + PARSE_GB_CART_PARAM("--gb-rom-3", "GB-rom-3") + PARSE_GB_CART_PARAM("--gb-ram-3", "GB-ram-3") + PARSE_GB_CART_PARAM("--gb-rom-4", "GB-rom-4") + PARSE_GB_CART_PARAM("--gb-ram-4", "GB-ram-4") +#undef PARSE_GB_CART_PARAM else if (ArgsLeft == 0) { /* this is the last arg, it should be a ROM filename */ @@ -606,6 +713,79 @@ static m64p_error ParseCommandLineFinal(int argc, const char **argv) return M64ERR_INPUT_INVALID; } +static char* gb_cart_loader_get_mem_file(void* cb_data, const char* mem, int control_id) +{ +#define MUPEN64PLUS_CFG_NAME "mupen64plus.cfg" + m64p_handle core_config; + char key[64]; + char value[4096]; + const char* configdir = NULL; + char* cfgfilepath = NULL; + + /* reset ROM filename */ + char* mem_filename = NULL; + + snprintf(key, sizeof(key), "GB-%s-%u", mem, control_id + 1); + + /* XXX: use external config API to force reload of file content */ + configdir = ConfigGetUserConfigPath(); + if (configdir == NULL) { + DebugMessage(M64MSG_ERROR, "Can't get user config path !"); + return NULL; + } + + cfgfilepath = combinepath(configdir, MUPEN64PLUS_CFG_NAME); + if (cfgfilepath == NULL) { + DebugMessage(M64MSG_ERROR, "Can't get config file path: %s + %s!", configdir, MUPEN64PLUS_CFG_NAME); + return NULL; + } + + if (ConfigExternalOpen(cfgfilepath, &core_config) != M64ERR_SUCCESS) { + DebugMessage(M64MSG_ERROR, "Can't open config file %s!", cfgfilepath); + goto release_cfgfilepath; + } + + if (ConfigExternalGetParameter(core_config, "Transferpak", key, value, sizeof(value)) != M64ERR_SUCCESS) { + DebugMessage(M64MSG_ERROR, "Can't get parameter %s", key); + goto close_config; + } + + size_t len = strlen(value); + if (len < 2 || value[0] != '"' || value[len-1] != '"') { + DebugMessage(M64MSG_ERROR, "Invalid string format %s", value); + goto close_config; + } + + value[len-1] = '\0'; + mem_filename = strdup(value + 1); + + ConfigSetParameter(l_ConfigTransferPak, key, M64TYPE_STRING, mem_filename); + +close_config: + ConfigExternalClose(core_config); +release_cfgfilepath: + free(cfgfilepath); + return mem_filename; +} + +static char* gb_cart_loader_get_rom(void* cb_data, int control_id) +{ + return gb_cart_loader_get_mem_file(cb_data, "rom", control_id); +} + +static char* gb_cart_loader_get_ram(void* cb_data, int control_id) +{ + return gb_cart_loader_get_mem_file(cb_data, "ram", control_id); +} + +static m64p_gb_cart_loader l_gb_cart_loader = +{ + NULL, + gb_cart_loader_get_rom, + gb_cart_loader_get_ram +}; + + /********************************************************************************************************* * main function */ @@ -785,6 +965,12 @@ int main(int argc, char *argv[]) } } + /* set gb cart loader */ + if ((*CoreDoCommand)(M64CMD_SET_GB_CART_LOADER, sizeof(l_gb_cart_loader), &l_gb_cart_loader) != M64ERR_SUCCESS) + { + DebugMessage(M64MSG_WARNING, "Couldn't set GB cart loader, transferpak and GB carts will not work."); + } + /* load savestate at startup */ if (l_SaveStatePath != NULL) { diff --git a/src/osal_files.h b/src/osal_files.h index 31ef4a6..87e9cae 100644 --- a/src/osal_files.h +++ b/src/osal_files.h @@ -29,6 +29,26 @@ #include "m64p_types.h" #include "osal_preproc.h" +/* some file-related preprocessor definitions */ +#if defined(WIN32) && !defined(__MINGW32__) + #include // For _unlink() + + #define unlink _unlink + + #define OSAL_DIR_SEPARATORS "\\/" + #define PATH_MAX _MAX_PATH +#else /* Not WIN32 */ + #include // for PATH_MAX + #include // for unlink() + + #define OSAL_DIR_SEPARATORS "/" + + /* PATH_MAX only may be defined by limits.h */ + #ifndef PATH_MAX + #define PATH_MAX 4096 + #endif +#endif + /* data structure for linked list of shared libraries found in a directory */ typedef struct _osal_lib_search { char filepath[PATH_MAX];