aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGeckoEidechse <gecko.eidechse+git@pm.me>2025-03-01 23:09:32 +0100
committerGeckoEidechse <gecko.eidechse+git@pm.me>2025-03-01 23:09:32 +0100
commitc277e3caf6c1f180a2f0f7a6171286c929f1ad59 (patch)
treea86a9294dcdc68cc6d6c2f23209046f5690e28f7
parentd8ba5e4292dcc59a933baf03eacb8a8cb5281e6c (diff)
parentc7e2d7e593a30d716bb529a3496c85544279cbeb (diff)
downloadNorthstarLauncher-feat/overhaul-mod-loading-locations.tar.gz
NorthstarLauncher-feat/overhaul-mod-loading-locations.zip
Merge branch 'main' into feat/overhaul-mod-loading-locationsfeat/overhaul-mod-loading-locations
-rw-r--r--.github/workflows/release.yml8
-rw-r--r--primedev/mods/autodownload/moddownloader.cpp57
-rw-r--r--primedev/mods/autodownload/moddownloader.h14
-rw-r--r--primedev/mods/modmanager.cpp245
-rw-r--r--primedev/mods/modmanager.h12
-rw-r--r--primedev/plugins/pluginmanager.cpp12
-rw-r--r--primedev/plugins/plugins.cpp14
-rw-r--r--primedev/plugins/plugins.h2
-rw-r--r--primedev/scripts/client/scriptbrowserhooks.cpp13
-rw-r--r--primedev/shared/misccommands.cpp1
10 files changed, 211 insertions, 167 deletions
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a440aea3..831b192e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -32,7 +32,7 @@ jobs:
- name: Build
run: cmake --build build/
- name: Upload launcher build as artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: northstar-launcher
path: |
@@ -40,7 +40,7 @@ jobs:
build/game/*.dll
build/game/bin/x64_retail/*.dll
- name: Upload debug build artifact
- uses: actions/upload-artifact@v3
+ uses: actions/upload-artifact@v4
with:
name: launcher-debug-files
path: |
@@ -53,12 +53,12 @@ jobs:
runs-on: ubuntu-22.04
steps:
- name: Download compiled launcher
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
name: northstar-launcher
path: northstar-launcher
- name: Download compiled launcher
- uses: actions/download-artifact@v3
+ uses: actions/download-artifact@v4
with:
name: launcher-debug-files
path: launcher-debug-files
diff --git a/primedev/mods/autodownload/moddownloader.cpp b/primedev/mods/autodownload/moddownloader.cpp
index 21b98942..de95a0c2 100644
--- a/primedev/mods/autodownload/moddownloader.cpp
+++ b/primedev/mods/autodownload/moddownloader.cpp
@@ -176,7 +176,7 @@ int ModDownloader::ModFetchingProgressCallback(
return 0;
}
-std::optional<fs::path> ModDownloader::FetchModFromDistantStore(std::string_view modName, VerifiedModVersion version)
+std::tuple<fs::path, bool> ModDownloader::FetchModFromDistantStore(std::string_view modName, VerifiedModVersion version)
{
std::string url = version.downloadLink;
std::string archiveName = fs::path(url).filename().generic_string();
@@ -190,7 +190,7 @@ std::optional<fs::path> ModDownloader::FetchModFromDistantStore(std::string_view
modState.state = DOWNLOADING;
// Download the actual archive
- bool failed = false;
+ bool success = false;
FILE* fp = fopen(downloadPath.generic_string().c_str(), "wb");
CURLcode result;
CURL* easyhandle;
@@ -219,13 +219,14 @@ std::optional<fs::path> ModDownloader::FetchModFromDistantStore(std::string_view
if (result == CURLcode::CURLE_OK)
{
spdlog::info("Mod archive successfully fetched.");
- return std::optional<fs::path>(downloadPath);
+ success = true;
}
else
{
spdlog::error("Fetching mod archive failed: {}", curl_easy_strerror(result));
- return std::optional<fs::path>();
}
+
+ return {downloadPath, success};
}
bool ModDownloader::IsModLegit(fs::path modPath, std::string_view expectedChecksum)
@@ -399,11 +400,9 @@ int GetModArchiveSize(unzFile file, unz_global_info64 info)
return totalSize;
}
-void ModDownloader::ExtractMod(fs::path modPath, VerifiedModPlatform platform)
+void ModDownloader::ExtractMod(fs::path modPath, fs::path destinationPath, VerifiedModPlatform platform)
{
unzFile file;
- std::string name;
- fs::path modDirectory;
file = unzOpen(modPath.generic_string().c_str());
ScopeGuard cleanup(
@@ -445,11 +444,6 @@ void ModDownloader::ExtractMod(fs::path modPath, VerifiedModPlatform platform)
return;
}
- // Mod directory name (removing the ".zip" fom the archive name)
- name = modPath.filename().string();
- name = name.substr(0, name.length() - 4);
- modDirectory = GetRemoteModFolderPath() / name;
-
for (int i = 0; i < gi.number_entry; i++)
{
char zipFilename[256];
@@ -459,7 +453,7 @@ void ModDownloader::ExtractMod(fs::path modPath, VerifiedModPlatform platform)
// Extract file
{
std::error_code ec;
- fs::path fileDestination = modDirectory / zipFilename;
+ fs::path fileDestination = destinationPath / zipFilename;
spdlog::info("=> {}", fileDestination.generic_string());
// Create parent directory if needed
@@ -589,6 +583,9 @@ void ModDownloader::ExtractMod(fs::path modPath, VerifiedModPlatform platform)
}
}
}
+
+ // Mod extraction went fine
+ modState.state = DONE;
}
void ModDownloader::DownloadMod(std::string modName, std::string modVersion)
@@ -603,11 +600,14 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion)
std::thread requestThread(
[this, modName, modVersion]()
{
+ std::string name;
fs::path archiveLocation;
+ fs::path modDirectory;
ScopeGuard cleanup(
[&]
{
+ // Remove downloaded archive
try
{
remove(archiveLocation);
@@ -617,14 +617,31 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion)
spdlog::error("Error while removing downloaded archive: {}", a.what());
}
+ // Remove mod if auto-download process failed
+ if (modState.state != DONE)
+ {
+ try
+ {
+ remove_all(modDirectory);
+ }
+ catch (const std::exception& e)
+ {
+ spdlog::error("Error while removing downloaded mod: {}", e.what());
+ }
+ }
+
spdlog::info("Done cleaning after downloading {}.", modName);
});
// Download mod archive
VerifiedModVersion fullVersion = verifiedMods[modName].versions[modVersion];
std::string expectedHash = fullVersion.checksum;
- std::optional<fs::path> fetchingResult = FetchModFromDistantStore(std::string_view(modName), fullVersion);
- if (!fetchingResult.has_value())
+
+ const std::tuple<fs::path, bool> downloadResult = FetchModFromDistantStore(std::string_view(modName), fullVersion);
+ archiveLocation = get<0>(downloadResult);
+ bool downloadSuccessful = get<1>(downloadResult);
+
+ if (!downloadSuccessful)
{
spdlog::error("Something went wrong while fetching archive, aborting.");
if (modState.state != ABORTED)
@@ -633,7 +650,7 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion)
}
return;
}
- archiveLocation = fetchingResult.value();
+
if (!IsModLegit(archiveLocation, std::string_view(expectedHash)))
{
spdlog::warn("Archive hash does not match expected checksum, aborting.");
@@ -641,9 +658,13 @@ void ModDownloader::DownloadMod(std::string modName, std::string modVersion)
return;
}
+ // Mod directory name (removing the ".zip" fom the archive name)
+ name = archiveLocation.filename().string();
+ name = name.substr(0, name.length() - 4);
+ modDirectory = GetRemoteModFolderPath() / name;
+
// Extract downloaded mod archive
- ExtractMod(archiveLocation, fullVersion.platform);
- modState.state = DONE;
+ ExtractMod(archiveLocation, modDirectory, fullVersion.platform);
});
requestThread.detach();
diff --git a/primedev/mods/autodownload/moddownloader.h b/primedev/mods/autodownload/moddownloader.h
index 2ac72a48..f8c0c664 100644
--- a/primedev/mods/autodownload/moddownloader.h
+++ b/primedev/mods/autodownload/moddownloader.h
@@ -43,14 +43,12 @@ private:
* input mod name as mod dependency string if bypass flag is set up; fetched
* archive is then stored in a temporary location.
*
- * If something went wrong during archive download, this will return an empty
- * optional object.
*
* @param modName name of the mod to be downloaded
* @param modVersion version of the mod to be downloaded
- * @returns location of the downloaded archive
+ * @returns tuple containing location of the downloaded archive and whether download completed successfully
*/
- std::optional<fs::path> FetchModFromDistantStore(std::string_view modName, VerifiedModVersion modVersion);
+ std::tuple<fs::path, bool> FetchModFromDistantStore(std::string_view modName, VerifiedModVersion modVersion);
/**
* Tells if a mod archive has not been corrupted.
@@ -69,14 +67,16 @@ private:
/**
* Extracts a mod archive to the game folder.
*
- * This extracts a downloaded mod archive from its original location to the
- * current game profile; the install folder is defined by the platform parameter.
+ * This extracts a downloaded mod archive from its original location, `modPath`,
+ * to the specified `destinationPath`; the way to decompress the archive is
+ * defined by the `platform` parameter.
*
* @param modPath location of the downloaded archive
+ * @param destinationPath destination of the extraction
* @param platform origin of the downloaded archive
* @returns nothing
*/
- void ExtractMod(fs::path modPath, VerifiedModPlatform platform);
+ void ExtractMod(fs::path modPath, fs::path destinationPath, VerifiedModPlatform platform);
public:
ModDownloader();
diff --git a/primedev/mods/modmanager.cpp b/primedev/mods/modmanager.cpp
index 43ee53ee..0794c97f 100644
--- a/primedev/mods/modmanager.cpp
+++ b/primedev/mods/modmanager.cpp
@@ -111,8 +111,6 @@ void ModManager::LoadMods()
if (m_bHasLoadedMods)
UnloadMods();
- std::vector<fs::path> modDirs;
-
// ensure dirs exist
fs::remove_all(GetCompiledAssetsPath());
fs::create_directories(GetCoreModFolderPath());
@@ -138,123 +136,8 @@ void ModManager::LoadMods()
m_bHasEnabledModsCfg = m_EnabledModsCfg.IsObject();
}
- // get mod directories
- std::filesystem::directory_iterator coreModsDir = fs::directory_iterator(GetCoreModFolderPath());
- std::filesystem::directory_iterator manualModsDir = fs::directory_iterator(GetManualModFolderPath());
- std::filesystem::directory_iterator remoteModsDir = fs::directory_iterator(GetRemoteModFolderPath());
- std::filesystem::directory_iterator thunderstoreModsDir = fs::directory_iterator(GetThunderstoreLegacyModFolderPath());
-
- // Set up regex for `Northstar.*` pattern
- std::regex northstar_pattern(R"(.*\\Northstar\..+)");
- for (fs::directory_entry dir : coreModsDir)
- {
- if (!std::regex_match(dir.path().string(), northstar_pattern))
- {
- spdlog::warn(
- "The following directory did not match 'Northstar.*' and is most likely an incorrectly manually installed mod: {}",
- dir.path().string());
- continue; // skip loading mod that doesn't match
- }
- if (fs::exists(dir.path() / "mod.json"))
- modDirs.push_back(dir.path());
- }
-
- for (fs::directory_entry dir : manualModsDir)
- if (fs::exists(dir.path() / "mod.json"))
- modDirs.push_back(dir.path());
-
- // Special case for Thunderstore and remote mods directories
- // Set up regex for `AUTHOR-MOD-VERSION` pattern
- std::regex pattern(R"(.*\\([a-zA-Z0-9_]+)-([a-zA-Z0-9_]+)-(\d+\.\d+\.\d+))");
-
- for (fs::directory_iterator dirIterator : {thunderstoreModsDir, remoteModsDir})
- {
- for (fs::directory_entry dir : dirIterator)
- {
- fs::path modsDir = dir.path() / "mods"; // Check for mods folder in the Thunderstore mod
- // Use regex to match `AUTHOR-MOD-VERSION` pattern
- if (!std::regex_match(dir.path().string(), pattern))
- {
- spdlog::warn("The following directory did not match 'AUTHOR-MOD-VERSION': {}", dir.path().string());
- continue; // skip loading mod that doesn't match
- }
- if (fs::exists(modsDir) && fs::is_directory(modsDir))
- {
- for (fs::directory_entry subDir : fs::directory_iterator(modsDir))
- {
- if (fs::exists(subDir.path() / "mod.json"))
- {
- modDirs.push_back(subDir.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 file at '{}' does not exist or could not be read, is it installed correctly?", (modDir / "mod.json").string());
- continue;
- }
-
- while (jsonStream.peek() != EOF)
- jsonStringStream << (char)jsonStream.get();
-
- jsonStream.close();
-
- Mod mod(modDir, (char*)jsonStringStream.str().c_str());
-
- for (auto& pair : mod.DependencyConstants)
- {
- if (m_DependencyConstants.find(pair.first) != m_DependencyConstants.end() && m_DependencyConstants[pair.first] != pair.second)
- {
- spdlog::error(
- "'{}' attempted to register a dependency constant '{}' for '{}' that already exists for '{}'. "
- "Change the constant name.",
- mod.Name,
- pair.first,
- pair.second,
- m_DependencyConstants[pair.first]);
- mod.m_bWasReadSuccessfully = false;
- break;
- }
- if (m_DependencyConstants.find(pair.first) == m_DependencyConstants.end())
- m_DependencyConstants.emplace(pair);
- }
-
- for (std::string& dependency : mod.PluginDependencyConstants)
- {
- m_PluginDependencyConstants.insert(dependency);
- }
-
- if (m_bHasEnabledModsCfg && m_EnabledModsCfg.HasMember(mod.Name.c_str()))
- mod.m_bEnabled = m_EnabledModsCfg[mod.Name.c_str()].IsTrue();
- else
- mod.m_bEnabled = true;
-
- if (mod.m_bWasReadSuccessfully)
- {
- if (mod.m_bEnabled)
- spdlog::info("'{}' loaded successfully, version {}", mod.Name, mod.Version);
- else
- spdlog::info("'{}' loaded successfully, version {} (DISABLED)", mod.Name, mod.Version);
-
- m_LoadedMods.push_back(mod);
- }
- else
- spdlog::warn("Mod file at '{}' failed to load", (modDir / "mod.json").string());
- }
-
- // sort by load prio, lowest-highest
- std::sort(m_LoadedMods.begin(), m_LoadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; });
+ // Load mod info from filesystem into `m_LoadedMods`
+ SearchFilesystemForMods();
// This is used to check if some mods have a folder but no entry in enabledmods.json
bool newModsDetected = false;
@@ -638,6 +521,130 @@ void ModManager::UnloadMods()
m_LoadedMods.clear();
}
+void ModManager::SearchFilesystemForMods()
+{
+ std::vector<fs::path> modDirs;
+ m_LoadedMods.clear();
+
+ // get mod directories
+ std::filesystem::directory_iterator coreModsDir = fs::directory_iterator(GetCoreModFolderPath());
+ std::filesystem::directory_iterator manualModsDir = fs::directory_iterator(GetManualModFolderPath());
+ std::filesystem::directory_iterator remoteModsDir = fs::directory_iterator(GetRemoteModFolderPath());
+ std::filesystem::directory_iterator thunderstoreModsDir = fs::directory_iterator(GetThunderstoreLegacyModFolderPath());
+
+ // Set up regex for `Northstar.*` pattern
+ std::regex northstar_pattern(R"(.*\\Northstar\..+)");
+ for (fs::directory_entry dir : coreModsDir)
+ {
+ if (!std::regex_match(dir.path().string(), northstar_pattern))
+ {
+ spdlog::warn(
+ "The following directory did not match 'Northstar.*' and is most likely an incorrectly manually installed mod: {}",
+ dir.path().string());
+ continue; // skip loading mod that doesn't match
+ }
+ if (fs::exists(dir.path() / "mod.json"))
+ modDirs.push_back(dir.path());
+ }
+
+ for (fs::directory_entry dir : manualModsDir)
+ if (fs::exists(dir.path() / "mod.json"))
+ modDirs.push_back(dir.path());
+
+ // Special case for Thunderstore and remote mods directories
+ // Set up regex for `AUTHOR-MOD-VERSION` pattern
+ std::regex pattern(R"(.*\\([a-zA-Z0-9_]+)-([a-zA-Z0-9_]+)-(\d+\.\d+\.\d+))");
+
+ for (fs::directory_iterator dirIterator : {thunderstoreModsDir, remoteModsDir})
+ {
+ for (fs::directory_entry dir : dirIterator)
+ {
+ fs::path modsDir = dir.path() / "mods"; // Check for mods folder in the Thunderstore mod
+ // Use regex to match `AUTHOR-MOD-VERSION` pattern
+ if (!std::regex_match(dir.path().string(), pattern))
+ {
+ spdlog::warn("The following directory did not match 'AUTHOR-MOD-VERSION': {}", dir.path().string());
+ continue; // skip loading mod that doesn't match
+ }
+ if (fs::exists(modsDir) && fs::is_directory(modsDir))
+ {
+ for (fs::directory_entry subDir : fs::directory_iterator(modsDir))
+ {
+ if (fs::exists(subDir.path() / "mod.json"))
+ {
+ modDirs.push_back(subDir.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 file at '{}' does not exist or could not be read, is it installed correctly?", (modDir / "mod.json").string());
+ continue;
+ }
+
+ while (jsonStream.peek() != EOF)
+ jsonStringStream << (char)jsonStream.get();
+
+ jsonStream.close();
+
+ Mod mod(modDir, (char*)jsonStringStream.str().c_str());
+
+ for (auto& pair : mod.DependencyConstants)
+ {
+ if (m_DependencyConstants.find(pair.first) != m_DependencyConstants.end() && m_DependencyConstants[pair.first] != pair.second)
+ {
+ spdlog::error(
+ "'{}' attempted to register a dependency constant '{}' for '{}' that already exists for '{}'. "
+ "Change the constant name.",
+ mod.Name,
+ pair.first,
+ pair.second,
+ m_DependencyConstants[pair.first]);
+ mod.m_bWasReadSuccessfully = false;
+ break;
+ }
+ if (m_DependencyConstants.find(pair.first) == m_DependencyConstants.end())
+ m_DependencyConstants.emplace(pair);
+ }
+
+ for (std::string& dependency : mod.PluginDependencyConstants)
+ {
+ m_PluginDependencyConstants.insert(dependency);
+ }
+
+ if (m_bHasEnabledModsCfg && m_EnabledModsCfg.HasMember(mod.Name.c_str()))
+ mod.m_bEnabled = m_EnabledModsCfg[mod.Name.c_str()].IsTrue();
+ else
+ mod.m_bEnabled = true;
+
+ if (mod.m_bWasReadSuccessfully)
+ {
+ if (mod.m_bEnabled)
+ spdlog::info("'{}' loaded successfully, version {}", mod.Name, mod.Version);
+ else
+ spdlog::info("'{}' loaded successfully, version {} (DISABLED)", mod.Name, mod.Version);
+
+ m_LoadedMods.push_back(mod);
+ }
+ else
+ spdlog::warn("Mod file at '{}' failed to load", (modDir / "mod.json").string());
+ }
+
+ // sort by load prio, lowest-highest
+ std::sort(m_LoadedMods.begin(), m_LoadedMods.end(), [](Mod& a, Mod& b) { return a.LoadPriority < b.LoadPriority; });
+}
+
std::string ModManager::NormaliseModFilePath(const fs::path path)
{
std::string str = path.lexically_normal().string();
diff --git a/primedev/mods/modmanager.h b/primedev/mods/modmanager.h
index 2beb4a06..ee6a78fe 100644
--- a/primedev/mods/modmanager.h
+++ b/primedev/mods/modmanager.h
@@ -47,6 +47,18 @@ public:
std::unordered_map<std::string, std::string> m_DependencyConstants;
std::unordered_set<std::string> m_PluginDependencyConstants;
+private:
+ /**
+ * Load information for all mods from filesystem.
+ *
+ * This looks for mods in several directories (expecting them to be formatted in
+ * some way); it then uses respective `mod.json` manifest files to create `Mod`
+ * instances, which are then stored in the `m_LoadedMods` variable.
+ *
+ * @returns nothing
+ **/
+ void SearchFilesystemForMods();
+
public:
ModManager();
void LoadMods();
diff --git a/primedev/plugins/pluginmanager.cpp b/primedev/plugins/pluginmanager.cpp
index 0b89f76e..aee3850e 100644
--- a/primedev/plugins/pluginmanager.cpp
+++ b/primedev/plugins/pluginmanager.cpp
@@ -1,6 +1,7 @@
#include "pluginmanager.h"
#include <regex>
+#include <ranges>
#include "plugins.h"
#include "config/profile.h"
#include "core/convar/concommand.h"
@@ -111,13 +112,14 @@ bool PluginManager::LoadPlugins(bool reloaded)
void PluginManager::ReloadPlugins()
{
- for (const Plugin& plugin : this->GetLoadedPlugins())
+ NS::log::PLUGINSYS->info("Reloading plugins");
+
+ for (const Plugin& plugin : this->plugins | std::views::reverse)
{
- plugin.Unload();
+ std::string name = plugin.GetName();
+ if (plugin.Reload())
+ NS::log::PLUGINSYS->info("Reloaded {}", name);
}
-
- this->plugins.clear();
- this->LoadPlugins(true);
}
void PluginManager::RemovePlugin(HMODULE handle)
diff --git a/primedev/plugins/plugins.cpp b/primedev/plugins/plugins.cpp
index 3e623167..8542f7ed 100644
--- a/primedev/plugins/plugins.cpp
+++ b/primedev/plugins/plugins.cpp
@@ -138,9 +138,9 @@ bool Plugin::Unload() const
if (IsValid())
{
- bool unloaded = m_callbacks->Unload();
+ bool shouldUnload = m_callbacks->Unload();
- if (!unloaded)
+ if (!shouldUnload)
return false;
}
@@ -154,14 +154,18 @@ bool Plugin::Unload() const
return true;
}
-void Plugin::Reload() const
+bool Plugin::Reload() const
{
+ std::string location = m_location;
+
bool unloaded = Unload();
if (!unloaded)
- return;
+ return false;
- g_pPluginManager->LoadPlugin(fs::path(m_location), true);
+ g_pPluginManager->LoadPlugin(fs::path(location), true);
+
+ return true;
}
void Plugin::Log(spdlog::level::level_enum level, char* msg) const
diff --git a/primedev/plugins/plugins.h b/primedev/plugins/plugins.h
index 95ec08b5..057c7438 100644
--- a/primedev/plugins/plugins.h
+++ b/primedev/plugins/plugins.h
@@ -28,7 +28,7 @@ public:
Plugin(std::string path);
bool Unload() const;
- void Reload() const;
+ bool Reload() const;
// sys
void Log(spdlog::level::level_enum level, char* msg) const;
diff --git a/primedev/scripts/client/scriptbrowserhooks.cpp b/primedev/scripts/client/scriptbrowserhooks.cpp
index dcf051d2..3c4af1b5 100644
--- a/primedev/scripts/client/scriptbrowserhooks.cpp
+++ b/primedev/scripts/client/scriptbrowserhooks.cpp
@@ -1,25 +1,22 @@
-AUTOHOOK_INIT()
-
bool* bIsOriginOverlayEnabled;
-// clang-format off
-AUTOHOOK(OpenExternalWebBrowser, engine.dll + 0x184E40,
-void, __fastcall, (char* pUrl, char flags))
-// clang-format on
+static void(__fastcall* o_pOpenExternalWebBrowser)(char* pUrl, char flags) = nullptr;
+static void __fastcall h_OpenExternalWebBrowser(char* pUrl, char flags)
{
bool bIsOriginOverlayEnabledOriginal = *bIsOriginOverlayEnabled;
bool isHttp = !strncmp(pUrl, "http://", 7) || !strncmp(pUrl, "https://", 8);
if (flags & 2 && isHttp) // custom force external browser flag
*bIsOriginOverlayEnabled = false; // if this bool is false, game will use an external browser rather than the origin overlay one
- OpenExternalWebBrowser(pUrl, flags);
+ o_pOpenExternalWebBrowser(pUrl, flags);
*bIsOriginOverlayEnabled = bIsOriginOverlayEnabledOriginal;
}
ON_DLL_LOAD_CLIENT("engine.dll", ScriptExternalBrowserHooks, (CModule module))
{
- AUTOHOOK_DISPATCH()
+ o_pOpenExternalWebBrowser = module.Offset(0x184E40).RCast<decltype(o_pOpenExternalWebBrowser)>();
+ HookAttach(&(PVOID&)o_pOpenExternalWebBrowser, (PVOID)h_OpenExternalWebBrowser);
bIsOriginOverlayEnabled = module.Offset(0x13978255).RCast<bool*>();
}
diff --git a/primedev/shared/misccommands.cpp b/primedev/shared/misccommands.cpp
index 29d92d2d..af936941 100644
--- a/primedev/shared/misccommands.cpp
+++ b/primedev/shared/misccommands.cpp
@@ -301,6 +301,7 @@ void FixupCvarFlags()
// these 2 could be FCVAR_CHEAT, i guess?
{"cl_draw_player_model", FCVAR_DEVELOPMENTONLY},
{"cl_always_draw_3p_player", FCVAR_DEVELOPMENTONLY},
+ {"cl_gib_lifetime", FCVAR_CHEAT},
{"idcolor_neutral", FCVAR_DEVELOPMENTONLY},
{"idcolor_ally", FCVAR_DEVELOPMENTONLY},
{"idcolor_ally_cb1", FCVAR_DEVELOPMENTONLY},