add basic mod support with support for mod convars

This commit is contained in:
BobTheBob 2021-07-20 02:14:17 +01:00
parent a71d52ffd1
commit 958d03d281
6 changed files with 207 additions and 5 deletions

View File

@ -1,5 +1,8 @@
#include "pch.h"
#include "convar.h"
#include <set>
std::set<std::string> g_CustomConvars; // this is used in modloading code to determine whether we've registered a mod convar already
typedef void(*ConVarConstructorType)(ConVar* newVar, const char* name, const char* defaultValue, int flags, const char* helpString);
ConVarConstructorType conVarConstructor;
@ -12,6 +15,8 @@ ConVar* RegisterConVar(const char* name, const char* defaultValue, int flags, co
ConVar* newVar = new ConVar;
conVarConstructor(newVar, name, defaultValue, flags, helpString);
g_CustomConvars.insert(name);
return newVar;
}

View File

@ -1,4 +1,5 @@
#pragma once
#include <set>
// taken directly from iconvar.h
// The default, no flags at all
@ -94,4 +95,6 @@ public:
ConVar* RegisterConVar(const char* name, const char* defaultValue, int flags, const char* helpString);
void InitialiseConVars(HMODULE baseAddress);
void InitialiseConVars(HMODULE baseAddress);
extern std::set<std::string> g_CustomConvars;

View File

