MODINFO lump

Lets you set a bunch of metadata for your add-on, and a minimum required Ring Racers version (to prevent annoying tech support questions for mod authors). This version value can also be leveraged to add in backwards compatibility behaviors for older mods.

Currently the values of the metadata are printed out when a mod is loaded, and aren't used otherwise.

Fix pointers for C interface copied strings

Apparently this was luck that it worked earlier.

I love C++'s strings! And conversely, I hate C's strings.
This commit is contained in:
Sally Coolatta 2024-08-29 02:23:44 -04:00
parent 59eb36eb91
commit ae59ee0ab9
5 changed files with 362 additions and 0 deletions

View file

@ -164,6 +164,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
k_bans.cpp k_bans.cpp
k_endcam.cpp k_endcam.cpp
k_credits.cpp k_credits.cpp
k_modinfo.cpp
music.cpp music.cpp
music_manager.cpp music_manager.cpp
sanitize.cpp sanitize.cpp

210
src/k_modinfo.cpp Normal file
View file

@ -0,0 +1,210 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour
// Copyright (C) 2024 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file k_modinfo.cpp
/// \brief Mod metadata
#include "k_modinfo.h"
#include <string>
#include <vector>
#include <algorithm>
#include <fmt/format.h>
#include <nlohmann/json.hpp>
#include "w_wad.h"
#include "z_zone.h"
using nlohmann::json;
void mod_metadata_t::init_defaults(void)
{
// Initialize default fields.
_game_version = 2; // Defaults to the latest version
_game_subversion = 3; // that did not support MODINFO lumps.
}
bool mod_metadata_t::parse_info_json(const char *contents, size_t contents_len)
{
if (contents_len == 0)
{
CONS_Alert(CONS_ERROR, "could not parse MODINFO; contents are empty\n");
return false;
}
json info_obj = json::parse(contents, contents + static_cast<unsigned int>(contents_len));
if (info_obj.is_object() == false)
{
CONS_Alert(CONS_ERROR, "could not parse MODINFO; is not a JSON object\n");
return false;
}
_name = info_obj.value("name", _name);
_author = info_obj.value("author", _author);
_description = info_obj.value("description", _description);
_info_url = info_obj.value("info_url", _info_url);
_version = info_obj.value("version", _version);
std::string game_version_str = info_obj.value("game_version", "");
if (game_version_str.empty() == false)
{
int game_version_int = 0, game_subversion_int = 0;
int result = sscanf(game_version_str.c_str(), "%d.%d", &game_version_int, &game_subversion_int);
if (result >= 1 && game_version_int >= 0 && game_version_int <= UINT16_MAX)
{
_game_version = static_cast<UINT16>(game_version_int);
_game_subversion = 0; // Just inputting "2" should be the same as "2.0"
}
if (result >= 2 && game_subversion_int >= 0 && game_subversion_int <= UINT16_MAX)
{
_game_subversion = static_cast<UINT16>(game_subversion_int);
}
}
return true;
}
// Create mod metadata from a WAD file ID.
// There is the possibility of allocating mod_metadata_t
// for an unloaded file for the addons menu, which should
// be a different constructor.
mod_metadata_t::mod_metadata_t(size_t wad_id)
{
init_defaults();
// Default to the WAD's name
_name = wadfiles[wad_id]->filename;
lumpnum_t lump_index;
lumpinfo_t *lump_p = wadfiles[wad_id]->lumpinfo;
lumpnum_t icon_lump = LUMPERROR;
lumpnum_t info_lump = LUMPERROR;
for (lump_index = 0; lump_index < wadfiles[wad_id]->numlumps; lump_index++, lump_p++)
{
if (info_lump == LUMPERROR && memcmp(lump_p->name, "MODINFO", 8) == 0)
{
info_lump = lump_index;
}
else if (icon_lump == LUMPERROR && memcmp(lump_p->name, "MODICON", 8) == 0)
{
icon_lump = lump_index;
}
if (info_lump != LUMPERROR && icon_lump != LUMPERROR)
{
// Found all lumps, exit early.
break;
}
}
if (info_lump != LUMPERROR)
{
// Parse info JSON
size_t info_lump_len = W_LumpLengthPwad(wad_id, info_lump);
char *info_contents = static_cast<char *>( W_CacheLumpNumPwad(wad_id, info_lump, PU_STATIC) );
parse_info_json(info_contents, info_lump_len);
Z_Free(info_contents);
}
if (icon_lump != LUMPERROR)
{
// We have an icon lump. Cache it!
_icon = static_cast<patch_t *>( W_CachePatchNumPwad(wad_id, icon_lump, PU_STATIC) );
}
// TODO: Make incompatibile WADs exit early and unload?
// It would require that we pass in the temporary lumpinfo and handle.
// It doesn't look hard, just very tedious, and I can't be assed rn.
mod_compat_e is_compatible = Compatible();
switch (is_compatible)
{
case MOD_INCOMPATIBLE_FUTURE:
{
CONS_Alert(
CONS_ERROR,
"Mod '%s' was designed for a newer version of Ring Racers (mod is for v%d.%d, you have v%d.%d). Update your copy!\n",
_name.c_str(),
_game_version, _game_subversion,
VERSION, SUBVERSION
);
break;
}
case MOD_INCOMPATIBLE_PAST:
{
CONS_Alert(
CONS_ERROR,
"Mod '%s' was designed for a backwards-incompatible version of Ring Racers (mod is for v%d.%d, you have v%d.%d). This mod will need updated before it can be used.\n",
_name.c_str(),
_game_version, _game_subversion,
VERSION, SUBVERSION
);
break;
}
default:
{
// Mod is compatible!
break;
}
}
}
//
// C interface functions
//
patch_t *ModMetadata_GetIcon(mod_metadata_t *meta)
{
return meta->icon();
}
UINT16 ModMetadata_GetGameVersion(mod_metadata_t *meta)
{
return meta->game_version();
}
UINT16 ModMetadata_GetGameSubVersion(mod_metadata_t *meta)
{
return meta->game_subversion();
}
char *ModMetadata_GetName(mod_metadata_t *meta)
{
return Z_StrDup( meta->name().c_str() );
}
char *ModMetadata_GetAuthor(mod_metadata_t *meta)
{
return Z_StrDup( meta->author().c_str() );
}
char *ModMetadata_GetDescription(mod_metadata_t *meta)
{
return Z_StrDup( meta->description().c_str() );
}
char *ModMetadata_GetInfoURL(mod_metadata_t *meta)
{
return Z_StrDup( meta->info_url().c_str() );
}
char *ModMetadata_GetVersion(mod_metadata_t *meta)
{
return Z_StrDup( meta->version().c_str() );
}
mod_compat_e ModMetadata_Compatible(mod_metadata_t *meta)
{
return meta->Compatible();
}

