Merge branch 'main' into mods

This commit is contained in:
PJB3005
2026-05-16 18:13:39 +02:00
24 changed files with 133 additions and 64 deletions
+6
View File
@@ -311,6 +311,12 @@ if (DUSK_ENABLE_SENTRY_NATIVE)
endif ()
endif ()
# Use signed char on ARM to match the original game (and x86)
string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR}" _arch)
if(_arch MATCHES "^(arm|aarch64)" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU")
add_compile_options(-fsigned-char)
endif()
if (CMAKE_SYSTEM_NAME STREQUAL Windows)
set(PLATFORM_NAME win32)
elseif (CMAKE_SYSTEM_NAME STREQUAL Darwin)
+23 -21
View File
@@ -1,33 +1,40 @@
# Installing Dusklight on iOS via AltStore
# Installing Dusklight on iOS via iloader
## Prerequisites
- Mac with Homebrew installed
- iPhone connected via USB
- A Windows, Linux, or macOS device
- iOS device connected to computer via USB
- Dusklight IPA file (download the latest `Dusklight-vX.X.X-ios-arm64.ipa` from the [releases page](https://github.com/TwilitRealm/dusk/releases))
- Game disc - `GZ2E01` (Gamecube USA) or `GZ2PE01` (Gamecube PAL)
## 1. Install AltServer
```sh
brew install altserver
open -a AltServer
```
AltServer will appear in your menu bar.
- Executable bundles can be installed from [iloader's main page](https://iloader.app/) or [their GitHub](https://github.com/nab138/iloader) for Windows, Linux, and macOS.
- Windows WILL require iTunes to be installed
- Linux WILL require usbmuxd to be installed, this is installed by default in most distros though
## 2. Enable Developer Mode (iOS 16+)
- On your iPhone, go to **Settings > Privacy & Security > Developer Mode**
- Toggle it on and restart when prompted
## 3. Install AltStore on Your iPhone
## 3. Install Dusklight on Your iPhone
- Click AltServer in the menu bar
- Click **Install AltStore > [Your iPhone]**
- Enter your Apple ID credentials when prompted
- On your iPhone, go to **Settings > General > VPN & Device Management**
- Tap your Apple ID under "Developer App" and tap **Trust**
1. Sign into your Apple ID (this is required for registering app IDs, it is sent securely and not stored by iloader)
* You may be prompted to put in a code from your iOS device, do so
2. Plug in your iOS device via USB into your PC. If you're missing a dependency, an error pop-up will tell you to install it
* You will need to hit `Refresh` after plugging it in at this stage so that it can be detected, it does not automatically refresh
3. Leave settings unchanged (the Anisette server should stay Sidestore (.io))
3.(a) Installing SideStore directly is not required, but provides you a way to install Dusklight on your phone without being plugged into a computer later
4. Press `Import IPA` and choose your downloaded `Dusk-v.X.X.X-ios-arm64.ipa`, it will begin installing on your device
**NOTE:** *At various stages, you may be prompted to trust your device, do so*
## 3. Getting Dusk trusted
When installing sideloaded iOS applications, at first you will need to manually trust the app due to Apple's security policies
* Go to **Settings > General > VPN & Device Management**
* Tap the Apple ID you signed into iloader with under "Developer App" and tap **Trust**
* Tap **Allow** on the pop-up
## 4. Copy Files to Your iPhone
@@ -38,9 +45,4 @@ Transfer the IPA and game disc to your iPhone so they're accessible in the Files
- **USB transfer** - Connect your iPhone and drag files via Finder's sidebar
- **Cloud storage** - Upload to Google Drive, Dropbox, etc. and download on your iPhone
## 5. Install via AltStore
- Open **AltStore** on your iPhone
- Go to the **My Apps** tab
- Tap the **+** button (top left)
- Open the **Files** app and select the `.ipa` file
You may now use Dusklight on iOS, iPadOS!
+1 -1
+1
View File
@@ -5,6 +5,7 @@
#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();
+1
View File
@@ -17,6 +17,7 @@ void ensure_initialized();
void begin_record();
void end_record();
void begin_sim_tick();
uint64_t sim_tick_seq();
void begin_frame(bool enabled, bool is_sim_frame, float step);
void interpolate();
float get_interpolation_step();
+1 -1
View File
@@ -108,7 +108,7 @@ struct fopAcM_search_prm {
struct fOpAcm_HIO_entry_c : public mDoHIO_entry_c {
virtual ~fOpAcm_HIO_entry_c() {}
#if DEBUG
#if DEBUG && !TARGET_PC
void removeHIO(const fopAc_ac_c* i_this) { removeHIO(*i_this); }
void removeHIO(const fopAc_ac_c& i_this) { removeHIO(i_this.base); }
void removeHIO(const fopEn_enemy_c& i_this) { removeHIO(i_this.base); }
+3 -1
View File
@@ -28,7 +28,9 @@
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSApplicationCategoryType</key>
<string>public.app-category.adventure-games</string>
<key>LSSupportsGameMode</key>
<true/>
<true/>
</dict>
</plist>
+4
View File
@@ -58,6 +58,10 @@ toast:active {
background-color: rgba(45, 43, 26, 80%);
}*/
b {
font-weight: bold;
}
toast heading {
display: flex;
gap: 18dp;
+4
View File
@@ -14,6 +14,10 @@ body {
color: #E0DBC8;
}
b {
font-weight: bold;
}
window {
display: flex;
flex-flow: column;
+10 -4
View File
@@ -22,6 +22,7 @@
#if TARGET_PC
#include "dusk/dusk.h"
#include "dusk/frame_interpolation.h"
namespace {
// FRAME INTERP NOTE: Sim tick control point snapshots for interpolation
@@ -32,6 +33,7 @@ int s_horseReinSimNumPrev;
int s_horseReinSimNumCurr;
bool s_horseReinSimPrevValid;
bool s_horseReinSimCurrValid;
uint64_t s_horseReinSimRolledSeq;
} // namespace
#endif
@@ -3033,10 +3035,14 @@ void daHorse_c::copyReinPos() {
}
#if TARGET_PC
if (field_0x1204 > 0) {
if (s_horseReinSimCurrValid && s_horseReinSimNumCurr > 0) {
memcpy(s_horseReinSimPrev, s_horseReinSimCurr, s_horseReinSimNumCurr * sizeof(cXyz));
s_horseReinSimNumPrev = s_horseReinSimNumCurr;
s_horseReinSimPrevValid = true;
const uint64_t simSeq = dusk::frame_interp::sim_tick_seq();
if (simSeq != s_horseReinSimRolledSeq) {
s_horseReinSimRolledSeq = simSeq;
if (s_horseReinSimCurrValid && s_horseReinSimNumCurr > 0) {
memcpy(s_horseReinSimPrev, s_horseReinSimCurr, s_horseReinSimNumCurr * sizeof(cXyz));
s_horseReinSimNumPrev = s_horseReinSimNumCurr;
s_horseReinSimPrevValid = true;
}
}
memcpy(s_horseReinSimCurr, m_reinLine.getPos(0), field_0x1204 * sizeof(cXyz));
s_horseReinSimNumCurr = field_0x1204;
+4
View File
@@ -1967,7 +1967,11 @@ static void demo_camera_shop(npc_henna_class* i_this) {
i_this->mMsgFlow.init(actor, 0x365, 0, NULL);
/* dSv_event_flag_c::KORO2_ALLCLEAR - Fishing - After all stages (8-8) of roll goal game cleared */
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[0x335]);
#if TARGET_PC
dComIfGp_setItemRupeeCount(dComIfGs_getRupeeMax());
#else
dComIfGp_setItemRupeeCount(1000);
#endif
} else if ((lbl_82_bss_91 & 0x38) == 0) {
i_this->mMsgFlow.init(actor, 0x34f, 0, NULL);
/* dSv_event_flag_c::F_0469 - Fishing Pond - Reserved for fishing */
+2 -1
View File
@@ -299,7 +299,8 @@ int daObjDrop_c::modeParentWait() {
#if TARGET_PC
static inline BOOL checkGetCargoRide() {
if ((daPy_getPlayerActorClass()->checkCargoCarry() && strcmp(dComIfGp_getStartStageName(), "F_SP112") == 0) ||
if (daPy_getPlayerActorClass()->checkCargoCarry() &&
strcmp(dComIfGp_getStartStageName(), "F_SP112") == 0 &&
dComIfGs_isLightDropGetFlag(dComIfGp_getStartStageDarkArea()))
{
return true;
+8
View File
@@ -19,6 +19,7 @@
#include "d/d_timer.h"
#include "f_op/f_op_msg_mng.h"
#include "f_op/f_op_scene_mng.h"
#include "m_Do/m_Do_MemCard.h"
#include "m_Do/m_Do_Reset.h"
#include "m_Do/m_Do_controller_pad.h"
#include "m_Do/m_Do_graphic.h"
@@ -1238,6 +1239,13 @@ BOOL dComIfG_resetToOpening(scene_class* i_scene) {
}
#endif
#ifdef TARGET_PC
if (!mDoMemCd_isCardCommNone()) {
return 0;
}
g_mDoMemCd_control.SaveSync();
#endif
dComIfG_changeOpeningScene(i_scene, fpcNm_OPENING_SCENE_e);
mDoAud_bgmStop(30);
mDoAud_resetProcess();
+8
View File
@@ -26,6 +26,10 @@
#include "f_op/f_op_overlap_mng.h"
#include "m_Do/m_Do_controller_pad.h"
#ifdef TARGET_PC
#include "dusk/frame_interpolation.h"
#endif
class dDlst_MENU_CAPTURE_c : public dDlst_base_c {
public:
virtual void draw() {
@@ -1088,6 +1092,10 @@ void dMw_c::dMw_ring_create(u8 i_origin) {
}
mpCapture->setCaptureFlag();
#ifdef TARGET_PC
dusk::frame_interp::request_presentation_sync();
#endif
}
bool dMw_c::dMw_ring_delete() {
+10 -7
View File
@@ -77,16 +77,16 @@ static const char* l_mojiEisu[65] = {
// That can't work on a modern platform, so instead I've filled them out ahead of time.
static const char* l_mojiEisuPal_1[65] = {
"A", "N", "\xC0", "\xCF", "1", "B", "O", "\xC1", "\xD0", "2", "C", "P", "\xC2", "\xD1", "3", "D", "Q",
"\xC3", "\xD2", "4", "E", "R", "\xC4", "\xD3", "5", "F", "S", "\xC5", "\xD4", "6", "G", "T", "\xC6", "\xD5",
"7", "H", "U", "\xC7", "\xD6", "8", "I", "V", "\xC8", "\xD7", "9", "J", "W", "\xC9", "\xD8", "0", "K",
"X", "\xCA", "\xD9", ",", "L", "Y", "\xCB", "\xDA", ".", "M", "Z", "\xCC", "\xDB", " ",
"\xC4", "\xD2", "4", "E", "R", "\xC6", "\xD3", "5", "F", "S", "\xC7", "\xD4", "6", "G", "T", "\xC8", "\xD6",
"7", "H", "U", "\xC9", "\x8C", "8", "I", "V", "\xCA", "\xD9", "9", "J", "W", "\xCB", "\xDA", "0", "K",
"X", "\xCC", "\xDB", ",", "L", "Y", "\xCD", "\xDC", ".", "M", "Z", "\xCE", "\x2D", " ",
};
static const char* l_mojiEisuPal_2[65] = {
"a", "n", "\xE0", "\xEF", "1", "b", "o", "\xE1", "\xF0", "2", "c", "p", "\xE2", "\xF1", "3", "d", "q",
"\xE3", "\xF2", "4", "e", "r", "\xE4", "\xF3", "5", "f", "s", "\xE5", "\xF4", "6", "g", "t", "\xE6",
"\xF5", "7", "h", "u", "\xE7", "\xF6", "8", "i", "v", "\xE8", "\xF7", "9", "j", "w", "\xE9", "\xF8", "0",
"k", "x", "\xEA", "\xF9", ",", "l", "y", "\xEB", "\xFA", ".", "m", "z", "\xEC", "\xFB", " ",
"\xE4", "\xF2", "4", "e", "r", "\xE6", "\xF3", "5", "f", "s", "\xE7", "\xF4", "6", "g", "t", "\xE8",
"\xF6", "7", "h", "u", "\xE9", "\x9C", "8", "i", "v", "\xEA", "\xF9", "9", "j", "w", "\xEB", "\xFA", "0",
"k", "x", "\xEC", "\xFB", ",", "l", "y", "\xED", "\xFC", ".", "m", "z", "\xEE", "\xDF", " ",
};
#elif REGION_PAL
static const char* l_mojiEisuPal_1[65] = {
@@ -295,6 +295,7 @@ void dName_c::_move() {
}
} else {
#endif
#if !TARGET_PC
if (mDoCPd_c::getTrigRight(PAD_1)) {
// BUG: this check only fails if the cursor is at exactly 7
// setMoji allows the cursor to reach 8, which is out of bounds here
@@ -311,7 +312,9 @@ void dName_c::_move() {
mCurPos--;
nameCursorMove();
}
} else if (mDoCPd_c::getTrigB(PAD_1)) {
} else
#endif
if (mDoCPd_c::getTrigB(PAD_1)) {
if (mCurPos == 0) {
mDoAud_seStart(Z2SE_SY_MENU_BACK, 0, 0, 0);
field_0x2ac = mSelProc;
+3 -5
View File
@@ -30,7 +30,9 @@
#if TARGET_PC
#include "dusk/settings.h"
#include <f_ap/f_ap_game.h>
#include <dusk/autosave.h>
#include "dusk/string.hpp"
#define strcpy dusk::SafeStringCopy
#endif
static u8 dSv_item_rename(u8 i_itemNo) {
@@ -349,10 +351,6 @@ void dSv_player_item_c::setItem(int i_slotNo, u8 i_itemNo) {
dComIfGp_setSelectItem(i);
}
}
#if TARGET_PC
triggerAutoSave();
#endif
}
u8 dSv_player_item_c::getItem(int i_slotNo, bool i_checkCombo) const {
+6 -3
View File
@@ -191,13 +191,17 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
}
bool hasJewelRod = false;
for (int slot = 0; slot < 24 && !hasJewelRod; ++slot) {
bool hasAncientDoc = false;
for (int slot = 0; slot < 24; ++slot) {
const u8 item = dComIfGs_getItem(slot, false);
if (item == dItemNo_JEWEL_ROD_e || item == dItemNo_JEWEL_BEE_ROD_e || item == dItemNo_JEWEL_WORM_ROD_e) {
hasJewelRod = true;
}
if (item == dItemNo_ANCIENT_DOCUMENT_e || item == dItemNo_ANCIENT_DOCUMENT2_e || item == dItemNo_AIR_LETTER_e) {
hasAncientDoc = true;
}
}
if (!hasJewelRod) {
if (!hasJewelRod || !hasAncientDoc) {
return;
}
@@ -212,7 +216,6 @@ std::vector<AchievementSystem::Entry> AchievementSystem::makeEntries() {
dItemNo_KANTERA_e,
dItemNo_PACHINKO_e,
dItemNo_HAWK_EYE_e,
dItemNo_ANCIENT_DOCUMENT_e,
dItemNo_HORSE_FLUTE_e,
};
for (u8 required : requiredWheelItems) {
+16 -2
View File
@@ -14,9 +14,23 @@ static AutoSaveFuncs AutoSaveFuncsProc[] = {
void noAutoSave() {}
bool canAutoSave() {
daAlink_c* player = (daAlink_c*)daAlink_getAlinkActorClass();
if (player == nullptr) {
return false;
}
if (player->checkCargoCarry() || player->checkCanoeRide()) {
return false;
}
return dusk::getSettings().game.autoSave && shouldAutoSave && mAutoSaveProc == 0 &&
strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0 &&
strcmp(dComIfGp_getStartStageName(), "F_SP112") != 0;
}
void triggerAutoSave() {
if (dusk::getSettings().game.autoSave && shouldAutoSave && mAutoSaveProc == 0 &&
strcmp(dComIfGp_getStartStageName(), "F_SP102") != 0)
if (canAutoSave())
{
mAutoSaveProc = 1;
}
+11 -6
View File
@@ -21,6 +21,7 @@ bool g_sync_presentation = false;
float g_step = 0.0f;
bool g_is_sim_frame = false;
bool g_ui_tick_pending = false;
uint64_t g_sim_tick_seq = 0;
Recording g_current_recording;
Recording g_previous_recording;
@@ -66,19 +67,18 @@ void copy_view_to_snap(CameraSnapshot* dst, const view_class& v) {
}
inline void lerp_matrix(Mtx out, const Mtx lhs, const Mtx rhs, float step) {
const float old_weight = 1.0f - step;
for (size_t row = 0; row < 3; ++row) {
for (size_t col = 0; col < 4; ++col) {
out[row][col] = lhs[row][col] * old_weight + rhs[row][col] * step;
const float l = lhs[row][col];
out[row][col] = l + (rhs[row][col] - l) * step;
}
}
}
inline void lerp_xyz(cXyz* out, const cXyz& lhs, const cXyz& rhs, float step) {
const float old_weight = 1.0f - step;
out->x = lhs.x * old_weight + rhs.x * step;
out->y = lhs.y * old_weight + rhs.y * step;
out->z = lhs.z * old_weight + rhs.z * step;
out->x = lhs.x + (rhs.x - lhs.x) * step;
out->y = lhs.y + (rhs.y - lhs.y) * step;
out->z = lhs.z + (rhs.z - lhs.z) * step;
}
static s16 lerp_bank(s16 a, s16 b, f32 t) {
@@ -135,6 +135,11 @@ void begin_sim_tick() {
s_interpolationCallBackWork.clear();
s_cam_prev = std::move(s_cam_curr);
++g_sim_tick_seq;
}
uint64_t sim_tick_seq() {
return g_sim_tick_seq;
}
void begin_frame(bool enabled, bool is_sim_frame, float step) {
+3 -3
View File
@@ -263,7 +263,7 @@ namespace dusk {
}
if (getSettings().game.enableResetKeybind && ImGui::GetIO().KeyCtrl &&
ImGui::IsKeyPressed(ImGuiKey_R) && !fpcM_SearchByName(fpcNm_LOGO_SCENE_e))
ImGui::IsKeyReleased(ImGuiKey_R) && !fpcM_SearchByName(fpcNm_LOGO_SCENE_e))
{
JUTGamePad::C3ButtonReset::sResetSwitchPushing = true;
}
@@ -322,8 +322,8 @@ namespace dusk {
}
ImGui::PushFont(ImGuiEngine::fontLarge);
ImGuiTextCenter("Failed to initialize any graphics backend.");
ImGuiTextCenter("\nYour system may be misconfigured, or your hardware may not support the required versions of any of the available backends.");
ImGuiTextCenter("\nA clean reinstall of Dusklight may help. For further assistance, please visit #tech-support on the Twilit Realm Discord server.");
ImGuiTextCenter("\nDusklight requires Vulkan 1.1+, or Direct X 12.0.");
ImGuiTextCenter("\nTry updating your Operating System and GPU drivers.");
const auto& style = ImGui::GetStyle();
const auto retrySize = ImGui::CalcTextSize("Retry (Auto backend)");
const auto quitSize = ImGui::CalcTextSize("Quit");
+2 -2
View File
@@ -147,7 +147,7 @@ Rml::String back_button_name() {
#if defined(TARGET_ANDROID) || (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST)
constexpr auto kMenuNotificationPrefix = "3-finger tap or";
#else
constexpr auto kMenuNotificationPrefix = "Press F1 or";
constexpr auto kMenuNotificationPrefix = "Press <b>F1</b> or";
#endif
Rml::Element* create_menu_notification(Rml::Element* parent) {
@@ -169,7 +169,7 @@ Rml::Element* create_menu_notification(Rml::Element* parent) {
append(row, "span")->SetInnerRML(kMenuNotificationPrefix);
auto* icon = append(row, "icon");
icon->SetClass("controller", true);
append(row, "span")->SetInnerRML(escape(padButton));
append(row, "span")->SetInnerRML("<b>" + escape(padButton) + "</b>");
append(row, "span")->SetInnerRML("to open menu");
return elem;
+3 -4
View File
@@ -1068,8 +1068,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
addOption("Faster Tears of Light", getSettings().game.fastTears,
"Tears of Light dropped by Shadow Insects pop out faster like the HD version.");
addSpeedrunDisabledOption("Autosave", getSettings().game.autoSave,
"Autosaves the game when going to a new area, opening a dungeon door, "
"or getting a new item.");
"Autosaves the game when going to a new area or opening a dungeon door.");
addOption("Instant Saves", getSettings().game.instantSaves,
"Skips the delay when writing to the Memory Card.");
addOption("Hold B for Instant Text", getSettings().game.instantText,
@@ -1166,7 +1165,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
addCheat("Always Greatspin", getSettings().game.alwaysGreatspin,
"Allows the Great Spin attack without requiring full health.");
addCheat("Fast Iron Boots", getSettings().game.enableFastIronBoots,
"Speeds up movement while wearing the Iron Boots.");
"Speeds up movement while heavy, including wearing the Iron Boots, holding the Ball and Chain, wearing Magic Armor without rupees, etc.");
addCheat("Can Transform Anywhere", getSettings().game.canTransformAnywhere,
"Allows transforming even if NPCs are looking.");
addCheat("Fast Roll", getSettings().game.fastRoll,
@@ -1301,7 +1300,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
config_bool_select(leftPane, rightPane, getSettings().game.enableDiscordPresence,
{
.key = "Enable Discord Rich Presence",
.helpText = "Enable Dusk to integrate with Discord Rich Presence. This allows Discord to show your status in-game.",
.helpText = "Enable Dusklight to integrate with Discord Rich Presence. This allows Discord to show your status in-game.",
.onChange = [](bool enabled) {
if (enabled) {
dusk::discord::initialize();
+1 -1
View File
@@ -808,7 +808,7 @@ static void duskExecute() {
}
if (dusk::getSettings().game.infiniteRupees) {
dComIfGs_setRupee(9999);
dComIfGs_setRupee(dComIfGs_getRupeeMax());
}
if (dusk::getSettings().game.infiniteOxygen) {
+2 -2
View File
@@ -96,8 +96,8 @@ void mDoLib_project(Vec* src, Vec* dst) {
xSize = FB_WIDTH;
} else {
#if TARGET_PC
xOffset = mDoGph_gInf_c::getSafeMinXF();
xSize = viewPort->width * mDoGph_gInf_c::hudAspectScaleUp;
xOffset = mDoGph_gInf_c::getMinXF();
xSize = mDoGph_gInf_c::getWidthF();
#else
xOffset = viewPort->x_orig;
xSize = viewPort->width;