Merge pull request #189 from jdgleaver/content-data-api

Add support for RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE API extension - enables soft patching of non-CD content
This commit is contained in:
Autechre 2021-06-04 16:29:16 +02:00 committed by GitHub
commit da3c202800
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 478 additions and 33 deletions

View File

@ -282,6 +282,7 @@ enum retro_language
RETRO_LANGUAGE_PERSIAN = 20,
RETRO_LANGUAGE_HEBREW = 21,
RETRO_LANGUAGE_ASTURIAN = 22,
RETRO_LANGUAGE_FINNISH = 23,
RETRO_LANGUAGE_LAST,
/* Ensure sizeof(enum) == sizeof(int) */
@ -712,6 +713,9 @@ enum retro_mod
* state of rumble motors in controllers.
* A strong and weak motor is supported, and they can be
* controlled indepedently.
* Should be called from either retro_init() or retro_load_game().
* Should not be called from retro_set_environment().
* Returns false if rumble functionality is unavailable.
*/
#define RETRO_ENVIRONMENT_GET_INPUT_DEVICE_CAPABILITIES 24
/* uint64_t * --
@ -1374,6 +1378,121 @@ enum retro_mod
* call will target the newly initialized driver.
*/
#define RETRO_ENVIRONMENT_SET_FASTFORWARDING_OVERRIDE 64
/* const struct retro_fastforwarding_override * --
* Used by a libretro core to override the current
* fastforwarding mode of the frontend.
* If NULL is passed to this function, the frontend
* will return true if fastforwarding override
* functionality is supported (no change in
* fastforwarding state will occur in this case).
*/
#define RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE 65
/* const struct retro_system_content_info_override * --
* Allows an implementation to override 'global' content
* info parameters reported by retro_get_system_info().
* Overrides also affect subsystem content info parameters
* set via RETRO_ENVIRONMENT_SET_SUBSYSTEM_INFO.
* This function must be called inside retro_set_environment().
* If callback returns false, content info overrides
* are unsupported by the frontend, and will be ignored.
* If callback returns true, extended game info may be
* retrieved by calling RETRO_ENVIRONMENT_GET_GAME_INFO_EXT
* in retro_load_game() or retro_load_game_special().
*
* 'data' points to an array of retro_system_content_info_override
* structs terminated by a { NULL, false, false } element.
* If 'data' is NULL, no changes will be made to the frontend;
* a core may therefore pass NULL in order to test whether
* the RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE and
* RETRO_ENVIRONMENT_GET_GAME_INFO_EXT callbacks are supported
* by the frontend.
*
* For struct member descriptions, see the definition of
* struct retro_system_content_info_override.
*
* Example:
*
* - struct retro_system_info:
* {
* "My Core", // library_name
* "v1.0", // library_version
* "m3u|md|cue|iso|chd|sms|gg|sg", // valid_extensions
* true, // need_fullpath
* false // block_extract
* }
*
* - Array of struct retro_system_content_info_override:
* {
* {
* "md|sms|gg", // extensions
* false, // need_fullpath
* true // persistent_data
* },
* {
* "sg", // extensions
* false, // need_fullpath
* false // persistent_data
* },
* { NULL, false, false }
* }
*
* Result:
* - Files of type m3u, cue, iso, chd will not be
* loaded by the frontend. Frontend will pass a
* valid path to the core, and core will handle
* loading internally
* - Files of type md, sms, gg will be loaded by
* the frontend. A valid memory buffer will be
* passed to the core. This memory buffer will
* remain valid until retro_deinit() returns
* - Files of type sg will be loaded by the frontend.
* A valid memory buffer will be passed to the core.
* This memory buffer will remain valid until
* retro_load_game() (or retro_load_game_special())
* returns
*
* NOTE: If an extension is listed multiple times in
* an array of retro_system_content_info_override
* structs, only the first instance will be registered
*/
#define RETRO_ENVIRONMENT_GET_GAME_INFO_EXT 66
/* const struct retro_game_info_ext ** --
* Allows an implementation to fetch extended game
* information, providing additional content path
* and memory buffer status details.
* This function may only be called inside
* retro_load_game() or retro_load_game_special().
* If callback returns false, extended game information
* is unsupported by the frontend. In this case, only
* regular retro_game_info will be available.
* RETRO_ENVIRONMENT_GET_GAME_INFO_EXT is guaranteed
* to return true if RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE
* returns true.
*
* 'data' points to an array of retro_game_info_ext structs.
*
* For struct member descriptions, see the definition of
* struct retro_game_info_ext.
*
* - If function is called inside retro_load_game(),
* the retro_game_info_ext array is guaranteed to
* have a size of 1 - i.e. the returned pointer may
* be used to access directly the members of the
* first retro_game_info_ext struct, for example:
*
* struct retro_game_info_ext *game_info_ext;
* if (environ_cb(RETRO_ENVIRONMENT_GET_GAME_INFO_EXT, &game_info_ext))
* printf("Content Directory: %s\n", game_info_ext->dir);
*
* - If the function is called inside retro_load_game_special(),
* the retro_game_info_ext array is guaranteed to have a
* size equal to the num_info argument passed to
* retro_load_game_special()
*/
/* VFS functionality */
/* File paths:
@ -2777,6 +2896,213 @@ struct retro_system_info
bool block_extract;
};
/* Defines overrides which modify frontend handling of
* specific content file types.
* An array of retro_system_content_info_override is
* passed to RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE
* NOTE: In the following descriptions, references to
* retro_load_game() may be replaced with
* retro_load_game_special() */
struct retro_system_content_info_override
{
/* A list of file extensions for which the override
* should apply, delimited by a 'pipe' character
* (e.g. "md|sms|gg")
* Permitted file extensions are limited to those
* included in retro_system_info::valid_extensions
* and/or retro_subsystem_rom_info::valid_extensions */
const char *extensions;
/* Overrides the need_fullpath value set in
* retro_system_info and/or retro_subsystem_rom_info.
* To reiterate:
*
* If need_fullpath is true and retro_load_game() is called:
* - retro_game_info::path is guaranteed to contain a valid
* path to an existent file
* - retro_game_info::data and retro_game_info::size are invalid
*
* If need_fullpath is false and retro_load_game() is called:
* - retro_game_info::path may be NULL
* - retro_game_info::data and retro_game_info::size are guaranteed
* to be valid
*
* In addition:
*
* If need_fullpath is true and retro_load_game() is called:
* - retro_game_info_ext::full_path is guaranteed to contain a valid
* path to an existent file
* - retro_game_info_ext::archive_path may be NULL
* - retro_game_info_ext::archive_file may be NULL
* - retro_game_info_ext::dir is guaranteed to contain a valid path
* to the directory in which the content file exists
* - retro_game_info_ext::name is guaranteed to contain the
* basename of the content file, without extension
* - retro_game_info_ext::ext is guaranteed to contain the
* extension of the content file in lower case format
* - retro_game_info_ext::data and retro_game_info_ext::size
* are invalid
*
* If need_fullpath is false and retro_load_game() is called:
* - If retro_game_info_ext::file_in_archive is false:
* - retro_game_info_ext::full_path is guaranteed to contain
* a valid path to an existent file
* - retro_game_info_ext::archive_path may be NULL
* - retro_game_info_ext::archive_file may be NULL
* - retro_game_info_ext::dir is guaranteed to contain a
* valid path to the directory in which the content file exists
* - retro_game_info_ext::name is guaranteed to contain the
* basename of the content file, without extension
* - retro_game_info_ext::ext is guaranteed to contain the
* extension of the content file in lower case format
* - If retro_game_info_ext::file_in_archive is true:
* - retro_game_info_ext::full_path may be NULL
* - retro_game_info_ext::archive_path is guaranteed to
* contain a valid path to an existent compressed file
* inside which the content file is located
* - retro_game_info_ext::archive_file is guaranteed to
* contain a valid path to an existent content file
* inside the compressed file referred to by
* retro_game_info_ext::archive_path
* e.g. for a compressed file '/path/to/foo.zip'
* containing 'bar.sfc'
* > retro_game_info_ext::archive_path will be '/path/to/foo.zip'
* > retro_game_info_ext::archive_file will be 'bar.sfc'
* - retro_game_info_ext::dir is guaranteed to contain a
* valid path to the directory in which the compressed file
* (containing the content file) exists
* - retro_game_info_ext::name is guaranteed to contain
* EITHER
* 1) the basename of the compressed file (containing
* the content file), without extension
* OR
* 2) the basename of the content file inside the
* compressed file, without extension
* In either case, a core should consider 'name' to
* be the canonical name/ID of the the content file
* - retro_game_info_ext::ext is guaranteed to contain the
* extension of the content file inside the compressed file,
* in lower case format
* - retro_game_info_ext::data and retro_game_info_ext::size are
* guaranteed to be valid */
bool need_fullpath;
/* If need_fullpath is false, specifies whether the content
* data buffer available in retro_load_game() is 'persistent'
*
* If persistent_data is false and retro_load_game() is called:
* - retro_game_info::data and retro_game_info::size
* are valid only until retro_load_game() returns
* - retro_game_info_ext::data and retro_game_info_ext::size
* are valid only until retro_load_game() returns
*
* If persistent_data is true and retro_load_game() is called:
* - retro_game_info::data and retro_game_info::size
* are valid until retro_deinit() returns
* - retro_game_info_ext::data and retro_game_info_ext::size
* are valid until retro_deinit() returns */
bool persistent_data;
};
/* Similar to retro_game_info, but provides extended
* information about the source content file and
* game memory buffer status.
* And array of retro_game_info_ext is returned by
* RETRO_ENVIRONMENT_GET_GAME_INFO_EXT
* NOTE: In the following descriptions, references to
* retro_load_game() may be replaced with
* retro_load_game_special() */
struct retro_game_info_ext
{
/* - If file_in_archive is false, contains a valid
* path to an existent content file (UTF-8 encoded)
* - If file_in_archive is true, may be NULL */
const char *full_path;
/* - If file_in_archive is false, may be NULL
* - If file_in_archive is true, contains a valid path
* to an existent compressed file inside which the
* content file is located (UTF-8 encoded) */
const char *archive_path;
/* - If file_in_archive is false, may be NULL
* - If file_in_archive is true, contain a valid path
* to an existent content file inside the compressed
* file referred to by archive_path (UTF-8 encoded)
* e.g. for a compressed file '/path/to/foo.zip'
* containing 'bar.sfc'
* > archive_path will be '/path/to/foo.zip'
* > archive_file will be 'bar.sfc' */
const char *archive_file;
/* - If file_in_archive is false, contains a valid path
* to the directory in which the content file exists
* (UTF-8 encoded)
* - If file_in_archive is true, contains a valid path
* to the directory in which the compressed file
* (containing the content file) exists (UTF-8 encoded) */
const char *dir;
/* Contains the canonical name/ID of the content file
* (UTF-8 encoded). Intended for use when identifying
* 'complementary' content named after the loaded file -
* i.e. companion data of a different format (a CD image
* required by a ROM), texture packs, internally handled
* save files, etc.
* - If file_in_archive is false, contains the basename
* of the content file, without extension
* - If file_in_archive is true, then string is
* implementation specific. A frontend may choose to
* set a name value of:
* EITHER
* 1) the basename of the compressed file (containing
* the content file), without extension
* OR
* 2) the basename of the content file inside the
* compressed file, without extension
* RetroArch sets the 'name' value according to (1).
* A frontend that supports routine loading of
* content from archives containing multiple unrelated
* content files may set the 'name' value according
* to (2). */
const char *name;
/* - If file_in_archive is false, contains the extension
* of the content file in lower case format
* - If file_in_archive is true, contains the extension
* of the content file inside the compressed file,
* in lower case format */
const char *ext;
/* String of implementation specific meta-data. */
const char *meta;
/* Memory buffer of loaded game content. Will be NULL:
* IF
* - retro_system_info::need_fullpath is true and
* retro_system_content_info_override::need_fullpath
* is unset
* OR
* - retro_system_content_info_override::need_fullpath
* is true */
const void *data;
/* Size of game content memory buffer, in bytes */
size_t size;
/* True if loaded content file is inside a compressed
* archive */
bool file_in_archive;
/* - If data is NULL, value is unset/ignored
* - If data is non-NULL:
* - If persistent_data is false, data and size are
* valid only until retro_load_game() returns
* - If persistent_data is true, data and size are
* are valid until retro_deinit() returns */
bool persistent_data;
};
struct retro_game_geometry
{
unsigned base_width; /* Nominal video width of game. */
@ -2934,6 +3260,47 @@ struct retro_framebuffer
Set by frontend in GET_CURRENT_SOFTWARE_FRAMEBUFFER. */
};
/* Used by a libretro core to override the current
* fastforwarding mode of the frontend */
struct retro_fastforwarding_override
{
/* Specifies the runtime speed multiplier that
* will be applied when 'fastforward' is true.
* For example, a value of 5.0 when running 60 FPS
* content will cap the fast-forward rate at 300 FPS.
* Note that the target multiplier may not be achieved
* if the host hardware has insufficient processing
* power.
* Setting a value of 0.0 (or greater than 0.0 but
* less than 1.0) will result in an uncapped
* fast-forward rate (limited only by hardware
* capacity).
* If the value is negative, it will be ignored
* (i.e. the frontend will use a runtime speed
* multiplier of its own choosing) */
float ratio;
/* If true, fastforwarding mode will be enabled.
* If false, fastforwarding mode will be disabled. */
bool fastforward;
/* If true, and if supported by the frontend, an
* on-screen notification will be displayed while
* 'fastforward' is true.
* If false, and if supported by the frontend, any
* on-screen fast-forward notifications will be
* suppressed */
bool notification;
/* If true, the core will have sole control over
* when fastforwarding mode is enabled/disabled;
* the frontend will not be able to change the
* state set by 'fastforward' until either
* 'inhibit_toggle' is set to false, or the core
* is unloaded */
bool inhibit_toggle;
};
/* Callbacks */
/* Environment callback. Gives implementations a way of performing

View File

@ -35,7 +35,7 @@ std::string retro_base_directory;
#define MEDNAFEN_CORE_NAME_MODULE "pce_fast"
#define MEDNAFEN_CORE_NAME "Beetle PCE Fast"
#define MEDNAFEN_CORE_VERSION "v0.9.38.7"
#define MEDNAFEN_CORE_EXTENSIONS "pce|cue|ccd|chd"
#define MEDNAFEN_CORE_EXTENSIONS "pce|cue|ccd|chd|toc|m3u"
#define MEDNAFEN_CORE_TIMING_FPS 59.82
#define MEDNAFEN_CORE_GEOMETRY_BASE_W 256
#define MEDNAFEN_CORE_GEOMETRY_BASE_H 243
@ -995,7 +995,7 @@ static int HuCLoad(const uint8 *data, uint32 len, uint32 crc32)
return(1);
}
static int Load(const char *name, MDFNFILE *fp)
static int Load(const uint8_t *data, size_t size)
{
int x;
uint32 headerlen = 0;
@ -1003,10 +1003,10 @@ static int Load(const char *name, MDFNFILE *fp)
LoadCommonPre();
if(GET_FSIZE_PTR(fp) & 0x200) // 512 byte header!
if(size & 0x200) // 512 byte header!
headerlen = 512;
r_size = GET_FSIZE_PTR(fp) - headerlen;
r_size = size - headerlen;
if(r_size > 4096 * 1024) r_size = 4096 * 1024;
for(x = 0; x < 0x100; x++)
@ -1015,9 +1015,9 @@ static int Load(const char *name, MDFNFILE *fp)
HuCPU.PCEWrite[x] = PCENullWrite;
}
uint32 crc = encoding_crc32(0, GET_FDATA_PTR(fp) + headerlen, GET_FSIZE_PTR(fp) - headerlen);
uint32 crc = encoding_crc32(0, data + headerlen, size - headerlen);
HuCLoad(GET_FDATA_PTR(fp) + headerlen, GET_FSIZE_PTR(fp) - headerlen, crc);
HuCLoad(data + headerlen, size - headerlen, crc);
if(crc == 0xfae0fc60)
OrderOfGriffonFix = true;
@ -1478,20 +1478,23 @@ end:
static std::vector<CDIF *> CDInterfaces; // FIXME: Cleanup on error out.
// TODO: LoadCommon()
static bool MDFNI_LoadCD(const char *devicename)
static bool MDFNI_LoadCD(const char *path, const char *ext)
{
bool ret = false;
size_t devicename_len = strlen(devicename);
bool ret = false;
log_cb(RETRO_LOG_INFO, "Loading %s...\n\n", devicename);
if (!path || !ext)
{
log_cb(RETRO_LOG_ERROR, "Error opening CD - invalid path\n");
return false;
}
if( devicename
&& devicename_len > 4
&& !strcasecmp(devicename + devicename_len - 4, ".m3u"))
log_cb(RETRO_LOG_INFO, "Loading %s...\n\n", path);
if(!strcasecmp(ext, "m3u"))
{
std::vector<std::string> file_list;
ReadM3U(file_list, devicename);
ReadM3U(file_list, path);
for(unsigned i = 0; i < file_list.size(); i++)
{
@ -1501,7 +1504,7 @@ static bool MDFNI_LoadCD(const char *devicename)
}
else
{
CDIF *cdif = CDIF_Open(devicename, cdimagecache);
CDIF *cdif = CDIF_Open(path, cdimagecache);
if (cdif)
{
@ -1535,33 +1538,55 @@ static bool MDFNI_LoadCD(const char *devicename)
return true;
}
static bool MDFNI_LoadGame(const char *name)
static bool MDFNI_LoadGame(const char *path, const char *ext,
const uint8_t *data, size_t size)
{
MDFNFILE *GameFile = NULL;
MDFNGameInfo = &EmulatedPCE_Fast;
size_t name_len = strlen(name);
MDFNFILE *GameFile = NULL;
MDFNGameInfo = &EmulatedPCE_Fast;
const uint8_t *content_data = NULL;
size_t content_size = 0;
if(name_len > 4 && (!strcasecmp(name + name_len - 4, ".cue") || !strcasecmp(name + name_len - 4, ".ccd") || !strcasecmp(name + name_len - 4, ".chd") || !strcasecmp(name + name_len - 4, ".toc") || !strcasecmp(name + name_len - 4, ".m3u")))
return MDFNI_LoadCD(name);
if(ext &&
(!strcasecmp(ext, "cue") ||
!strcasecmp(ext, "ccd") ||
!strcasecmp(ext, "chd") ||
!strcasecmp(ext, "toc") ||
!strcasecmp(ext, "m3u")))
return MDFNI_LoadCD(path, ext);
GameFile = file_open(name);
/* Check whether we already have a valid
* data buffer */
if (data)
{
content_data = data;
content_size = size;
}
else
{
if (!path)
{
log_cb(RETRO_LOG_ERROR, "Error loading content - invalid path\n");
goto error;
}
if(!GameFile)
goto error;
/* Load content from file */
GameFile = file_open(path);
//
// Load per-game settings
//
// Maybe we should make a "pgcfg" subdir, and automatically load all files in it?
// End load per-game settings
//
if(!GameFile)
goto error;
if(Load(name, GameFile) <= 0)
content_data = GET_FDATA_PTR(GameFile);
content_size = GET_FSIZE_PTR(GameFile);
}
if(Load(content_data, content_size) <= 0)
goto error;
MDFN_LoadGameCheats(NULL);
MDFNMP_InstallReadPatches();
if (GameFile)
file_close(GameFile);
return true;
error:
@ -2009,14 +2034,57 @@ bool retro_load_game(const struct retro_game_info *info)
{ 0 },
};
if (!info || failed_init)
const struct retro_game_info_ext *info_ext = NULL;
const uint8_t *content_data = NULL;
size_t content_size = 0;
const char *content_path = NULL;
char content_ext[8];
content_ext[0] = '\0';
if (failed_init)
return false;
/* Attempt to fetch extended game info */
if (environ_cb(RETRO_ENVIRONMENT_GET_GAME_INFO_EXT, &info_ext))
{
content_data = (const uint8_t *)info_ext->data;
content_size = info_ext->size;
/* Content path information is only required
* if we do not have a valid data buffer */
if (!content_data)
{
content_path = info_ext->full_path;
strncpy(content_ext, info_ext->ext, sizeof(content_ext));
content_ext[sizeof(content_ext) - 1] = '\0';
}
}
else
{
const char *ext = NULL;
if (!info || !info->path)
return false;
content_data = NULL;
content_size = 0;
content_path = info->path;
if ((ext = strrchr(info->path, '.')))
{
strncpy(content_ext, ext + 1, sizeof(content_ext));
content_ext[sizeof(content_ext) - 1] = '\0';
}
}
environ_cb(RETRO_ENVIRONMENT_SET_INPUT_DESCRIPTORS, desc);
check_variables(true);
if (!MDFNI_LoadGame(info->path))
if (!MDFNI_LoadGame(content_path, content_ext,
content_data, content_size))
return false;
surf = (MDFN_Surface*)calloc(1, sizeof(*surf));
@ -2497,8 +2565,18 @@ void retro_set_environment(retro_environment_t cb)
{ 0 },
};
static const struct retro_system_content_info_override content_overrides[] = {
{
"pce", /* extensions */
false, /* need_fullpath */
false /* persistent_data */
},
{ NULL, false, false }
};
libretro_set_core_options(cb);
environ_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO, (void*)ports);
environ_cb(RETRO_ENVIRONMENT_SET_CONTENT_INFO_OVERRIDE, (void*)content_overrides);
vfs_iface_info.required_interface_version = 1;
vfs_iface_info.iface = NULL;