@ -1,9 +1,136 @@
#include "pch.h"
#include "ModManager.h"
#include "rapidjson/rapidjson.h"
#include "modmanager.h"
#include "convar.h"
#include "rapidjson/error/en.h"
#include "rapidjson/document.h"
#include <filesystem>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
ModManager* g_ModManager;
Mod::Mod(fs::path modDir, char* jsonBuf)
{
wasReadSuccessfully = false;
rapidjson::Document modJson;
modJson.Parse<rapidjson::ParseFlag::kParseCommentsFlag | rapidjson::ParseFlag::kParseTrailingCommasFlag>(jsonBuf);
// fail if parse error
if (modJson.HasParseError())
{
spdlog::error("Failed reading mod file {}: encountered parse error \"{}\" at offset {}", (modDir / "mod.json").string(), GetParseError_En(modJson.GetParseError()), modJson.GetErrorOffset());
return;
}
// fail if it's not a json obj (could be an array, string, etc)
if (!modJson.IsObject())
{
spdlog::error("Failed reading mod file {}: file is not a JSON object", (modDir / "mod.json").string());
return;
}
// basic mod info
// name is required
if (!modJson.HasMember("Name"))
{
spdlog::error("Failed reading mod file {}: missing required member \"Name\"", (modDir / "mod.json").string());
return;
}
Name = modJson["Name"].GetString();
if (modJson.HasMember("Description"))
Description = modJson["Description"].GetString();
else
Description = "";
if (modJson.HasMember("Version"))
Version = modJson["Version"].GetString();
else
{
Version = "0.0.0";
spdlog::warn("Mod file {} is missing a version, consider adding a version", (modDir / "mod.json").string());
}
if (modJson.HasMember("DownloadLink"))
DownloadLink = modJson["DownloadLink"].GetString();
else
DownloadLink = "";
if (modJson.HasMember("RequiredOnClient"))
RequiredOnClient = modJson["RequiredOnClient"].GetBool();
else
RequiredOnClient = false;
// mod convars
if (modJson.HasMember("ConVars") && modJson["ConVars"].IsArray())
{
for (auto& convarObj : modJson["ConVars"].GetArray())
{
if (!convarObj.IsObject() || !convarObj.HasMember("Name") || !convarObj.HasMember("DefaultValue"))
continue;
ModConVar* convar = new ModConVar;
convar->Name = convarObj["Name"].GetString();
convar->DefaultValue = convarObj["DefaultValue"].GetString();
if (convarObj.HasMember("HelpString"))
convar->HelpString = convarObj["HelpString"].GetString();
else
convar->HelpString = "";
// todo: could possibly parse FCVAR names here instead
if (convarObj.HasMember("Flags"))
convar->Flags = convarObj["Flags"].GetInt();
else
convar->Flags = FCVAR_NONE;
ConVars.push_back(convar);
}
}
// mod scripts
if (modJson.HasMember("Scripts") && modJson["Scripts"].IsArray())
{
for (auto& scriptObj : modJson["Scripts"].GetArray())
{
if (!scriptObj.IsObject() || !scriptObj.HasMember("Path") || !scriptObj.HasMember("RunOn"))
continue;
ModScript* script = new ModScript;
script->Path = scriptObj["Path"].GetString();
script->RsonRunOn = scriptObj["RunOn"].GetString();
// callbacks
for (auto iterator = scriptObj.MemberBegin(); iterator != scriptObj.MemberEnd(); iterator++)
{
if (!iterator->name.IsString() || !iterator->value.IsObject())
continue;
ModScriptCallback* callback = new ModScriptCallback;
callback->HookedCodeCallback = iterator->name.GetString();
if (iterator->value.HasMember("Before") && iterator->value["Before"].IsString())
callback->BeforeCallback = iterator->value["Before"].GetString();
if (iterator->value.HasMember("After") && iterator->value["After"].IsString())
callback->AfterCallback = iterator->value["After"].GetString();
script->Callbacks.push_back(callback);
}
Scripts.push_back(script);
}
}
wasReadSuccessfully = true;
}
ModManager::ModManager()
{
LoadMods();
@ -11,10 +138,55 @@ ModManager::ModManager()
void ModManager::LoadMods()
{
std::vector<fs::path> modDirs;
// get mod directories
for (fs::directory_entry dir : fs::directory_iterator(MOD_FOLDER_PATH))
if (fs::exists(dir.path() / "mod.json"))
modDirs.push_back(dir.path());
for (fs::path modDir : modDirs)
{
// read mod json file
std::ifstream jsonStream(modDir / "mod.json");
std::stringstream jsonStringStream;
// fail if no mod json
if (jsonStream.fail())
{
spdlog::warn("Mod {} has a directory but no mod.json", modDir.string());
continue;
}
while (jsonStream.peek() != EOF)
jsonStringStream << (char)jsonStream.get();
jsonStream.close();
Mod* mod = new Mod(modDir, (char*)jsonStringStream.str().c_str());
if (mod->wasReadSuccessfully)
{
spdlog::info("Loaded mod {} successfully", mod->Name);
loadedMods.push_back(mod);
}
else
{
spdlog::warn("Skipping loading mod file {}", (modDir / "mod.json").string());
delete mod;
}
}
for (Mod* mod : loadedMods)
{
for (ModConVar* convar : mod->ConVars)
if (g_CustomConvars.find(convar->Name) == g_CustomConvars.end()) // make sure convar isn't registered yet
RegisterConVar(convar->Name.c_str(), convar->DefaultValue.c_str(), convar->Flags, convar->HelpString.c_str());
}
}
void InitialiseModManager(HMODULE baseAddress)
{
g_ModManager = new ModManager;
g_ModManager = new ModManager();
}

View File

@ -1,9 +1,15 @@
#pragma once
#include <string>
#include <vector>
#include <filesystem>
namespace fs = std::filesystem;
const fs::path MOD_FOLDER_PATH = "R2Northstar/mods";
class ModConVar
{
public:
std::string Name;
std::string DefaultValue;
std::string HelpString;
@ -12,6 +18,7 @@ class ModConVar
class ModScriptCallback
{
public:
std::string HookedCodeCallback;
// called before the codecallback is executed
@ -22,8 +29,9 @@ class ModScriptCallback
class ModScript
{
public:
std::string Path;
std::string ScriptsRsonSide;
std::string RsonRunOn;
std::vector<ModScriptCallback*> Callbacks;
};
@ -54,6 +62,13 @@ public:
std::vector<std::string> Vpks;
//std::vector<ModKeyValues*> KeyValues;
// other stuff
bool wasReadSuccessfully = false;
public:
Mod(fs::path modPath, char* jsonBuf);
};
class ModManager

View File

@ -2,6 +2,8 @@
#define PCH_H
#define _CRT_SECURE_NO_WARNINGS
#define RAPIDJSON_NOMEMBERITERATORCLASS // need this for rapidjson
#define NOMINMAX // this too
// add headers that you want to pre-compile here
#include <Windows.h>

View File

@ -28,6 +28,8 @@ void OnCommandSubmittedHook(CConsoleDialog* consoleDialog, const char* pCommand)
consoleDialog->m_pConsolePanel->Print("\n");
// todo: call the help command in the future
onCommandSubmittedOriginal(consoleDialog, pCommand);
}
// called from sourceinterface.cpp in client createinterface hooks, on GameClientExports001
@ -51,6 +53,9 @@ void InitialiseSourceConsole(HMODULE baseAddress)
RegisterConCommand("toggleconsole", ConCommand_toggleconsole, "toggles the console", FCVAR_NONE);
}
// logging stuff
SourceConsoleSink::SourceConsoleSink()
{
logColours.emplace(spdlog::level::trace, SourceColor(0, 255, 255, 255));