125
src/k_modinfo.h Normal file
View file

@ -0,0 +1,125 @@
// DR. ROBOTNIK'S RING RACERS
//-----------------------------------------------------------------------------
// Copyright (C) 2024 by Sally "TehRealSalt" Cochenour
// Copyright (C) 2024 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file k_modinfo.h
/// \brief Mod metadata
#ifndef __K_MODINFO_H__
#define __K_MODINFO_H__
#include "typedef.h"
#include "doomtype.h"
#include "doomdef.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum
{
MOD_COMPATIBLE = 0, // Fully compatible.
MOD_INCOMPATIBLE_FUTURE, // For a future version.
MOD_INCOMPATIBLE_PAST // For a way way too old version.
} mod_compat_e;
#ifdef __cplusplus
} // extern "C"
#endif
#ifdef __cplusplus
#include <string>
class mod_metadata_t
{
private:
patch_t *_icon;
UINT16 _game_version;
UINT16 _game_subversion;
std::string _name;
std::string _author;
std::string _description;
std::string _info_url;
std::string _version;
void init_defaults(void);
bool parse_info_json(const char *contents, size_t contents_len);
public:
mod_metadata_t(size_t wad_id);
patch_t *icon() const { return _icon; }
UINT16 game_version() const { return _game_version; }
UINT16 game_subversion() const { return _game_subversion; }
std::string name() const { return _name; }
std::string author() const { return _author; }
std::string description() const { return _description; }
std::string info_url() const { return _info_url; }
std::string version() const { return _version; }
mod_compat_e Compatible() const
{
#if (VERSION == 0 && SUBVERSION == 0)
// DEVELOP builds don't have any relevant version info.
// Just disable the checks entirely, you probably
// know what you're doing.
return MOD_COMPATIBLE;
#else
if (_game_version > VERSION || (_game_version == VERSION && _game_subversion > SUBVERSION))
{
// This mod might use features that don't exist
// in earlier versions, so tell the user to
// update their game to play with this mod.
return MOD_INCOMPATIBLE_FUTURE;
}
if (_game_version < VERSION)
{
// Whenever VERSION is incremented, it's because mod
// backwards compatibility has been broken beyond
// repair. This mod is too old to support!
return MOD_INCOMPATIBLE_PAST;
}
return MOD_COMPATIBLE;
#endif
}
};
#else
// C compatibility interface
struct mod_metadata_t;
typedef struct mod_metadata_t mod_metadata_t;
#endif
#ifdef __cplusplus
extern "C" {
#endif
patch_t *ModMetadata_GetIcon(mod_metadata_t *meta);
UINT16 ModMetadata_GetGameVersion(mod_metadata_t *meta);
UINT16 ModMetadata_GetGameSubVersion(mod_metadata_t *meta);
char *ModMetadata_GetName(mod_metadata_t *meta);
char *ModMetadata_GetAuthor(mod_metadata_t *meta);
char *ModMetadata_GetDescription(mod_metadata_t *meta);
char *ModMetadata_GetInfoURL(mod_metadata_t *meta);
char *ModMetadata_GetVersion(mod_metadata_t *meta);
mod_compat_e ModMetadata_Compatible(mod_metadata_t *meta);
#ifdef __cplusplus
} // extern "C"
#endif
#endif // __K_MODINFO_H__

