Merge branch 'main' of https://github.com/TwilitRealm/dusk into randomizer

This commit is contained in:
gymnast86
2026-07-15 01:08:32 -07:00
243 changed files with 2746 additions and 1741 deletions
+1 -1
View File
@@ -5,7 +5,7 @@
#include "f_pc/f_pc_base.h"
#include "SSystem/SComponent/c_bg_s_grp_pass_chk.h"
#include "SSystem/SComponent/c_bg_s_poly_pass_chk.h"
#include "dusk/endian.h"
#include "helpers/endian.h"
struct cBgD_Vtx_t : public Vec {};
+1 -1
View File
@@ -2,7 +2,7 @@
#define C_M3D_G_TRI_H_
#include "SSystem/SComponent/c_m3d_g_pla.h"
#include "dusk/endian.h"
#include "helpers/endian.h"
class cM3dGCyl;
-6
View File
@@ -95,12 +95,6 @@ static void __THPAudioInitialize(THPAudioDecodeInfo* info, u8* ptr);
#define THP_TEXTURE_SET_COUNT 3
#endif
#if TARGET_PC
namespace dusk {
void MoviePlayerShutdown();
}
#endif
struct daMP_THPPlayer {
/* 0x000 */ DVDFileInfo fileInfo;
/* 0x03C */ THPHeader header;
+6 -6
View File
@@ -5,7 +5,7 @@
#include "SSystem/SComponent/c_xyz.h"
#if TARGET_PC
#include "dusk/batch.hpp"
#include "helpers/batch.hpp"
#endif
class cCcD_Obj;
@@ -112,11 +112,11 @@ public:
TGXTexObj mTexObj_l_J_Ohana00_64TEX;
TGXTexObj mTexObj_l_J_Ohana01_64128_0419TEX;
dusk::batch::LeafTemplate mTplHana00; // l_J_hana00DL
dusk::batch::LeafTemplate mTplHana00Cut; // l_J_hana00_cDL
dusk::batch::LeafTemplate mTplHana01; // l_J_hana01DL
dusk::batch::LeafTemplate mTplHana01Cut00; // l_J_hana01_c_00DL
dusk::batch::LeafTemplate mTplHana01Cut; // l_J_hana01_c_01DL
batch::LeafTemplate mTplHana00; // l_J_hana00DL
batch::LeafTemplate mTplHana00Cut; // l_J_hana00_cDL
batch::LeafTemplate mTplHana01; // l_J_hana01DL
batch::LeafTemplate mTplHana01Cut00; // l_J_hana01_c_00DL
batch::LeafTemplate mTplHana01Cut; // l_J_hana01_c_01DL
#endif
}; // Size: 0x12A54
+4 -4
View File
@@ -5,7 +5,7 @@
#include "SSystem/SComponent/c_xyz.h"
#if TARGET_PC
#include "../../../src/dusk/batch.hpp"
#include <helpers/batch.hpp>
#endif
class cCcD_Obj;
@@ -115,9 +115,9 @@ public:
TGXTexObj mTexObj_l_M_Hijiki00TEX;
TGXTexObj mTexObj_l_M_kusa05_RGBATEX;
dusk::batch::LeafTemplate mTplKusa9q; // l_M_Kusa_9qDL
dusk::batch::LeafTemplate mTplKusa9qCut; // l_M_Kusa_9q_cDL
dusk::batch::LeafTemplate mTplTengusa; // l_M_TenGusaDL
batch::LeafTemplate mTplKusa9q; // l_M_Kusa_9qDL
batch::LeafTemplate mTplKusa9qCut; // l_M_Kusa_9q_cDL
batch::LeafTemplate mTplTengusa; // l_M_TenGusaDL
#endif
}; // Size: 0x1D718
+3 -3
View File
@@ -6,9 +6,9 @@
#include "d/d_bg_w_base.h"
#include <mtx.h>
#include <types.h>
#include "dusk/offset_ptr.h"
#include "dusk/endian.h"
#include "dusk/endian_ssystem.h"
#include "helpers/offset_ptr.h"
#include "helpers/endian.h"
#include "helpers/endian_ssystem.h"
class cBgS_GrpPassChk;
class cBgS_PolyPassChk;
+1 -1
View File
@@ -6,7 +6,7 @@
#include "d/d_bg_plc.h"
#include "d/d_bg_s_sph_chk.h"
#include "d/d_bg_w_base.h"
#include "dusk/offset_ptr.h"
#include "helpers/offset_ptr.h"
class cBgS_GrpPassChk;
class cBgS_PolyPassChk;
+18 -16
View File
@@ -17,8 +17,14 @@
#include "m_Do/m_Do_graphic.h"
#include <cstring>
#if TARGET_PC
#include "dusk/profiling.hpp"
#if defined(DUSK_BUILDING_GAME)
#include <tracy/Tracy.hpp>
#include "dusk/settings.h"
#else
#ifndef ZoneScoped
#define ZoneScoped
#define ZoneScopedN(name)
#endif
#endif
enum dComIfG_ButtonStatus {
@@ -4907,27 +4913,23 @@ inline void dComIfGd_drawXluListDark() {
g_dComIfG_gameInfo.drawlist.drawXluListDark();
}
#if TARGET_PC
void dComIfGd_drawXluListInvisible();
#else
inline void dComIfGd_drawXluListInvisible() {
ZoneScoped;
#ifdef TARGET_PC
if (!dusk::getSettings().game.disableWaterRefraction) {
#endif
g_dComIfG_gameInfo.drawlist.drawXluListInvisible();
#ifdef TARGET_PC
}
#endif
g_dComIfG_gameInfo.drawlist.drawXluListInvisible();
}
#endif
#if TARGET_PC
void dComIfGd_drawOpaListInvisible();
#else
inline void dComIfGd_drawOpaListInvisible() {
ZoneScoped;
#ifdef TARGET_PC
if (!dusk::getSettings().game.disableWaterRefraction) {
#endif
g_dComIfG_gameInfo.drawlist.drawOpaListInvisible();
#ifdef TARGET_PC
}
#endif
g_dComIfG_gameInfo.drawlist.drawOpaListInvisible();
}
#endif
inline void dComIfGd_drawXluListZxlu() {
ZoneScoped;
+1 -1
View File
@@ -5,7 +5,7 @@
#include "JSystem/J2DGraph/J2DScreen.h"
#include "JSystem/J3DGraphBase/J3DSys.h"
#include "SSystem/SComponent/c_m3d_g_pla.h"
#include "dusk/gx_helper.h"
#include "helpers/gx_helper.h"
#include "f_op/f_op_view.h"
#include "global.h"
#include "m_Do/m_Do_ext.h"
+1 -1
View File
@@ -3,7 +3,7 @@
#include "global.h"
#include "f_pc/f_pc_base.h"
#include "dusk/endian.h"
#include "helpers/endian.h"
struct msg_class;
+2 -2
View File
@@ -4,8 +4,8 @@
#include "JSystem/JMessage/control.h"
#include "JSystem/JMessage/JMessage.h"
#include "SSystem/SComponent/c_xyz.h"
#include "dusk/endian.h"
#include "dusk/string.hpp"
#include "helpers/endian.h"
#include "helpers/string.hpp"
#if REGION_JPN
#define D_MSG_CLASS_PAGE_CNT_MAX 30
+1 -1
View File
@@ -2,7 +2,7 @@
#define D_MSG_D_MSG_FLOW_H
#include <types.h>
#include "dusk/endian.h"
#include "helpers/endian.h"
enum {
NODETYPE_MESSAGE_e = 1,
+3 -3
View File
@@ -8,7 +8,7 @@
#include "d/d_item_data.h"
#include "JSystem/JUtility/JUTAssert.h"
#include "JSystem/JHostIO/JORReflexible.h"
#include "dusk/endian.h"
#include "helpers/endian.h"
#if TARGET_PC
#include <unordered_map>
@@ -522,7 +522,7 @@ public:
#endif
void setPlayerName(const char* i_name) {
#if AVOID_UB
dusk::SafeStringCopyTruncate(mPlayerName, i_name);
SafeStringCopyTruncate(mPlayerName, i_name);
#else
strcpy(mPlayerName, i_name);
#endif
@@ -534,7 +534,7 @@ public:
#endif
void setHorseName(const char* i_name) {
#if AVOID_UB
dusk::SafeStringCopyTruncate(mHorseName, i_name);
SafeStringCopyTruncate(mHorseName, i_name);
#else
strcpy(mHorseName, i_name);
#endif
+1 -1
View File
@@ -4,7 +4,7 @@
#include "SSystem/SComponent/c_lib.h"
#include "d/d_kankyo.h"
#include "d/d_kankyo_data.h"
#include "dusk/offset_ptr.h"
#include "helpers/offset_ptr.h"
#include "f_op/f_op_actor_mng.h"
#include "global.h"
#include "os_report.h"
+1 -1
View File
@@ -2,7 +2,7 @@
#define D_D_TRESURE_H
#include <mtx.h>
#include "dusk/offset_ptr.h"
#include "helpers/offset_ptr.h"
class dTres_c {
public:
+1 -1
View File
@@ -8,7 +8,7 @@
#endif
#ifndef __MWERKS__
#include "dusk/math.h"
#include "helpers/math.h"
#endif
#endif // dolzel.h
-71
View File
@@ -1,71 +0,0 @@
#pragma once
#include <cstdint>
#include <functional>
#include <queue>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include "nlohmann/json.hpp"
namespace dusk {
enum class AchievementCategory : uint8_t {
Challenge,
Collection,
Minigame,
Misc,
Glitched
};
struct Achievement {
const char* key;
const char* name;
const char* description;
AchievementCategory category;
bool isCounter;
int32_t goal;
int32_t progress;
bool unlocked;
};
// Responsible for updating a.progress.
// Use extra for any per-achievement state that must survive across frames or sessions, extra is saved
using AchievementCheckFn = std::function<void(Achievement& a, nlohmann::json& extra)>;
class AchievementSystem {
public:
static AchievementSystem& get();
void load();
void save();
void tick();
void clearAll();
void clearOne(const char* key);
// Signals are visible to all achievement checks within the same tick, then cleared.
void signal(const char* key);
bool hasSignal(const char* key) const;
int signalCount(const char* key) const;
std::vector<Achievement> getAchievements() const;
private:
struct Entry {
Achievement achievement;
AchievementCheckFn check;
nlohmann::json extra;
};
AchievementSystem();
static std::vector<Entry> makeEntries();
void processEntry(Entry& e);
std::vector<Entry> m_entries;
std::unordered_map<std::string_view, int> m_signals;
bool m_loaded = false;
bool m_dirty = false;
};
} // namespace dusk
-51
View File
@@ -1,51 +0,0 @@
#pragma once
#include <unordered_map>
#include "dusk/config_var.hpp"
namespace dusk {
enum class ActionBinds {
FIRST_PERSON_CAMERA,
CALL_MIDNA,
OPEN_MAP_SCREEN,
TOGGLE_MINIMAP,
OPEN_DUSKLIGHT_MENU,
TURBO_SPEED_BUTTON,
COUNT,
};
struct ActionBindData {
std::array<config::ActionBindConfigVar, 4>* configVars{};
std::string actionName{};
};
struct ActionBindPressData {
bool pressedCurFrame{false};
bool pressedPrevFrame{false};
};
using ActionBindsMap = std::unordered_map<ActionBinds, ActionBindData>;
ActionBindsMap& getActionBinds();
bool isActionBound(ActionBinds action, u32 port);
void updateActionBindings();
void setVirtualActionBind(ActionBinds action, u32 port, bool pressed, bool available = true);
void clearVirtualActionBind(ActionBinds action, u32 port);
void clearAllVirtualActionBinds();
bool getActionBindTrig(ActionBinds action, u32 port);
bool getActionBindHold(ActionBinds action, u32 port);
bool getActionBindHoldAnyPort(ActionBinds action);
int getActionBindButton(ActionBinds action, u32 port);
}
-25
View File
@@ -1,25 +0,0 @@
#ifndef DUSK_APPNAME_HPP
#define DUSK_APPNAME_HPP
namespace dusk {
/**
* \brief The internal application name for the game.
*
* This gets used for file paths and such, and cannot be changed!
*/
constexpr auto AppName = "Dusklight";
/**
* Previous AppName to migrate data from.
*/
constexpr auto LegacyAppName = "Dusk";
/**
* \brief The internal organization name for the game.
*
* This gets used for file paths and such, and cannot be changed!
*/
constexpr auto OrgName = "TwilitRealm";
}
#endif // DUSK_APPNAME_HPP
-16
View File
@@ -1,16 +0,0 @@
#ifndef DUSK_AUDIO_H
#define DUSK_AUDIO_H
#if TARGET_PC
#define DUSK_AUDIO_DISABLED 0
#else
#define DUSK_AUDIO_DISABLED 0
#endif
#if TARGET_PC
#define DUSK_AUDIO_SKIP(...)
#else
#define DUSK_AUDIO_SKIP(...)
#endif
#endif // DUSK_AUDIO_H
-31
View File
@@ -1,31 +0,0 @@
#pragma once
#include <cmath>
#include <dolphin/types.h>
namespace dusk::audio {
// Converts a 0-1 volume to a linear amplitude multiplier.
// The curve is -4 dB per 10% step: 100% = 0 dB, 90% = -4 dB, ..., 0% = -inf dB
inline f32 MasterVolumeToLinear(f32 v) {
if (v <= 0.0f) {
return 0.0f;
}
return std::pow(10.0f, (v - 1.0f) * 2.0f);
}
/**
* Initialize the audio system and start playing audio.
*/
void Initialize();
void SetEnableReverb(bool value);
void SetMasterVolume(f32 value);
void SetPaused(bool paused);
u32 GetResetCount(int channelIdx);
f32 VolumeFromU16(u16 value);
}
-19
View File
@@ -1,19 +0,0 @@
#pragma once
#ifndef AUTOSAVE_H
#define AUTOSAVE_H
#include <m_Do/m_Do_MemCardRWmng.h>
#include <m_Do/m_Do_MemCard.h>
#include <d/actor/d_a_alink.h>
void noAutoSave();
void triggerAutoSave();
void updateAutoSave();
void enterAutoSave();
void autoSaving();
void waitingForWrite();
void endAutoSave();
void toggleAutoSave(bool enabled);
#endif
-197
View File
@@ -1,197 +0,0 @@
#ifndef DUSK_CONFIG_HPP
#define DUSK_CONFIG_HPP
#include <concepts>
#include <functional>
#include <nlohmann/json.hpp>
#include <stdexcept>
#include "config_var.hpp"
namespace dusk::config {
/*
* config.hpp is a heavier "full" header for the configuration system.
* For a basic overview and the basic types (sufficient for accessing settings),
* look at config_var.hpp.
*
* Avoid including this header in the entire game, it's heavier than I'd like!
*/
/**
* \brief Base class containing virtual functions used for save/load of CVars.
*/
class ConfigImplBase {
protected:
virtual ~ConfigImplBase() = default;
public:
/**
* \brief Load a JSON value into a CVar at the Value layer.
*/
virtual void loadFromJson(ConfigVarBase& cVar, const nlohmann::json& jsonValue) const = 0;
/**
* \brief Load a simple launch argument into the CVar at the Override layer.
*/
virtual void loadFromArg(ConfigVarBase& cVar, std::string_view stringValue) const = 0;
/**
* \brief Dump the value contained in the CVar to JSON.
*/
[[nodiscard]] virtual nlohmann::json dumpToJson(const ConfigVarBase& cVar) const = 0;
};
template <ConfigValue T>
class ConfigImpl : public ConfigImplBase {
// Just downcasting the references...
void loadFromJson(ConfigVarBase& cVar, const nlohmann::json& jsonValue) const final {
assert(typeid(cVar) == typeid(ConfigVar<T>));
loadFromJson(dynamic_cast<ConfigVar<T>&>(cVar), jsonValue);
}
void loadFromArg(ConfigVarBase& cVar, std::string_view stringValue) const final {
assert(typeid(cVar) == typeid(ConfigVar<T>));
loadFromArg(dynamic_cast<ConfigVar<T>&>(cVar), stringValue);
}
[[nodiscard]] nlohmann::json dumpToJson(const ConfigVarBase& cVar) const final {
assert(typeid(cVar) == typeid(ConfigVar<T>));
return dumpToJson(dynamic_cast<const ConfigVar<T>&>(cVar));
}
/**
* \brief Load a JSON value into a CVar at the Value layer.
*/
static void loadFromJson(ConfigVar<T>& cVar, const nlohmann::json& jsonValue);
/**
* \brief Load a simple launch argument into the CVar at the Override layer.
*/
static void loadFromArg(ConfigVar<T>& cVar, std::string_view stringValue);
/**
* \brief Dump the value contained in the CVar to JSON.
*/
[[nodiscard]] static nlohmann::json dumpToJson(const ConfigVar<T>& cVar);
};
/**
* \brief Thrown by config loading functions if the value provided is invalid for the CVar.
*/
class InvalidConfigError : public std::runtime_error {
public:
explicit InvalidConfigError(const char* what) : runtime_error(what) {}
};
/**
* \brief Register a CVar to make the config system aware of it.
*
* This must be done on startup *before* config has been loaded.
*/
void Register(ConfigVarBase& configVar);
/**
* \brief Unregister a CVar, detaching it from the config system.
*
* If the CVar carries a user-set value (Value or Speedrun layer), it is stashed as an
* unregistered key: Save() keeps writing it, and a later Register() of the same name restores
* it through the normal back-fill path. The CVar may be destroyed after this returns.
*/
void unregister(ConfigVarBase& configVar);
/**
* \brief Load config from the standard user preferences location.
*/
void load_from_user_preferences();
void load_from_file_name(const char* path);
void load_arg_override(std::string_view name, std::string_view value);
void shutdown();
/**
* \brief Save the config to file.
*/
void save();
/**
* \brief Get a registered CVar by name.
*
* @return null if the CVar does not exist.
*/
ConfigVarBase* GetConfigVar(std::string_view name);
/**
* \brief Resets all custom action bindings for a specific port to nothing
*
* @param port The port to be cleared of action bindings
*/
void ClearAllActionBindings(int port);
/**
* \brief Call a function on every registered CVar.
*/
void EnumerateRegistered(std::function<void(ConfigVarBase&)> callback);
/**
* \brief Type-erased change callback. previousValue points at the value before the mutation
* (a `const T*` for a `ConfigVar<T>`) and is valid only for the duration of the call.
*/
using ChangeCallback = std::function<void(ConfigVarBase& cVar, const void* previousValue)>;
/**
* \brief Token identifying a change subscription. 0 is never a valid token.
*/
using Subscription = u64;
/**
* \brief Subscribe to changes of the named CVar (registered or not yet registered).
*
* Fired synchronously on the mutating thread (in practice the game thread) whenever the CVar's
* effective value changes at runtime: setValue, override/speedrun setters and clears. Values
* applied by config load or launch arguments do *not* notify: loads happen during startup
* before the subsystems callbacks push values into are initialized, and each subsystem reads
* its initial value itself at its own init. Callbacks may mutate other CVars; a nested
* mutation of the same CVar applies but does not re-notify.
*/
Subscription subscribe(std::string_view name, ChangeCallback callback);
/**
* \brief Typed convenience overload: the callback receives the current and previous values.
*/
template <ConfigValue T, typename Callback>
requires std::invocable<Callback, const T&, const T&> Subscription subscribe(
ConfigVar<T>& cVar, Callback&& callback) {
return subscribe(cVar.getName(),
[&cVar, cb = std::forward<Callback>(callback)](ConfigVarBase&, const void* previousValue) {
cb(cVar.getValue(), *static_cast<const T*>(previousValue));
});
}
void unsubscribe(Subscription token);
/**
* \brief Register a CVar and attach a change callback in one step.
*
* Useful for pushing settings into external systems (e.g. aurora) from one place instead of
* every UI setter. The callback fires only for runtime changes (see subscribe); not when
* loaded from config or launch arguments.
*/
template <ConfigValue T, typename Callback>
requires std::invocable<Callback, const T&, const T&> Subscription Register(
ConfigVar<T>& cVar, Callback&& onChange) {
auto subscription = subscribe(cVar, std::forward<Callback>(onChange));
Register(static_cast<ConfigVarBase&>(cVar));
return subscription;
}
template <ConfigValue T>
const ConfigImplBase* GetConfigImpl() {
static ConfigImpl<T> config;
return &config;
}
} // namespace dusk::config
#endif
-390
View File
@@ -1,390 +0,0 @@
#ifndef DUSK_CONFIG_VAR_HPP
#define DUSK_CONFIG_VAR_HPP
#include "dolphin/types.h"
#include <concepts>
#include <cstdlib>
#include <limits>
#include <optional>
#include <string>
#include <type_traits>
/**
* The configuration system.
*
* Configuration works via "configuration variables" aka "CVars". Each stores a single value that
* may be individually written to/from a configuration file.
*
* CVars, like ogres, have layers. Higher layers (e.g. a set value) override lower layers
* (e.g. the default value).
*
* To define a CVar, simply make a global variable of type ConfigVar<T>,
* and make sure Register() is called on it during program startup.
*
* config_var.hpp contains the simplest "just the configuration vars themselves".
* This should be safe to include for files that need to *access* configuration,
* without blowing up compile times on implementation details.
*
* config.hpp on the other hand contains far more calls for mutating, loading, and defining CVars.
*/
namespace dusk::config {
/**
* \brief Layers that a configuration variable can currently be at.
*
* A configuration variable can be on one of multiple *layers*, which determines where
* the current value is coming from.
*/
enum class ConfigVarLayer : u8 {
/**
* The CVar is at the default value defined by the application code.
*/
Default,
/**
* The CVar has been modified by the user and may be saved to config.
*/
Value,
/**
* The CVar is modified by launch argument, overruling the normal config value.
* Will not get saved to config.
*/
Override,
/**
* The CVar is temporarily overridden by speedrun mode.
* Will not get saved to config. Cleared when speedrun mode is disabled.
* Lower priority than Override, so launch args still win.
*/
Speedrun,
};
class ConfigImplBase;
/**
* Base class that all CVars inherit from.
* You want the templated ConfigVar instead for actual usage.
*/
class ConfigVarBase {
protected:
/**
* The name of this CVar, used in the configuration file.
*/
std::string name;
/**
* Whether this CVar has been registered with the global managing logic.
* If this is not done, it is not functional.
*/
bool registered;
/**
* The layer this CVar is at.
*/
ConfigVarLayer layer;
/**
* Pointer to an implementation struct for various load/save calls.
*/
const ConfigImplBase* impl;
ConfigVarBase(const ConfigVarBase&) = delete;
ConfigVarBase(std::string name, const ConfigImplBase* impl);
/**
* Check that the CVar is registered, aborting if this is not the case.
*/
void checkRegistered() const {
if (!registered)
abort();
}
/**
* Whether any change subscriber (see config::subscribe) is attached to this CVar's name.
*/
[[nodiscard]] bool has_subscribers() const;
/**
* Notify change subscribers (see config::subscribe) that the effective value of this CVar
* changed. Called by mutators after the change has been applied; previousValue points at
* the old value (a `const T*` for a `ConfigVar<T>`), valid only for the duration of the
* call.
*/
void notify_changed(const void* previousValue);
public:
virtual ~ConfigVarBase();
/**
* Get the name of this CVar, used in the configuration file.
*/
[[nodiscard]] const char* getName() const noexcept;
/**
* Get the pointer to the implementation struct.
*/
[[nodiscard]] const ConfigImplBase* getImpl() const noexcept;
/**
* Get the layer this CVar is currently at.
*/
[[nodiscard]] constexpr ConfigVarLayer getLayer() const noexcept {
return layer;
}
/**
* Mark this CVar as being registered with the central save/load logic.
* This is necessary to make it legal to access.
*/
void markRegistered();
void unmarkRegistered();
/**
* Clear a speedrun-mode override if one is active on this CVar.
* Safe to call on any CVar, no-op if not at the Speedrun layer.
*/
virtual void clearSpeedrunOverride() {}
};
template <typename T>
concept ConfigValueInteger =
std::is_same_v<T, s8>
|| std::is_same_v<T, u8>
|| std::is_same_v<T, s16>
|| std::is_same_v<T, u16>
|| std::is_same_v<T, s32>
|| std::is_same_v<T, u32>
|| std::is_same_v<T, s64>
|| std::is_same_v<T, u64>;
template <typename T>
struct ConfigValueTraits {
static constexpr bool enabled = false;
};
/**
* \brief Concept that defines the legal set of types that can be used for CVar values.
*
* Valid types cannot be cv-qualified and must be basic primitive types (int, float, bool),
* strings, enums of the basic primitives, or explicitly-enabled structured settings.
*/
template <typename T>
concept ConfigValue =
!std::is_const_v<T>
&& !std::is_volatile_v<T>
&& std::equality_comparable<T>
&& (std::is_same_v<T, bool>
|| ConfigValueInteger<T>
|| std::is_same_v<T, f32>
|| std::is_same_v<T, f64>
|| std::is_same_v<T, std::string>
|| (std::is_enum_v<T> && ConfigValueInteger<std::underlying_type_t<T>>)
|| ConfigValueTraits<T>::enabled);
template <ConfigValue T>
const ConfigImplBase* GetConfigImpl();
template <ConfigValue T>
class ConfigImpl;
template <typename T>
struct ConfigEnumRange {
static constexpr auto min = std::numeric_limits<std::underlying_type_t<T>>::min();
static constexpr auto max = std::numeric_limits<std::underlying_type_t<T>>::max();
};
/**
* \brief A CVar storing values.
*
* @tparam T The type of value stored in the CVar.
*/
template <ConfigValue T>
class ConfigVar : public ConfigVarBase {
T defaultValue;
T value;
T overrideValue;
ConfigVarLayer priorLayer = ConfigVarLayer::Default;
public:
/**
* \brief Construct a CVar.
*
* @param name The name of this CVar. Must be unique.
* @param arg Arguments to forward to construct the default value.
*/
template <typename... Args>
ConfigVar(std::string name, Args&&... arg)
: ConfigVarBase(std::move(name), GetConfigImpl<T>()), defaultValue(std::forward<Args>(arg)...),
value(), overrideValue() {}
ConfigVar(ConfigVar const&) = delete;
/**
* \brief Get the current value of the CVar.
*
* This reference is not guaranteed to remain up-to-date after modification of the CVar.
* It will, however, remain sound to access.
*/
[[nodiscard]] constexpr const T& getValue() const noexcept {
checkRegistered();
switch (layer) {
case ConfigVarLayer::Default:
return defaultValue;
case ConfigVarLayer::Value:
return value;
case ConfigVarLayer::Override:
case ConfigVarLayer::Speedrun:
return overrideValue;
default:
abort();
}
}
[[nodiscard]] constexpr const T& getDefaultValue() const noexcept {
checkRegistered();
return defaultValue;
}
/**
* \brief Change the value of a CVar.
*
* The new value is always stored at the Value layer.
*
* @param newValue The new value the CVar will get.
* @param replaceOverride If true, clear an existing override layer if there is one.
* If this is false and there is an override layer,
* the result of getValue() will not change immediately.
*/
void setValue(T newValue, bool replaceOverride = true) {
checkRegistered();
const auto previous = previous_for_notify();
value = std::move(newValue);
if (replaceOverride) {
overrideValue = {};
layer = ConfigVarLayer::Value;
} else if (layer != ConfigVarLayer::Override) {
layer = ConfigVarLayer::Value;
}
notify_if_changed(previous);
}
operator const T&() {
return getValue();
}
/**
* \brief Give a CVar an override value.
*
* This overrides (but does not replace) the apparent set value of this CVar.
* The overriden value will not get saved to config.
*
* @param newValue The new value the CVar will get.
*/
void setOverrideValue(T newValue) {
checkRegistered();
const auto previous = previous_for_notify();
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Override;
notify_if_changed(previous);
}
/**
* \brief Give a CVar a speedrun-mode override value.
*
* Lower priority than a launch-arg override. Cleared when speedrun mode is disabled.
* The overridden value will not get saved to config.
*
* @param newValue The new value the CVar will get.
*/
void setSpeedrunValue(T newValue) {
checkRegistered();
if (layer != ConfigVarLayer::Override) {
const auto previous = previous_for_notify();
priorLayer = layer;
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Speedrun;
notify_if_changed(previous);
}
}
void clearOverride() {
checkRegistered();
if (layer == ConfigVarLayer::Override) {
const auto previous = previous_for_notify();
overrideValue = {};
layer = ConfigVarLayer::Value;
notify_if_changed(previous);
}
}
void clearSpeedrunOverride() override {
checkRegistered();
if (layer == ConfigVarLayer::Speedrun) {
const auto previous = previous_for_notify();
overrideValue = {};
layer = priorLayer;
notify_if_changed(previous);
}
}
/**
* \brief Get the user-persisted value, ignoring any temporary overrides.
*
* Used by Save() to write the correct value even when a speedrun override is active.
*/
[[nodiscard]] constexpr const T& getValueForSave() const noexcept {
checkRegistered();
const ConfigVarLayer effectiveLayer = (layer == ConfigVarLayer::Speedrun) ? priorLayer : layer;
return effectiveLayer == ConfigVarLayer::Default ? defaultValue : value;
}
private:
// The config loader applies values through the silent load_* methods below.
friend class ConfigImpl<T>;
/**
* Copy of the effective value before a mutation, taken only when someone is subscribed.
*/
[[nodiscard]] std::optional<T> previous_for_notify() const {
return has_subscribers() ? std::optional<T>{getValue()} : std::nullopt;
}
/**
* Notify subscribers if the effective value actually changed across a mutation.
*/
void notify_if_changed(const std::optional<T>& previous) {
if (previous.has_value() && !(getValue() == *previous)) {
notify_changed(&*previous);
}
}
/**
* setValue(newValue, false) without notifying change subscribers. Used when loading config:
* loads happen during startup before the subsystems change callbacks push values into are
* initialized, and each subsystem applies the loaded value itself at its own init.
*/
void load_value(T newValue) {
checkRegistered();
value = std::move(newValue);
if (layer != ConfigVarLayer::Override) {
layer = ConfigVarLayer::Value;
}
}
/**
* setOverrideValue without notifying change subscribers (see load_value).
*/
void load_override_value(T newValue) {
checkRegistered();
overrideValue = std::move(newValue);
layer = ConfigVarLayer::Override;
}
};
using ActionBindConfigVar = ConfigVar<int>;
}
#endif // DUSK_CONFIG_VAR_HPP
-7
View File
@@ -1,7 +0,0 @@
#pragma once
namespace dusk::crash_handler {
void install();
} // namespace dusk::crash_handler
-17
View File
@@ -1,17 +0,0 @@
#pragma once
namespace dusk::crash_reporting {
enum class Consent {
Unavailable,
Unknown,
Given,
Revoked,
};
void initialize();
void shutdown();
Consent get_consent();
void set_consent(bool enabled);
} // namespace dusk::crash_reporting
-14
View File
@@ -1,14 +0,0 @@
#pragma once
#ifdef DUSK_DISCORD
namespace dusk::discord {
void initialize();
void run_callbacks();
void update_presence();
void shutdown();
} // namespace dusk::discord
#endif // DUSK_DISCORD
-23
View File
@@ -1,23 +0,0 @@
#ifndef DUSK_DUSK_H
#define DUSK_DUSK_H
#include <aurora/aurora.h>
#include "aurora/gfx.h"
extern AuroraInfo auroraInfo;
namespace dusk {
extern AuroraStats lastFrameAuroraStats;
extern float frameUsagePct;
}
constexpr u32 defaultWindowWidth = 608;
constexpr u32 defaultWindowHeight = 448;
constexpr u32 defaultAspectRatioW = 19;
constexpr u32 defaultAspectRatioH = 14;
static_assert(defaultWindowWidth / defaultAspectRatioW == defaultWindowHeight / defaultAspectRatioH);
#endif // DUSK_DUSK_H
-31
View File
@@ -1,31 +0,0 @@
#pragma once
#include "dolphin/types.h"
#include "version.hpp"
namespace dusk {
struct OffsetVersion {
version::GameVersion mGameVersion;
u32 mOffset;
constexpr OffsetVersion(const version::GameVersion gameVersion, const u32 offset)
: mGameVersion(gameVersion), mOffset(offset) {}
};
/**
* Load bytes from the main DOL by GameCube virtual address
*/
bool LoadDolAsset(void* dst, std::initializer_list<OffsetVersion> virtualAddress, s32 size);
/**
* Load bytes from a REL file in the ISO filesystem, dst must be 32-byte aligned
*/
bool LoadRelAsset(void* dst, const char* dvdPath, std::initializer_list<OffsetVersion> offset, s32 size);
/**
* Load bytes from a REL inside RELS.arc
*/
bool LoadArchivedRelAsset(void* dst, u32 memType, const char* relFileName, std::initializer_list<OffsetVersion> offset, s32 size);
} // namespace dusk
-17
View File
@@ -1,17 +0,0 @@
#ifndef _SRC_EXTRAS_H_
#define _SRC_EXTRAS_H_
#ifdef __cplusplus
extern "C" {
#endif
#ifndef _MSC_VER
int strnicmp(const char* str1, const char* str2, int n);
int stricmp(const char* str1, const char* str2);
#endif
#ifdef __cplusplus
}
#endif
#endif // _SRC_EXTRAS_H_
-54
View File
@@ -1,54 +0,0 @@
#pragma once
#include <dolphin/mtx.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "settings.h"
class camera_process_class;
class view_class;
#ifdef __cplusplus
namespace dusk {
namespace frame_interp {
void ensure_initialized();
void begin_record();
void end_record();
void begin_sim_tick();
uint64_t sim_tick_seq();
void begin_frame(FrameInterpMode mode, bool is_sim_frame, float step);
void interpolate();
float get_interpolation_step();
void request_presentation_sync();
bool presentation_sync_active();
bool is_enabled();
// TODO: These should be phased out as UI is progressively updated to use game_clock
void set_ui_tick_pending(bool value);
bool get_ui_tick_pending();
bool is_sim_frame();
void record_camera(::camera_process_class* cam, int camera_id);
void interp_view(::view_class* view);
void record_final_mtx(Mtx m, const void *key);
void record_final_mtx(Mtx m);
bool lookup_replacement(const void* key, Mtx out);
bool lookup_concat_replacement(const void* lhs, const void* rhs, Mtx out);
typedef void (*InterpolationCallBack)(bool isSimFrame, void* pUserWork);
// call on a sim tick, will get called during presentation
void add_interpolation_callback(InterpolationCallBack pCallBack, void* pUserWork);
void begin_presentation_camera();
void end_presentation_camera();
} // namespace frame_interp
} // namespace dusk
#endif
-26
View File
@@ -1,26 +0,0 @@
#pragma once
namespace dusk::game_clock {
void ensure_initialized();
void reset_frame_timer();
constexpr float sim_pace() { return 1.0f / 30.0f; }
constexpr float period_for_original_frames(float frame_count) { return frame_count * sim_pace(); }
constexpr float ui_maximum_dt() { return 0.05f; }
constexpr float ui_initial_dt() { return 1.0f / 60.0f; }
struct MainLoopPacer {
float presentation_dt_seconds;
bool is_interpolating;
int sim_ticks_to_run;
float sim_pace;
};
MainLoopPacer advance_main_loop();
void commit_sim_tick();
float sample_interpolation_step();
float consume_interval(const void* consumer);
} // namespace dusk::game_clock
-11
View File
@@ -1,11 +0,0 @@
#pragma once
#ifndef GAMEPAD_COLOR_H
#define GAMEPAD_COLOR_H
namespace dusk::input {
void handleGamepadColor();
bool pad_has_led(int port) noexcept;
}
#endif
-10
View File
@@ -1,10 +0,0 @@
#pragma once
#include "mods/svc/gfx.h"
namespace dusk::mods {
void gfx_run_stage(GfxStage stage, const view_class* gameView = nullptr,
const view_port_class* gameViewport = nullptr);
} // namespace dusk::mods
-15
View File
@@ -1,15 +0,0 @@
#pragma once
namespace dusk::gyro {
void read(float dt);
void getAimDeltas(float& out_yaw, float& out_pitch);
bool queryGyroAimContext();
void rollgoalTick(bool play_active, s16 camera_yaw);
void rollgoalTableOffset(s16& out_ax, s16& out_az);
extern bool s_sensor_keep_alive;
bool get_sensor_keep_alive();
void set_sensor_keep_alive(bool value);
bool rollgoal_gyro_enabled();
} // namespace dusk::gyro
-27
View File
@@ -1,27 +0,0 @@
#ifndef DUSK_HOTKEYS_H
#define DUSK_HOTKEYS_H
namespace dusk::hotkeys {
#if __APPLE__
constexpr const char* DO_RESET = "Cmd+R";
#else
constexpr const char* DO_RESET = "Ctrl+R";
#endif
constexpr const char* SHOW_PROCESS_MANAGEMENT = "F2";
constexpr const char* SHOW_DEBUG_OVERLAY = "F3";
constexpr const char* SHOW_HEAP_VIEWER = "F4";
constexpr const char* SHOW_PLAYER_INFO = "F5";
constexpr const char* SHOW_SAVE_EDITOR = "F6";
constexpr const char* SHOW_STATE_SHARE = "F8";
constexpr const char* SHOW_DEBUG_CAMERA = "F9";
constexpr const char* SHOW_AUDIO_DEBUG = "F10";
constexpr const char* TOGGLE_FULLSCREEN = "F11";
constexpr const char* TURBO = "Tab";
}
#endif // DUSK_HOTKEYS_H
-18
View File
@@ -1,18 +0,0 @@
#ifndef _SRC_IMGUI_H_
#define _SRC_IMGUI_H_
#include <aurora/aurora.h>
#ifdef __cplusplus
extern "C"
{
#endif
void imgui_main(const AuroraInfo* info);
void frame_limiter();
#ifdef __cplusplus
}
#endif
#endif
-110
View File
@@ -1,110 +0,0 @@
#ifndef DUSK_IO_HPP
#define DUSK_IO_HPP
#include <filesystem>
#include <vector>
// I can't believe it's 2026 and neither SDL (no error codes) nor
// C++ (no error codes) have a file system API functional enough for me to use.
// Here you go, this one's inspired by C#. I only wrote the functions I need.
namespace dusk::io {
/**
* \brief A simple file stream wrapping cstdio FILE*.
*
* Methods on this class throw appropriate C++ exceptions when an error occurs.
*/
class FileStream {
FILE* file;
public:
FileStream() noexcept;
/**
* \brief Take ownership of a FILE* handle.
*/
explicit FileStream(FILE* file);
FileStream(const FileStream& other) = delete;
FileStream(FileStream&& other) noexcept;
~FileStream();
/**
* \brief Flush buffered writes and throw if the flush fails.
*/
void Flush();
/**
* \brief Open a file for reading at the given path.
*/
static FileStream OpenRead(const char* utf8Path);
/**
* \brief Open a file for reading at the given path.
*/
static FileStream OpenRead(const std::filesystem::path& path);
/**
* \brief Create a file for writing.
*
* If there is an existing file, its contents are demolished.
*/
static FileStream Create(const char* utf8Path);
/**
* \brief Create a file for writing.
*
* If there is an existing file, its contents are demolished.
*/
static FileStream Create(const std::filesystem::path& path);
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static std::vector<u8> ReadAllBytes(const char* utf8Path);
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static std::vector<u8> ReadAllBytes(const std::filesystem::path& path);
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static void WriteAllText(const char* utf8Path, std::string_view text);
/**
* \brief Read the byte contents of a file directly into a vector.
*/
static void WriteAllText(const std::filesystem::path& path, std::string_view text);
/**
* \brief Read the remaining contents of the file directly into a vector.
*/
std::vector<u8> ReadFull();
/**
* Get direct access to the underlying FILE* handle.
*/
[[nodiscard]] void* GetFileHandle() const noexcept { return file; }
/**
* Write data to the file.
*/
void Write(const char* data, size_t dataLen);
FILE* ToInner();
};
/**
* Converts a std::filesystem::path to a std::string, UTF-8, without exploding on Windows.
*/
inline std::string fs_path_to_string(const std::filesystem::path& path) {
const auto u8str = path.u8string();
return {reinterpret_cast<const char*>(u8str.c_str())};
}
} // namespace dusk::io
#endif // DUSK_IO_HPP
-37
View File
@@ -1,37 +0,0 @@
#ifndef DUSK_LAYOUT_H
#define DUSK_LAYOUT_H
#include "dolphin/types.h"
namespace dusk {
/**
* Helper struct for laying things out on the screen. Represents a rectangle via two corner
* positions.
*/
struct LayoutRect {
f32 PosX;
f32 PosY;
f32 PosX2;
f32 PosY2;
[[nodiscard]] constexpr f32 Width() const {
return PosX2 - PosX;
}
[[nodiscard]] constexpr f32 Height() const {
return PosY2 - PosY;
}
/**
* Calculates the position to render one rectangle inside another, centered and maintaining aspect ratio.
*/
[[nodiscard]] static LayoutRect FitRectInRect(
f32 widthOuter,
f32 heightOuter,
f32 widthInner,
f32 heightInner);
};
}
#endif // DUSK_LAYOUT_H
-16
View File
@@ -1,16 +0,0 @@
#pragma once
#include <cstdint>
namespace dusk::speedrun {
void onGameFrame();
uint64_t getFrameCount();
void start();
void reset();
void connectLiveSplit(const char* host = "127.0.0.1", int port = 16834);
void disconnectLiveSplit();
bool consumeConnectedEvent();
bool consumeDisconnectedEvent();
void updateLiveSplit();
void shutdown();
}
-38
View File
@@ -1,38 +0,0 @@
#ifndef DUSK_LOGGING_H
#define DUSK_LOGGING_H
#include <aurora/aurora.h>
#include <aurora/lib/logging.hpp>
#include <filesystem>
void aurora_log_callback(AuroraLogLevel level, const char* module, const char* message, unsigned int len);
namespace dusk {
void InitializeFileLogging(const std::filesystem::path& configDir, AuroraLogLevel logLevel);
void ShutdownFileLogging();
const char* GetLogFilePath();
int GetLogFileDescriptor();
void SendToStubLog(AuroraLogLevel level, const char* module, const char* message);
}
extern bool StubLogEnabled;
extern aurora::Module DuskLog;
#ifndef NDEBUG
#define STUB_LOG() DuskLog.debug("{} is a stub", __FUNCTION__)
#else
#define STUB_LOG()
#endif
#if TARGET_PC
#define STUB_RET(...) \
STUB_LOG(); \
return __VA_ARGS__;
#else
#define STUB_RET() (void)0
#endif
#endif
-38
View File
@@ -1,38 +0,0 @@
#ifndef DUSK_MAIN_H
#define DUSK_MAIN_H
#include <filesystem>
namespace dusk {
extern bool IsRunning;
extern bool IsShuttingDown;
extern bool IsGameLaunched;
extern bool RestartRequested;
extern std::filesystem::path ConfigPath;
extern std::filesystem::path CachePath;
extern uint8_t SaveRequested;
struct StageRequest {
std::string stage;
bool set;
s8 room;
s16 point;
s8 layer;
};
extern StageRequest StageRequested;
#if defined(__ANDROID__) || (defined(TARGET_OS_IOS) && TARGET_OS_IOS) || \
(defined(TARGET_OS_TV) && TARGET_OS_TV)
inline constexpr bool SupportsProcessRestart = false;
#else
inline constexpr bool SupportsProcessRestart = true;
#endif
void RequestRestart() noexcept;
} // namespace dusk
#endif // DUSK_MAIN_H
-626
View File
@@ -1,626 +0,0 @@
#pragma once
#include <array>
struct RoomEntry {
u8 roomNo;
std::vector<s16> roomPoints = {};
constexpr RoomEntry() : roomNo(0) {}
constexpr RoomEntry(const RoomEntry& other) = default;
template <int N>
constexpr RoomEntry(const u8 roomNo, const s16 (&points)[N]) :
roomNo(roomNo) {
for (int i = 0; i < N; i++) {
roomPoints.push_back(points[i]);
}
}
constexpr RoomEntry(const u8 roomNo) :
roomNo(roomNo) {
roomPoints.push_back(0);
}
};
struct MapEntry {
const char* mapName;
const char* mapFile;
std::vector<RoomEntry> mapRooms = {};
constexpr MapEntry() : mapName(nullptr), mapFile(nullptr) {}
constexpr MapEntry(const MapEntry& other) = default;
template <int N>
constexpr MapEntry(const char* mapName, const char* mapFile, const RoomEntry (&rooms)[N], const char*) : mapName(mapName),
mapFile(mapFile) {
for (int i = 0; i < N; i++) {
mapRooms.push_back(rooms[i]);
}
}
template <int N>
constexpr MapEntry(const char* mapName, const char* mapFile, const RoomEntry (&rooms)[N]) :
mapName(mapName), mapFile(mapFile) {
for (int i = 0; i < N; i++) {
mapRooms.push_back(rooms[i]);
}
}
constexpr MapEntry(const char* mapName, const char* mapFile) : mapName(mapName),
mapFile(mapFile) {}
};
struct RegionEntry {
const char* regionName = nullptr;
std::vector<MapEntry> maps = {};
template <int N>
constexpr RegionEntry(const char* regionName, const MapEntry (&maps)[N]) : regionName(regionName) {
for (int i = 0; i < N; i++) {
this->maps.push_back(maps[i]);
}
}
};
static const auto gameRegions = std::to_array({
RegionEntry("Hyrule Field", {
MapEntry("Hyrule Field", "F_SP121",
{
{0, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 50}},
{1, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 20, 21, 50}},
{2, {0, 1, 10, 20, 30}},
{3, {0, 1, 2, 3, 4, 5, 6, 10, 14, 15, 16, 17, 20, 21, 22, 88, 99}},
{4, {0, 1}},
{5, {0}},
{6, {0, 1, 2, 3, 10, 11, 12, 21, 100, 101}},
{7, {0, 1, 2, 6, 14, 22}},
{9, {0, 1, 2, 10}},
{10, {0, 1, 2, 3, 4, 5, 6, 7, 8, 14, 15, 16, 20, 21, 22, 23}},
{11, {0, 1}},
{12, {0, 1, 2, 3, 20, 21}},
{13, {0, 1, 2, 3, 4, 14, 20, 21, 22, 23, 98, 99}},
{14, {0, 1}},
{15, {0, 1, 2, 3, 4, 5, 20, 53, 100, 101}},
}),
}),
RegionEntry("Ordon", {
MapEntry("Ordon Village", "F_SP103", {
{0, {0, 1, 2, 4, 5, 6, 7, 9, 11, 13, 14, 15, 20, 21, 22, 23, 24, 25, 26, 27, 30, 99, 100, 101, 102, 103}},
}),
MapEntry("Outside Link's House", "F_SP103", {
{1, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 20, 21, 23, 24, 25, 26, 27, 30, 99, 100}},
}, "F_SP103_1"),
MapEntry("Ordon Ranch", "F_SP00", {
{0, {0, 1, 2, 3, 4, 5, 6, 7, 20, 30, 99, 127}},
}),
MapEntry("Ordon Spring", "F_SP104", {
{1, {0, 1, 2, 3, 4, 5, 6, 10, 20, 21, 22, 23, 24, 25, 26, 30, 99, 100, 111, 200, 254}}
}),
MapEntry("Bo's House", "R_SP01", {
{0, {0, 1, 2, 3}},
}),
MapEntry("Sera's Sundries", "R_SP01", {
{1, {0}},
}, "R_SP01_1"),
MapEntry("Jaggle's House", "R_SP01", {
{2, {0, 1, 2, 3}},
}, "R_SP01_2"),
MapEntry("Link's House", "R_SP01", {
{4, {0, 1, 2, 3, 4}},
{7, {0}},
}, "R_SP01_4"),
MapEntry("Rusl's House", "R_SP01", {
{5, {0, 1, 2}},
}, "R_SP01_5"),
}),
RegionEntry("Faron", {
MapEntry("South Faron Woods", "F_SP108", {
{0, {0, 3, 4, 20, 21, 22, 23, 24, 25, 100, 254}},
{1, {0, 1, 2, 3, 6, 20, 21, 100}},
{2, {0}},
{3, {0, 5, 99}},
{4, {0, 1, 2, 7, 8, 9, 23, 100}},
{5, {0, 1, 2, 3, 4, 6, 7, 10, 24, 25, 50, 60, 98, 100}},
{8, {0, 1, 2, 3}},
{11, {0}},
{14, {0, 1, 2, 3, 10, 50, 100, 150, 200, 254}},
}),
MapEntry("North Faron Woods", "F_SP108", {
{6, {0, 1, 2, 3, 10, 50, 100, 150, 200, 254}},
}, "F_SP108"),
MapEntry("Lost Woods", "F_SP117", {
{3, {0, 1, 2, 3, 4, 5, 6}},
}),
MapEntry("Sacred Grove", "F_SP117", {
{1, {1, 3, 4, 5, 6, 10, 20, 21, 50, 51, 99, 100, 102, 150, 200, 254}}
}, "F_SP117_1"),
MapEntry("Temple of Time (Past)", "F_SP117", {
{2, {0, 1, 3, 52, 101, 102}},
}, "F_SP117_2"),
MapEntry("Faron Woods Cave", "D_SB10", {
{0, {0, 1, 20, 21}},
}),
MapEntry("Coro's House", "R_SP108", {
{0, {0, 1}},
}),
}),
RegionEntry("Eldin", {
MapEntry("Kakariko Village", "F_SP109", {
{0, {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 30, 31, 32, 33, 34, 35,
36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51,
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67,
68, 69, 70, 71, 100, 101,
}},
}),
MapEntry("Death Mountain Trail", "F_SP110", {
{0, {0, 1, 2, 100, 200}},
{1, {0}},
{2, {0}},
{3, {0, 1, 2, 3, 4, 5, 6}},
}),
MapEntry("Kakariko Graveyard", "F_SP111", {
{0, {0, 1, 2, 3, 4, 5, 6, 111}},
}),
MapEntry("Hidden Village", "F_SP128", {
{0, {0, 1, 2, 3, 4, 5, 100}},
}),
MapEntry("Renado's Sanctuary", "R_SP109", {
{0, {0, 2, 3, 5, 6, 7, 8, 10, 20, 21, 22}},
}),
MapEntry("Sanctuary Basement", "R_SP209", {
{7, {0, 1, 2}},
}),
MapEntry("Barnes' Bombs", "R_SP109", {
{1, {0, 1, 2, 3}},
}, "R_SP109_1"),
MapEntry("Elde Inn", "R_SP109", {
{2, {0, 1, 2, 3}},
}, "R_SP109_2"),
MapEntry("Malo Mart", "R_SP109", {
{3, {0, 1}},
}, "R_SP109_3"),
MapEntry("Lookout Tower", "R_SP109", {
{4, {0, 1, 2}},
}, "R_SP109_4"),
MapEntry("Bomb Warehouse", "R_SP109", {
{5, {0, 1}},
}, "R_SP109_5"),
MapEntry("Abandoned House", "R_SP109", {
{6, {0, 1, 5}},
}, "R_SP109_6"),
MapEntry("Goron Elder's Hall", "R_SP110", {
{0, {0, 1, 2, 3, 4, 100}},
}),
}),
RegionEntry("Lanayru", {
MapEntry("Outside Castle Town - West", "F_SP122", {
{8, {0, 1, 2, 3, 4, 5, 6, 7, 76, 100, 101, 111, 200, 254}},
}),
MapEntry("Outside Castle Town - South", "F_SP122", {
{16, {0, 1, 2, 3, 4, 111}},
}, "F_SP122_16"),
MapEntry("Outside Castle Town - East", "F_SP122", {
{17, {0, 1, 4}},
}, "F_SP122_17"),
MapEntry("Castle Town", "F_SP116", {
{0, {0, 3, 4, 5, 6, 11, 12, 13, 14, 15, 16, 20, 50, 99, 100}},
{1, {0, 1, 30, 40, 50, 100, 111}},
{2, {0, 1, 2, 3, 4}},
{3, {0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 12, 13, 30}},
{4, {0, 2, 3, 4, 5, 6}},
}),
MapEntry("Zora's River", "F_SP112", {
{1, {0, 1, 2, 3, 4, 5, 6, 7, 10, 11, 12, 13, 14, 15, 16, 17}},
}),
MapEntry("Zora's Domain", "F_SP113", {
{0, {0, 1, 3, 4, 5, 7, 8, 10, 50, 97, 99, 254}},
{1, {0, 1, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 20, 30, 34, 98, 100, 101}},
}),
MapEntry("Lake Hylia", "F_SP115", {
{0, {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
16, 17, 20, 25, 29, 30, 31, 32, 33, 34, 40, 50, 55, 70, 75, 76,
77, 78, 99, 100, 101, 133, 134, 150, 200, 254,
}},
}),
MapEntry("Lanayru Spring", "F_SP115", {
{1, {0, 1, 20, 21, 22, 23, 100}},
}, "F_SP115_1"),
MapEntry("Upper Zora's River", "F_SP126", {
{0, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 99, 100, 101, 200}},
}),
MapEntry("Fishing Pond", "F_SP127", {
{0, {0, 1, 2, 3, 4, 5, 100}},
}),
MapEntry("Castle Town Sewers", "R_SP107", {
{0, {0, 1, 2, 3, 21, 22, 23, 24, 25}},
{1, {0, 1, 2, 3, 4, 5, 6, 7}},
{2, {0, 1, 2, 2, 20}},
{3, {0, 1, 20, 21, 22, 23, 24}},
}),
MapEntry("Telma's Bar / Secret Passage", "R_SP116", {
{5, {0, 1, 2, 3, 4, 5, 6, 20, 30}},
{6, {10, 11, 12, 20, 21}},
}),
MapEntry("Hena's Cabin", "R_SP127", {
{0, {0, 1}},
}),
MapEntry("Impaz's House", "R_SP128", {
{0, {0}},
}),
MapEntry("Malo Mart", "R_SP160", {
{0, {0, 1, 2}},
}),
MapEntry("Fanadi's Palace", "R_SP160", {
{1, {0, 1, 2}},
}, "R_SP160_1"),
MapEntry("Medical Clinic", "R_SP160", {
{2, {0, 1, 2}},
}, "R_SP160_2"),
MapEntry("Agitha's Castle", "R_SP160", {
{3, {0, 1, 2}},
}, "R_SP160_3"),
MapEntry("Goron Shop", "R_SP160", {
{4, {0, 1, 2}},
}, "R_SP160_4"),
MapEntry("Jovani's House", "R_SP160", {
{5, {0, 1, 2, 3, 4}},
}, "R_SP160_5"),
MapEntry("STAR Tent", "R_SP161", {
{7, {0, 1, 2, 3, 4}},
}),
}),
RegionEntry("Gerudo Desert", {
MapEntry("Bulblin Camp", "F_SP118", {
{0, {0}}, //TODO: can't load this one far enough to see its valid points
{1, {0, 1, 2, 6}},
{3, {0, 2, 3, 4, 5, 7}},
}),
MapEntry("Bulblin Camp Beta Room", "F_SP118", {
{2, {0}},
}, "F_SP118_2"),
MapEntry("Gerudo Desert", "F_SP124", {
{0, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 50, 51, 52, 53, 111}},
}),
MapEntry("Mirror Chamber", "F_SP125", {
{4, {0, 1, 2, 3, 4, 5, 6, 7, 8, 51, 52, 54, 55, 56, 57, 58}},
}),
}),
RegionEntry("Snowpeak", {
MapEntry("Snowpeak Mountain", "F_SP114", {
{0, {0, 1, 2, 4, 5, 6, 7, 10, 13, 14, 15, 100}},
{1, {1, 2, 3, 5, 6, 9, 10, 11, 12, 13, 20, 21, 22, 100}},
{2, {8, 12, 13}},
}),
}),
RegionEntry("Forest Temple", {
MapEntry("Forest Temple", "D_MN05", {
{0, {0}},
{1, {0}},
{2, {0}},
{3, {0, 1}},
{4, {0, 1, 2}},
{5, {0, 1}},
{7, {0}},
{9, {0}},
{10, {0}},
{11, {0}},
{12, {0, 1}},
{19, {0}},
{22, {0}},
}),
MapEntry("Diababa Arena", "D_MN05A", {
{50, {0, 1}},
}),
MapEntry("Ook Arena", "D_MN05B", {
{51, {0, 1, 2}},
}),
}),
RegionEntry("Goron Mines", {
MapEntry("Goron Mines", "D_MN04", {
{1, {0, 1}},
{3, {0}},
{4, {0, 1}},
{5, {0}},
{6, {0, 1}},
{7, {0, 1}},
{9, {0, 1, 2, 3}},
{11, {0, 1}},
{12, {0, 1}},
{13, {0}},
{14, {0, 1}},
{16, {0}},
{17, {0, 1}},
}),
MapEntry("Fyrus Arena", "D_MN04A", {
{50, {0, 1}},
}),
MapEntry("Dangoro Arena", "D_MN04B", {
{51, {0, 1, 2, 3}},
}),
}),
RegionEntry("Lakebed Temple", {
MapEntry("Lakebed Temple", "D_MN01", {
{0, {0, 1, 2}},
{1, {0}},
{2, {0}},
{3, {0, 1, 2}},
{5, {0, 1, 2}},
{6, {0, 1, 2}},
{7, {0}},
{8, {0, 2}},
{9, {0, 1, 2, 3, 4}},
{10, {0, 1}},
{11, {0}},
{12, {0, 1, 2}},
{13, {0}},
}),
MapEntry("Morpheel Arena", "D_MN01A", {
{50, {0, 1, 2, 3}},
}),
MapEntry("Deku Toad Arena", "D_MN01B", {
{51, {0, 1, 2, 3}},
}),
}),
RegionEntry("Arbiter's Grounds", {
MapEntry("Arbiter's Grounds", "D_MN10", {
{0, {0, 1, 2, 3}},
{1, {0}},
{2, {0, 1, 2, 3}},
{3, {0}},
{4, {0, 1, 2, 3}},
{5, {0}},
{6, {0, 1}},
{7, {0}},
{8, {0}},
{9, {0, 1, 2}},
{10, {0}},
{11, {0, 1, 2, 3}},
{12, {0}},
{13, {0, 1}},
{14, {0}},
{15, {0, 1}},
{16, {0}},
}),
MapEntry("Stallord Arena", "D_MN10A", {
{50, {0, 1, 2, 3}},
}),
MapEntry("Death Sword Arena", "D_MN10B", {
{51, {0, 1, 2, 3}},
}),
}),
RegionEntry("Snowpeak Ruins", {
MapEntry("Snowpeak Ruins", "D_MN11", {
{0, {0, 1, 2, 3}},
{1, {0}},
{2, {0, 1, 2}},
{3, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}},
{4, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}},
{5, {0, 1, 2, 3, 4}},
{6, {0, 1, 2}},
{7, {0, 10}},
{8, {0}},
{9, {0}},
{11, {0}},
{13, {0}},
}),
MapEntry("Blizzeta Arena", "D_MN11A", {
{50, {0, 1, 2, 3}},
}),
MapEntry("Darkhammer Arena", "D_MN11B", {
{51, {0, 1, 2, 3}},
}),
MapEntry("Darkhammer Beta Arena", "D_MN11B", {
{49, {0, 1, 2}},
}),
}),
RegionEntry("Temple of Time", {
MapEntry("Temple of Time", "D_MN06", {
{0, {0, 1}},
{1, {0}},
{2, {0, 1, 2}},
{3, {0, 1}},
{4, {0, 1}},
{5, {0}},
{6, {0}},
{7, {0, 1, 2}},
{8, {0, 1, 2}},
}),
MapEntry("Armogohma Arena", "D_MN06A", {
{50, {0, 1}},
}),
MapEntry("Darknut Arena", "D_MN06B", {
{51, {0}},
}),
}),
RegionEntry("City in the Sky", {
MapEntry("City in the Sky", "D_MN07", {
{0, {0, 1, 2, 3, 4, 5}},
{1, {0}},
{2, {0, 1, 2, 3, 4}},
{3, {0, 1, 2, 3}},
{4, {0, 1, 2}},
{5, {0, 1, 2}},
{6, {0, 1, 2, 3, 4, 5, 6, 7, 8}},
{7, {0}},
{8, {0}},
{10, {0, 1, 3}},
{11, {0, 1}},
{12, {0, 1, 2, 3}},
{13, {0, 1}},
{14, {0, 1, 3}},
{15, {0, 1, 3, 4}},
{16, {0, 1, 2}},
}),
MapEntry("Argorok Arena", "D_MN07A", {
{50, {0, 1, 2, 3}},
}),
MapEntry("Aeralfos Arena", "D_MN07B", {
{51, {0, 1, 2}},
}),
}),
RegionEntry("Palace of Twilight", {
MapEntry("Palace of Twilight", "D_MN08", {
{0, {0, 1, 2, 3, 4, 10, 20, 21, 22}},
{1, {0, 1}},
{2, {0, 1}},
{4, {0, 1}},
{5, {0, 1, 2}},
{7, {0, 1}},
{8, {0}},
{9, {0, 1, 20, 21}},
{10, {0, 1}},
{11, {0, 1, 20, 21, 22}},
}),
MapEntry("Palace of Twilight Throne Room", "D_MN08A", {
{10, {0, 1, 21, 23, 24, 25}},
}),
MapEntry("Phantom Zant Arena 1", "D_MN08B", {
{51, {0, 1, 2, 3}},
}),
MapEntry("Phantom Zant Arena 2", "D_MN08C", {
{52, {0, 2}},
}),
MapEntry("Zant Arenas", "D_MN08D", {
{50, {0, 20}},
{53, {0}},
{54, {0}},
{55, {0, 1}},
{56, {0}},
{57, {0}},
{60, {0}},
}),
}),
RegionEntry("Hyrule Castle", {
MapEntry("Hyrule Castle", "D_MN09", {
{1, {0, 1, 2, 3}},
{2, {0, 2, 3}},
{3, {0}},
{4, {0, 1, 2}},
{5, {0}},
{6, {0, 1}},
{8, {0}},
{9, {0, 1, 2}},
{11, {0, 1, 2, 3, 5}},
{12, {0, 1, 2, 3, 4, 5, 6, 7, 8}},
{13, {0}},
{14, {0, 1, 2, 3, 4, 5}},
{15, {0, 1, 2, 3, 4, 5, 6, 7}},
}),
MapEntry("Hyrule Castle Throne Room", "D_MN09A", {
{50, {0, 1, 2, 10, 20, 21, 22, 120, 121, 122}},
{51, {0, 1, 2, 10, 20, 21, 22, 120, 121, 122}},
}),
MapEntry("Horseback Ganondorf Arena", "D_MN09B", {
{0, {0, 1}},
}),
MapEntry("Dark Lord Ganondorf Arena", "D_MN09C", {
{0, {0, 20, 21, 22, 23}},
}),
}),
RegionEntry("Mini-Dungeons and Grottos", {
MapEntry("Ice Cavern", "D_SB00", {
{0, {0}},
}),
MapEntry("Cave Of Ordeals", "D_SB01", {
{0, {0}},
{1, {0}},
{2, {0}},
{3, {0}},
{4, {0}},
{5, {0}},
{6, {0}},
{7, {0}},
{8, {0, 1}},
{9, {0}},
{10, {0}},
{11, {0}},
{12, {0}},
{13, {0}},
{14, {0}},
{15, {0}},
{16, {0}},
{17, {0}},
{18, {0, 1}},
{19, {0}},
{20, {0}},
{21, {0}},
{22, {0}},
{23, {0}},
{24, {0}},
{25, {0}},
{26, {0}},
{27, {0}},
{28, {0, 1}},
{29, {0}},
{30, {0}},
{31, {0}},
{32, {0}},
{33, {0}},
{34, {0}},
{35, {0}},
{36, {0}},
{37, {0}},
{38, {0, 1}},
{39, {0}},
{40, {0}},
{41, {0}},
{42, {0}},
{43, {0}},
{44, {0}},
{45, {0}},
{46, {0}},
{47, {0}},
{48, {0, 1}},
{49, {0}},
}
),
MapEntry("Kakariko Gorge Cavern", "D_SB02", {
{0, {0}},
}),
MapEntry("Lake Hylia Cavern", "D_SB03", {
{0, {0, 1}},
}),
MapEntry("Goron Stockcave", "D_SB04", {
{10, {0, 1}},
}),
MapEntry("Grotto 1", "D_SB05", {
{0, {0, 1}},
}),
MapEntry("Grotto 2", "D_SB06", {
{1, {0, 1}},
}),
MapEntry("Grotto 3", "D_SB07", {
{2, {0, 1}},
}),
MapEntry("Grotto 4", "D_SB08", {
{3, {0, 1}},
}),
MapEntry("Grotto 5", "D_SB09", {
{4, {0, 1}},
}),
}),
RegionEntry("Misc", {
MapEntry("Title Screen / King Bulblin 1", "F_SP102", {
{0, {0, 1, 2, 3, 4, 5, 20, 53, 100, 101}},
}),
MapEntry("King Bulblin 2", "F_SP123", {
{13, {0}},
}),
MapEntry("Wolf Howling Cutscene Map", "F_SP200", {
{0, {0, 1, 2, 3, 4, 5, 6, 7}},
}),
MapEntry("Cutscene: Light Arrow Area", "R_SP300", {
{0, {0, 20, 120}},
}),
MapEntry("Cutscene: Hyrule Castle Throne Room", "R_SP301", {
{0, {0, 20, 100}},
}),
MapEntry("Title screen movie map", "S_MV000", {
{0, {0, 1}},
}),
})
});
-10
View File
@@ -1,10 +0,0 @@
#ifndef DUSK_MEMORY_H
#define DUSK_MEMORY_H
#if TARGET_PC
#define HEAP_SIZE(original, dusk) (dusk)
#else
#define HEAP_SIZE(original, dusk) (original)
#endif
#endif
-65
View File
@@ -1,65 +0,0 @@
#pragma once
#include "dolphin/types.h"
class CPaneMgr;
namespace dusk::menu_pointer {
using TargetId = u16;
constexpr TargetId InvalidTarget = 0xffff;
enum class Context {
None,
FileSelect,
Save,
ItemWheel,
Collection,
Options,
Dialog,
};
enum class Phase {
Move,
Press,
Release,
Cancel,
};
struct State {
f32 x = 0.0f;
f32 y = 0.0f;
bool valid = false;
bool down = false;
bool pressed = false;
bool released = false;
bool clicked = false;
bool touch = false;
};
void begin_game_frame() noexcept;
void end_game_frame() noexcept;
void begin_context(Context context) noexcept;
bool handle_fallthrough_pointer(f32 x, f32 y, Phase phase, bool touch, s32 mouseButton = -1) noexcept;
bool active() noexcept;
bool enabled() noexcept;
bool mouse_capture_active() noexcept;
const State& state() noexcept;
void set_hover_target(TargetId target) noexcept;
bool consume_click() noexcept;
bool peek_click() noexcept;
void set_dialog_choice(u8 choice, bool clicked) noexcept;
bool get_dialog_choice(u8& choice) noexcept;
bool consume_dialog_click(u8& choice) noexcept;
void defer_activation(Context context, TargetId target) noexcept;
bool consume_deferred_activation(Context context, TargetId target) noexcept;
void clear_deferred_activation(Context context) noexcept;
u32 suppressed_pad_buttons(u32 port) noexcept;
void finish_pad_suppression_read(u32 port) noexcept;
bool hit_rect(f32 left, f32 top, f32 right, f32 bottom, f32 padding = 0.0f) noexcept;
bool hit_pane(CPaneMgr* pane, f32 padding = 0.0f) noexcept;
bool hit_pane(J2DPane* pane, f32 padding = 0.0f) noexcept;
} // namespace dusk::menu_pointer
-245
View File
@@ -1,245 +0,0 @@
#pragma once
#include <filesystem>
#include <memory>
#include <ranges>
#include <string>
#include <string_view>
#include <vector>
#include "dusk/config.hpp"
#include "dusk/config_var.hpp"
#include "mods/api.h"
namespace dusk::mods {
struct LoadedMod;
class ModBundle;
} // namespace dusk::mods
struct ModContext {
dusk::mods::LoadedMod* mod = nullptr;
};
namespace dusk::mods::loader {
class NativeModule;
} // namespace dusk::mods::loader
namespace dusk::mods {
struct ModDependencyEdge {
LoadedMod* mod = nullptr;
bool required = false;
};
struct ModManifestInfo {
struct Import {
std::string id;
uint16_t major = 0;
bool required = false;
bool operator==(const Import&) const = default;
};
struct Export {
std::string id;
uint16_t major = 0;
bool operator==(const Export&) const = default;
};
std::vector<Import> imports;
std::vector<Export> exports;
bool operator==(const ModManifestInfo&) const = default;
};
struct ModMetadata {
std::string id;
std::string name;
std::string version;
std::string author;
std::string description;
std::string iconPath;
std::string bannerPath;
};
struct ModSearchDir {
std::filesystem::path path;
// Directory bundles dlopen their native lib in place instead of extracting it to the cache.
// Required where extracted code cannot run (iOS), desirable for signed/read-only installs.
bool inPlaceNative = false;
// Native library location for platforms that restrict placement (e.g. iOS/tvOS Frameworks/)
std::filesystem::path nativeLibDir;
};
struct NativeMod {
std::unique_ptr<loader::NativeModule> handle;
const ModManifest* manifest = nullptr;
ModContext** contextSymbol = nullptr;
ModInitializeFn fn_initialize = nullptr;
ModUpdateFn fn_update = nullptr;
ModShutdownFn fn_shutdown = nullptr;
};
enum class NativeModStatus : u8 {
/**
* Mod does not have native code included.
*/
None,
/**
* Native code mod loaded successfully.
*
* Note that this only indicates load status of the native library. If the native lib throws in
* its init function, it will still be disabled!
*/
Loaded,
/**
* This build was compiled without native mod support!
*/
BuildDisabled,
/**
* Mod ships native libraries, but none matches this build's platform and architecture.
*/
ModMissingPlatform,
/**
* Mod is built for a different ABI version than this build of the game.
*/
ApiVersionMismatch,
/**
* Mod is missing a required native API export.
*/
MissingExport,
/**
* Unknown error loading the native mod.
*/
Unknown,
};
struct LoadedMod {
ModMetadata metadata;
std::string modPath;
std::string dir;
uint32_t searchDirIndex = 0;
// Native lib is dlopen'd in place and stays resident for the session. Reload is unsupported.
bool inPlace = false;
std::unique_ptr<ConfigVar<bool> > cvarIsEnabled;
config::Subscription enabledSubscription = 0;
bool active = false;
bool loadFailed = false;
std::string failureReason;
// mod_initialize succeeded; a mod_shutdown is owed on deactivation.
bool initialized = false;
// Static service exports are currently present in the registry.
bool servicesRegistered = false;
// Lifecycle state last applied by the loader; diffed against cvarIsEnabled to pick up
// runtime enable/disable requests.
bool enabledApplied = false;
// Deactivated because a provider it imports from was disabled, not by its own cvar.
bool suspendedByProvider = false;
// Bumped per native lib extraction so every dlopen sees a fresh path (and thus a fresh
// image with fresh statics; a previous dlclose may not fully unmap). Also bumped by
// asset-only reloads, so it doubles as a generation for anything caching per-mod content.
uint32_t cacheGeneration = 0;
// Currently extracted native library, empty if none.
std::string nativePath;
NativeModStatus nativeStatus = NativeModStatus::None;
std::unique_ptr<NativeMod> native;
std::unique_ptr<ModContext> context;
// Shared with overlay file registrations so in-flight DVD reads survive disable/reload.
std::shared_ptr<ModBundle> bundle;
ModManifestInfo manifestInfo;
// Mods this mod imports services from, and mods importing services from this mod.
std::vector<ModDependencyEdge> dependencies;
std::vector<ModDependencyEdge> dependents;
};
class ModLoader {
public:
static ModLoader& instance();
void set_search_dirs(std::vector<ModSearchDir> dirs) { m_searchDirs = std::move(dirs); }
void set_cache_dir(std::filesystem::path dir) { m_cacheDir = std::move(dir); }
void init();
void tick();
void shutdown();
void request_enable(std::string_view id);
void request_disable(std::string_view id);
void request_reload(std::string_view id);
void notify_mod_failure(LoadedMod& mod, bool firstFailure);
[[nodiscard]] auto mods() const {
return m_mods | std::views::transform([](const auto& m) -> LoadedMod& { return *m; });
}
[[nodiscard]] auto active_mods() const {
return mods() | std::views::filter([](const auto& m) { return m.active; });
}
private:
enum class RequestKind : u8 { Enable, Disable, Reload };
struct Request {
std::string modId;
RequestKind kind;
};
// ModLoader::tick runs inside fapGm_Execute, so code from an unloading mod can still be
// live on the stack (its frame unwinds after the tick). dlclose is therefore deferred to
// the next tick, by which point every per-frame entry into the mod should have returned.
struct RetiredNative {
std::unique_ptr<NativeMod> native;
std::string path;
};
std::vector<std::unique_ptr<LoadedMod> > m_mods;
std::vector<ModSearchDir> m_searchDirs;
std::filesystem::path m_cacheDir;
std::vector<Request> m_pendingRequests;
std::vector<std::string> m_pendingFailures;
std::vector<RetiredNative> m_retiredNatives;
bool m_initialized = false;
bool m_startupComplete = false;
void try_load_mod(const std::filesystem::path& modPath, bool fromDir, uint32_t searchDirIndex);
void load_native(LoadedMod& mod, const std::string& dllEntry);
// Resolved <nativeLibDir>/<mod id><ext> if it exists on disk, empty otherwise.
[[nodiscard]] std::filesystem::path external_native_lib_path(const LoadedMod& mod) const;
void unload_native(LoadedMod& mod);
// Registers exports (if needed), resolves imports and runs mod_initialize.
// Returns whether the mod ended up active; failures go through fail_mod.
bool activate_mod(LoadedMod& mod);
// Runs mod_shutdown (if needed), detaches the mod from every service, and unloads the
// native lib. Must only run with no mod code on the stack (startup, shutdown, or top of tick).
void deactivate_mod(LoadedMod& mod);
void init_services();
bool register_static_service_exports(LoadedMod& mod);
bool resolve_service_imports(LoadedMod& mod);
[[nodiscard]] std::string describe_missing_import(
const char* serviceId, uint16_t majorVersion, uint16_t minMinorVersion) const;
LoadedMod* find_mod(std::string_view id) const;
void drain_retired_natives();
void apply_pending_requests();
void flush_toasts();
void on_enabled_changed(LoadedMod& mod);
// Deactivates `target` (if needed) and its transitive dependents, optionally re-reads the
// bundle from disk, then reactivates whatever the current cvar/provider state allows.
void apply_lifecycle_change(LoadedMod& target, bool reload);
// `target` plus transitive active/suspended dependents, in m_mods (init) order.
std::vector<LoadedMod*> collect_lifecycle_set(LoadedMod& target);
bool reload_bundle(LoadedMod& mod);
bool ensure_native_loaded(LoadedMod& mod);
};
using ModIndex = std::ranges::range_difference_t<decltype(std::declval<ModLoader>().mods())>;
} // namespace dusk::mods
-12
View File
@@ -1,12 +0,0 @@
#pragma once
#include <SDL3/SDL_events.h>
namespace dusk::mouse {
void read();
void get_aim_deltas(float& out_yaw, float& out_pitch);
void get_camera_deltas(float& out_yaw, float& out_pitch);
void handle_event(const SDL_Event& event) noexcept;
void on_focus_lost();
void on_focus_gained();
} // namespace dusk::mouse
-14
View File
@@ -1,14 +0,0 @@
#ifndef DUSK_OS_H
#define DUSK_OS_H
#ifdef __cplusplus
extern "C" {
#endif
void OSSetCurrentThreadName(const char* name);
#ifdef __cplusplus
}
#endif
#endif // DUSK_OS_H
-12
View File
@@ -1,12 +0,0 @@
#pragma once
#if defined(__has_include)
#if __has_include(<tracy/Tracy.hpp>)
#include <tracy/Tracy.hpp>
#endif
#endif
#ifndef ZoneScoped
#define ZoneScoped
#define ZoneScopedN(name)
#endif
-20
View File
@@ -1,20 +0,0 @@
#ifndef DUSK_SCOPE_GUARD_HPP
#define DUSK_SCOPE_GUARD_HPP
#include <functional>
class SimpleScopeGuard {
public:
// Store the function in the constructor
explicit SimpleScopeGuard(const std::function<void()>& func) : m_func(func) {}
// Run the function when the object goes out of scope
~SimpleScopeGuard() {
if (m_func) m_func();
}
private:
std::function<void()> m_func;
};
#endif //DUSK_SCOPE_GUARD_HPP
-368
View File
@@ -1,368 +0,0 @@
#pragma once
#include <array>
#include "dusk/config_var.hpp"
#include "dusk/ui/controls.hpp"
namespace dusk {
using config::ConfigVar;
using config::ActionBindConfigVar;
enum class BloomMode : int {
Off = 0,
Classic = 1,
Dusk = 2,
};
enum class DepthOfFieldMode : int {
Off = 0,
Classic = 1,
Dusk = 2,
};
enum class Resampler : int {
Bilinear = 0,
Area = 1,
};
enum class GameLanguage : u8 {
English = OS_LANGUAGE_ENGLISH,
German = OS_LANGUAGE_GERMAN,
French = OS_LANGUAGE_FRENCH,
Spanish = OS_LANGUAGE_SPANISH,
Italian = OS_LANGUAGE_ITALIAN,
};
enum class DiscVerificationState : u8 {
Unknown = 0,
Success,
HashMismatch,
};
enum class FrameInterpMode : u8 {
Off = 0,
Capped = 1,
Unlimited = 2,
};
enum class TouchTargeting : u8 {
Hybrid = 0,
Hold = 1,
Switch = 2,
};
enum class MenuScaling : u8 {
GameCube = 0,
Wii = 1,
Dusklight = 2,
};
enum class MagicArmorMode : u8 {
NORMAL = 0,
ON_DAMAGE = 1,
DOUBLE_DEFENSE = 2,
INVINCIBLE = 3,
COSMETIC = 4,
};
namespace config {
template <>
struct ConfigEnumRange<BloomMode> {
static constexpr auto min = BloomMode::Off;
static constexpr auto max = BloomMode::Dusk;
};
template <>
struct ConfigEnumRange<DepthOfFieldMode> {
static constexpr auto min = DepthOfFieldMode::Off;
static constexpr auto max = DepthOfFieldMode::Dusk;
};
template <>
struct ConfigEnumRange<Resampler> {
static constexpr auto min = Resampler::Bilinear;
static constexpr auto max = Resampler::Area;
};
template <>
struct ConfigEnumRange<GameLanguage> {
static constexpr auto min = GameLanguage::English;
static constexpr auto max = GameLanguage::Italian;
};
template <>
struct ConfigEnumRange<DiscVerificationState> {
static constexpr auto min = DiscVerificationState::Unknown;
static constexpr auto max = DiscVerificationState::HashMismatch;
};
template <>
struct ConfigEnumRange<FrameInterpMode> {
static constexpr auto min = FrameInterpMode::Off;
static constexpr auto max = FrameInterpMode::Unlimited;
};
template <>
struct ConfigEnumRange<TouchTargeting> {
static constexpr auto min = TouchTargeting::Hybrid;
static constexpr auto max = TouchTargeting::Switch;
};
template <>
struct ConfigEnumRange<MenuScaling> {
static constexpr auto min = MenuScaling::GameCube;
static constexpr auto max = MenuScaling::Dusklight;
};
template <>
struct ConfigEnumRange<MagicArmorMode> {
static constexpr auto min = MagicArmorMode::NORMAL;
static constexpr auto max = MagicArmorMode::COSMETIC;
};
template <>
struct ConfigValueTraits<ui::ControlLayout> {
static constexpr bool enabled = true;
};
} // namespace config
// Persistent user settings
struct UserSettings {
// Program settings
struct {
// Video
ConfigVar<bool> enableFullscreen;
ConfigVar<bool> enableVsync;
ConfigVar<bool> lockAspectRatio;
ConfigVar<bool> enableFpsOverlay;
ConfigVar<int> fpsOverlayCorner;
ConfigVar<int> maxFrameRate;
ConfigVar<bool> rememberWindowSize;
ConfigVar<int> lastWindowWidth;
ConfigVar<int> lastWindowHeight;
} video;
struct {
// Audio
ConfigVar<int> masterVolume;
ConfigVar<int> mainMusicVolume;
ConfigVar<int> subMusicVolume;
ConfigVar<int> soundEffectsVolume;
ConfigVar<int> fanfareVolume;
ConfigVar<bool> enableReverb;
ConfigVar<bool> enableHrtf;
ConfigVar<bool> menuSounds;
} audio;
// Game settings
struct {
ConfigVar<GameLanguage> language;
// QoL
ConfigVar<bool> enableQuickTransform;
ConfigVar<bool> hideTvSettingsScreen;
ConfigVar<bool> biggerWallets;
ConfigVar<bool> noReturnRupees;
ConfigVar<bool> disableRupeeCutscenes;
ConfigVar<bool> noSwordRecoil;
ConfigVar<int> damageMultiplier;
ConfigVar<bool> noHeartDrops;
ConfigVar<bool> instantDeath;
ConfigVar<bool> fastClimbing;
ConfigVar<bool> noMissClimbing;
ConfigVar<bool> fastTears;
ConfigVar<bool> no2ndFishForCat;
ConfigVar<bool> buttonFishing;
ConfigVar<bool> instantSaves;
ConfigVar<bool> instantText;
ConfigVar<bool> sunsSong;
ConfigVar<bool> autoSave;
ConfigVar<bool> enhancedMapMenus;
// Preferences
ConfigVar<bool> enableMirrorMode;
ConfigVar<bool> minimalHUD;
ConfigVar<float> hudScale;
ConfigVar<bool> pauseOnFocusLost;
ConfigVar<bool> enableLinkDollRotation;
ConfigVar<bool> enableAchievementToasts;
ConfigVar<bool> enableControllerToasts;
ConfigVar<bool> enableDiscordPresence;
ConfigVar<MenuScaling> menuScalingMode;
// Graphics
ConfigVar<BloomMode> bloomMode;
ConfigVar<float> bloomMultiplier;
ConfigVar<DepthOfFieldMode> depthOfFieldMode;
ConfigVar<bool> disableWaterRefraction;
ConfigVar<bool> enableTextureReplacements;
ConfigVar<FrameInterpMode> enableFrameInterpolation;
ConfigVar<int> internalResolutionScale;
ConfigVar<int> shadowResolutionMultiplier;
ConfigVar<Resampler> resampler;
ConfigVar<bool> enableMapBackground;
ConfigVar<bool> disableCutscenePillarboxing;
// Audio
ConfigVar<bool> noLowHpSound;
ConfigVar<bool> midnasLamentNonStop;
// Input
ConfigVar<bool> enableGyroAim;
ConfigVar<bool> enableGyroRollgoal;
ConfigVar<float> gyroSensitivityX;
ConfigVar<float> gyroSensitivityY;
ConfigVar<float> gyroSensitivityRollgoal;
ConfigVar<float> gyroSmoothing;
ConfigVar<float> gyroDeadband;
ConfigVar<bool> gyroInvertPitch;
ConfigVar<bool> gyroInvertYaw;
ConfigVar<bool> enableMouseCamera;
ConfigVar<bool> enableMouseAim;
ConfigVar<float> mouseAimSensitivity;
ConfigVar<float> mouseCameraSensitivity;
ConfigVar<bool> invertMouseY;
ConfigVar<bool> freeCamera;
ConfigVar<bool> enableTouchControls;
ConfigVar<TouchTargeting> touchTargeting;
ConfigVar<bool> enableMenuPointer;
ConfigVar<ui::ControlLayout> touchControlsLayout;
ConfigVar<bool> invertCameraXAxis;
ConfigVar<bool> invertCameraYAxis;
ConfigVar<bool> invertFirstPersonXAxis;
ConfigVar<bool> invertFirstPersonYAxis;
ConfigVar<bool> invertAirSwimX;
ConfigVar<bool> invertAirSwimY;
ConfigVar<float> freeCameraXSensitivity;
ConfigVar<float> freeCameraYSensitivity;
ConfigVar<float> touchCameraXSensitivity;
ConfigVar<float> touchCameraYSensitivity;
ConfigVar<bool> debugFlyCam;
ConfigVar<bool> debugFlyCamLockEvents;
ConfigVar<bool> allowBackgroundInput;
std::array<ConfigVar<bool>, 4> enableLED;
ConfigVar<bool> swapDirectSelect;
// Cheats
ConfigVar<bool> infiniteHearts;
ConfigVar<bool> infiniteArrows;
ConfigVar<bool> infiniteSeeds;
ConfigVar<bool> infiniteBombs;
ConfigVar<bool> infiniteOil;
ConfigVar<bool> infiniteOxygen;
ConfigVar<bool> infiniteRupees;
ConfigVar<bool> enableIndefiniteItemDrops;
ConfigVar<bool> moonJump;
ConfigVar<bool> superClawshot;
ConfigVar<bool> alwaysGreatspin;
ConfigVar<bool> enableFastIronBoots;
ConfigVar<bool> canTransformAnywhere;
ConfigVar<bool> fastRoll;
ConfigVar<bool> fastSpinner;
ConfigVar<MagicArmorMode> armorRupeeDrain;
ConfigVar<bool> invincibleEnemies;
// Technical
ConfigVar<bool> restoreWiiGlitches;
// Controls
ConfigVar<bool> enableTurboKeybind;
ConfigVar<bool> enableResetKeybind;
// Tools
ConfigVar<bool> speedrunMode;
ConfigVar<bool> liveSplitEnabled;
ConfigVar<bool> showSpeedrunRTATimer;
ConfigVar<bool> recordingMode;
// Misc
ConfigVar<bool> removeQuestMapMarkers;
ConfigVar<bool> showInputViewer;
ConfigVar<bool> showInputViewerGyro;
} game;
struct {
ConfigVar<std::string> isoPath;
ConfigVar<DiscVerificationState> isoVerification;
ConfigVar<std::string> graphicsBackend;
ConfigVar<bool> skipPreLaunchUI;
ConfigVar<bool> wasPresetChosen;
ConfigVar<bool> checkForUpdates;
ConfigVar<int> cardFileType;
ConfigVar<bool> enableAdvancedSettings;
} backend;
// Arrays of size 4 for 4 ports
struct {
std::array<ActionBindConfigVar, 4> firstPersonCamera;
std::array<ActionBindConfigVar, 4> callMidna;
std::array<ActionBindConfigVar, 4> openMapScreen;
std::array<ActionBindConfigVar, 4> toggleMinimap;
std::array<ActionBindConfigVar, 4> openDusklightMenu;
std::array<ActionBindConfigVar, 4> turboSpeedButton;
} actionBindings;
// Randomizer seed hashes, 1 per file
struct {
std::array<ConfigVar<std::string>, 3> seedHashes;
} randomizer;
// Cosmetics
struct {
ConfigVar<std::string> herosTunicCapColor;
ConfigVar<std::string> herosTunicTorsoColor;
ConfigVar<std::string> herosTunicSkirtColor;
ConfigVar<std::string> zoraArmorCapColor;
ConfigVar<std::string> zoraArmorHelmetColor;
ConfigVar<std::string> zoraArmorTorsoColor;
ConfigVar<std::string> zoraArmorScalesColor;
ConfigVar<std::string> zoraArmorFlippersColor;
ConfigVar<std::string> lanternGlowColor;
ConfigVar<std::string> woodenSwordColor;
ConfigVar<std::string> msBladeColor;
ConfigVar<std::string> msHandleColor;
ConfigVar<std::string> lightSwordGlowColor;
ConfigVar<std::string> boomerangColor;
ConfigVar<std::string> ironBootsColor;
ConfigVar<std::string> spinnerColor;
ConfigVar<std::string> midnaHairBaseColor;
ConfigVar<std::string> midnaHairTipsColor;
ConfigVar<std::string> midnaChargeRingColor;
ConfigVar<std::string> linkHairColor;
ConfigVar<std::string> wolfLinkColor;
ConfigVar<std::string> eponaColor;
} cosmetics;
};
UserSettings& getSettings();
void registerSettings();
// Transient settings
struct CollisionViewSettings {
bool enableTerrainView;
bool enableWireframe;
bool enableAtView;
bool enableTgView;
bool enableCoView;
float terrainViewOpacity;
float colliderViewOpacity;
float drawRange;
};
struct TransientSettings {
CollisionViewSettings collisionView;
bool skipFrameRateLimit;
bool moveLinkActive;
bool stateShareLoadActive;
};
TransientSettings& getTransientSettings();
} // namespace dusk
-42
View File
@@ -1,42 +0,0 @@
#pragma once
#include <aurora/aurora.h>
namespace dusk {
struct SpeedrunInfo {
void startRun() {
m_isRunStarted = true;
m_startTimestamp = OSGetTime();
}
void stopRun() {
m_isRunStarted = false;
m_endTimestamp = OSGetTime() - m_startTimestamp;
}
void reset() {
m_isRunStarted = false;
m_startTimestamp = 0;
m_endTimestamp = 0;
m_isPauseIGT = false;
m_loadStartTimestamp = 0;
m_totalLoadTime = 0;
m_igtTimer = 0;
}
bool m_isRunStarted = false;
OSTime m_startTimestamp = 0;
OSTime m_endTimestamp = 0;
bool m_isPauseIGT = false;
OSTime m_loadStartTimestamp = 0;
OSTime m_totalLoadTime = 0;
OSTime m_igtTimer = 0;
};
extern SpeedrunInfo m_speedrunInfo;
void resetForSpeedrunMode();
void restoreFromSpeedrunMode();
} // namespace dusk
-17
View File
@@ -1,17 +0,0 @@
#ifndef DUSK_TEXTURE_REPLACEMENTS_HPP
#define DUSK_TEXTURE_REPLACEMENTS_HPP
#include <cstdint>
namespace dusk::texture_replacements {
// Mod replacements are prioritized *over* user replacements (<data folder>/texture_replacements/)
inline constexpr int32_t kUserTextureReplacementPriority = -1'000'000;
void reload();
void set_enabled(bool enabled);
void shutdown();
}
#endif
-142
View File
@@ -1,142 +0,0 @@
#ifndef DUSK_TIME_H
#define DUSK_TIME_H
#include <array>
#include <numeric>
#include "SDL3/SDL_timer.h"
#ifdef _WIN32
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <Windows.h>
#include <shellapi.h>
#include <intrin.h>
#endif
#ifdef __APPLE__
#include <mach/mach_time.h>
#if defined(__x86_64__) || defined(__i386__)
#include <immintrin.h>
#endif
#endif
class Limiter {
public:
using duration_t = Uint64;
void Reset() {
m_oldTime = SDL_GetTicksNS();
}
duration_t Sleep(duration_t targetFrameTime) {
if (targetFrameTime == 0) {
return 0;
}
const Uint64 start = SDL_GetTicksNS();
duration_t adjustedSleepTime = SleepTime(targetFrameTime);
if (adjustedSleepTime > 0) {
NanoSleep(adjustedSleepTime);
const duration_t elapsed = TimeSince(start);
const duration_t overslept = elapsed > adjustedSleepTime ? elapsed - adjustedSleepTime : 0;
if (overslept < targetFrameTime) {
m_overheadTimes[m_overheadTimeIdx] = overslept;
m_overheadTimeIdx = (m_overheadTimeIdx + 1) % m_overheadTimes.size();
}
}
Reset();
return adjustedSleepTime;
}
duration_t SleepTime(duration_t targetFrameTime) {
const duration_t elapsed = TimeSince(m_oldTime);
const duration_t sleepTime = elapsed < targetFrameTime ? targetFrameTime - elapsed : 0;
m_overhead = std::accumulate(m_overheadTimes.begin(), m_overheadTimes.end(), duration_t{0}) /
m_overheadTimes.size();
if (sleepTime > m_overhead) {
return sleepTime - m_overhead;
}
return 0;
}
private:
Uint64 m_oldTime = 0;
std::array<duration_t, 4> m_overheadTimes{};
size_t m_overheadTimeIdx = 0;
duration_t m_overhead = 0;
duration_t TimeSince(Uint64 start) const { return SDL_GetTicksNS() - start; }
#if _WIN32
void NanoSleep(const duration_t duration) {
static bool initialized = false;
static double countPerNs;
static size_t numSleeps = 0;
// QueryPerformanceFrequency's result is constant, but calling it occasionally
// appears to stabilize QueryPerformanceCounter. Without it, the game drifts
// from 60hz to 144hz. (Cursed, but I suspect it's NVIDIA/G-SYNC related)
if (!initialized || numSleeps++ % 1000 == 0) {
LARGE_INTEGER freq;
if (QueryPerformanceFrequency(&freq) == 0) {
return;
}
countPerNs = static_cast<double>(freq.QuadPart) / 1e9;
initialized = true;
numSleeps = 0;
}
LARGE_INTEGER start, current;
QueryPerformanceCounter(&start);
const LONGLONG ticksToWait = static_cast<LONGLONG>(duration * countPerNs);
const Uint64 ms = duration / 1'000'000ULL;
if (ms > 1) {
::Sleep(static_cast<DWORD>(ms - 1));
}
do {
QueryPerformanceCounter(&current);
#if defined(_M_ARM64) || defined(_M_ARM)
__yield();
#else
_mm_pause();
#endif
} while (current.QuadPart - start.QuadPart < ticksToWait);
}
#elif defined (__APPLE__)
void NanoSleep(const duration_t duration) {
// Hybrid approach using Apple Mach
uint64_t start_mach = mach_absolute_time();
mach_timebase_info_data_t timebase_info;
mach_timebase_info(&timebase_info);
uint64_t total_mach_ticks = (duration * timebase_info.denom) / timebase_info.numer;
uint64_t target_mach = start_mach + total_mach_ticks;
uint64_t buffer_ns = 2'000'000ULL;
uint64_t buffer_mach_ticks = (buffer_ns * timebase_info.denom) / timebase_info.numer;
if (total_mach_ticks > buffer_mach_ticks) {
uint64_t sleep_until_mach = target_mach - buffer_mach_ticks;
mach_wait_until(sleep_until_mach);
}
while (mach_absolute_time() < target_mach) {
#if defined(__aarch64__) || defined(__arm__)
asm volatile("yield" ::: "memory"); // Hardware hint, not a scheduler hint.
#else
_mm_pause();
#endif
}
}
#else
void NanoSleep(const duration_t duration) { SDL_DelayPrecise(duration); }
#endif
};
#endif
-12
View File
@@ -1,12 +0,0 @@
#pragma once
namespace dusk::touch_camera {
constexpr float YAW_DEGREES_PER_DP = 0.34f;
constexpr float PITCH_DEGREES_PER_DP = 0.22f;
void add_delta(float yaw_dp, float pitch_dp) noexcept;
bool consume_delta(float& yaw_dp, float& pitch_dp) noexcept;
void clear() noexcept;
} // namespace dusk::touch_camera
-67
View File
@@ -1,67 +0,0 @@
#ifndef DUSK_VERSION_HPP
#define DUSK_VERSION_HPP
/**
* Functionality for switching game behavior based on the loaded game version (e.g. PAL/JPN, GC/Wii)
*/
namespace dusk::version {
enum class GameVersion : u8 {
GcnUsa = VERSION_GCN_USA,
GcnPal = VERSION_GCN_PAL,
GcnJpn = VERSION_GCN_JPN,
WiiUsaRev0 = VERSION_WII_USA_R0,
WiiUsa = VERSION_WII_USA_R2,
WiiPal = VERSION_WII_PAL,
WiiJpn = VERSION_WII_JPN,
WiiKor = VERSION_WII_KOR,
};
bool isGcn();
bool isWii();
bool isPalOrAtLeastWiiR2();
bool isRegionPal();
bool isRegionJpn();
bool isRegionUsa();
GameVersion getGameVersion();
const DVDDiskID& getDiskID();
void init();
template<typename T>
struct VersionOption {
GameVersion mVersion;
T mValue;
constexpr VersionOption(GameVersion version, T value) : mVersion(version), mValue(value) {}
};
template<typename T>
const T& versionSelect(const std::initializer_list<VersionOption<T>> options) {
const auto version = getGameVersion();
for (const auto& opt : options) {
if (opt.mVersion == version) {
return opt.mValue;
}
}
// Unable to find value.
abort();
}
template<typename T>
const T& versionSelect(const std::initializer_list<VersionOption<T>> options, const T& defaultValue) {
const auto version = getGameVersion();
for (const auto& opt : options) {
if (opt.mVersion == version) {
return opt.mValue;
}
}
return defaultValue;
}
} // namespace dusk::version
#endif // DUSK_VERSION_HPP
+1 -1
View File
@@ -12,7 +12,7 @@
#include "f_pc/f_pc_manager.h"
#include "m_Do/m_Do_hostIO.h"
#include "SSystem/SComponent/c_phase.h"
#include "dusk/endian_ssystem.h"
#include "helpers/endian_ssystem.h"
#if !__MWERKS__
// mwerks compiler makes value initialization act like default initialization so we need
+12
View File
@@ -0,0 +1,12 @@
# Public game helpers
Headers in this directory provide port-specific types and utilities used by ordinary game
headers. Their corresponding implementations live in `src/helpers/`.
These helpers **must not** depend on internal `src/dusk/` declarations in their public interface.
Unlike the internal `dusk::` namespace, they are exposed to mods that use the `game` feature
and therefore must remain ABI stable within `GameService` major versions.
APIs _specifically_ for mod use do not belong here; instead they belong in mod services,
which are individually versioned, can provide backwards compatibility, and are designed to
keep track of per-mod runtime state.
+25
View File
@@ -0,0 +1,25 @@
#pragma once
#include <dolphin/types.h>
namespace batch {
struct LeafTemplate {
static constexpr u32 kMaxVtx = 192;
static constexpr u32 kMaxPosRefs = 64;
struct Vtx {
u8 pos;
u8 nrm;
u8 clr;
u8 tex;
};
Vtx vtx[kMaxVtx];
u16 vtxCount = 0;
u8 posRefs[kMaxPosRefs];
u8 posRefCount = 0;
};
void decode_leaf_template(const u8* dl, u32 size, LeafTemplate& out);
} // namespace batch
@@ -1,5 +1,4 @@
#ifndef DOLPHIN_ENDIAN_H
#define DOLPHIN_ENDIAN_H
#pragma once
#include <bit>
@@ -292,6 +291,3 @@ inline void be_swap(Mtx& val) {
#define BE(T) T
#define BE_HOST(T) (T)
#endif
#endif // DOLPHIN_ENDIAN_H
@@ -1,5 +1,4 @@
#ifndef _DUSK_ENDIAN_SSYSTEM_H_
#define _DUSK_ENDIAN_SSYSTEM_H_
#pragma once
#include "SSystem/SComponent/c_sxyz.h"
#include "endian.h"
@@ -61,5 +60,3 @@ struct BE<cXyz> {
};
}
};
#endif
@@ -1,12 +1,17 @@
#ifndef DUSK_GX_HELPER_H
#define DUSK_GX_HELPER_H
#pragma once
#include <cstring>
#include <dolphin/gx/GXAurora.h>
#include <dolphin/gx/GXExtra.h>
#include "profiling.hpp"
#if defined(DUSK_BUILDING_GAME)
#include <tracy/Tracy.hpp>
#else
#ifndef ZoneScopedN
#define ZoneScopedN(name)
#endif
#endif
#if DUSK_GFX_DEBUG_GROUPS
#define GX_DEBUG_GROUP(name, ...) \
@@ -79,5 +84,3 @@ struct GXScopedDebugGroup {
};
#define GX_AND_TRACY_SCOPED(name) GXScopedDebugGroup scope(name); ZoneScopedN(name);
#endif // DUSK_GX_HELPER_H
@@ -1,5 +1,4 @@
#ifndef _SRC_DUSK_MATH_H_
#define _SRC_DUSK_MATH_H_
#pragma once
#include <cmath>
@@ -17,5 +16,3 @@ inline float i_tanf(float x) { return tan(x); }
inline float i_acosf(float x) { return acos(x); }
#include <dolphin/ppc_math.h>
#endif // _SRC_DUSK_MATH_H_
@@ -1,5 +1,4 @@
#ifndef DUSK_OFFSET_PTR_H
#define DUSK_OFFSET_PTR_H
#pragma once
#if TARGET_PC
@@ -47,21 +46,17 @@ struct OffsetPtrT {
}
operator T*() const {
return (T*) value;
}
return (T*)value; }
template<typename TOther>
template <typename TOther>
explicit operator TOther*() const {
return (TOther*) value;
return (TOther*)value;
}
};
#define OFFSET_PTR(T) OffsetPtrT<T>
#define OFFSET_PTR_RAW OffsetPtr
#else
#define OFFSET_PTR(T) T*
#define OFFSET_PTR_RAW u32
#endif
#endif // DUSK_OFFSET_PTR_H
@@ -1,8 +1,7 @@
#ifndef DUSK_STRING_HPP
#define DUSK_STRING_HPP
#include <cstdarg>
#pragma once
namespace dusk {
#include <cstdarg>
#include <cstddef>
struct TextSpan {
char* buffer;
@@ -44,7 +43,7 @@ private:
};
#if TARGET_PC
#define TEXT_SPAN dusk::TextSpan
#define TEXT_SPAN TextSpan
#else
#define TEXT_SPAN char*
#endif
@@ -111,11 +110,11 @@ int SafeStringPrintf(char (&buffer)[BufSize], const char* format, ...) {
}
#if TARGET_PC
#define SAFE_STRCPY dusk::SafeStringCopy
#define SAFE_STRCAT dusk::SafeStringCat
#define SAFE_SPRINTF dusk::SafeStringPrintf
#define SAFE_STRCPY_BOUNDED dusk::SafeStringCopy
#define SAFE_STRCAT_BOUNDED dusk::SafeStringCat
#define SAFE_STRCPY SafeStringCopy
#define SAFE_STRCAT SafeStringCat
#define SAFE_SPRINTF SafeStringPrintf
#define SAFE_STRCPY_BOUNDED SafeStringCopy
#define SAFE_STRCAT_BOUNDED SafeStringCat
#else
#define SAFE_STRCPY strcpy
#define SAFE_STRCAT strcat
@@ -123,6 +122,3 @@ int SafeStringPrintf(char (&buffer)[BufSize], const char* format, ...) {
#define SAFE_STRCPY_BOUNDED strcpy
#define SAFE_STRCPY_BOUNDED strcat
#endif
}
#endif // DUSK_STRING_HPP
+5 -9
View File
@@ -4,8 +4,12 @@
#include "Z2AudioLib/Z2AudioMgr.h"
#include "Z2AudioLib/Z2EnvSeMgr.h"
#include "Z2AudioLib/Z2LinkMgr.h"
#if defined(DUSK_BUILDING_GAME)
#include "dusk/audio.h"
#include "dusk/settings.h"
#else
#define DUSK_AUDIO_SKIP(...)
#endif
class mDoAud_zelAudio_c : public Z2AudioMgr {
public:
@@ -134,15 +138,7 @@ inline void mDoAud_seStart(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_re
}
#if TARGET_PC
inline void mDoAud_seStartMenu(u32 i_sfxID) {
if (!mDoAud_zelAudio_c::isInitFlag()) {
return;
}
if (!dusk::getSettings().audio.menuSounds.getValue()) {
return;
}
mDoAud_seStart(i_sfxID, nullptr, 0, 0);
}
void mDoAud_seStartMenu(u32 i_sfxID);
#endif
inline void mDoAud_seStartLevel(u32 i_sfxID, const Vec* i_sePos, u32 param_2, s8 i_reverb) {
+11 -16
View File
@@ -3,7 +3,10 @@
#include "JSystem/JUtility/JUTGamePad.h"
#include "SSystem/SComponent/c_API_controller_pad.h"
#if defined(DUSK_BUILDING_GAME)
#include "dusk/settings.h"
#endif
// Controller Ports 1 - 4
enum { PAD_1, PAD_2, PAD_3, PAD_4 };
@@ -54,29 +57,21 @@ public:
static f32 getStickValue(u32 pad) { return getCpadInfo(pad).mMainStickValue; }
static s16 getStickAngle(u32 pad) { return getCpadInfo(pad).mMainStickAngle; }
#if TARGET_PC
static s16 getStickAngle3D(u32 pad);
#else
static s16 getStickAngle3D(u32 pad) {
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
return -getCpadInfo(pad).mMainStickAngle;
} else {
return getCpadInfo(pad).mMainStickAngle;
}
#else
return getCpadInfo(pad).mMainStickAngle;
#endif
}
#endif
#if TARGET_PC
static f32 getSubStickX3D(u32 pad);
#else
static f32 getSubStickX3D(u32 pad) {
#if TARGET_PC
if (dusk::getSettings().game.enableMirrorMode) {
return -getCpadInfo(pad).mCStickPosX;
} else {
return getCpadInfo(pad).mCStickPosX;
}
#else
return getCpadInfo(pad).mCStickPosX;
#endif
}
#endif
static f32 getSubStickX(u32 pad) { return getCpadInfo(pad).mCStickPosX; }
static f32 getSubStickY(u32 pad) { return getCpadInfo(pad).mCStickPosY; }
+1 -1
View File
@@ -8,7 +8,7 @@
#include "JSystem/JGeometry.h"
#endif
#include "dusk/gx_helper.h"
#include "helpers/gx_helper.h"
typedef struct Vec Vec;
struct ResTIMG;
+1 -1
View File
@@ -6,7 +6,7 @@
#include <mtx.h>
#include "JSystem/JMath/JMath.h"
#include "dusk/endian.h"
#include "helpers/endian.h"
extern u8 g_printCurrentHeapDebug;
DUSK_GAME_EXTERN u8 g_printOtherHeapDebug;
+1 -1
View File
@@ -2,7 +2,7 @@
#define M_DO_M_DO_PRINTF_H
#include <os.h>
#include "dusk/endian.h"
#include "helpers/endian.h"
void my_PutString(const char*);
void mDoPrintf_vprintf_Interrupt(char const*, va_list);
-152
View File
@@ -1,152 +0,0 @@
#pragma once
#ifdef __cplusplus
#include <cstddef>
#include <cstdint>
extern "C" {
#else
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#endif
#if defined(_WIN32)
#define MOD_EXPORT __declspec(dllexport)
#else
#define MOD_EXPORT __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
#define MOD_EXTERN_C extern "C"
#else
#define MOD_EXTERN_C
#endif
#define MOD_ABI_VERSION 5u
#define MOD_ERROR_MESSAGE_SIZE 512u
typedef struct ModContext ModContext;
typedef enum ModResult {
MOD_OK = 0,
MOD_ERROR = 1,
MOD_UNAVAILABLE = 2,
MOD_UNSUPPORTED = 3,
MOD_CONFLICT = 4,
MOD_INVALID_ARGUMENT = 5,
} ModResult;
static_assert(sizeof(ModResult) == 4, "mod SDK enums must be int-sized; do not build mods with -fshort-enums");
typedef struct ModError {
uint32_t struct_size;
ModResult code;
char message[MOD_ERROR_MESSAGE_SIZE];
} ModError;
#define MOD_ERROR_INIT {sizeof(ModError), MOD_OK, {0}}
/*
* Opaque per-mod context, populated by the host before mod_initialize is called.
* Pass it as the first argument to every service call; it identifies the calling
* mod for attribution (logging, resource lookup, hook ownership, etc.).
*/
MOD_EXPORT extern ModContext* mod_ctx;
/*
* Service versioning contract:
*
* A service is a struct of function pointers, beginning with a ServiceHeader.
* Compatibility is tracked with a major/minor version pair:
*
* - A major version bump is a breaking change. Different majors are distinct
* services; the registry never matches an import against a different major.
* - A minor version bump may only append fields to the end of the struct.
* Existing fields must keep their offsets and semantics.
*
* Providers: exporting minor N means every function pointer introduced at or
* below N is populated (non-NULL). struct_size reflects the compiled struct.
*
* Importers: importing with min_minor_version N guarantees (enforced at load
* time) that the resolved service is at least minor N, so any field introduced
* at or below N may be used unconditionally, with no availability checks.
* Fields newer than the declared min_minor_version must be gated behind
* SERVICE_HAS plus a NULL check on the pointer itself.
*
* Load ordering: a manifest import of another mod's service (required or
* optional) guarantees that the provider's mod_initialize completed before the
* importer's runs, and deferred services published during the provider's
* initialization resolve into import slots just like static exports. If a
* provider fails to load, mods that required its services fail in turn. Mods
* whose required imports form a cycle all fail to load; a cycle involving an
* optional import is broken by dropping the ordering guarantee (not the
* resolution) of that optional import. Dynamic lookups via
* HostService::get_service carry no ordering guarantee: they see whatever has
* been published at call time.
*/
typedef struct ServiceHeader {
uint32_t struct_size;
uint16_t major_version;
uint16_t minor_version;
} ServiceHeader;
#define SERVICE_HEADER(service_type, major, minor) {sizeof(service_type), (major), (minor)}
#define SERVICE_HAS(service, service_type, field) \
((service) != NULL && \
(service)->header.struct_size >= \
(uint32_t)(offsetof(service_type, field) + sizeof(((service_type*)0)->field)))
typedef enum ServiceImportFlags {
SERVICE_IMPORT_REQUIRED = 0u,
SERVICE_IMPORT_OPTIONAL = 1u << 0u,
} ServiceImportFlags;
typedef enum ServiceExportFlags {
SERVICE_EXPORT_STATIC = 0u,
SERVICE_EXPORT_DEFERRED = 1u << 0u,
} ServiceExportFlags;
typedef struct ServiceImport {
uint32_t struct_size;
const char* service_id;
uint16_t major_version;
uint16_t min_minor_version;
uint32_t flags;
void* slot;
} ServiceImport;
typedef struct ServiceExport {
uint32_t struct_size;
const char* service_id;
uint16_t major_version;
uint16_t minor_version;
uint32_t flags;
const void* service;
} ServiceExport;
typedef struct ModManifest {
uint32_t struct_size;
uint32_t abi_version;
const ServiceImport* imports;
size_t import_count;
const ServiceExport* exports;
size_t export_count;
} ModManifest;
typedef const ModManifest* (*ModGetManifestFn)(void);
typedef ModResult (*ModInitializeFn)(ModError* out_error);
typedef ModResult (*ModUpdateFn)(ModError* out_error);
typedef ModResult (*ModShutdownFn)(ModError* out_error);
MOD_EXPORT const ModManifest* mod_get_manifest(void);
MOD_EXPORT ModResult mod_initialize(ModError* out_error);
MOD_EXPORT ModResult mod_update(ModError* out_error);
MOD_EXPORT ModResult mod_shutdown(ModError* out_error);
#ifdef __cplusplus
}
#endif
-464
View File
@@ -1,464 +0,0 @@
#pragma once
#include "mods/svc/hook.h"
#include <array>
#include <cstddef>
#include <cstring>
#include <memory>
#include <string_view>
#include <type_traits>
namespace dusk::mods {
template <class T>
T arg(void* argsRaw, int n) noexcept {
void** args = static_cast<void**>(argsRaw);
return *static_cast<std::add_pointer_t<std::remove_reference_t<T> > >(args[n]);
}
template <class T>
std::remove_reference_t<T>& arg_ref(void* argsRaw, int n) noexcept {
void** args = static_cast<void**>(argsRaw);
return *static_cast<std::add_pointer_t<std::remove_reference_t<T> > >(args[n]);
}
template <class F>
void* mfp_addr(F fn) noexcept {
void* p = nullptr;
static_assert(sizeof(fn) >= sizeof(void*), "unexpected function pointer size");
std::memcpy(&p, &fn, sizeof(void*));
return p;
}
/* A string usable as a template argument: carries the hook target's symbol name and
* makes each NamedHook instantiation's static state unique. */
template <size_t N>
struct FixedString {
char chars[N]{};
constexpr FixedString(const char (&s)[N]) noexcept {
for (size_t i = 0; i < N; ++i) {
chars[i] = s[i];
}
}
};
namespace detail {
template <class T>
constexpr std::string_view class_name() {
#if defined(__clang__) || defined(__GNUC__)
// "... class_name() [T = daAlink_c]" / "... [with T = daAlink_c; ...]"
constexpr std::string_view fn = __PRETTY_FUNCTION__;
constexpr size_t start = fn.find("T = ") + 4;
return fn.substr(start, fn.find_first_of(";]", start) - start);
#elif defined(_MSC_VER)
// "... class_name<class daAlink_c>(void)"
constexpr std::string_view fn = __FUNCSIG__;
constexpr size_t start = fn.find("class_name<") + 11;
constexpr std::string_view name = fn.substr(start, fn.rfind(">(") - start);
if constexpr (name.starts_with("class ")) {
return name.substr(6);
} else if constexpr (name.starts_with("struct ")) {
return name.substr(7);
} else {
return name;
}
#else
#error "unsupported compiler"
#endif
}
/* The manifest name of C's vtable. Only unscoped, non-template class names are
* supported (an empty result fails resolution and the install reports it). */
template <class C>
constexpr auto vtable_symbol() {
constexpr std::string_view name = class_name<C>();
constexpr bool simple = name.find_first_of(":<> ") == std::string_view::npos;
// "_ZTV" + decimal length + name / "??_7" + name + "@@6B@", NUL-terminated
std::array<char, name.size() + 12> out{};
if constexpr (!simple) {
return out;
}
size_t n = 0;
#if defined(_WIN32)
for (char c : {'?', '?', '_', '7'}) {
out[n++] = c;
}
for (char c : name) {
out[n++] = c;
}
for (char c : {'@', '@', '6', 'B', '@'}) {
out[n++] = c;
}
#else
for (char c : {'_', 'Z', 'T', 'V'}) {
out[n++] = c;
}
size_t len = name.size();
char digits[8]{};
size_t d = 0;
while (len != 0) {
digits[d++] = static_cast<char>('0' + len % 10);
len /= 10;
}
while (d != 0) {
out[n++] = digits[--d];
}
for (char c : name) {
out[n++] = c;
}
#endif
return out;
}
#if defined(_WIN32)
/* Follow jump stubs, then match the MSVC vcall thunk a virtual mfp points at.
* Returns the vtable slot's byte offset, or npos when fn is not a vcall thunk. */
inline size_t vcall_slot_offset(const void*& fn) noexcept {
constexpr size_t npos = static_cast<size_t>(-1);
#if defined(_M_X64) || defined(__x86_64__)
const auto* p = static_cast<const uint8_t*>(fn);
for (int i = 0; i < 8 && p[0] == 0xE9; ++i) { // incremental-link stubs
int32_t rel;
std::memcpy(&rel, p + 1, 4);
p += 5 + rel;
}
fn = p;
// The vptr load. Unoptimized clang-cl thunks spill/reload rcx first
// (push rax; mov [rsp], rcx; mov rcx, [rsp]), so scan a short window.
const uint8_t* q = nullptr;
for (int i = 0; i <= 12; ++i) {
if (p[i] == 0x48 && p[i + 1] == 0x8B && p[i + 2] == 0x01) { // mov rax, [rcx]
q = p + i + 3;
break;
}
}
if (q == nullptr) {
return npos;
}
if (q[0] == 0xFF && q[1] == 0x20) { // jmp [rax] (MSVC)
return 0;
}
if (q[0] == 0xFF && q[1] == 0x60) { // jmp [rax + imm8]
return static_cast<int8_t>(q[2]);
}
if (q[0] == 0xFF && q[1] == 0xA0) { // jmp [rax + imm32]
int32_t off;
std::memcpy(&off, q + 2, 4);
return off;
}
// clang-cl: mov rax, [rax + off]; (pop r10;) jmp rax. Requiring the jmp rax
// distinguishes the thunk from an ordinary getter that begins the same way.
if (q[0] == 0x48 && q[1] == 0x8B && (q[2] == 0x00 || q[2] == 0x40 || q[2] == 0x80)) {
size_t off = 0;
const uint8_t* r = q + 3;
if (q[2] == 0x40) {
off = static_cast<int8_t>(q[3]);
r = q + 4;
} else if (q[2] == 0x80) {
int32_t off32;
std::memcpy(&off32, q + 3, 4);
off = off32;
r = q + 7;
}
for (int i = 0; i <= 8; ++i) {
if (r[i] == 0xFF && r[i + 1] == 0xE0) { // jmp rax (48 REX optional)
return off;
}
}
}
return npos;
#elif defined(_M_ARM64) || defined(__aarch64__)
const auto* p = static_cast<const uint8_t*>(fn);
uint32_t insn[3];
for (int i = 0; i < 8; ++i) { // incremental-link `b` stubs
std::memcpy(insn, p, 4);
if ((insn[0] & 0xFC000000u) != 0x14000000u) {
break;
}
const auto imm26 = static_cast<int32_t>(insn[0] << 6) >> 6;
p += static_cast<intptr_t>(imm26) * 4;
}
fn = p;
std::memcpy(insn, p, 12);
// ldr Xt, [x0]; ldr Xs, [Xt, #imm12*8]; br Xs
if ((insn[0] & 0xFFFFFFE0u) != 0xF9400000u) {
return npos;
}
const uint32_t t = insn[0] & 0x1Fu;
if ((insn[1] & 0xFFC003E0u) != (0xF9400000u | (t << 5))) {
return npos;
}
const uint32_t s = insn[1] & 0x1Fu;
if (insn[2] != (0xD61F0000u | (s << 5))) {
return npos;
}
return ((insn[1] >> 10) & 0xFFFu) * 8;
#else
(void)fn;
return npos;
#endif
}
#endif
/* Code address of the member function a mfp designates. Virtual mfps don't carry
* one; recover it from the class's vtable (resolved from the symbol manifest), so
* Hook works uniformly on virtual and non-virtual members. */
template <class C, class F>
ModResult member_target(const HookService* hooks, F mfp, void** out) {
*out = nullptr;
uintptr_t words[sizeof(F) > sizeof(uintptr_t) ? 2 : 1] = {};
std::memcpy(words, &mfp, sizeof(words) < sizeof(F) ? sizeof(words) : sizeof(F));
#if defined(_WIN32)
const void* fn = reinterpret_cast<const void*>(words[0]);
const size_t slot = vcall_slot_offset(fn);
if (slot == static_cast<size_t>(-1)) { // not a vcall thunk: direct address
*out = const_cast<void*>(fn);
return MOD_OK;
}
void* vtable = nullptr;
const ModResult resolved = hooks->resolve(mod_ctx, vtable_symbol<C>().data(), &vtable, nullptr);
if (resolved != MOD_OK) {
return resolved;
}
// ??_7 points at the first slot.
*out = *reinterpret_cast<void**>(static_cast<char*>(vtable) + slot);
#else
#if defined(__aarch64__) || defined(__arm__)
// AAPCS C++ ABI: the virtual flag is bit 0 of the adjustment word (function
// addresses can't spare their low bit), and ptr holds the slot offset directly.
const bool isVirtual = (words[1] & 1) != 0;
const uintptr_t thisAdjust = words[1] >> 1;
const uintptr_t slotOffset = words[0];
#else
// Itanium C++ ABI: virtual mfps set bit 0 of ptr; the slot offset is ptr - 1.
const bool isVirtual = (words[0] & 1) != 0;
const uintptr_t thisAdjust = words[1];
const uintptr_t slotOffset = words[0] - 1;
#endif
if (!isVirtual) { // non-virtual: the address itself
*out = reinterpret_cast<void*>(words[0]);
return MOD_OK;
}
if (thisAdjust != 0) {
// this-adjusting mfp (member of a secondary base): the slot offset is
// relative to a vtable we can't locate. Hook the overrider by name instead.
return MOD_UNSUPPORTED;
}
void* vtable = nullptr;
const ModResult resolved = hooks->resolve(mod_ctx, vtable_symbol<C>().data(), &vtable, nullptr);
if (resolved != MOD_OK) {
return resolved;
}
// _ZTV points at the offset-to-top slot; the address point mfps index from is
// two pointers in (past offset-to-top and the typeinfo pointer).
*out = *reinterpret_cast<void**>(static_cast<char*>(vtable) + 2 * sizeof(void*) + slotOffset);
#endif
return *out != nullptr ? MOD_OK : MOD_UNAVAILABLE;
}
} // namespace detail
/* Trampoline generator + per-target state shared by Hook and NamedHook. Tag makes
* each hooked target's statics distinct; the target address is filled in at install. */
template <class Tag, class R, class... A>
struct HookImpl {
static inline R (*g_orig)(A...) = nullptr;
static inline const HookService* hooks = nullptr;
static inline void* target = nullptr;
static bool dispatch_pre(void* args, void* retval) {
if (hooks == nullptr) {
return false;
}
int skipOriginal = 0;
const ModResult result = hooks->dispatch_pre(mod_ctx, target, args, retval, &skipOriginal);
return result == MOD_OK && skipOriginal != 0;
}
static void dispatch_post(void* args, void* retval) {
if (hooks != nullptr) {
hooks->dispatch_post(mod_ctx, target, args, retval);
}
}
static R trampoline(A... args) {
if constexpr (sizeof...(A) == 0) {
if constexpr (std::is_void_v<R>) {
const bool skipOriginal = dispatch_pre(nullptr, nullptr);
if (!skipOriginal) {
g_orig(args...);
}
dispatch_post(nullptr, nullptr);
} else {
R result{};
const bool skipOriginal =
dispatch_pre(nullptr, static_cast<void*>(std::addressof(result)));
if (!skipOriginal) {
result = g_orig(args...);
}
dispatch_post(nullptr, static_cast<void*>(std::addressof(result)));
return result;
}
} else {
void* ptrs[] = {static_cast<void*>(std::addressof(args))...};
if constexpr (std::is_void_v<R>) {
const bool skipOriginal = dispatch_pre(static_cast<void*>(ptrs), nullptr);
if (!skipOriginal) {
g_orig(args...);
}
dispatch_post(static_cast<void*>(ptrs), nullptr);
} else {
R result{};
const bool skipOriginal = dispatch_pre(
static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
if (!skipOriginal) {
result = g_orig(args...);
}
dispatch_post(static_cast<void*>(ptrs), static_cast<void*>(std::addressof(result)));
return result;
}
}
}
};
namespace detail {
template <auto Target>
using TargetTag = std::integral_constant<decltype(Target), Target>;
template <FixedString Name>
struct NameTag {};
} // namespace detail
/*
* Typed hook on a function named at compile time (&daAlink_c::execute, &free_fn).
* Member functions may be virtual: the install decodes the member function pointer and hooks the
* class's own overrider.
*/
template <auto Target>
struct Hook;
template <class C, class R, class... A, R (C::*Target)(A...)>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, C*, A...> {
static ModResult resolve_target(const HookService* hooks, void** out) {
return detail::member_target<C>(hooks, Target, out);
}
};
template <class C, class R, class... A, R (C::*Target)(A...) const>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, const C*, A...> {
static ModResult resolve_target(const HookService* hooks, void** out) {
return detail::member_target<C>(hooks, Target, out);
}
};
template <class R, class... A, R (*Target)(A...)>
struct Hook<Target> : HookImpl<detail::TargetTag<Target>, R, A...> {
static ModResult resolve_target(const HookService*, void** out) {
*out = mfp_addr(Target);
return MOD_OK;
}
};
/*
* Typed hook on a function by its symbol name, for targets you can't name in C++: file-local
* statics, private members, or symbols without a header. The signature is written free-style with
* the receiver first and is *not* compiler-checked.
*
* using HookshotHit = dusk::mods::NamedHook<
* "daAlink_hookshotAtHitCallBack",
* void(fopAc_ac_c*, dCcD_GObjInf*, fopAc_ac_c*, dCcD_GObjInf*)>;
* dusk::mods::hook_add_pre<HookshotHit>(svc_hook, on_hookshot_hit);
*/
template <FixedString Name, class Sig>
struct NamedHook;
template <FixedString Name, class R, class... A>
struct NamedHook<Name, R(A...)> : HookImpl<detail::NameTag<Name>, R, A...> {
static ModResult resolve_target(const HookService* hooks, void** out) {
HookSymbolFlags flags{};
const ModResult resolved = hooks->resolve(mod_ctx, Name.chars, out, &flags);
if (resolved == MOD_OK && (flags & HOOK_SYMBOL_CODE) == 0) {
*out = nullptr;
return MOD_INVALID_ARGUMENT;
}
return resolved;
}
};
template <class Entry>
ModResult hook_install(const HookService* hooks) {
if (hooks == nullptr) {
return MOD_UNAVAILABLE;
}
Entry::hooks = hooks;
if (Entry::target == nullptr) {
const ModResult resolved = Entry::resolve_target(hooks, &Entry::target);
if (resolved != MOD_OK) {
return resolved;
}
}
return hooks->install(mod_ctx, Entry::target, reinterpret_cast<void*>(Entry::trampoline),
reinterpret_cast<void**>(&Entry::g_orig));
}
template <auto Target>
ModResult hook_install(const HookService* hooks) {
return hook_install<Hook<Target> >(hooks);
}
template <class Entry>
ModResult hook_add_pre(
const HookService* hooks, HookPreFn callback, const HookOptions* options = nullptr) {
const ModResult installed = hook_install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hooks->add_pre(mod_ctx, Entry::target, callback, options);
}
template <auto Target>
ModResult hook_add_pre(
const HookService* hooks, HookPreFn callback, const HookOptions* options = nullptr) {
return hook_add_pre<Hook<Target> >(hooks, callback, options);
}
template <class Entry>
ModResult hook_add_post(
const HookService* hooks, HookPostFn callback, const HookOptions* options = nullptr) {
const ModResult installed = hook_install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hooks->add_post(mod_ctx, Entry::target, callback, options);
}
template <auto Target>
ModResult hook_add_post(
const HookService* hooks, HookPostFn callback, const HookOptions* options = nullptr) {
return hook_add_post<Hook<Target> >(hooks, callback, options);
}
template <class Entry>
ModResult hook_replace(
const HookService* hooks, HookReplaceFn callback, const HookOptions* options = nullptr) {
const ModResult installed = hook_install<Entry>(hooks);
if (installed != MOD_OK) {
return installed;
}
return hooks->replace(mod_ctx, Entry::target, callback, options);
}
template <auto Target>
ModResult hook_replace(
const HookService* hooks, HookReplaceFn callback, const HookOptions* options = nullptr) {
return hook_replace<Hook<Target> >(hooks, callback, options);
}
} // namespace dusk::mods
-138
View File
@@ -1,138 +0,0 @@
#pragma once
#include "mods/api.h"
#include <cstdint>
#include <cstdio>
#include <type_traits>
#include <vector>
namespace dusk::mods {
template <class Service>
struct ServiceTraits;
namespace detail {
inline std::vector<ServiceImport>& imports() {
static std::vector<ServiceImport> entries;
return entries;
}
inline std::vector<ServiceExport>& exports() {
static std::vector<ServiceExport> entries;
return entries;
}
inline int register_import(ServiceImport entry) {
imports().push_back(entry);
return 0;
}
inline int register_export(ServiceExport entry) {
exports().push_back(entry);
return 0;
}
inline const ModManifest* manifest() {
static ModManifest manifest{
sizeof(ModManifest),
MOD_ABI_VERSION,
nullptr,
0,
nullptr,
0,
};
auto& importEntries = imports();
auto& exportEntries = exports();
manifest.imports = importEntries.data();
manifest.import_count = importEntries.size();
manifest.exports = exportEntries.data();
manifest.export_count = exportEntries.size();
return &manifest;
}
} // namespace detail
inline ModResult set_error(ModError* outError, ModResult code, const char* message) {
if (outError != nullptr && outError->struct_size >= sizeof(ModError)) {
outError->code = code;
outError->message[0] = '\0';
if (message != nullptr) {
std::snprintf(outError->message, sizeof(outError->message), "%s", message);
}
}
return code;
}
} // namespace dusk::mods
#define DEFINE_MOD() \
extern "C" { \
MOD_EXPORT ModContext* mod_ctx = nullptr; \
MOD_EXPORT const ModManifest* mod_get_manifest(void) { \
return ::dusk::mods::detail::manifest(); \
} \
}
// Declares `static const service_type* variable`, filled in by the host before mod_initialize.
// Required imports are guaranteed non-null (the mod fails to load otherwise); optional imports
// must be checked against nullptr before use.
#define IMPORT_SERVICE_EX( \
service_type, variable, service_id_value, major_value, min_minor_value, flags_value) \
static const service_type* variable = nullptr; \
[[maybe_unused]] static const int mod_import_registration_##variable = \
::dusk::mods::detail::register_import(ServiceImport{ \
sizeof(ServiceImport), \
(service_id_value), \
static_cast<uint16_t>(major_value), \
static_cast<uint16_t>(min_minor_value), \
static_cast<uint32_t>(flags_value), \
&(variable), \
})
#define IMPORT_SERVICE_VERSION(service_type, variable, min_minor_value) \
IMPORT_SERVICE_EX(service_type, variable, ::dusk::mods::ServiceTraits<service_type>::id, \
::dusk::mods::ServiceTraits<service_type>::major_version, min_minor_value, \
SERVICE_IMPORT_REQUIRED)
#define IMPORT_SERVICE(service_type, variable) IMPORT_SERVICE_VERSION(service_type, variable, 0)
#define IMPORT_OPTIONAL_SERVICE_VERSION(service_type, variable, min_minor_value) \
IMPORT_SERVICE_EX(service_type, variable, ::dusk::mods::ServiceTraits<service_type>::id, \
::dusk::mods::ServiceTraits<service_type>::major_version, min_minor_value, \
SERVICE_IMPORT_OPTIONAL)
#define IMPORT_OPTIONAL_SERVICE(service_type, variable) \
IMPORT_OPTIONAL_SERVICE_VERSION(service_type, variable, 0)
#define EXPORT_SERVICE_AS(instance, service_id_value) \
namespace { \
const int mod_export_registration_##instance = \
::dusk::mods::detail::register_export(ServiceExport{ \
sizeof(ServiceExport), \
(service_id_value), \
(instance).header.major_version, \
(instance).header.minor_version, \
SERVICE_EXPORT_STATIC, \
&(instance), \
}); \
}
#define EXPORT_SERVICE(instance) \
EXPORT_SERVICE_AS( \
instance, ::dusk::mods::ServiceTraits<std::remove_cv_t<decltype(instance)> >::id)
#define EXPORT_DEFERRED_SERVICE(token, service_id_value, major_value, minor_value) \
namespace { \
const int mod_deferred_export_registration_##token = \
::dusk::mods::detail::register_export(ServiceExport{ \
sizeof(ServiceExport), \
(service_id_value), \
static_cast<uint16_t>(major_value), \
static_cast<uint16_t>(minor_value), \
SERVICE_EXPORT_DEFERRED, \
nullptr, \
}); \
}
-64
View File
@@ -1,64 +0,0 @@
#pragma once
#include "mods/api.h"
#define CAMERA_SERVICE_ID "dev.twilitrealm.dusklight.camera"
#define CAMERA_SERVICE_MAJOR 1u
#define CAMERA_SERVICE_MINOR 0u
/*
* Snapshot of a game camera for the frame currently being recorded.
*
* Matrix conventions: every matrix is a column-major float[16] using the matrix * column-vector
* convention, ready to memcpy into a WGSL mat4x4f uniform. NOTE: this is the TRANSPOSE of the
* game's row-major Mtx/Mtx44 layout; mods that want the raw game matrices should read the
* view_class directly instead.
*
* View space is right-handed with -Z forward. Projection matrices are in WebGPU clip convention
* and follow the renderer's depth mode: reversed-Z by default (depth 1.0 at the near plane,
* 0.0 at far).
*
* Unprojecting a depth-buffer texel at uv with sampled depth d:
* let ndc = vec3f(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0, d); // WebGPU framebuffer y is down
* let world4 = world_from_proj * vec4f(ndc, 1.0);
* let world = world4.xyz / world4.w;
*/
typedef struct CameraInfo {
uint32_t struct_size;
float view_from_world[16]; /* the view matrix */
float world_from_view[16]; /* its inverse; column 3 is the camera position */
float proj_from_view[16]; /* WebGPU-convention projection (+ Aurora reversed-Z) */
float view_from_proj[16]; /* its inverse */
float proj_from_world[16]; /* proj_from_view * view_from_world */
float world_from_proj[16]; /* one-step depth-buffer -> world unproject */
float eye[3]; /* camera position in world space */
float fovy; /* vertical field of view, degrees */
float aspect;
float near_plane;
float far_plane;
} CameraInfo;
#define CAMERA_INFO_INIT {sizeof(CameraInfo)}
typedef struct CameraService {
ServiceHeader header;
/*
* Snapshots a camera. game_view must be a view_class pointer, such as from a render stage
* callback's game view. Game thread only. Returns MOD_UNAVAILABLE when the view is not a valid
* perspective camera.
*/
ModResult (*get_camera)(ModContext* ctx, const void* game_view, CameraInfo* out_info);
} CameraService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<CameraService> {
static constexpr const char* id = CAMERA_SERVICE_ID;
static constexpr uint16_t major_version = CAMERA_SERVICE_MAJOR;
};
#endif
-107
View File
@@ -1,107 +0,0 @@
#pragma once
#include "mods/api.h"
#define CONFIG_SERVICE_ID "dev.twilitrealm.dusklight.config"
#define CONFIG_SERVICE_MAJOR 1u
#define CONFIG_SERVICE_MINOR 0u
/* Handle for a config var registered by the calling mod. 0 is never a valid handle. */
typedef uint64_t ConfigVarHandle;
/* Handle for a change subscription. 0 is never a valid handle. */
typedef uint64_t ConfigSubscriptionHandle;
typedef enum ConfigVarType {
CONFIG_VAR_BOOL = 0, /* bool */
CONFIG_VAR_INT = 1, /* int64_t */
CONFIG_VAR_FLOAT = 2, /* double */
CONFIG_VAR_STRING = 3, /* UTF-8 */
} ConfigVarType;
typedef struct ConfigVarDesc {
uint32_t struct_size;
/* Name fragment: 1-64 characters from [A-Za-z0-9_-]. The full config key is
* "mod.<escaped mod id>.<name>", persisted in config.json alongside host settings.
* "enabled" is reserved by the loader. */
const char* name;
ConfigVarType type;
/* Default value; only the field matching `type` is read. */
bool default_bool;
int64_t default_int;
double default_float;
const char* default_string; /* NULL means "" */
} ConfigVarDesc;
#define CONFIG_VAR_DESC_INIT {sizeof(ConfigVarDesc), NULL, CONFIG_VAR_BOOL, false, 0, 0.0, NULL}
/* Snapshot of a var's value; only the field matching `type` is meaningful. */
typedef struct ConfigVarValue {
uint32_t struct_size;
ConfigVarType type;
bool bool_value;
int64_t int_value;
double float_value;
const char* string_value; /* NUL-terminated; NULL for non-string vars */
size_t string_length; /* excludes the NUL */
} ConfigVarValue;
/*
* Fired on the game thread whenever the var's effective value changes at runtime: the calling mod's
* own set_* calls and any other runtime writer. Writes that leave the value unchanged do not fire,
* and neither do values applied from config.json or --cvar during registration. `value` holds the
* new (current) value and `previous` the one it replaced; both snapshots are valid only for the
* duration of the call (copy string_value if you need to keep it). Setting the same var from inside
* its own callback applies the write but is not re-notified.
*/
typedef void (*ConfigChangedFn)(ModContext* ctx, ConfigVarHandle var, const ConfigVarValue* value,
const ConfigVarValue* previous, void* user_data);
/*
* Scoped configuration variables.
*
* Registrations are owned by the calling mod and removed automatically (subscriptions included)
* when it is disabled, reloaded, or fails. Values are saved to config.json. Writes are debounced,
* not flushed per set.
*/
typedef struct ConfigService {
ServiceHeader header;
/* Register a config var. If a value for the full key was saved earlier (or set via --cvar),
* it takes effect immediately; otherwise the var starts at the default. Registering a name
* that is already live is MOD_CONFLICT. */
ModResult (*register_var)(
ModContext* ctx, const ConfigVarDesc* desc, ConfigVarHandle* out_handle);
/* Unregister a var previously registered by the calling mod. Its persisted value is kept. */
ModResult (*unregister_var)(ModContext* ctx, ConfigVarHandle var);
/* Typed accessors; the type must match the registration (MOD_INVALID_ARGUMENT otherwise). */
ModResult (*get_bool)(ModContext* ctx, ConfigVarHandle var, bool* out_value);
ModResult (*set_bool)(ModContext* ctx, ConfigVarHandle var, bool value);
ModResult (*get_int)(ModContext* ctx, ConfigVarHandle var, int64_t* out_value);
ModResult (*set_int)(ModContext* ctx, ConfigVarHandle var, int64_t value);
ModResult (*get_float)(ModContext* ctx, ConfigVarHandle var, double* out_value);
ModResult (*set_float)(ModContext* ctx, ConfigVarHandle var, double value);
/* Copies the NUL-terminated value into buffer. out_length (optional) receives the full
* length excluding the NUL regardless of buffer size; call with buffer == NULL and
* buffer_size == 0 to query the length. A non-NULL buffer that is too small fails with
* MOD_INVALID_ARGUMENT and writes nothing. */
ModResult (*get_string)(
ModContext* ctx, ConfigVarHandle var, char* buffer, size_t buffer_size, size_t* out_length);
ModResult (*set_string)(ModContext* ctx, ConfigVarHandle var, const char* value);
/* Subscribe to changes of a var registered by the calling mod. out_handle may be NULL if
* the subscription is never removed manually (cleanup on mod teardown is automatic). */
ModResult (*subscribe)(ModContext* ctx, ConfigVarHandle var, ConfigChangedFn callback,
void* user_data, ConfigSubscriptionHandle* out_handle);
ModResult (*unsubscribe)(ModContext* ctx, ConfigSubscriptionHandle handle);
} ConfigService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<ConfigService> {
static constexpr const char* id = CONFIG_SERVICE_ID;
static constexpr uint16_t major_version = CONFIG_SERVICE_MAJOR;
};
#endif
-30
View File
@@ -1,30 +0,0 @@
#pragma once
#include "mods/api.h"
/*
* Mods that link or hook game code directly must import this service; service-only and asset-only
* mods must not.
*
* Major version is the game-code ABI epoch: it is bumped when game-visible struct or vtable layouts
* change incompatibly (e.g. a TARGET_PC field added to an existing game struct). The loader's
* ordinary version check then fails mods built against the old epoch with a clear message instead
* of letting them corrupt memory.
*/
#define GAME_SERVICE_ID "dev.twilitrealm.dusklight.game"
#define GAME_SERVICE_MAJOR 1u
#define GAME_SERVICE_MINOR 0u
typedef struct GameService {
ServiceHeader header;
} GameService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<GameService> {
static constexpr const char* id = GAME_SERVICE_ID;
static constexpr uint16_t major_version = GAME_SERVICE_MAJOR;
};
#endif
-209
View File
@@ -1,209 +0,0 @@
#pragma once
#include "mods/api.h"
#include <webgpu/webgpu.h>
/*
* Direct WebGPU access at various stages of the rendering pipeline. Mods use the wgpu* C API
* (via webgpu/webgpu.h) for custom draws and compute dispatches.
*
* Every service function must be called on the game thread. GfxStageFn callbacks run on the game
* thread during frame recording. push_draw, push_* and pass functions are valid from a stage
* callback and anywhere else GX commands are being recorded.
*
* GfxDrawFn and GfxComputeFn callbacks run on the render worker thread while the frame is encoded.
* They may use only the handles in their context struct and raw wgpu* calls; no other service may
* be called from them.
*
* All WGPU handles provided by this service are borrowed. Handles in callback contexts are valid
* only for the duration of the callback; views in GfxResolvedTargets are valid for the current
* frame only. GPU objects a mod creates through raw wgpu calls are its own responsibility and
* should be released in mod_shutdown. The device outlives all mods.
*/
#define GFX_SERVICE_ID "dev.twilitrealm.dusklight.gfx"
#define GFX_SERVICE_MAJOR 1u
#define GFX_SERVICE_MINOR 0u
/* Maximum size for push_draw payload */
#define GFX_INLINE_DRAW_PAYLOAD_SIZE 128u
/* 0 is never a valid handle. */
typedef uint64_t GfxDrawTypeHandle;
typedef uint64_t GfxStageHookHandle;
typedef uint64_t GfxComputeTypeHandle;
/* A suballocation in one of the shared per-frame streaming buffers. */
typedef struct GfxRange {
uint32_t offset;
uint32_t size;
} GfxRange;
/*
* Device and scene pass configuration. Valid from mod_initialize onward and stable for the
* session. Offscreen passes from create_pass are always single-sample.
*/
typedef struct GfxDeviceInfo {
uint32_t struct_size;
WGPUDevice device; /* borrowed */
WGPUQueue queue; /* borrowed */
WGPUTextureFormat color_format; /* scene color target format */
WGPUTextureFormat depth_format; /* scene depth target format */
uint32_t sample_count; /* scene pass MSAA sample count */
bool uses_reversed_z; /* true means depth 1.0 is near */
} GfxDeviceInfo;
#define GFX_DEVICE_INFO_INIT \
{sizeof(GfxDeviceInfo), NULL, NULL, WGPUTextureFormat_Undefined, WGPUTextureFormat_Undefined, \
1u, false}
/*
* Passed to GfxDrawFn on the render worker thread; valid only during the call. The pass pipeline,
* bind group, viewport, and scissor state is restored by the host after the callback returns.
*/
typedef struct GfxDrawContext {
uint32_t struct_size;
WGPUDevice device;
WGPUQueue queue;
WGPURenderPassEncoder pass;
WGPUBuffer vertex_buffer;
WGPUBuffer index_buffer;
WGPUBuffer uniform_buffer;
WGPUBuffer storage_buffer;
WGPUTextureFormat color_format;
WGPUTextureFormat depth_format;
uint32_t sample_count;
uint32_t target_width;
uint32_t target_height;
bool uses_reversed_z;
} GfxDrawContext;
typedef void (*GfxDrawFn)(ModContext* ctx, const GfxDrawContext* draw_ctx, const void* payload,
size_t payload_size, void* user_data);
typedef struct GfxDrawTypeDesc {
uint32_t struct_size;
const char* label; /* optional debug label */
GfxDrawFn draw; /* required; called from the render worker thread */
void* user_data;
} GfxDrawTypeDesc;
#define GFX_DRAW_TYPE_DESC_INIT {sizeof(GfxDrawTypeDesc), NULL, NULL, NULL}
typedef enum GfxStage {
GFX_STAGE_SCENE_AFTER_TERRAIN = 0,
GFX_STAGE_FRAME_BEFORE_HUD = 1,
GFX_STAGE_FRAME_AFTER_HUD = 2,
GFX_STAGE_SCENE_BEGIN = 3,
GFX_STAGE_SCENE_AFTER_OPAQUE = 4,
} GfxStage;
typedef struct GfxStageContext {
uint32_t struct_size;
GfxStage stage;
const void* game_view; /* view_class* for world-camera stages; NULL otherwise */
const void* game_viewport; /* view_port_class* for world-camera stages; NULL otherwise */
} GfxStageContext;
typedef void (*GfxStageFn)(ModContext* ctx, const GfxStageContext* stage_ctx, void* user_data);
typedef struct GfxStageHookDesc {
uint32_t struct_size;
GfxStageFn callback; /* required */
void* user_data;
} GfxStageHookDesc;
#define GFX_STAGE_HOOK_DESC_INIT {sizeof(GfxStageHookDesc), NULL, NULL}
typedef struct GfxResolveDesc {
uint32_t struct_size;
bool color;
bool depth;
} GfxResolveDesc;
#define GFX_RESOLVE_DESC_INIT {sizeof(GfxResolveDesc), true, false}
typedef struct GfxResolvedTargets {
uint32_t struct_size;
WGPUTextureView color; /* single-sample snapshot in color_format */
WGPUTextureView depth; /* single-sample raw depth snapshot, R32Float when available */
WGPUTextureFormat color_format;
uint32_t width;
uint32_t height;
} GfxResolvedTargets;
#define GFX_RESOLVED_TARGETS_INIT \
{sizeof(GfxResolvedTargets), NULL, NULL, WGPUTextureFormat_Undefined, 0u, 0u}
/*
* Passed to GfxComputeFn on the render worker thread; valid only during the call. The encoder is
* the frame command encoder between scene render passes. Leave no pass open and never finish or
* release the encoder.
*/
typedef struct GfxComputeContext {
uint32_t struct_size;
WGPUDevice device;
WGPUQueue queue;
WGPUCommandEncoder encoder;
WGPUBuffer vertex_buffer;
WGPUBuffer index_buffer;
WGPUBuffer uniform_buffer;
WGPUBuffer storage_buffer;
} GfxComputeContext;
typedef void (*GfxComputeFn)(ModContext* ctx, const GfxComputeContext* compute_ctx,
const void* payload, size_t payload_size, void* user_data);
typedef struct GfxComputeTypeDesc {
uint32_t struct_size;
const char* label; /* optional debug label */
GfxComputeFn callback; /* required; called from the render worker thread */
void* user_data;
} GfxComputeTypeDesc;
#define GFX_COMPUTE_TYPE_DESC_INIT {sizeof(GfxComputeTypeDesc), NULL, NULL, NULL}
typedef struct GfxService {
ServiceHeader header;
ModResult (*get_device_info)(ModContext* ctx, GfxDeviceInfo* out_info);
void* (*get_proc_address)(ModContext* ctx, const char* name);
ModResult (*register_draw_type)(
ModContext* ctx, const GfxDrawTypeDesc* desc, GfxDrawTypeHandle* out_handle);
ModResult (*unregister_draw_type)(ModContext* ctx, GfxDrawTypeHandle handle);
ModResult (*push_draw)(
ModContext* ctx, GfxDrawTypeHandle handle, const void* payload, size_t payload_size);
ModResult (*register_compute_type)(
ModContext* ctx, const GfxComputeTypeDesc* desc, GfxComputeTypeHandle* out_handle);
ModResult (*unregister_compute_type)(ModContext* ctx, GfxComputeTypeHandle handle);
ModResult (*push_compute)(
ModContext* ctx, GfxComputeTypeHandle handle, const void* payload, size_t payload_size);
ModResult (*push_verts)(
ModContext* ctx, const void* data, size_t size, size_t alignment, GfxRange* out_range);
ModResult (*push_indices)(
ModContext* ctx, const void* data, size_t size, size_t alignment, GfxRange* out_range);
ModResult (*push_uniform)(ModContext* ctx, const void* data, size_t size, GfxRange* out_range);
ModResult (*push_storage)(ModContext* ctx, const void* data, size_t size, GfxRange* out_range);
ModResult (*register_stage_hook)(ModContext* ctx, GfxStage stage, const GfxStageHookDesc* desc,
GfxStageHookHandle* out_handle);
ModResult (*unregister_stage_hook)(ModContext* ctx, GfxStageHookHandle handle);
ModResult (*resolve_pass)(
ModContext* ctx, const GfxResolveDesc* desc, GfxResolvedTargets* out_targets);
ModResult (*create_pass)(ModContext* ctx, uint32_t width, uint32_t height);
} GfxService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<GfxService> {
static constexpr const char* id = GFX_SERVICE_ID;
static constexpr uint16_t major_version = GFX_SERVICE_MAJOR;
};
#endif
-125
View File
@@ -1,125 +0,0 @@
#pragma once
#include "mods/api.h"
/*
* Intercept game functions by address. Prefer the typed helpers in mods/hook.hpp
* (hook_add_pre/hook_add_post/hook_replace over a &Class::method): they generate the
* trampoline and hide install/dispatch, which are the low-level primitives those helpers
* build. resolve() maps a symbol name to an address for targets you can't name at compile time
* (file-local statics included).
*
* Every call is game-thread-only. Install and removal must run with no hooked function on the
* stack; the loader guarantees this by applying mod lifecycle changes between frames, which is
* why hooking a function that never returns (the outermost loop) makes a mod un-unloadable.
*/
#define HOOK_SERVICE_ID "dev.twilitrealm.dusklight.hook"
#define HOOK_SERVICE_MAJOR 1u
#define HOOK_SERVICE_MINOR 0u
/* Symbol flags reported by resolve() */
typedef enum HookSymbolFlags {
HOOK_SYMBOL_CODE = 1u << 0u,
HOOK_SYMBOL_DATA = 1u << 1u,
/* Not exported/dynamically visible: hookable, but never linkable. */
HOOK_SYMBOL_LOCAL = 1u << 2u,
/* Other names share this address (ICF fold/alias): a hook intercepts them all. */
HOOK_SYMBOL_MULTI_NAME = 1u << 3u,
/* Resolved through a demangled display-name alias rather than the real symbol. */
HOOK_SYMBOL_DISPLAY = 1u << 6u,
} HookSymbolFlags;
/* A pre-hook's return value: whether to run the original function. */
typedef enum HookAction {
HOOK_CONTINUE = 0, /* run the original (and any lower-priority pre-hooks) */
HOOK_SKIP_ORIGINAL = 1, /* cancel the original and remaining pre-hooks; post-hooks still run */
} HookAction;
/* How replace resolves a second replace-hook on a target that already has one. */
typedef enum HookReplacePolicy {
HOOK_REPLACE_CONFLICT = 0, /* refuse with MOD_CONFLICT (the default) */
HOOK_REPLACE_PRIORITY = 1, /* take over only if this options.priority is strictly higher */
HOOK_REPLACE_OVERRIDE = 2, /* take over unconditionally */
} HookReplacePolicy;
/*
* Hook callbacks. `args` is an array of pointers to the call's arguments (index 0 is `this`
* for member functions); `retval` points at the return slot (NULL for void). Read and write
* them through dusk::mods::arg<T> / arg_ref<T> from mods/hook.hpp. `userdata` is the pointer
* from HookOptions. All run on the game thread, in the hooked call's own stack frame.
*/
typedef HookAction (*HookPreFn)(ModContext* ctx, void* args, void* retval, void* userdata);
typedef void (*HookPostFn)(ModContext* ctx, void* args, void* retval, void* userdata);
typedef void (*HookReplaceFn)(ModContext* ctx, void* args, void* retval, void* userdata);
typedef struct HookOptions {
uint32_t struct_size;
/* Higher runs first; ties break by registration order. Applies to pre/post ordering and,
* with HOOK_REPLACE_PRIORITY, to replace-hook takeover. */
int32_t priority;
HookReplacePolicy replace_policy;
void* userdata; /* passed back to the callback */
} HookOptions;
#define HOOK_OPTIONS_INIT {sizeof(HookOptions), 0, HOOK_REPLACE_CONFLICT, NULL}
typedef struct HookService {
ServiceHeader header;
/*
* Install a trampoline detour on fn_addr and return the address to call the original through in
* *out_original_fn. The typed helpers generate the trampoline and call this; mods normally
* don't. The first mod to install a given target owns the live detour; later mods register as
* candidates so a hook survives the owner unloading (the detour is handed off and every
* original pointer is rewritten). Idempotent per (mod, out slot).
*/
ModResult (*install)(
ModContext* ctx, void* fn_addr, void* trampoline_fn, void** out_original_fn);
/*
* Register a callback on an already-installed target. Pre runs before the original (and can
* cancel it), post runs after (even if cancelled). Any number of mods may add pre/post to the
* same target; they run in priority then registration order. replace installs a single
* substitute for the original, managed by options.replace_policy, MOD_CONFLICT if refused.
*/
ModResult (*add_pre)(
ModContext* ctx, void* fn_addr, HookPreFn callback, const HookOptions* options);
ModResult (*add_post)(
ModContext* ctx, void* fn_addr, HookPostFn callback, const HookOptions* options);
ModResult (*replace)(
ModContext* ctx, void* fn_addr, HookReplaceFn callback, const HookOptions* options);
/*
* Run the registered callbacks for a target. The generated trampoline calls these; they
* are not a mod-facing entry point. dispatch_pre reports through *out_skip_original
* whether the original should be skipped (a pre-hook returned HOOK_SKIP_ORIGINAL, or a
* replace-hook ran).
*/
ModResult (*dispatch_pre)(
ModContext* ctx, void* fn_addr, void* args, void* retval, int* out_skip_original);
ModResult (*dispatch_post)(ModContext* ctx, void* fn_addr, void* args, void* retval);
/*
* Resolve a game symbol by name from the symbol manifest, including non-exported (static)
* functions. Names can be either the platform's mangled name (i.e. the name passed to dlopen;
* no Mach-O leading underscore) or the qualified function name without parameters (e.g.
* "daAlink_c::execute"). out_flags (optional) receives HookSymbolFlags.
*
* Results: MOD_OK; MOD_UNSUPPORTED (no manifest for this build, missing or stale);
* MOD_UNAVAILABLE (symbol not found); MOD_CONFLICT (name maps to more than one address: C++
* overloads or per-TU statics; use the mangled name).
*/
ModResult (*resolve)(
ModContext* ctx, const char* symbol, void** out_addr, HookSymbolFlags* out_flags);
} HookService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<HookService> {
static constexpr const char* id = HOOK_SERVICE_ID;
static constexpr uint16_t major_version = HOOK_SERVICE_MAJOR;
};
#endif
-104
View File
@@ -1,104 +0,0 @@
#pragma once
#include "mods/api.h"
/*
* The host service: the calling mod's identity and its runtime interface to the loader.
* Always available; every other service can be reached from it.
*/
#define HOST_SERVICE_ID "dev.twilitrealm.dusklight.host"
#define HOST_SERVICE_MAJOR 2u
#define HOST_SERVICE_MINOR 0u
/*
* Ignore unknown values: later service minors may add events.
*/
typedef enum ModLifecycleEvent {
/*
* The subject mod is gone: its mod_shutdown has run (when it initialized at all) and
* every service has already dropped the state it held for it. The subject's library is
* still mapped, so pointers into it are valid to compare against, but they must not be
* called or dereferenced after the callback returns. Drop everything keyed to the
* mod: callbacks it registered, its ModContext*, state indexed by it.
*/
MOD_LIFECYCLE_DETACHED = 0,
} ModLifecycleEvent;
/*
* ctx is the watching mod's own context; subject identifies the mod the event is about.
* subject_id is valid only for the duration of the call.
*/
typedef void (*ModLifecycleFn)(ModContext* ctx, ModContext* subject, const char* subject_id,
ModLifecycleEvent event, void* user_data);
typedef struct HostService {
ServiceHeader header;
/* Version string of the current Dusklight build. (e.g. "1.4.2") */
const char* version;
/* Build id of the running game binary: PDB GUID+age on Windows, LC_UUID on macOS, GNU build-id
* on Linux. May be empty (len 0) if the identity could not be determined. */
const uint8_t* build_id;
uint32_t build_id_len;
/*
* Look up a service by id at call time. Unlike a manifest import, this sees whatever is
* currently published and carries no initialization-order guarantee (see mods/api.h).
* MOD_UNAVAILABLE if no matching service is published; *out_service is null on failure.
*/
ModResult (*get_service)(ModContext* ctx, const char* service_id, uint16_t major_version,
uint16_t min_minor_version, const void** out_service);
/*
* Publish a service the calling mod declared as a deferred export in its manifest.
* Must happen during mod_initialize so importers can resolve it; `service` must stay
* valid until the mod shuts down.
*/
ModResult (*publish_service)(
ModContext* ctx, const char* service_id, uint16_t major_version, const void* service);
/*
* Report an unrecoverable failure. The calling mod's services stop resolving immediately
* and the loader fully disables it at the next safe point; `message` is shown to the user.
* Safe to call from any mod callback.
*/
void (*fail)(ModContext* ctx, ModResult code, const char* message);
/*
* The calling mod's manifest metadata. Returned strings remain valid while the mod is
* loaded.
*/
const char* (*mod_id)(ModContext* ctx);
const char* (*mod_name)(ModContext* ctx);
const char* (*mod_version)(ModContext* ctx);
/*
* A writable scratch directory reserved for the calling mod. Contents survive disable
* and reload within a session, but the directory is wiped at game startup.
*/
const char* (*mod_dir)(ModContext* ctx);
/*
* Observe other mods' lifecycle events. Any mod whose service hands out per-caller state
* (registrations, callbacks, handles) should watch for MOD_LIFECYCLE_DETACHED and drop what it
* holds for the subject.
*
* Callbacks fire on the game thread at a lifecycle safe point (never mid-frame), for
* every mod but the watcher itself (use mod_shutdown for self-cleanup).
*/
ModResult (*watch_mod_lifecycle)(
ModContext* ctx, ModLifecycleFn fn, void* user_data, uint64_t* out_handle);
ModResult (*unwatch_mod_lifecycle)(ModContext* ctx, uint64_t handle);
} HostService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<HostService> {
static constexpr const char* id = HOST_SERVICE_ID;
static constexpr uint16_t major_version = HOST_SERVICE_MAJOR;
};
#endif
-47
View File
@@ -1,47 +0,0 @@
#pragma once
#include "mods/api.h"
/*
* Logging into the game's console and log files. Messages are attributed to the calling mod
* (prefixed with its ID).
*/
#define LOG_SERVICE_ID "dev.twilitrealm.dusklight.log"
#define LOG_SERVICE_MAJOR 1u
#define LOG_SERVICE_MINOR 0u
typedef enum LogLevel {
LOG_LEVEL_TRACE = 0,
LOG_LEVEL_DEBUG = 1,
LOG_LEVEL_INFO = 2,
LOG_LEVEL_WARN = 3,
LOG_LEVEL_ERROR = 4,
} LogLevel;
typedef struct LogService {
ServiceHeader header;
/*
* Write a log message at the given level.
* `message` is a plain UTF-8 string and is copied before returning.
*/
void (*write)(ModContext* ctx, LogLevel level, const char* message);
/* Per-level shorthands for write. */
void (*trace)(ModContext* ctx, const char* message);
void (*debug)(ModContext* ctx, const char* message);
void (*info)(ModContext* ctx, const char* message);
void (*warn)(ModContext* ctx, const char* message);
void (*error)(ModContext* ctx, const char* message);
} LogService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<LogService> {
static constexpr const char* id = LOG_SERVICE_ID;
static constexpr uint16_t major_version = LOG_SERVICE_MAJOR;
};
#endif
-57
View File
@@ -1,57 +0,0 @@
#pragma once
#include "mods/api.h"
#define OVERLAY_SERVICE_ID "dev.twilitrealm.dusklight.overlay"
#define OVERLAY_SERVICE_MAJOR 1u
#define OVERLAY_SERVICE_MINOR 0u
/* Handle for a runtime overlay registration. 0 is never a valid handle. */
typedef uint64_t OverlayHandle;
/*
* Runtime DVD file overlays.
*
* Registrations are owned by the calling mod and removed automatically when it is disabled,
* reloaded, or fails. Changes are applied at the next frame boundary; data the game has already
* read stays in memory until it re-reads the file (sometimes on scene reload, sometimes on
* restart).
*
* disc_path names the file to overlay: absolute with a leading '/', matched against the disc
* case-insensitively (e.g. "/res/Stage/R04_00.arc"). Paths that do not exist on the disc are added
* as new files.
*
* If multiple sources overlay the same path, the last one wins: a mod's runtime registrations beat
* its static overlay/ files, and later-loaded mods beat earlier ones.
*/
typedef struct OverlayService {
ServiceHeader header;
/*
* Overlay disc_path with a file from the calling mod's bundle (bundle-relative path, e.g.
* "res/replacement.arc"). The file's contents are read lazily on each open, so the bundle
* file must not change size while registered.
*/
ModResult (*add_file)(
ModContext* ctx, const char* disc_path, const char* bundle_path, OverlayHandle* out_handle);
/*
* Overlay disc_path with a caller-owned buffer. The data is copied; the caller may free it
* as soon as this returns.
*/
ModResult (*add_buffer)(ModContext* ctx, const char* disc_path, const void* data, size_t size,
OverlayHandle* out_handle);
/* Remove a runtime overlay previously added by the calling mod. */
ModResult (*remove)(ModContext* ctx, OverlayHandle handle);
} OverlayService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<OverlayService> {
static constexpr const char* id = OVERLAY_SERVICE_ID;
static constexpr uint16_t major_version = OVERLAY_SERVICE_MAJOR;
};
#endif
-52
View File
@@ -1,52 +0,0 @@
#pragma once
#include "mods/api.h"
/*
* Read-only access to the res/ tree of the calling mod's own bundle. Reload serves the new
* bundle's contents. For writable storage, use HostService::mod_dir.
*/
#define RESOURCE_SERVICE_ID "dev.twilitrealm.dusklight.resource"
#define RESOURCE_SERVICE_MAJOR 1u
#define RESOURCE_SERVICE_MINOR 0u
/*
* A loaded resource, allocated by the service. Return every successful load with free;
* buffers still live when the mod is disabled or reloaded are reclaimed with a warning.
*/
typedef struct ResourceBuffer {
uint32_t struct_size;
void* data;
size_t size;
} ResourceBuffer;
#define RESOURCE_BUFFER_INIT {sizeof(ResourceBuffer), NULL, 0u}
typedef struct ResourceService {
ServiceHeader header;
/*
* Load a file into a fresh allocation. `relative_path` is resolved against the bundle's
* res/ directory. Absolute paths and ".." are rejected. MOD_UNAVAILABLE if the file does not
* exist. An empty file loads as data == NULL with size 0. Previous contents of `out_buffer` are
* overwritten, not freed.
*/
ModResult (*load)(ModContext* ctx, const char* relative_path, ResourceBuffer* out_buffer);
/*
* Release a loaded buffer and reset it to the empty state. Safe to call on an empty or
* already-freed buffer.
*/
void (*free)(ModContext* ctx, ResourceBuffer* buffer);
} ResourceService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<ResourceService> {
static constexpr const char* id = RESOURCE_SERVICE_ID;
static constexpr uint16_t major_version = RESOURCE_SERVICE_MAJOR;
};
#endif
-88
View File
@@ -1,88 +0,0 @@
#pragma once
#include "mods/api.h"
#define TEXTURE_SERVICE_ID "dev.twilitrealm.dusklight.texture"
#define TEXTURE_SERVICE_MAJOR 1u
#define TEXTURE_SERVICE_MINOR 0u
/* Handle for a runtime texture replacement registration. 0 is never a valid handle. */
typedef uint64_t TextureReplacementHandle;
typedef enum TextureKeyKind {
/* Match a texture by the address of its in-memory GX texel data. */
TEXTURE_KEY_POINTER = 0,
/* Match by content: XXH64 of the base mip level (and of the referenced TLUT range for
* palette formats), as encoded in replacement filenames / texture dumps. */
TEXTURE_KEY_SOURCE = 1,
} TextureKeyKind;
/* Wildcard values for TEXTURE_KEY_SOURCE hashes ("$" in the filename convention). */
#define TEXTURE_HASH_WILDCARD UINT64_C(0xFFFFFFFFFFFFFFFF)
#define TEXTURE_TLUT_WILDCARD UINT64_C(0xFFFFFFFFFFFFFFFE)
typedef struct TextureKey {
uint32_t struct_size;
TextureKeyKind kind;
const void* pointer; /* TEXTURE_KEY_POINTER only */
uint64_t texture_hash; /* TEXTURE_KEY_SOURCE */
uint64_t tlut_hash; /* TEXTURE_KEY_SOURCE, palette formats only */
uint32_t width;
uint32_t height;
uint32_t gx_format;
bool has_tlut;
} TextureKey;
#define TEXTURE_KEY_INIT {sizeof(TextureKey), TEXTURE_KEY_POINTER, NULL, 0u, 0u, 0u, 0u, 0u, false}
typedef struct TextureData {
uint32_t struct_size;
const void* data; /* texel data laid out in gx_format; copied by the service */
size_t size;
uint32_t width;
uint32_t height;
uint32_t mip_count;
uint32_t gx_format; /* any GX texture format supported by Aurora's converter */
} TextureData;
#define TEXTURE_DATA_INIT {sizeof(TextureData), NULL, 0u, 0u, 0u, 1u, 0u}
/*
* Runtime texture replacements.
*
* Registrations are owned by the calling mod and removed automatically when it is disabled,
* reloaded, or fails. When multiple sources replace the same texture, the highest priority wins:
* later-loaded mods beat earlier ones, and any mod beats the user's texture_replacements config
* directory. Files shipped in a mod's textures/ directory register automatically with the same
* ownership and priority; this service is for replacements decided at runtime.
*/
typedef struct TextureService {
ServiceHeader header;
/* Register a replacement from raw texel data. The data is copied; the caller may free it as
* soon as this returns. */
ModResult (*register_data)(ModContext* ctx, const TextureKey* key, const TextureData* data,
TextureReplacementHandle* out_handle);
/*
* Register a replacement from an encoded .dds/.png inside the calling mod's bundle. The
* filename encodes the key (same convention as the texture_replacements directory, e.g.
* "tex1_{w}x{h}_{hash}_{fmt}.dds"); "_mipN" sidecars next to it are picked up automatically.
* The file is decoded lazily on first use by the renderer.
*/
ModResult (*register_file)(ModContext* ctx, const char* bundle_path,
TextureReplacementHandle* out_handle);
/* Remove a replacement previously registered by the calling mod. */
ModResult (*unregister)(ModContext* ctx, TextureReplacementHandle handle);
} TextureService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<TextureService> {
static constexpr const char* id = TEXTURE_SERVICE_ID;
static constexpr uint16_t major_version = TEXTURE_SERVICE_MAJOR;
};
#endif
-284
View File
@@ -1,284 +0,0 @@
#pragma once
#include "mods/api.h"
#include "mods/svc/config.h"
#define UI_SERVICE_ID "dev.twilitrealm.dusklight.ui"
#define UI_SERVICE_MAJOR 1u
#define UI_SERVICE_MINOR 0u
/*
* UI primitives: a panel inside the host Mods window, mod-owned windows, dialogs, scoped
* RCSS stylesheets and menu bar tabs.
*
* All calls must be made on the game thread from mod callbacks (initialize, update, hooks, or UI
* callbacks). Handles are opaque, generation-checked ids; a stale or unknown handle fails with
* MOD_INVALID_ARGUMENT. Element handles die with the content that owns them: a panel or tab rebuild
* destroys the previous build's elements, so re-acquire handles inside the build callback rather
* than caching them. Strings are UTF-8 and, in both directions, only valid for the duration of the
* call.
*/
/* 0 is never a valid handle. */
typedef uint64_t UiWindowHandle;
typedef uint64_t UiDialogHandle;
typedef uint64_t UiElementHandle;
typedef uint64_t UiStyleHandle;
typedef uint64_t UiMenuTabHandle;
typedef enum UiStyleScope {
UI_SCOPE_PRELAUNCH = 0, /* the pre-launch menu */
UI_SCOPE_WINDOW = 1, /* every tabbed/small window, host and mod alike */
UI_SCOPE_MENU_BAR = 2, /* the in-game menu bar */
UI_SCOPE_OVERLAY = 3, /* the passive overlay (toasts, FPS counter, timers) */
UI_SCOPE_TOUCH_CONTROLS = 4, /* touch controls and their editor */
UI_SCOPE_GRAPHICS_TUNER = 5, /* the graphics tuner overlay window */
} UiStyleScope;
typedef enum UiDialogVariant {
UI_DIALOG_NORMAL = 0,
UI_DIALOG_WARNING = 1, /* warning icon by default */
UI_DIALOG_DANGER = 2, /* red styling, error icon by default */
} UiDialogVariant;
typedef enum UiControlKind {
UI_CONTROL_BUTTON = 0, /* action button (on_pressed) */
UI_CONTROL_TOGGLE = 1, /* boolean on/off */
UI_CONTROL_NUMBER = 2, /* integer stepper with min/max/step */
UI_CONTROL_STRING = 3, /* text input */
UI_CONTROL_SELECT = 4, /* one of `options`; the value is the option index */
} UiControlKind;
typedef enum UiControlBinding {
/* Values flow through the `get`/`set` callbacks. Getters are polled every frame while the
control is visible and must be cheap. */
UI_BINDING_CALLBACKS = 0,
/* The control reads and writes `config_var` (a ConfigService handle owned by the calling mod)
* directly: persistence, change notifications and the modified indicator (value != default) are
* wired automatically. The var type must match the control kind: TOGGLE = bool, NUMBER and
* SELECT = int, STRING = string. Float vars are not bindable; use callbacks. */
UI_BINDING_CONFIG_VAR = 1,
} UiControlBinding;
/* Tagged by the control's kind: TOGGLE reads bool_value, NUMBER and SELECT read int_value, STRING
* reads string_value. string_value passed to a setter is only valid during the call; a getter
* should point it at storage owned by the mod (e.g. a static buffer) that stays valid until the
* next call into the mod the host copies it right after the getter returns. */
typedef struct UiControlValue {
uint32_t struct_size;
bool bool_value;
int64_t int_value;
const char* string_value;
} UiControlValue;
#define UI_CONTROL_VALUE_INIT {sizeof(UiControlValue), false, 0, NULL}
typedef void (*UiControlGetFn)(ModContext* ctx, void* user_data, UiControlValue* out_value);
typedef void (*UiControlSetFn)(ModContext* ctx, void* user_data, const UiControlValue* value);
/* Polled every frame while the control is visible. */
typedef bool (*UiPredicateFn)(ModContext* ctx, void* user_data);
typedef void (*UiPressedFn)(ModContext* ctx, void* user_data);
typedef struct UiControlDesc {
uint32_t struct_size;
UiControlKind kind;
/* Row label (plain text). Required. */
const char* label;
/* Optional RML shown as contextual help when the control is focused or hovered. Only rendered
* where a help pane exists (mod window tabs). */
const char* help_rml;
UiControlBinding binding; /* ignored for BUTTON */
ConfigVarHandle config_var; /* UI_BINDING_CONFIG_VAR */
UiControlGetFn get; /* UI_BINDING_CALLBACKS (all kinds but BUTTON) */
UiControlSetFn set; /* UI_BINDING_CALLBACKS (all kinds but BUTTON) */
UiPressedFn on_pressed; /* BUTTON only. Required for BUTTON. */
UiPredicateFn is_disabled; /* optional */
/* Optional override for the modified indicator. CONFIG_VAR controls derive it from value !=
* default when this is NULL. */
UiPredicateFn is_modified;
/* Passed to every callback above. */
void* user_data;
/* NUMBER: inclusive clamp range and step. min == max means the defaults (0 .. INT32_MAX); step
* < 1 means 1. */
int64_t min;
int64_t max;
int64_t step;
const char* prefix; /* NUMBER: optional text before the value */
const char* suffix; /* NUMBER: optional text after the value */
/* SELECT: option labels (plain text). Required for SELECT. SELECT controls
* present their options in the help pane, so they are only available where
* one exists (mod window tabs); MOD_UNSUPPORTED elsewhere. */
const char* const* options;
size_t option_count;
int32_t max_length; /* STRING: maximum input length; < 1 means unlimited */
} UiControlDesc;
#define UI_CONTROL_DESC_INIT \
{sizeof(UiControlDesc), UI_CONTROL_BUTTON, NULL, NULL, UI_BINDING_CALLBACKS, 0u, NULL, NULL, \
NULL, NULL, NULL, NULL, 0, 0, 1, NULL, NULL, NULL, 0u, 0}
/* Build the panel contents. `panel` accepts the pane_add_* functions; it and
* every element created in it are destroyed (handles invalidated) whenever the
* panel is rebuilt, e.g. on tab switches. A non-MOD_OK result fails the mod. */
typedef ModResult (*UiPanelBuildFn)(
ModContext* ctx, UiElementHandle panel, void* user_data, ModError* out_error);
/* Called every frame while the panel is the visible tab. */
typedef ModResult (*UiPanelUpdateFn)(ModContext* ctx, void* user_data, ModError* out_error);
/* A panel rendered inside the calling mod's tab of the host Mods window. */
typedef struct UiModsPanelDesc {
uint32_t struct_size;
UiPanelBuildFn build; /* required */
UiPanelUpdateFn update;
void* user_data;
} UiModsPanelDesc;
#define UI_MODS_PANEL_DESC_INIT {sizeof(UiModsPanelDesc), NULL, NULL, NULL}
/* Build one tab of a mod window. `left_pane` is the interactive column,
* `right_pane` shows contextual help (controls' help_rml and SELECT options
* render there). Rebuilt on every tab activation. */
typedef ModResult (*UiTabBuildFn)(ModContext* ctx, UiWindowHandle window, UiElementHandle left_pane,
UiElementHandle right_pane, void* user_data, ModError* out_error);
typedef struct UiTabDesc {
uint32_t struct_size;
const char* title; /* required */
UiTabBuildFn build; /* required */
UiPanelUpdateFn update;
void* user_data;
} UiTabDesc;
#define UI_TAB_DESC_INIT {sizeof(UiTabDesc), NULL, NULL, NULL, NULL}
typedef void (*UiWindowClosedFn)(ModContext* ctx, UiWindowHandle window, void* user_data);
typedef struct UiWindowDesc {
uint32_t struct_size;
const UiTabDesc* tabs; /* at least one */
size_t tab_count;
/* Optional RCSS applied to this window's document only (on top of any
* UI_SCOPE_WINDOW sheets, which apply automatically). */
const char* rcss;
/* Fired when the window is destroyed by user close or window_close. Not
* fired during owning-mod teardown/shutdown. The handle is already invalid. */
UiWindowClosedFn on_closed;
void* user_data;
} UiWindowDesc;
#define UI_WINDOW_DESC_INIT {sizeof(UiWindowDesc), NULL, 0u, NULL, NULL, NULL}
typedef void (*UiDialogActionFn)(ModContext* ctx, UiDialogHandle dialog, void* user_data);
/* Note: array element without struct_size; a future change requires appending
* a v2 desc struct rather than growing this one. */
typedef struct UiDialogAction {
const char* label; /* required */
UiDialogActionFn on_pressed;
void* user_data;
bool keep_open; /* false = the dialog closes after on_pressed returns */
} UiDialogAction;
typedef struct UiDialogDesc {
uint32_t struct_size;
const char* title; /* plain text; required */
const char* body_rml; /* RML body; required */
UiDialogVariant variant;
/* Optional icon name overriding the variant default: "warning", "error",
* "question-mark", "verifying", "celebration". */
const char* icon;
const UiDialogAction* actions; /* at least one */
size_t action_count;
/* Fired on cancel (B/Escape) before the dialog closes; the dialog always
* closes on dismiss. */
UiDialogActionFn on_dismiss;
void* user_data; /* passed to on_dismiss */
} UiDialogDesc;
#define UI_DIALOG_DESC_INIT \
{sizeof(UiDialogDesc), NULL, NULL, UI_DIALOG_NORMAL, NULL, NULL, 0u, NULL, NULL}
/* A tab added to the in-game menu bar. */
typedef struct UiMenuTabDesc {
uint32_t struct_size;
const char* label; /* plain text; required */
UiPressedFn on_selected; /* fired when the user activates the tab; required */
void* user_data;
} UiMenuTabDesc;
#define UI_MENU_TAB_DESC_INIT {sizeof(UiMenuTabDesc), NULL, NULL, NULL}
typedef struct UiService {
ServiceHeader header;
/* Register or replace the panel shown in the calling mod's Mods-window tab. */
ModResult (*register_mods_panel)(ModContext* ctx, const UiModsPanelDesc* desc);
/* Content builders. `pane` is a panel or tab pane handle; out_elem (where
* present, optional) receives a handle for later elem_set_* updates. */
ModResult (*pane_add_section)(ModContext* ctx, UiElementHandle pane, const char* title);
ModResult (*pane_add_text)(
ModContext* ctx, UiElementHandle pane, const char* text, UiElementHandle* out_elem);
ModResult (*pane_add_rml)(
ModContext* ctx, UiElementHandle pane, const char* rml, UiElementHandle* out_elem);
ModResult (*pane_add_progress)(
ModContext* ctx, UiElementHandle pane, float value, UiElementHandle* out_elem);
ModResult (*pane_add_control)(ModContext* ctx, UiElementHandle pane, const UiControlDesc* desc,
UiElementHandle* out_elem);
/* Element updates. The handle kind must match the setter (text/rml on text
* rows, progress on progress bars). */
ModResult (*elem_set_text)(ModContext* ctx, UiElementHandle elem, const char* text);
ModResult (*elem_set_rml)(ModContext* ctx, UiElementHandle elem, const char* rml);
ModResult (*elem_set_progress)(ModContext* ctx, UiElementHandle elem, float value);
/* Set or clear an RCSS class on any element handle (rows, progress bars,
* controls, panes), for styling via scoped or window RCSS. */
ModResult (*elem_set_class)(
ModContext* ctx, UiElementHandle elem, const char* name, bool active);
/* Push a tabbed two-pane window onto the document stack and show it. */
ModResult (*window_push)(ModContext* ctx, const UiWindowDesc* desc, UiWindowHandle* out_window);
ModResult (*window_close)(ModContext* ctx, UiWindowHandle window);
/* Push a modal dialog onto the document stack and show it. */
ModResult (*dialog_push)(ModContext* ctx, const UiDialogDesc* desc, UiDialogHandle* out_dialog);
ModResult (*dialog_close)(ModContext* ctx, UiDialogHandle dialog);
ModResult (*dialog_set_body)(ModContext* ctx, UiDialogHandle dialog, const char* body_rml);
/* Replace the dialog icon ("" removes it; names as in UiDialogDesc.icon). */
ModResult (*dialog_set_icon)(ModContext* ctx, UiDialogHandle dialog, const char* icon);
/* Append one action button (same callback rules as at push). */
ModResult (*dialog_add_action)(
ModContext* ctx, UiDialogHandle dialog, const UiDialogAction* action);
/* Whether any focus-stack document is currently visible (visible documents block gamepad
* input). */
ModResult (*is_any_document_visible)(ModContext* ctx, bool* out_visible);
/* Register an RCSS sheet applied to every document of `scope`, now and in the future, until
* unregistered or the calling mod is torn down. Sheets apply in registration order after the
* host styles. Fails with MOD_INVALID_ARGUMENT if the RCSS does not parse. */
ModResult (*register_styles)(
ModContext* ctx, UiStyleScope scope, const char* rcss, UiStyleHandle* out_style);
/* Like register_styles, but reads the RCSS from the mod bundle's res/ directory (same path
* rules as ResourceService::load). MOD_UNAVAILABLE if the file does not exist. */
ModResult (*register_styles_file)(
ModContext* ctx, UiStyleScope scope, const char* path, UiStyleHandle* out_style);
ModResult (*unregister_styles)(ModContext* ctx, UiStyleHandle style);
/* Add a tab to the in-game menu bar, on_selected runs with the menu open; pushing a window from
* it stacks the window over the menu like the host tabs do. The tab is removed when
* unregistered or the mod is torn down. */
ModResult (*register_menu_tab)(
ModContext* ctx, const UiMenuTabDesc* desc, UiMenuTabHandle* out_tab);
ModResult (*unregister_menu_tab)(ModContext* ctx, UiMenuTabHandle tab);
} UiService;
#ifdef __cplusplus
#include "mods/service.hpp"
template <>
struct dusk::mods::ServiceTraits<UiService> {
static constexpr const char* id = UI_SERVICE_ID;
static constexpr uint16_t major_version = UI_SERVICE_MAJOR;
};
#endif
-6
View File
@@ -31,10 +31,4 @@ extern u8 __OSReport_Warning_disable;
extern u8 __OSReport_System_disable;
extern u8 __OSReport_enable;
#if TARGET_PC
namespace dusk {
DUSK_GAME_EXTERN bool OSReportReallyForceEnable;
}
#endif
#endif // _OS_REPORT_H