mod loader

This commit is contained in:
madeline
2026-04-20 04:17:42 -07:00
parent c43f33a5bc
commit 22d906a248
30 changed files with 1170 additions and 51 deletions
+6
View File
@@ -142,6 +142,12 @@ DynamicModuleControl::DynamicModuleControl(char const* name) {
}
#endif
#if TARGET_PC
// dump() is declared but its definition is inside #if !TARGET_PC above; stub it out.
void DynamicModuleControlBase::dump() {}
void DynamicModuleControlBase::dump(char*) {}
#endif
u32 DynamicModuleControl::sAllocBytes;
JKRArchive* DynamicModuleControl::sArchive;
+13
View File
@@ -0,0 +1,13 @@
#include "dusk/gx_helper.h"
#ifdef TARGET_PC
GXTexObjRAII::~GXTexObjRAII() { GXDestroyTexObj(this); }
void GXTexObjRAII::reset() { GXDestroyTexObj(this); }
#endif
GXScopedDebugGroup::GXScopedDebugGroup(const char* text) {
GXPushDebugGroup(text);
}
GXScopedDebugGroup::~GXScopedDebugGroup() {
GXPopDebugGroup();
}
+149
View File
@@ -0,0 +1,149 @@
#include "dusk/hook_system.hpp"
#include "dusk/logging.h"
#include <cstdint>
#include <cstring>
#include <funchook.h>
#include <unordered_map>
#include <vector>
namespace dusk {
extern void* g_dusk_hook_current_mod;
struct PreHookFn {
void* mod;
int32_t (*fn)(void* args);
};
struct VoidHookFn {
void* mod;
void (*fn)(void* args);
};
struct HookSlot {
std::vector<PreHookFn> pre;
VoidHookFn replace = {};
std::vector<VoidHookFn> post;
};
static std::unordered_map<uintptr_t, HookSlot>& registry() {
static std::unordered_map<uintptr_t, HookSlot> s;
return s;
}
static std::unordered_map<uintptr_t, void*>& installed() {
static std::unordered_map<uintptr_t, void*> s;
return s;
}
// Follow E9/FF25 chains to skip MSVC incremental-link and import stubs
static void* resolveImportThunk(void* addr) {
#if _WIN32
for (int i = 0; i < 8; ++i) {
const auto* p = static_cast<const uint8_t*>(addr);
if (p[0] == 0xFF && p[1] == 0x25) {
int32_t offset;
std::memcpy(&offset, p + 2, 4);
addr = const_cast<void*>(*reinterpret_cast<const void* const*>(p + 6 + offset));
break;
} else if (p[0] == 0xE9) {
int32_t offset;
std::memcpy(&offset, p + 1, 4);
addr = const_cast<uint8_t*>(p) + 5 + offset;
} else
break;
}
#endif
return addr;
}
struct ModGuard {
void* prev;
explicit ModGuard(void* mod) : prev(g_dusk_hook_current_mod) { g_dusk_hook_current_mod = mod; }
~ModGuard() { g_dusk_hook_current_mod = prev; }
};
void hookInstallByAddr(void* fn_addr, void* tramp_fn, void** orig_store) {
fn_addr = resolveImportThunk(fn_addr);
auto key = reinterpret_cast<uintptr_t>(fn_addr);
auto it = installed().find(key);
if (it != installed().end()) {
*orig_store = it->second;
return;
}
funchook_t* fh = funchook_create();
void* fn = fn_addr;
int prep = funchook_prepare(fh, &fn, tramp_fn);
int inst = (prep == 0) ? funchook_install(fh, 0) : -1;
if (prep != 0 || inst != 0) {
DuskLog.warn("HookSystem: funchook failed for {:p} (prepare={} install={})", fn_addr, prep,
inst);
funchook_destroy(fh);
return;
}
funchook_destroy(fh);
installed()[key] = fn;
*orig_store = fn;
}
bool hookDispatchPre(void* fn_addr, void* args) {
auto it = registry().find(reinterpret_cast<uintptr_t>(fn_addr));
if (it == registry().end())
return false;
auto& slot = it->second;
for (auto& h : slot.pre) {
ModGuard g(h.mod);
if (h.fn(args) != 0)
return true;
}
if (slot.replace.fn) {
ModGuard g(slot.replace.mod);
slot.replace.fn(args);
return true;
}
return false;
}
void hookDispatchPost(void* fn_addr, void* args) {
auto it = registry().find(reinterpret_cast<uintptr_t>(fn_addr));
if (it == registry().end())
return;
for (auto& h : it->second.post) {
if (h.fn) {
ModGuard g(h.mod);
h.fn(args);
}
}
}
void hookRegisterPre(void* fn_addr, void* mod, int32_t (*fn)(void* args)) {
registry()[reinterpret_cast<uintptr_t>(fn_addr)].pre.push_back({mod, fn});
}
void hookRegisterPost(void* fn_addr, void* mod, void (*fn)(void* args)) {
registry()[reinterpret_cast<uintptr_t>(fn_addr)].post.push_back({mod, fn});
}
void hookSetReplace(void* fn_addr, void* mod, void (*fn)(void* args)) {
auto& slot = registry()[reinterpret_cast<uintptr_t>(fn_addr)];
if (slot.replace.fn)
DuskLog.warn("HookSystem: replace hook for {} already set — overwriting", fn_addr);
slot.replace = {mod, fn};
}
void hookClearMod(void* mod) {
for (auto& [addr, slot] : registry()) {
auto erase = [&](auto& v) {
v.erase(
std::remove_if(v.begin(), v.end(), [mod](const auto& h) { return h.mod == mod; }),
v.end());
};
erase(slot.pre);
erase(slot.post);
if (slot.replace.mod == mod)
slot.replace = {};
}
}
} // namespace dusk
+2
View File
@@ -322,6 +322,7 @@ namespace dusk {
if (showMenu && ImGui::BeginMainMenuBar()) {
m_menuGame.draw();
m_menuEnhancements.draw();
m_menuMods.draw();
m_menuTools.draw();
const auto fpsLabel =
@@ -365,6 +366,7 @@ namespace dusk {
m_menuTools.ShowPlayerInfo();
m_menuTools.ShowAudioDebug();
m_menuTools.ShowSaveEditor();
m_menuMods.showModsWindow();
}
m_menuTools.ShowStateShare();
DuskDebugPad(); // temporary, remove later
+2
View File
@@ -11,6 +11,7 @@
#include "ImGuiFirstRunPreset.hpp"
#include "ImGuiMenuEnhancements.hpp"
#include "ImGuiMenuGame.hpp"
#include "ImGuiMenuMods.hpp"
#include "ImGuiMenuTools.hpp"
#include "ImGuiPreLaunchWindow.hpp"
#include "imgui.h"
@@ -53,6 +54,7 @@ private:
ImGuiFirstRunPreset m_firstRunPreset;
ImGuiMenuGame m_menuGame;
ImGuiMenuEnhancements m_menuEnhancements;
ImGuiMenuMods m_menuMods;
ImGuiPreLaunchWindow m_preLaunchWindow;
// Keep always last
+78
View File
@@ -0,0 +1,78 @@
#include "ImGuiMenuMods.hpp"
#include "ImGuiConsole.hpp"
#include "dusk/mod_loader.hpp"
#include "imgui.h"
namespace dusk {
void ImGuiMenuMods::draw() {
const auto& mods = ModLoader::instance().mods();
if (mods.empty()) return;
if (ImGui::BeginMenu("Mods")) {
if (ImGui::MenuItem("Mod Manager", nullptr, m_showWindow)) {
m_showWindow = !m_showWindow;
}
for (const auto& mod : mods) {
if (mod.menu_items.empty()) continue;
ImGui::Separator();
if (ImGui::BeginMenu(mod.name.c_str())) {
for (const auto& item : mod.menu_items) {
ModLoader::callDrawCallback(mod, item);
}
ImGui::EndMenu();
}
}
ImGui::EndMenu();
}
}
void ImGuiMenuMods::showModsWindow() {
if (!m_showWindow) return;
ImGui::SetNextWindowSize(ImVec2(520, 420), ImGuiCond_FirstUseEver);
if (!ImGui::Begin("Mod Manager", &m_showWindow)) {
ImGui::End();
return;
}
const auto& mods = ModLoader::instance().mods();
if (mods.empty()) {
ImGuiTextCenter("No mods loaded.");
ImGui::End();
return;
}
if (ImGui::BeginTabBar("##ModsOuter")) {
for (const auto& mod : mods) {
const std::string tabLabel = mod.name + (mod.active ? "" : " [disabled]");
if (ImGui::BeginTabItem(tabLabel.c_str())) {
ImGui::Text("Version: %s", mod.version.c_str());
ImGui::Text("Author: %s", mod.author.c_str());
ImGui::Text("Status: %s", mod.active ? "Active" : "Disabled");
ImGui::Text("Path: %s", mod.mod_path.c_str());
if (!mod.description.empty()) {
ImGui::Separator();
ImGui::TextWrapped("%s", mod.description.c_str());
}
for (const auto& cb : mod.tab_content) {
ImGui::Separator();
ModLoader::callDrawCallback(mod, cb);
}
ImGui::EndTabItem();
}
}
ImGui::EndTabBar();
}
ImGui::End();
}
} // namespace dusk
+15
View File
@@ -0,0 +1,15 @@
#pragma once
namespace dusk {
class ImGuiMenuMods {
public:
void draw();
void showModsWindow();
private:
bool m_showWindow = false;
};
} // namespace dusk
+15
View File
@@ -0,0 +1,15 @@
/**
* Thin Windows launcher EXE. The game lives in dusk.dll, this just forwards
* the Windows entry point to it. Keeping the game as a DLL lets mod .dll
* files link against dusk.lib and resolve all game symbols at load time.
*/
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
// see src/dusk/main.cpp
extern "C" int WINAPI dusk_WinMain(HINSTANCE hInst, HINSTANCE hPrev, PWSTR cmd, int show);
int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE hPrev, PWSTR cmd, int show) {
return dusk_WinMain(hInst, hPrev, cmd, show);
}
+3 -2
View File
@@ -1,5 +1,5 @@
#if _WIN32
#define WINDOWS_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <shellapi.h>
#endif
@@ -120,7 +120,8 @@ int main(int argc, char* argv[]) {
}
#if _WIN32
int WINAPI wWinMain(HINSTANCE, HINSTANCE, PWSTR, int) {
// Entry point called by the launcher executable.
extern "C" int WINAPI dusk_WinMain(HINSTANCE, HINSTANCE, PWSTR, int) {
return RunWindowsGuiEntryPoint();
}
#endif
+377
View File
@@ -0,0 +1,377 @@
#include "dusk/mod_loader.hpp"
#include "dusk/hook_system.hpp"
#include "dusk/logging.h"
#include <algorithm>
#include <cstdarg>
#include <filesystem>
#include <string>
#include "imgui.h"
#include "miniz.h"
#include "nlohmann/json.hpp"
#if defined(_WIN32)
#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
static void* pl_dlopen(const std::filesystem::path& p) {
return LoadLibraryW(p.wstring().c_str());
}
static void* pl_dlsym(void* h, const char* name) {
return reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(h), name));
}
static void pl_dlclose(void* h) {
FreeLibrary(static_cast<HMODULE>(h));
}
static std::string pl_dlerror() {
char buf[256]{};
FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,
GetLastError(), 0, buf, sizeof(buf), nullptr);
std::string s = buf;
while (!s.empty() && (s.back() == '\r' || s.back() == '\n'))
s.pop_back();
return s;
}
static constexpr const char* k_libExt = ".dll";
#else
#include <dlfcn.h>
static void* pl_dlopen(const std::filesystem::path& p) {
return dlopen(p.c_str(), RTLD_LAZY | RTLD_LOCAL);
}
static void* pl_dlsym(void* h, const char* name) {
return dlsym(h, name);
}
static void pl_dlclose(void* h) {
dlclose(h);
}
static std::string pl_dlerror() {
const char* e = dlerror();
return e ? e : "(unknown error)";
}
#if defined(__APPLE__)
static constexpr const char* k_libExt = ".dylib";
#else
static constexpr const char* k_libExt = ".so";
#endif
#endif
static dusk::LoadedMod* g_currentMod = nullptr;
namespace dusk {
void* g_dusk_hook_current_mod = nullptr;
}
struct ModGuard {
explicit ModGuard(dusk::LoadedMod* m) {
g_currentMod = m;
dusk::g_dusk_hook_current_mod = m;
}
~ModGuard() {
g_currentMod = nullptr;
dusk::g_dusk_hook_current_mod = nullptr;
}
};
static const char* modName() {
return g_currentMod ? g_currentMod->name.c_str() : "mod";
}
static void cb_log_info(const char* fmt, ...) {
va_list ap, ap2; va_start(ap, fmt); va_copy(ap2, ap);
std::string s(vsnprintf(nullptr, 0, fmt, ap2), '\0'); va_end(ap2);
vsnprintf(s.data(), s.size() + 1, fmt, ap); va_end(ap);
DuskLog.info("[{}] {}", modName(), s);
}
static void cb_log_warn(const char* fmt, ...) {
va_list ap, ap2; va_start(ap, fmt); va_copy(ap2, ap);
std::string s(vsnprintf(nullptr, 0, fmt, ap2), '\0'); va_end(ap2);
vsnprintf(s.data(), s.size() + 1, fmt, ap); va_end(ap);
DuskLog.warn("[{}] {}", modName(), s);
}
static void cb_log_error(const char* fmt, ...) {
va_list ap, ap2; va_start(ap, fmt); va_copy(ap2, ap);
std::string s(vsnprintf(nullptr, 0, fmt, ap2), '\0'); va_end(ap2);
vsnprintf(s.data(), s.size() + 1, fmt, ap); va_end(ap);
DuskLog.error("[{}] {}", modName(), s);
}
static void* cb_load_resource(const char* relative_path, size_t* out_size) {
if (out_size)
*out_size = 0;
if (!g_currentMod || !relative_path)
return nullptr;
mz_zip_archive zip{};
if (!mz_zip_reader_init_file(&zip, g_currentMod->mod_path.c_str(), 0)) {
DuskLog.warn("[{}] load_resource: could not open {}", g_currentMod->name,
g_currentMod->mod_path);
return nullptr;
}
std::string entry = std::string("res/") + relative_path;
size_t sz = 0;
void* data = mz_zip_reader_extract_file_to_heap(&zip, entry.c_str(), &sz, 0);
mz_zip_reader_end(&zip);
if (!data) {
DuskLog.warn("[{}] load_resource: '{}' not found in zip", g_currentMod->name, entry);
return nullptr;
}
if (out_size)
*out_size = sz;
return data;
}
static void cb_free_resource(void* data) {
mz_free(data);
}
static void cb_register_tab_content(void (*draw_fn)(void*), void* userdata) {
if (g_currentMod && draw_fn)
g_currentMod->tab_content.push_back({draw_fn, userdata});
}
static void cb_register_menu_item(void (*draw_fn)(void*), void* userdata) {
if (g_currentMod && draw_fn)
g_currentMod->menu_items.push_back({draw_fn, userdata});
}
static void api_hook_pre(void* addr, int32_t (*fn)(void* args)) {
dusk::hookRegisterPre(addr, g_currentMod, fn);
}
static void api_hook_post(void* addr, void (*fn)(void* args)) {
dusk::hookRegisterPost(addr, g_currentMod, fn);
}
static void api_hook_replace(void* addr, void (*fn)(void* args)) {
dusk::hookSetReplace(addr, g_currentMod, fn);
}
namespace dusk {
ModLoader& ModLoader::instance() {
static ModLoader inst;
return inst;
}
void ModLoader::buildAPI(LoadedMod& mod) {
mod.api.api_version = DUSK_MOD_API_VERSION;
mod.api.mod_dir = mod.dir.c_str();
mod.api.log_info = cb_log_info;
mod.api.log_warn = cb_log_warn;
mod.api.log_error = cb_log_error;
mod.api.load_resource = cb_load_resource;
mod.api.free_resource = cb_free_resource;
mod.api.register_tab_content = cb_register_tab_content;
mod.api.register_menu_item = cb_register_menu_item;
mod.api.hook_install = hookInstallByAddr;
mod.api.hook_pre = api_hook_pre;
mod.api.hook_post = api_hook_post;
mod.api.hook_replace = api_hook_replace;
mod.api.hook_dispatch_pre = hookDispatchPre;
mod.api.hook_dispatch_post = hookDispatchPost;
}
void ModLoader::tryLoadDusk(const std::filesystem::path& modPath) {
namespace fs = std::filesystem;
std::string metaName, metaVersion, metaAuthor, metaDescription;
{
mz_zip_archive zip{};
if (mz_zip_reader_init_file(&zip, modPath.string().c_str(), 0)) {
size_t jsonSize = 0;
void* jsonData = mz_zip_reader_extract_file_to_heap(&zip, "mod.json", &jsonSize, 0);
mz_zip_reader_end(&zip);
if (jsonData) {
try {
std::string jsonStr(static_cast<char*>(jsonData), jsonSize);
mz_free(jsonData);
jsonData = nullptr;
auto j = nlohmann::json::parse(jsonStr);
metaName = j.value("name", "");
metaVersion = j.value("version", "");
metaAuthor = j.value("author", "");
metaDescription = j.value("description", "");
} catch (const std::exception& e) {
mz_free(jsonData);
DuskLog.warn("ModLoader: bad mod.json in {}: {}", modPath.filename().string(),
e.what());
}
}
}
}
mz_zip_archive zip{};
if (!mz_zip_reader_init_file(&zip, modPath.string().c_str(), 0)) {
DuskLog.error("ModLoader: failed to open {}", modPath.filename().string());
return;
}
std::string dllEntry;
for (mz_uint i = 0, n = mz_zip_reader_get_num_files(&zip); i < n; ++i) {
mz_zip_archive_file_stat stat{};
if (!mz_zip_reader_file_stat(&zip, i, &stat))
continue;
if (mz_zip_reader_is_file_a_directory(&zip, i))
continue;
if (fs::path(stat.m_filename).extension() == k_libExt) {
dllEntry = stat.m_filename;
break;
}
}
if (dllEntry.empty()) {
mz_zip_reader_end(&zip);
DuskLog.warn("ModLoader: no *{} found in {} — skipping", k_libExt,
modPath.filename().string());
return;
}
const fs::path cacheDir = fs::path("mods") / ".cache" / modPath.stem();
std::error_code ec;
fs::create_directories(cacheDir, ec);
const fs::path dllCachePath = cacheDir / fs::path(dllEntry).filename();
if (!mz_zip_reader_extract_file_to_file(&zip, dllEntry.c_str(), dllCachePath.string().c_str(),
0))
{
mz_zip_reader_end(&zip);
DuskLog.error("ModLoader: failed to extract {} from {}", dllEntry,
modPath.filename().string());
return;
}
mz_zip_reader_end(&zip);
void* handle = pl_dlopen(dllCachePath);
if (!handle) {
DuskLog.error("ModLoader: failed to open {}: {}", dllCachePath.string(), pl_dlerror());
return;
}
LoadedMod mod;
mod.mod_path = fs::absolute(modPath).string();
mod.dir = fs::absolute(cacheDir).string();
mod.handle = handle;
mod.fn_init = reinterpret_cast<LoadedMod::FnInit>(pl_dlsym(handle, "mod_init"));
mod.fn_tick = reinterpret_cast<LoadedMod::FnTick>(pl_dlsym(handle, "mod_tick"));
mod.fn_cleanup = reinterpret_cast<LoadedMod::FnCleanup>(pl_dlsym(handle, "mod_cleanup"));
mod.fn_set_imgui_ctx =
reinterpret_cast<LoadedMod::FnSetImguiCtx>(pl_dlsym(handle, "dusk_mod_set_imgui_ctx"));
if (!mod.fn_init || !mod.fn_tick) {
DuskLog.error("ModLoader: {} missing mod_init or mod_tick — skipping",
fs::path(dllEntry).filename().string());
pl_dlclose(handle);
return;
}
mod.name = metaName.empty() ? modPath.stem().string() : metaName;
mod.version = metaVersion.empty() ? "?" : metaVersion;
mod.author = metaAuthor.empty() ? "unknown" : metaAuthor;
mod.description = metaDescription;
m_mods.push_back(std::move(mod));
DuskLog.info("ModLoader: found '{}' v{} by {} ({})", m_mods.back().name, m_mods.back().version,
m_mods.back().author, modPath.filename().string());
}
void ModLoader::init() {
if (m_initialized)
return;
m_initialized = true;
namespace fs = std::filesystem;
if (!fs::exists(m_modsDir)) {
DuskLog.info("ModLoader: mods directory '{}' not found — mod loading skipped",
m_modsDir.string());
return;
}
std::error_code ec;
std::vector<fs::directory_entry> entries;
for (auto& e : fs::directory_iterator(m_modsDir, ec))
if (e.is_regular_file() && e.path().extension() == ".dusk")
entries.push_back(e);
std::sort(entries.begin(), entries.end(),
[](const fs::directory_entry& a, const fs::directory_entry& b) {
return a.path().filename() < b.path().filename();
});
for (auto& entry : entries)
tryLoadDusk(entry.path());
if (m_mods.empty()) {
DuskLog.info("ModLoader: no mods found");
return;
}
DuskLog.info("ModLoader: initializing {} mod(s)...", m_mods.size());
for (auto& mod : m_mods)
buildAPI(mod);
for (auto& mod : m_mods) {
ModGuard guard(&mod);
try {
mod.fn_init(&mod.api);
mod.active = true;
DuskLog.info("ModLoader: '{}' initialized", mod.name);
} catch (const std::exception& e) {
DuskLog.error("ModLoader: exception in {}.mod_init(): {}", mod.name, e.what());
} catch (...) {
DuskLog.error("ModLoader: unknown exception in {}.mod_init()", mod.name);
}
}
auto active =
std::count_if(m_mods.begin(), m_mods.end(), [](const LoadedMod& m) { return m.active; });
DuskLog.info("ModLoader: {}/{} mod(s) active", active, m_mods.size());
}
void ModLoader::tick() {
for (auto& mod : m_mods) {
if (!mod.active)
continue;
ModGuard guard(&mod);
try {
mod.fn_tick(&mod.api);
} catch (const std::exception& e) {
DuskLog.error("ModLoader: exception in {}.mod_tick(): {} — disabling", mod.name,
e.what());
mod.active = false;
} catch (...) {
DuskLog.error("ModLoader: unknown exception in {}.mod_tick() — disabling", mod.name);
mod.active = false;
}
}
}
void ModLoader::shutdown() {
for (auto& mod : m_mods) {
hookClearMod(&mod);
if (mod.fn_cleanup) {
ModGuard guard(&mod);
try {
mod.fn_cleanup(&mod.api);
} catch (...) {
}
}
if (mod.handle) {
pl_dlclose(mod.handle);
mod.handle = nullptr;
}
}
m_mods.clear();
DuskLog.info("ModLoader: all mods unloaded");
}
void ModLoader::callDrawCallback(const LoadedMod& mod, const ModDrawCallback& cb) {
if (mod.fn_set_imgui_ctx)
mod.fn_set_imgui_ctx(ImGui::GetCurrentContext());
cb.draw_fn(cb.userdata);
}
} // namespace dusk
+2
View File
@@ -16,6 +16,7 @@
#include "d/d_tresure.h"
#include "dusk/frame_interpolation.h"
#include "dusk/logging.h"
#include "dusk/mod_loader.hpp"
#include "f_op/f_op_camera_mng.h"
#include "f_op/f_op_draw_tag.h"
#include "f_op/f_op_overlap_mng.h"
@@ -812,6 +813,7 @@ void fapGm_Execute() {
fpcM_ManagementFunc(NULL, fapGm_After);
#endif
cCt_Counter(0);
dusk::ModLoader::instance().tick();
}
fapGm_HIO_c g_HIO;
+6 -2
View File
@@ -53,6 +53,7 @@
#include "dusk/game_clock.h"
#include "dusk/gyro.h"
#include "dusk/imgui/ImGuiEngine.hpp"
#include "dusk/mod_loader.hpp"
#include "dusk/logging.h"
#include "dusk/main.h"
#include "dusk/imgui/ImGuiConsole.hpp"
@@ -190,6 +191,8 @@ void main01(void) {
OSReport("Calling cDyl_InitAsync()...\n");
cDyl_InitAsync();
dusk::ModLoader::instance().init();
g_mDoAud_audioHeap = JKRCreateSolidHeap(audioHeapSize, JKRGetCurrentHeap(), false);
JKRHEAP_NAME(g_mDoAud_audioHeap, "g_mDoAud_audioHeap");
@@ -292,6 +295,7 @@ void main01(void) {
} while (dusk::IsRunning);
exit:;
dusk::ModLoader::instance().shutdown();
}
static bool IsBackendAvailable(AuroraBackend backend) {
@@ -479,6 +483,7 @@ int game_main(int argc, char* argv[]) {
("h,help", "Print usage")
("console", "Show the Windows console window for logs", cxxopts::value<bool>()->default_value("false")->implicit_value("true"))
("dvd", "Path to DVD image file", cxxopts::value<std::string>())
("mods", "Path to mods directory", cxxopts::value<std::string>()->default_value("mods"))
("backend", "Graphics API backend to use (auto, d3d12, metal, vulkan, null)", cxxopts::value<std::string>())
("cvar", "Override configuration variables without modifying config", cxxopts::value<std::vector<std::string>>());
@@ -596,9 +601,8 @@ int game_main(int argc, char* argv[]) {
mDoMain::developmentMode = 1; // Force Dev Mode for Debugging
mDoDvdThd::SyncWidthSound = false;
dusk::ModLoader::instance().setModsDir(parsed_arg_options["mods"].as<std::string>());
OSReport("Starting main01 (Game Loop)...\n");
main01();
dusk::ShutdownCrashReporting();