View file

@ -919,6 +919,28 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
wadfiles[numwadfiles] = wadfile; wadfiles[numwadfiles] = wadfile;
numwadfiles++; // must come BEFORE W_LoadDehackedLumps, so any addfile called by COM_BufInsertText called by Lua doesn't overwrite what we just loaded numwadfiles++; // must come BEFORE W_LoadDehackedLumps, so any addfile called by COM_BufInsertText called by Lua doesn't overwrite what we just loaded
//
// fill out metadata
//
if (mainfile == false) // main files do not need a MODINFO
{
wadfile->metadata = new mod_metadata_t(numwadfiles - 1);
CONS_Printf(
"== %s (version %s) - by %s ==\n"
"%s\n"
"More @ %s\n"
"[ DESIGNED FOR RING RACERS %d.%d ]\n",
wadfile->metadata->name().c_str(),
wadfile->metadata->version().c_str(),
wadfile->metadata->author().c_str(),
wadfile->metadata->description().c_str(),
wadfile->metadata->info_url().c_str(),
wadfile->metadata->game_version(),
wadfile->metadata->game_subversion()
);
}
#ifdef HWRENDER #ifdef HWRENDER
// Read shaders from file // Read shaders from file
if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED)) if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))

View file

@ -19,6 +19,8 @@
#include "hardware/hw_data.h" #include "hardware/hw_data.h"
#endif #endif
#include "k_modinfo.h"
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
@ -130,6 +132,8 @@ struct wadfile_t
UINT8 md5sum[16]; UINT8 md5sum[16];
boolean important; // also network - !W_VerifyNMUSlumps boolean important; // also network - !W_VerifyNMUSlumps
mod_metadata_t *metadata;
}; };
#define WADFILENUM(lumpnum) (UINT16)((lumpnum)>>16) // wad flumpnum>>16) // wad file number in upper word #define WADFILENUM(lumpnum) (UINT16)((lumpnum)>>16) // wad flumpnum>>16) // wad file number in upper word