Merge branch 'mod-info' into 'master'

MODINFO lump

See merge request kart-krew-dev/ring-racers-internal!2443
This commit is contained in:
AJ Martinez 2025-07-22 05:35:21 +00:00
commit c06fc9cccf
5 changed files with 365 additions and 0 deletions

View file

@ -164,6 +164,7 @@ add_executable(SRB2SDL2 MACOSX_BUNDLE WIN32
k_bans.cpp
k_endcam.cpp
k_credits.cpp
k_modinfo.cpp
music.cpp
music_manager.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

@ -993,6 +993,28 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup, const
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
//
// 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
// Read shaders from file
if (rendermode == render_opengl && (vid.glstate == VID_GL_LIBRARY_LOADED))
@ -2432,6 +2454,9 @@ int W_VerifyNMUSlumps(const char *filename, boolean exit_on_error)
{"TLG_", 4}, // Generic button legends
{"MODINFO", 7}, // Addon metadata
{"MODICON", 7}, // Addon icon
#ifdef HWRENDER
{"SHADERS", 7},
{"SH_", 3},

View file

@ -19,6 +19,8 @@
#include "hardware/hw_data.h"
#endif
#include "k_modinfo.h"
#ifdef __cplusplus
extern "C" {
#endif
@ -130,6 +132,8 @@ struct wadfile_t
UINT8 md5sum[16];
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