122 lines
6.5 KiB
C++
122 lines
6.5 KiB
C++
// DS2Revival.asi - the Broken World side of the plugin set. It is the only plugin in "Broken World\scripts".
|
|
//
|
|
// In a Broken World process it loads the release plugins from the main game's scripts folder (..\scripts of the
|
|
// Broken World folder), so both games run the very same plugin files: one set to install and to update, one settings file
|
|
// (the main game's DS2Revival.ini), one [Plugins] switch per plugin. Each plugin finds out for itself (mods/common/
|
|
// plugin.h) that it runs in Broken World from the main folder.
|
|
//
|
|
// Never twice: when "Broken World\scripts" (or another folder the ASI loader reads) still holds a full copy of a plugin from
|
|
// an older install, the plugin of that name is not loaded from the main folder - the old copy keeps working as before - and
|
|
// the log tells the player to delete the old copies. A plugin also refuses to start when a module of its name is already
|
|
// loaded (plugin.h), which covers copies this check cannot see.
|
|
//
|
|
// Without a main game next to the Broken World folder (a Broken World folder on its own) it does nothing: the folder's own
|
|
// plugins stand alone. In the Steam game it does nothing.
|
|
//
|
|
// Its log is the main game's scripts\logs\DS2Revival.log (in a Broken World folder on its own: its own scripts\logs).
|
|
#include "../common/plugin.h"
|
|
|
|
namespace {
|
|
|
|
// The plugins that may be loaded, in the order the ASI loader used to load them (alphabetical): the release plugins
|
|
// (mods/build.py RELEASE; tools/test_settings_ini.py checks the list) and the opt-in controller preview.
|
|
const char* const kPlugins[] = {"DS2BrokenWorld", "DS2Controller", "DS2Fixes", "DS2Graphics", "DS2ModLoader", "DS2Multiplayer", "DS2Widescreen"};
|
|
|
|
// Folders of the Broken World exe in which the ASI loader looks for .asi files.
|
|
const char* const kLoaderFolders[] = {"", "\\scripts", "\\plugins", "\\update"};
|
|
|
|
// The main game's scripts folder (next to the Broken World folder, with the base game's exe above it): true and the path in
|
|
// `scripts`, else false. `base` (optional) gets the main game's folder.
|
|
bool MainScripts(char* scripts, size_t cap, char* base = nullptr) {
|
|
char bwDir[MAX_PATH], parent[MAX_PATH], baseExe[MAX_PATH];
|
|
ds2::ExeDir(bwDir, sizeof(bwDir));
|
|
strcpy_s(parent, bwDir);
|
|
if (char* s = std::strrchr(parent, '\\')) *s = 0;
|
|
sprintf_s(baseExe, "%s\\DungeonSiege2.exe", parent);
|
|
sprintf_s(scripts, cap, "%s\\scripts", parent);
|
|
if (base) strcpy_s(base, MAX_PATH, parent);
|
|
return ds2::FileExists(scripts) && ds2::FileExists(baseExe);
|
|
}
|
|
|
|
void LoadFromMainGame() {
|
|
char bwDir[MAX_PATH], base[MAX_PATH], scripts[MAX_PATH];
|
|
ds2::ExeDir(bwDir, sizeof(bwDir));
|
|
if (!MainScripts(scripts, sizeof(scripts), base)) {
|
|
ds2::log().Write("no Dungeon Siege II with a scripts folder next to the Broken World folder (%s): nothing to load, "
|
|
"the plugins in this folder run on their own", base);
|
|
return;
|
|
}
|
|
ds2::log().Write("main game: %s", base);
|
|
std::string oldCopies;
|
|
int loaded = 0, skipped = 0;
|
|
for (const char* name : kPlugins) {
|
|
char file[MAX_PATH];
|
|
sprintf_s(file, "%s\\%s.asi", scripts, name);
|
|
if (!ds2::FileExists(file)) {
|
|
ds2::log().Write("%s: not in %s (deleted there = off for Broken World too)", name, scripts);
|
|
continue;
|
|
}
|
|
std::string old; // a copy of the same plugin that the ASI loader loads from the Broken World folder
|
|
for (const char* sub : kLoaderFolders) {
|
|
char copy[MAX_PATH];
|
|
sprintf_s(copy, "%s%s\\%s.asi", bwDir, sub, name);
|
|
if (ds2::FileExists(copy)) { old = copy; break; }
|
|
}
|
|
if (!old.empty()) {
|
|
ds2::log().Write("%s: NOT loaded from the main game - an old copy is here: %s (it keeps working as before)", name, old.c_str());
|
|
oldCopies += (oldCopies.empty() ? "" : ", ") + old.substr(std::strlen(bwDir) + 1);
|
|
++skipped;
|
|
continue;
|
|
}
|
|
if (HMODULE m = GetModuleHandleA((std::string(name) + ".asi").c_str())) {
|
|
char at[MAX_PATH] = {};
|
|
GetModuleFileNameA(m, at, MAX_PATH);
|
|
ds2::log().Write("%s: already loaded from %s - not loaded again", name, at);
|
|
++skipped;
|
|
continue;
|
|
}
|
|
HMODULE m = LoadLibraryA(file);
|
|
if (m) {
|
|
ds2::log().Write("%s: loaded from %s", name, file);
|
|
++loaded;
|
|
} else {
|
|
ds2::log().Write("%s: FAILED to load %s (Windows error %lu)", name, file, GetLastError());
|
|
}
|
|
}
|
|
if (!oldCopies.empty())
|
|
ds2::log().Write("old copies in Broken World\\scripts: delete them (%s). Broken World runs the plugins of the main game's "
|
|
"scripts folder; the old copies are older versions and are why those plugins are not loaded from there.",
|
|
oldCopies.c_str());
|
|
ds2::log().Write("done: %d plugin(s) loaded from the main game, %d left to old copies / already loaded", loaded, skipped);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
BOOL WINAPI DllMain(HMODULE self, DWORD reason, LPVOID) {
|
|
if (reason != DLL_PROCESS_ATTACH) return TRUE;
|
|
DisableThreadLibraryCalls(self);
|
|
// The log goes to the main game's scripts\logs\DS2Revival.log when this runs in the Steam game's Broken World folder, else (a
|
|
// Broken World folder on its own) to its own scripts folder.
|
|
char path[MAX_PATH], own[MAX_PATH], mainScripts[MAX_PATH], ext[32] = ".log";
|
|
if (GetEnvironmentVariableA("DS2DEV_DOCS", nullptr, 0)) // test instances: one log each (as plugin.h)
|
|
sprintf_s(ext, ".%lu.log", GetCurrentProcessId());
|
|
GetModuleFileNameA(self, own, MAX_PATH);
|
|
if (char* s = std::strrchr(own, '\\')) *s = 0;
|
|
const bool inMainGame = MainScripts(mainScripts, sizeof(mainScripts));
|
|
ds2::RetireOldLogs(own); // the loader's own old log (the main game's folder is cleaned by its plugins)
|
|
ds2::LogPathIn(inMainGame ? mainScripts : own, "DS2Revival", ext, path, sizeof(path));
|
|
ds2::log().Open(path);
|
|
ds2::Exe exe = ds2::ExeKind();
|
|
ds2::log().Write("DS2Revival loaded; exe timestamp %08X (%s)", ds2::ExeTimestamp(),
|
|
exe == ds2::Exe::Steam23 ? "Steam v2.3" : exe == ds2::Exe::BrokenWorld ? "Broken World" : "unknown");
|
|
if (exe != ds2::Exe::BrokenWorld) {
|
|
ds2::log().Write("not Broken World: nothing to do (this plugin belongs in \"Broken World\\scripts\" only)");
|
|
return TRUE;
|
|
}
|
|
if (ds2::OtherCopyLoaded(self).size()) {
|
|
ds2::log().Write("another DS2Revival.asi is loaded: nothing to do");
|
|
return TRUE;
|
|
}
|
|
LoadFromMainGame();
|
|
return TRUE;
|
|
}
|