Merge remote-tracking branch 'origin/main' into better-tools

# Conflicts:
#	files.cmake
#	src/dusk/game_clock.cpp
#	src/dusk/imgui/ImGuiConsole.cpp
#	src/dusk/settings.cpp
#	src/dusk/settings.h
#	src/m_Do/m_Do_main.cpp
This commit is contained in:
Luke Street
2026-08-20 17:21:22 -06:00
205 changed files with 8854 additions and 1428 deletions
+10 -5
View File
@@ -68,13 +68,18 @@ body:
required: true
- type: dropdown
id: game-region
id: game-disc
attributes:
label: Game Region
description: The game region you are playing on
label: Game Disc
description: The game disc you are playing on
options:
- NTSC-U (North America)
- PAL (Europe)
- GameCube NTSC-U (North America)
- GameCube PAL (Europe)
- GameCube NTSC-J (Japan)
- Wii NTSC-U (North America) Revision 0
- Wii NTSC-U (North America) Revision 2
- Wii PAL (Europe)
- Wii NTSC-J (Japan)
validations:
required: true
+1
View File
@@ -519,6 +519,7 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR
add_subdirectory(mods/ao_mod)
add_subdirectory(mods/shadow_mod)
add_subdirectory(mods/window_demo)
add_subdirectory(mods/flow_demo)
endif ()
if (APPLE)
+4 -1
View File
@@ -26,7 +26,10 @@ It aims to be as accurate as possible to the original while also providing new o
You must dump your own copy of the game. Please see [this article](https://wiki.dolphin-emu.org/index.php?title=Ripping_Games) for instructions. After dumping, you can use a program like [Dolphin](https://dolphin-emu.org/) or [nodtool](https://github.com/encounter/nod/releases) to convert the `.iso` to `.rvz` to save space.
Currently, only the GameCube releases are supported. Support for other versions of the game is planned in the future.
Dusklight currently supports all commercial discs except for Wii's Korean release.
> [!NOTE]
> Dusklight is based on the [Twilight Princess decompilation](https://github.com/zeldaret/tp), which is currently only matching for GameCube. As a result, even when playing Dusklight with a Wii disc, you will be presented with the GameCube version's HUD and certain other specificities.
### 2. Install Dusklight
+7 -1
View File
@@ -5,7 +5,13 @@
- 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/dusklight/releases))
- Legally acquired game disc - `GZ2E01` (Gamecube USA) or `GZ2PE01` (Gamecube PAL)
- Legally acquired game disc:
- `GZ2E01` (GameCube USA)
- `GZ2P01` (GameCube PAL)
- `GZ2J01` (GameCube JPN)
- `RZDE01` (Wii USA Rev. 0 *or* Rev. 2)
- `RZDP01` (Wii PAL)
- `RZDJ01` (Wii JPN)
## 1. Install iloader
+131 -2
View File
@@ -262,14 +262,16 @@ Installs hooks on game functions and resolves symbols by name. You'll rarely cal
### OverlayService (`mods/svc/overlay.h`)
Registers DVD file overlays at runtime: the dynamic counterpart to the static `overlay/` directory (see
[Asset Overlays](#asset-overlays)). Overlay a disc path with a file from your bundle, or with a caller-owned buffer
[Asset Overlays](#asset-overlays)). Overlay a disc path with a file from your bundle, a file within an archive,
or with a caller-owned buffer
(copied on registration):
```cpp
IMPORT_SERVICE(OverlayService, svc_overlay);
OverlayHandle handle = 0;
svc_overlay->add_file(mod_ctx, "/res/Msgus.arc", "res/replacement.arc", &handle);
svc_overlay->add_file(mod_ctx, "/Movie/demo_movie98_00.thp", "res/replacement.thp", &handle); // Replaces the demo movie
svc_overlay->add_file(mod_ctx, "/res/Object/Kmdl/archive/bmwr/al.bmd", "res/link_model.bmd", &handle); // Replaces link's model
svc_overlay->add_buffer(mod_ctx, "/generated.txt", data, size, nullptr);
svc_overlay->remove(mod_ctx, handle);
```
@@ -278,6 +280,9 @@ svc_overlay->remove(mod_ctx, handle);
on the disc are added as new files. Changes are applied at the next frame boundary, and data the game already read
stays in memory until the file is re-read: sometimes a scene reload, and in the worst case, a full restart.
Dusklight reloads core archive files during scene transitions so modifications to Link, Midna or other globally-loaded
data get refreshed without a full restart.
See [Asset Overlays](#asset-overlays) for priority and conflict handling.
### TextureService (`mods/svc/texture.h`)
@@ -636,6 +641,125 @@ first in-game frame. Projection matrices match the renderer's WebGPU clip conven
Camera operators allow overriding the main camera. When an operator callback returns true, its values replace the camera
state for the current frame. Register and unregister using `register_camera_operator` / `unregister_camera_operator`.
### GameModeService (`mods/svc/game_mode.h`)
Allows a mod to register a game mode with callbacks for key gameplay and save lifecycle events. Registered game modes
appear in the prelaunch menu. Game modes may use a unique set of saves by configuring `save_name`; leave it empty to use
the vanilla `gczelda2` save.
```cpp
IMPORT_SERVICE(LogService, svc_log);
IMPORT_SERVICE(HookService, svc_hook);
IMPORT_SERVICE(GameModeService, svc_game_mode);
DEFINE_HOOK(fopAcM_createItem, CreateItem);
#define MY_GAME_MODE_ID "game-mode-id"
static HookAction my_function_hook(ModContext* ctx, void* args, void*, void*) {
// If we wish to have this hook only run while the gamemode is registered, we need to hook the function from the
// gamemode's onActivatedFunction, and uninstall the hook during the onDeactivatedFunction. Example below.
// Alternatively, check with `svc_game_mode->is_active(mod_ctx, MY_GAME_MODE_ID, &active) == MOD_OK && active`.
return HOOK_CONTINUE;
}
ModResult on_game_mode_activated(void*, ModError* outError) {
// Setup the gamemode, Add any hooks that are gamemode specific
// Overlay any files that are gamemode specific
ModResult result = mods::hook_add_pre<CreateItem>(svc_hook, my_function_hook);
if (result != MOD_OK) {
return mods::set_error(outError, result, "failed to install fopAcM_createItem hook");
}
return MOD_OK;
}
ModResult on_game_mode_deactivated(void*, ModError* outError) {
// Uninstall any hooks that are gamemode specific
// Remove any file overlays that are gamemode specific
ModResult result = mods::hook_uninstall<CreateItem>();
if (result != MOD_OK) {
return mods::set_error(outError, result, "failed to uninstall fopAcM_createItem hook");
}
return MOD_OK;
}
ModResult on_save_loaded(void*, ModError*) {
// This function will be invoked by the game as a save is loaded
return MOD_OK;
}
const GameModeDesc gameModeDesc = {
.struct_size = sizeof(GameModeDesc),
.game_mode_id = MY_GAME_MODE_ID,
.full_name = "My Game Mode",
.save_name = "my-unique-save",
.user_data = nullptr,
.on_activated = on_game_mode_activated,
.on_deactivated = on_game_mode_deactivated,
.on_save_loaded = on_save_loaded,
};
svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc);
```
A game mode can also open UI for per-save settings when creating a new file. The state begins as
`GAME_MODE_STATE_PENDING` and remains valid until the mod selects `PROCEED` or `RETURN`.
```cpp
IMPORT_SERVICE(GameModeService, svc_game_mode);
IMPORT_SERVICE(UiService, svc_ui);
ModResult on_new_save_select(void*, GameModeNewSaveState* state, ModError* outError) {
static GameModeNewSaveState* newSaveState;
static UiWindowHandle windowHandle;
newSaveState = state;
UiTabDesc tabs[1]{};
tabs[0].struct_size = sizeof(UiTabDesc);
tabs[0].title = "Play";
tabs[0].build = [](ModContext* ctx, UiWindowHandle, UiElementHandle leftPane, UiElementHandle rightPane, void*, ModError*) {
UiControlDesc desc = UI_CONTROL_DESC_INIT;
desc.kind = UI_CONTROL_BUTTON;
desc.label = "Play";
desc.help_rml = "Play Button";
desc.on_pressed = [](ModContext* ctx, void* userdata) {
*newSaveState = GAME_MODE_STATE_PROCEED;
svc_ui->window_close(ctx, *static_cast<UiWindowHandle*>(userdata));
};
desc.user_data = &windowHandle;
svc_ui->pane_add_control(mod_ctx, leftPane, &desc, nullptr);
return MOD_OK;
};
UiWindowDesc desc = UI_WINDOW_DESC_INIT;
desc.tabs = tabs;
desc.tab_count = 1;
desc.on_closed = [](ModContext *, UiWindowHandle, void *userdata) {
// If closing the window through backing out, return to file select
if (*newSaveState == GAME_MODE_STATE_PENDING) {
*newSaveState = GAME_MODE_STATE_RETURN;
}
};
ModResult result = svc_ui->window_push(mod_ctx, &desc, &windowHandle);
if (result != MOD_OK) {
return mods::set_error(outError, result, "failed to open new-save settings");
}
return MOD_OK;
}
const GameModeDesc gameModeDesc = {
.struct_size = sizeof(GameModeDesc),
.game_mode_id = "my-game-mode-id",
.full_name = "My Game Mode",
.save_name = "my-unique-save",
.on_new_save_select = on_new_save_select,
};
svc_game_mode->register_game_mode(mod_ctx, &gameModeDesc);
```
---
## Hooking Game Functions
@@ -763,6 +887,11 @@ For reference parameters (e.g. `const cXyz& pos`), `arg_ref<cXyz>` yields a dire
Files placed under `overlay/` in the `.dusk` archive override game files at the corresponding path, equivalent to
replacing files in the .iso. This requires no code: an archive with just `mod.json` and `overlay/` is a complete mod.
To replace a file within an `.arc` archive, replace the archive suffix with a directory and place the replacement at
its path within the archive.
- `overlay/Audiores/Stream/menu_select.ast` replaces the main title's audio stream.
- `overlay/res/Layout/main2D/main2d/timg/midona64.bti` replaces Midna's UI icon inside `main2D.arc`.
Files placed under `textures/` register as texture replacements, and act just like the user's general
`texture_replacements/` directory: Dolphin-style naming, matched by texture hash
+1 -1
+12
View File
@@ -1412,6 +1412,7 @@ set(DOLPHIN_FILES
set(DUSK_FILES
include/helpers/batch.hpp
include/helpers/bits.hpp
include/helpers/endian_gx.hpp
src/d/actor/d_a_alink_dusk.cpp
src/dusk/OSContext.cpp
@@ -1435,12 +1436,14 @@ set(DUSK_FILES
src/dusk/commands.cpp
src/dusk/commands.hpp
src/dusk/game_clock.cpp
src/dusk/game_mode.cpp
src/dusk/gamepad_color.cpp
src/dusk/globals.cpp
src/dusk/gyro.cpp
src/dusk/game_combos.cpp
src/dusk/d_trigger_view.cpp
#src/dusk/m_Do_ext_dusk.cpp
src/dusk/hq_minimap.cpp
src/dusk/imgui/ImGuiActorSpawner.cpp
src/dusk/imgui/ImGuiBloomWindow.cpp
src/dusk/imgui/ImGuiBloomWindow.hpp
@@ -1461,6 +1464,8 @@ set(DUSK_FILES
src/dusk/imgui/ImGuiStubLog.cpp
src/dusk/io.cpp
src/dusk/iso_validate.cpp
src/dusk/language.cpp
src/dusk/language.hpp
src/dusk/layout.cpp
src/dusk/livesplit.cpp
src/dusk/logging.cpp
@@ -1477,6 +1482,9 @@ set(DUSK_FILES
src/dusk/mods/loader/native_module.hpp
src/dusk/mods/loader/prepatch.cpp
src/dusk/mods/loader/prepatch.hpp
src/dusk/mods/item.hpp
src/dusk/mods/item_checks.cpp
src/dusk/mods/item_gives.cpp
src/dusk/mods/log_buffer.cpp
src/dusk/mods/log_buffer.hpp
src/dusk/mods/manifest.cpp
@@ -1486,8 +1494,11 @@ set(DUSK_FILES
src/dusk/mods/svc/config.hpp
src/dusk/mods/svc/game.cpp
src/dusk/mods/svc/gfx.cpp
src/dusk/mods/svc/flow.cpp
src/dusk/mods/svc/hook.cpp
src/dusk/mods/svc/host.cpp
src/dusk/mods/svc/item.cpp
src/dusk/mods/svc/item.hpp
src/dusk/mods/svc/log.cpp
src/dusk/mods/svc/overlay.cpp
src/dusk/mods/svc/registry.cpp
@@ -1496,6 +1507,7 @@ set(DUSK_FILES
src/dusk/mods/svc/texture.cpp
src/dusk/mods/svc/ui.cpp
src/dusk/mods/svc/ui.hpp
src/dusk/mods/svc/game_mode.cpp
src/dusk/mods/svc/window.cpp
src/dusk/mods/svc/window.hpp
src/dusk/mods/svc/save.cpp
+3
View File
@@ -96,6 +96,9 @@ public:
/* 0xDE0 */ cXyz field_0xde0;
/* 0xDEC */ u8 field_0xdec[0xdf9 - 0xdec];
/* 0xDF9 */ u8 field_0xdf9;
#if TARGET_PC
bool mItemCheckOverridden;
#endif
};
STATIC_ASSERT(sizeof(daE_HP_c) == 0xdfc);
+5
View File
@@ -88,6 +88,11 @@ private:
/* 0x99C */ dPa_followEcallBack mEffect2;
/* 0x9B0 */ Z2SoundObjSimple mSound;
/* 0x9D0 */ u8 mIsHookCarry;
#if TARGET_PC
u8 mOriginalItemNo;
bool mItemOverridden;
bool mOverrideHover;
#endif
};
STATIC_ASSERT(sizeof(daObjLife_c) == 0x9d4);
+3
View File
@@ -194,6 +194,9 @@ private:
/* 0x9FC */ u8 field_0x9fc;
/* 0x9FD */ u8 field_0x9fd;
/* 0xA00 */ Mtx mDrawMtx;
#if TARGET_PC
u8 mOriginalItemNo;
#endif
};
STATIC_ASSERT(sizeof(daTbox_c) == 0xA30);
+4
View File
@@ -83,6 +83,10 @@ private:
/* 0xAC0 */ u8 field_0xAC0[0xAC4 - 0xAC0];
/* 0xAC4 */ int mStaffIdx;
/* 0xAC8 */ dBgW* mBoxBgW;
#if TARGET_PC
bool mParamsInit;
u8 mOriginalItemNo;
#endif
};
STATIC_ASSERT(sizeof(daTbox2_c) == 0xACC);
+4
View File
@@ -145,6 +145,10 @@ public:
/* 0x9BC */ u8 field_0x9bc[4];
/* 0x9C0 */ u8 field_0x9c0;
/* 0x9C1 */ u8 field_0x9c1;
#if TARGET_PC
u8 mOriginalItemNo;
bool mItemOverridden;
#endif
}; // Size: 0x9C4
#endif /* D_A_D_A_ITEM_STATIC_H */
+7
View File
@@ -50,6 +50,9 @@ public:
csXyz* getRotateP();
cXyz* getPosP();
const char* getShopArcname();
#if TARGET_PC
const ResourceData& getResourceData() const;
#endif
u16 getHeapSize();
void CreateInit();
void set_mtx();
@@ -110,6 +113,10 @@ private:
/* 0x960 */ s16 mAngleX;
/* 0x962 */ s16 mAngleY;
/* 0x964 */ u8 mShopItemID;
#if TARGET_PC
ResourceData mOverrideData;
bool mItemOverridden;
#endif
};
int CheckShopItemCreateHeap(fopAc_ac_c* i_this);
+13
View File
@@ -9,6 +9,10 @@
#include "JSystem/J3DGraphLoader/J3DModelLoader.h"
#include "JSystem/J3DGraphLoader/J3DAnmLoader.h"
#if TARGET_PC
#include "mods/svc/game_mode.h"
#endif
class dFile_info_c;
class J2DPicture;
@@ -420,6 +424,13 @@ public:
bool pointerMenuSelect();
bool pointerCopyDataToSelect();
bool pointerYesNoSelect(bool errorSelect);
void backToDataSelectMove() {
headerTxtSet(0x43, 1, 0);
fileRecScaleAnmInitSet2(0.0f, 1.0f);
nameMoveAnmInitSet(0xd29, 0xd1f);
modoruTxtDispAnmInit(0);
mDataSelProc = DATASELPROC_NAME_TO_DATA_SELECT_MOVE;
}
#endif
void _draw();
void errorMoveAnmInitSet(int, int);
@@ -733,6 +744,8 @@ public:
#endif
#ifdef TARGET_PC
dDlst_FileSelFade_c mFadeDlst;
bool mGameModeSaveStartBuildUi = true;
GameModeNewSaveState mGameModeNewSaveState = GAME_MODE_STATE_PENDING;
#endif
#if PLATFORM_WII || PLATFORM_SHIELD
+3 -1
View File
@@ -11,7 +11,9 @@ public:
static DUSK_GAME_DATA u8* mData;
};
void execItemGet(u8 item_id);
class fopAc_ac_c;
void execItemGet(
u8 item_id IF_DUSK_ARG(u32 item_give_tag = 0) IF_DUSK_ARG(fopAc_ac_c* giver = NULL));
void item_func_HEART();
void item_func_GREEN_RUPEE();
+14 -12
View File
@@ -27,18 +27,20 @@ public:
// Attributes
/* 0x04 */ BE(u16) message_id;
/* 0x06 */ BE(u16) event_label_id;
/* 0x08 */ u8 se_speaker;
/* 0x09 */ u8 fuki_kind;
/* 0x0A */ u8 output_type;
/* 0x0B */ u8 fuki_pos_type;
/* 0x0C */ u8 unk_0xc;
/* 0x0D */ u8 unk_0xd;
/* 0x0E */ u8 se_mood;
/* 0x0F */ u8 camera_id;
/* 0x10 */ u8 base_anm_id;
/* 0x11 */ u8 face_anm_id;
/* 0x12 */ BE(u16) unk_0x12;
/* 0x06 */ BE(u16) event_label_id; // saveBitLabels index set when the message displays
/* 0x08 */ u8 speaker; // Z2SpeechMgr2 voice bank ID
/* 0x09 */ u8 box_kind; // screen class, see dMsgObject_c::talkStartInit
/* 0x0A */ u8 draw_type; // text pacing, see jmessage_tSequenceProcessor::do_begin
/* 0x0B */ u8 box_position; // see dMsgObject_c::fukiPosCalc
/* 0x0C */ u8 item_no; // unused; legacy dItemNo, 0xFF = none
/* 0x0D */ u8 line_alignment; // 0 centered (JP only), 1 left; also copied to
// jmessage_tReference::mForm
/* 0x0E */ u8 speaker_mood; // grunt emotion index for the voice bank
/* 0x0F */ u8 camera_attr; // 1-10 talk-actor slot, >=11 talk-camera style
/* 0x10 */ u8 talk_anim; // NPC talk motion attribute
/* 0x11 */ u8 face_anim; // NPC talk face attribute
/* 0x12 */ u8 lines_per_page; // unused; runtime uses getLineMax()
/* 0x13 */ u8 _pad;
};
class JMSMesgInfo_c {
+2 -2
View File
@@ -22,7 +22,7 @@ struct msg_class;
// all mesg_flow_node structs members might be wrong
struct mesg_flow_node {
/* 0x00 */ u8 type;
/* 0x01 */ u8 field_0x1;
/* 0x01 */ u8 subtype;
/* 0x02 */ BE(u16) msg_index;
/* 0x04 */ BE(u16) next_node_idx;
/* 0x06 */ BE(u16) unk_0x6;
@@ -30,7 +30,7 @@ struct mesg_flow_node {
struct mesg_flow_node_branch {
/* 0x00 */ u8 type;
/* 0x01 */ u8 field_0x1;
/* 0x01 */ u8 result_count;
/* 0x02 */ BE(u16) query_idx;
/* 0x04 */ BE(u16) param;
/* 0x06 */ BE(u16) next_node_idx;
+2 -2
View File
@@ -280,13 +280,13 @@ public:
/* 0x150 */ f32 field_0x150;
/* 0x154 */ u32 mMessageID;
/* 0x158 */ u32 field_0x158;
/* 0x15C */ u32 field_0x15c;
/* 0x15C */ u32 mSelectMessageID; // message ID of the selection options message; 1000 = none
/* 0x160 */ int mIdx;
/* 0x164 */ u16 mNodeIdx;
/* 0x166 */ u16 field_0x166;
/* 0x168 */ u16 field_0x168;
/* 0x16A */ s16 field_0x16a;
/* 0x16C */ s16 field_0x16c;
/* 0x16C */ s16 mCurrentGroupID; // group whose BMG is parsed; -1 none, 0 common, 1-8 stage
/* 0x16E */ s16 field_0x16e;
/* 0x170 */ s16 mNowTalkFlowNo;
/* 0x172 */ s16 field_0x172;
+5
View File
@@ -318,6 +318,11 @@ public:
/* 0x566 */ s8 field_0x566;
/* 0x567 */ s8 field_0x567;
#if TARGET_PC
u32 mItemGiveTag;
u8 mItemGiveOriginalNo;
#endif
#if !__MWERKS__
s8 actor_last_base_field;
#endif
+20 -13
View File
@@ -79,6 +79,10 @@ struct fopAcM_prm_class {
/* 0x1C */ fpc_ProcID parent_id;
/* 0x20 */ s8 argument;
/* 0x21 */ s8 room_no;
#if TARGET_PC
u32 mItemGiveTag;
u8 mItemGiveOriginalNo;
#endif
};
struct fopAcM_search4ev_prm {
@@ -516,8 +520,9 @@ s32 fopAcM_SearchByName(s16 i_procName, fopAc_ac_c** i_outActor);
fopAcM_prm_class* fopAcM_CreateAppend();
fopAcM_prm_class* createAppend(u16 i_setId, u32 i_parameters, const cXyz* i_pos, int i_roomNo,
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
fpc_ProcID i_parentId);
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
fpc_ProcID i_parentId IF_DUSK_ARG(u32 i_itemGiveTag = 0)
IF_DUSK_ARG(u8 i_itemOriginalNo = 0xFF));
void fopAcM_Log(fopAc_ac_c const* i_actor, char const* i_message);
@@ -526,19 +531,20 @@ s32 fopAcM_delete(fopAc_ac_c* i_actor);
s32 fopAcM_delete(fpc_ProcID i_actorID);
fpc_ProcID fopAcM_create(s16 i_procName, u16 i_setId, u32 i_parameters, const cXyz* i_pos,
int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
createFunc i_createFunc);
int i_roomNo, const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
createFunc i_createFunc IF_DUSK_ARG(u32 i_itemGiveTag = 0));
fpc_ProcID fopAcM_create(s16 i_procName, u32 i_parameters, const cXyz* i_pos, int i_roomNo,
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument);
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument IF_DUSK_ARG(u32 i_itemGiveTag = 0));
inline fpc_ProcID fopAcM_Create(s16 i_procName, createFunc i_createFunc, void* params) {
return fpcM_Create(i_procName, i_createFunc,params);
}
fopAc_ac_c* fopAcM_fastCreate(s16 i_procName, u32 i_parameters, const cXyz* i_pos, int i_roomNo,
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument,
createFunc i_createFunc, void* i_createFuncData);
const csXyz* i_angle, const cXyz* i_scale, s8 i_argument, createFunc i_createFunc,
void* i_createFuncData IF_DUSK_ARG(u32 i_itemGiveTag = 0)
IF_DUSK_ARG(u8 i_itemOriginalNo = 0xFF));
fopAc_ac_c* fopAcM_fastCreate(const char* i_actorname, u32 i_parameters, const cXyz* i_pos,
int i_roomNo, const csXyz* i_angle, const cXyz* i_scale,
@@ -623,11 +629,11 @@ fopAc_ac_c* fopAcM_getItemEventPartner(const fopAc_ac_c*);
fopAc_ac_c* fopAcM_getEventPartner(const fopAc_ac_c*);
fpc_ProcID fopAcM_createItemForPresentDemo(cXyz const* i_pos, int i_itemNo, u8 param_2,
int i_itemBitNo, int i_roomNo, csXyz const* i_angle,
cXyz const* i_scale);
int i_itemBitNo, int i_roomNo, csXyz const* i_angle,
cXyz const* i_scale IF_DUSK_ARG(u32 i_itemGiveTag = 0));
fpc_ProcID fopAcM_createItemForTrBoxDemo(cXyz const* i_pos, int i_itemNo, int i_itemBitNo,
int i_roomNo, csXyz const* i_angle, cXyz const* i_scale);
int i_roomNo, csXyz const* i_angle, cXyz const* i_scale IF_DUSK_ARG(u32 i_itemGiveTag = 0));
u8 fopAcM_getItemNoFromTableNo(u8 i_tableNo);
@@ -641,11 +647,12 @@ fpc_ProcID fopAcM_createItemFromTable(cXyz const* i_pos, int i_tableNo, int i_it
bool i_createDirect);
fpc_ProcID fopAcM_createDemoItem(const cXyz* i_pos, int i_itemNo, int i_itemBitNo,
const csXyz* i_angle, int i_roomNo, const cXyz* scale, u8 param_7);
const csXyz* i_angle, int i_roomNo, const cXyz* scale,
u8 param_7 IF_DUSK_ARG(u32 i_itemGiveTag = 0));
fpc_ProcID fopAcM_createItemForBoss(const cXyz* i_pos, int i_itemNo, int i_roomNo,
const csXyz* i_angle, const cXyz* i_scale, f32 i_speedF,
f32 i_speedY, int param_8);
const csXyz* i_angle, const cXyz* i_scale, f32 i_speedF, f32 i_speedY,
int param_8 IF_DUSK_ARG(const char* i_itemCheckName = NULL));
fpc_ProcID fopAcM_createItemForMidBoss(const cXyz* i_pos, int i_itemNo, int i_roomNo,
const csXyz* i_angle, const cXyz* i_scale, int param_6,
+1 -1
View File
@@ -30,7 +30,7 @@ struct msg_class {
/* 0xDC */ fopAc_ac_c* talk_actor;
/* 0xE0 */ cXyz pos;
/* 0xEC */ u32 msg_idx;
/* 0xF0 */ u32 field_0xf0;
/* 0xF0 */ u32 select_msg_idx; // selection options message ID; 1000 = none
/* 0xF4 */ u32 field_0xf4;
/* 0xF8 */ u16 mode;
/* 0xFA */ u8 select_idx;
+3 -3
View File
@@ -19,7 +19,7 @@ struct fopMsg_prm_class {
/* 0x00 */ fopAc_ac_c* talk_actor;
/* 0x04 */ cXyz pos;
/* 0x10 */ u32 msg_idx;
/* 0x14 */ u32 field_0x14;
/* 0x14 */ u32 select_msg_idx; // selection options message ID; 1000 = none
/* 0x18 */ fpc_ProcID field_0x18;
}; // Size: 0x1C
@@ -46,8 +46,8 @@ void fopMsgM_setMessageID(fpc_ProcID msg_id);
void fopMsgM_destroyExpHeap(JKRExpHeap* i_heap);
f32 fopMsgM_valueIncrease(int param_0, int param_1, u8 i_type);
s32 fopMsgM_setStageLayer(void* i_process);
fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, fopAc_ac_c* i_talkActor, u32 param_2);
fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, u32 param_1);
fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, fopAc_ac_c* i_talkActor, u32 i_selectMsgIdx);
fpc_ProcID fopMsgM_messageSet(u32 i_msgIdx, u32 i_selectMsgIdx);
fpc_ProcID fopMsgM_messageSetDemo(u32 i_msgidx);
msg_class* fopMsgM_SearchByID(fpc_ProcID i_id);
TEXT_SPAN fopMsgM_messageGet(TEXT_SPAN i_stringBuf, u32 i_msgId);
+13
View File
@@ -256,4 +256,17 @@ using std::isnan;
#define DUSK_CONST IF_DUSK(const)
#define DUSK_CONSTEXPR IF_DUSK(constexpr)
#if TARGET_PC && defined(DUSK_BUILDING_GAME)
#include "dusk/mods/item.hpp"
#define DUSK_ITEM_CHECK(name, item_no, giver) \
(item_no) = ::dusk::mods::item_check(name, (item_no), giver)
#define DUSK_ITEM_CHECK_EXPR(name, item_no, giver) \
(::dusk::mods::item_check(name, (item_no), giver))
#define DUSK_GIVE_TAG(name) IF_DUSK_ARG(::dusk::mods::item_give_tag(name))
#else
#define DUSK_ITEM_CHECK(name, item_no, giver)
#define DUSK_ITEM_CHECK_EXPR(name, item_no, giver) (item_no)
#define DUSK_GIVE_TAG(name)
#endif
#endif
+178
View File
@@ -0,0 +1,178 @@
#pragma once
#include <bit>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <type_traits>
namespace dusk {
namespace detail {
template <size_t Size>
struct uint_of_size;
template <>
struct uint_of_size<1> {
using type = uint8_t;
};
template <>
struct uint_of_size<2> {
using type = uint16_t;
};
template <>
struct uint_of_size<4> {
using type = uint32_t;
};
template <>
struct uint_of_size<8> {
using type = uint64_t;
};
template <size_t Size>
using uint_of_size_t = uint_of_size<Size>::type;
template <typename T>
requires(std::is_trivially_copyable_v<T>)
T unaligned_load(const void* source) noexcept {
T value;
std::memcpy(&value, source, sizeof(value));
return value;
}
template <typename T>
requires(std::is_trivially_copyable_v<T>)
void unaligned_store(void* destination, T value) noexcept {
std::memcpy(destination, &value, sizeof(value));
}
} // namespace detail
template <typename T>
requires(std::is_unsigned_v<T>)
constexpr T bswap(T value) noexcept {
if constexpr (sizeof(T) == 1) {
return value;
} else if constexpr (sizeof(T) == 2) {
return static_cast<T>((value << 8) | (value >> 8));
} else if constexpr (sizeof(T) == 4) {
return static_cast<T>(((value & 0x000000ffU) << 24) | ((value & 0x0000ff00U) << 8) |
((value & 0x00ff0000U) >> 8) | ((value & 0xff000000U) >> 24));
} else {
static_assert(sizeof(T) == 8);
return static_cast<T>(
((value & 0x00000000000000ffULL) << 56) | ((value & 0x000000000000ff00ULL) << 40) |
((value & 0x0000000000ff0000ULL) << 24) | ((value & 0x00000000ff000000ULL) << 8) |
((value & 0x000000ff00000000ULL) >> 8) | ((value & 0x0000ff0000000000ULL) >> 24) |
((value & 0x00ff000000000000ULL) >> 40) | ((value & 0xff00000000000000ULL) >> 56));
}
}
/// Reads an unaligned integral value in the specified byte order.
template <typename T>
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
T read_bits(const void* source, std::endian endian = std::endian::big) noexcept {
using Bits = std::make_unsigned_t<T>;
Bits value = detail::unaligned_load<Bits>(source);
if constexpr (sizeof(Bits) > 1) {
if (endian != std::endian::native) {
value = bswap(value);
}
}
return std::bit_cast<T>(value);
}
template <typename T>
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
constexpr T read_bits(const uint8_t* source, std::endian endian = std::endian::big) noexcept {
if (!std::is_constant_evaluated()) {
return read_bits<T>(static_cast<const void*>(source), endian);
}
using Bits = std::make_unsigned_t<T>;
Bits value{};
if (endian == std::endian::big) {
for (size_t i = 0; i < sizeof(Bits); ++i) {
value = static_cast<Bits>((value << 8) | source[i]);
}
} else {
for (size_t i = 0; i < sizeof(Bits); ++i) {
value |= static_cast<Bits>(source[i]) << (i * 8);
}
}
return std::bit_cast<T>(value);
}
/// Reads an unaligned floating-point value in the specified byte order.
template <typename T>
requires(
std::is_floating_point_v<T> && requires { typename detail::uint_of_size_t<sizeof(T)>; })
T read_bits(const void* source, std::endian endian = std::endian::big) noexcept {
using Bits = detail::uint_of_size_t<sizeof(T)>;
return std::bit_cast<T>(read_bits<Bits>(source, endian));
}
template <typename T>
requires(
std::is_floating_point_v<T> && requires { typename detail::uint_of_size_t<sizeof(T)>; })
constexpr T read_bits(const uint8_t* source, std::endian endian = std::endian::big) noexcept {
using Bits = detail::uint_of_size_t<sizeof(T)>;
return std::bit_cast<T>(read_bits<Bits>(source, endian));
}
/// Writes an unaligned integral value in the specified byte order.
template <typename T>
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
void write_bits(void* destination, T value, std::endian endian = std::endian::big) noexcept {
using Bits = std::make_unsigned_t<T>;
Bits bits = std::bit_cast<Bits>(value);
if constexpr (sizeof(Bits) > 1) {
if (endian != std::endian::native) {
bits = bswap(bits);
}
}
detail::unaligned_store(destination, bits);
}
template <typename T>
requires(std::is_integral_v<T> && !std::is_same_v<T, bool>)
constexpr void write_bits(
uint8_t* destination, T value, std::endian endian = std::endian::big) noexcept {
if (!std::is_constant_evaluated()) {
write_bits(static_cast<void*>(destination), value, endian);
return;
}
using Bits = std::make_unsigned_t<T>;
const Bits bits = std::bit_cast<Bits>(value);
if (endian == std::endian::big) {
for (size_t i = 0; i < sizeof(Bits); ++i) {
destination[sizeof(Bits) - i - 1] = static_cast<uint8_t>(bits >> (i * 8));
}
} else {
for (size_t i = 0; i < sizeof(Bits); ++i) {
destination[i] = static_cast<uint8_t>(bits >> (i * 8));
}
}
}
/// Writes an unaligned floating-point value in the specified byte order.
template <typename T>
requires(
std::is_floating_point_v<T> && requires { typename detail::uint_of_size_t<sizeof(T)>; })
void write_bits(void* destination, T value, std::endian endian = std::endian::big) noexcept {
using Bits = detail::uint_of_size_t<sizeof(T)>;
write_bits(destination, std::bit_cast<Bits>(value), endian);
}
template <typename T>
requires(
std::is_floating_point_v<T> && requires { typename detail::uint_of_size_t<sizeof(T)>; })
constexpr void write_bits(
uint8_t* destination, T value, std::endian endian = std::endian::big) noexcept {
using Bits = detail::uint_of_size_t<sizeof(T)>;
write_bits(destination, std::bit_cast<Bits>(value), endian);
}
} // namespace dusk
+20
View File
@@ -112,6 +112,11 @@ public:
mSerialNo = serial_no;
}
#ifdef TARGET_PC
void setFileName(const std::string& fileName);
const char* getFileName();
#endif
/* 0x0000 */ u8 mData[SAVEFILE_SIZE];
/* 0x1FBC */ u8 mChannel;
/* 0x1FBD */ u8 mCopyToPos;
@@ -124,6 +129,11 @@ public:
/* 0x1FEC */ s32 mNandState;
/* 0x1FF0 */ u64 mSerialNo;
/* 0x1FF8 */ u32 mDataVersion;
#ifdef TARGET_PC
bool mInitialized;
std::string mFileName;
#endif
}; // Size: 0x2000
STATIC_ASSERT(sizeof(mDoMemCd_Ctrl_c) == 8192);
@@ -230,4 +240,14 @@ inline s32 mDoMemCd_checkNANDFile() {
}
#endif
#ifdef TARGET_PC
inline void mDoMemCd_SetFileName(const std::string& fileName) {
g_mDoMemCd_control.setFileName(fileName);
}
inline const char* mDoMemCd_GetFileName() {
return g_mDoMemCd_control.getFileName();
}
#endif
#endif /* M_DO_M_DO_MEMCARD_H */
@@ -6,11 +6,17 @@
#include "global.h"
#include "helpers/endian.h"
#if TARGET_PC
#include <atomic>
#include <string>
#include <unordered_map>
#endif
class JKRHeap;
/**
* @ingroup jsystem-jkernel
*
*
*/
struct SArcHeader {
/* 0x00 */ BE(u32) signature;
@@ -25,7 +31,7 @@ struct SArcHeader {
/**
* @ingroup jsystem-jkernel
*
*
*/
struct SArcDataInfo {
/* 0x00 */ BE(u32) num_nodes;
@@ -59,7 +65,7 @@ extern u32 sCurrentDirID__10JKRArchive; // JKRArchive::sCurrentDirID
/**
* @ingroup jsystem-jkernel
*
*
*/
class JKRArchive : public JKRFileLoader {
public:
@@ -192,9 +198,7 @@ public:
u32 countFile() const { return mArcInfoBlock->num_file_entries; }
s32 countDirectory() const { return mArcInfoBlock->num_nodes; }
u8 getMountMode() const { return mMountMode; }
bool isFileEntry(u32 param_0) const {
return getFileAttribute(param_0) & 1;
}
bool isFileEntry(u32 param_0) const { return getFileAttribute(param_0) & 1; }
public:
/* 0x00 */ // vtable
@@ -210,7 +214,36 @@ public:
/* 0x54 */ const char* mStringTable;
#if TARGET_PC
u32 getFileSize(SDIFileEntry* entry) const;
void* getOverlayData(SDIFileEntry* entry, u32* outSize);
bool getOverlayFileSize(SDIFileEntry* entry, u32* outSize) const;
static void notifyOverlayFilesChanged();
protected:
void** mFileData;
struct ArcOverlayResource {
u32 entryIndex;
u32 size;
u64 generation;
};
// Resource pointers remain valid until removed through the JKR resource APIs.
// That way, any pointers to old overlay data remain valid even when the overlay changed.
std::unordered_map<void*, ArcOverlayResource> mArcOverlayResources;
mutable std::unordered_map<u32, void*> mActiveArcOverlayResources;
mutable std::string mArcOverlaysPath;
mutable std::unordered_map<u32, std::string> mIdxToPathMap;
mutable bool mArcOverlaysPathResolved = false;
bool buildArcOverlaysPath() const;
void buildIndexToPathMap(u32 dirIndex, const std::string& currentPath) const;
bool getOverlayPath(SDIFileEntry* entry, std::string& path) const;
void* getActiveOverlayData(SDIFileEntry* entry, u32* outSize) const;
bool copyOverlayData(void* buffer, u32 bufferSize, SDIFileEntry* entry, u32* outSize);
bool getOverlayResourceSize(const void* data, u32* outSize) const;
bool removeOverlayResource(void* resource, bool freeResource);
void removeAllOverlayResources();
#endif
protected:
@@ -234,7 +267,7 @@ public:
} else if (attr & JKRARCHIVE_ATTR_YAZ0) {
return COMPRESSION_YAZ0;
} else {
return COMPRESSION_YAY0;
return COMPRESSION_YAY0;
}
}
@@ -243,6 +276,9 @@ public:
protected:
static DUSK_GAME_DATA u32 sCurrentDirID;
#if TARGET_PC
static std::atomic<u64> sArcOverlayGeneration;
#endif
};
inline JKRCompression JKRConvertAttrToCompressionType(int attr) {
@@ -261,8 +297,8 @@ inline bool JKRRemoveResource(void* resource, JKRFileLoader* fileLoader) {
return JKRFileLoader::removeResource(resource, fileLoader);
}
inline JKRArchive* JKRMountArchive(void* ptr, JKRHeap* heap,
JKRArchive::EMountDirection mountDirection) {
inline JKRArchive* JKRMountArchive(
void* ptr, JKRHeap* heap, JKRArchive::EMountDirection mountDirection) {
return JKRArchive::mount(ptr, heap, mountDirection);
}
@@ -174,10 +174,14 @@ struct TProcessor {
}
void on_character(int iCharacter) { do_character(iCharacter); }
#if TARGET_PC
const char* on_message_limited(u16 u16Index) const;
#else
const char* on_message_limited(u16 u16Index) const {
JUT_ASSERT(482, pResourceCache_!=NULL);
return pResourceCache_->getMessageText_messageIndex(u16Index);
}
#endif
bool on_setBegin_isReady_() const { return do_setBegin_isReady_(); }
@@ -195,6 +199,10 @@ struct TProcessor {
return 1;
}
#if TARGET_PC
void* getMessageEntry_messageCode(u16 u16Code, u16 u16Index) const;
const char* getMessageText_messageCode(u16 u16Code, u16 u16Index) const;
#else
void* getMessageEntry_messageCode(u16 u16Code, u16 u16Index) const {
const TResource* pResource = getResource_groupID(u16Code);
@@ -214,6 +222,7 @@ struct TProcessor {
return pResourceCache_->getMessageText_messageEntry(pEntry);
}
#endif
void stack_pushCurrent_(const char* pszText) {
oStack_.push(getCurrent());
+8 -1
View File
@@ -17,7 +17,7 @@ size_t JASResArcLoader::getResSize(JKRArchive const* i_archiveP, u16 i_resourceI
return 0;
}
return file->data_size;
return DUSK_IF_ELSE(i_archiveP->getFileSize(file), file->data_size);
}
size_t JASResArcLoader::getResMaxSize(JKRArchive const* i_archiveP) {
@@ -27,9 +27,16 @@ size_t JASResArcLoader::getResMaxSize(JKRArchive const* i_archiveP) {
for (index = 0; index < fileEntries; index++) {
JKRArchive::SDIFileEntry* file = i_archiveP->findIdxResource(index);
if (file) {
#if TARGET_PC
const u32 fileSize = i_archiveP->getFileSize(file);
if (maxSize < fileSize) {
maxSize = fileSize;
}
#else
if (maxSize < file->data_size) {
maxSize = file->data_size;
}
#endif
}
}
+7 -12
View File
@@ -38,7 +38,7 @@ void JFWDisplay::ctor_subroutine(bool enableAlpha) {
mTickRate = 0;
mCombinationRatio = 0.0f;
field_0x30 = 0;
field_0x2c = OSGetTick();
field_0x2c = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
field_0x34 = 0;
field_0x48 = 0;
field_0x4a = 0;
@@ -258,7 +258,7 @@ void JFWDisplay::beginRender() {
waitForTick(mTickRate, mFrameRate);
JUTVideo::getManager()->waitRetraceIfNeed();
OSTick tick = OSGetTick();
OSTick tick = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
field_0x30 = tick - field_0x2c;
field_0x2c = tick;
field_0x34 = field_0x2c - JUTVideo::getVideoLastTick();
@@ -371,7 +371,7 @@ constexpr auto FRAME_PERIOD = std::chrono::duration_cast<std::chrono::nanosecond
constexpr auto RETRACE_PERIOD = FRAME_PERIOD / 2;
static void waitPrecise(Limiter& limiter, Limiter::duration_t targetNs) {
const auto sleepTime = limiter.Sleep(targetNs);
const auto sleepTime = limiter.Sleep(targetNs);
dusk::frameUsagePct =
100.0f * (1.0f - static_cast<float>(sleepTime) / static_cast<float>(targetNs));
}
@@ -381,15 +381,12 @@ static void waitForTick(u32 p1, u16 p2) {
#if TARGET_PC
static Limiter limiter;
if (dusk::frame_interp::is_enabled() && !dusk::getTransientSettings().skipFrameRateLimit) {
dusk::frameUsagePct = 0.f;
return;
if (dusk::frame_interp::is_enabled() || dusk::getTransientSettings().turboMode) {
limiter.Reset();
dusk::frameUsagePct = 0.f;
return;
}
if (dusk::getTransientSettings().skipFrameRateLimit) {
p1 = OS_TIMER_CLOCK / 120;
}
if (fopOvlpM_IsPeek() && dusk::getTransientSettings().stateShareLoadActive) {
return;
}
@@ -399,7 +396,6 @@ static void waitForTick(u32 p1, u16 p2) {
if (p1 != 0) {
#if TARGET_PC
static Limiter limiter;
waitPrecise(limiter, static_cast<Uint64>(OSTicksToMicroseconds(p1)) * 1000ULL);
#else
static OSTime nextTick = OSGetTime();
@@ -413,7 +409,6 @@ static void waitForTick(u32 p1, u16 p2) {
} else {
u32 uVar1 = (p2 == 0) ? 1 : p2;
#if TARGET_PC
static Limiter limiter;
waitPrecise(limiter, static_cast<Uint64>((RETRACE_PERIOD * uVar1).count()));
#else
static u32 nextCount = VIGetRetraceCount();
@@ -196,6 +196,13 @@ cleanup:
void* JKRAramArchive::fetchResource(SDIFileEntry* pEntry, u32* pOutSize) {
JUT_ASSERT(442, isMounted());
#if TARGET_PC
if (void* data = getOverlayData(pEntry, pOutSize); data != nullptr) {
return data;
}
#endif
u32 outSize;
u8* outBuf;
if (pOutSize == NULL) {
@@ -231,6 +238,13 @@ void* JKRAramArchive::fetchResource(SDIFileEntry* pEntry, u32* pOutSize) {
void* JKRAramArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* pEntry,
u32* resourceSize) {
JUT_ASSERT(515, isMounted());
#if TARGET_PC
if (copyOverlayData(buffer, bufferSize, pEntry, resourceSize)) {
return buffer;
}
#endif
u32 size = pEntry->data_size;
if (size > bufferSize) {
size = bufferSize;
@@ -337,6 +351,12 @@ u32 JKRAramArchive::getExpandedResSize(const void* ptr) const {
return this->getResSize(ptr);
}
#if TARGET_PC
if (u32 size; getOverlayResourceSize(ptr, &size)) {
return size;
}
#endif
JKRArchive::SDIFileEntry* entry = this->findPtrResource(ptr);
if (entry == NULL) {
return 0xFFFFFFFF;
+268
View File
@@ -7,6 +7,37 @@
#if TARGET_PC
#include <cassert>
#include <cstddef>
#include <cstdlib>
#include <limits>
#include <ranges>
#include <string_view>
#include "JSystem/JKernel/JKRDvdRipper.h"
#if _WIN32
#include <malloc.h>
#endif
std::atomic<u64> JKRArchive::sArcOverlayGeneration{0};
namespace {
void* alloc_overlay_buffer(u32 size) {
#if _WIN32
return _aligned_malloc(size, alignof(std::max_align_t));
#else
return std::malloc(size);
#endif
}
void free_overlay_buffer(void* data) {
#if _WIN32
_aligned_free(data);
#else
std::free(data);
#endif
}
} // namespace
#endif
DUSK_GAME_DATA u32 JKRArchive::sCurrentDirID;
@@ -42,6 +73,7 @@ JKRArchive::JKRArchive(s32 entryNumber, JKRArchive::EMountMode mountMode) {
JKRArchive::~JKRArchive() {
#if TARGET_PC
removeAllOverlayResources();
if (mFileData != nullptr) {
JKRHeap::getSystemHeap()->free(mFileData);
mFileData = nullptr;
@@ -270,4 +302,240 @@ void JKRArchive::initFileDataPointers() {
mFiles[i].index = i;
}
}
void JKRArchive::notifyOverlayFilesChanged() {
sArcOverlayGeneration.fetch_add(1, std::memory_order_release);
}
bool JKRArchive::buildArcOverlaysPath() const {
if (mArcOverlaysPathResolved) {
return !mArcOverlaysPath.empty();
}
mArcOverlaysPathResolved = true;
if (mEntryNum < 0) {
return false;
}
char pathBuffer[1024];
if (!DVDConvertEntrynumToPath(mEntryNum, pathBuffer, sizeof(pathBuffer))) {
return false;
}
std::string path{pathBuffer};
constexpr std::string_view extension{".arc"};
if (path.size() < extension.size() ||
path.compare(path.size() - extension.size(), extension.size(), extension) != 0)
{
return false;
}
path.resize(path.size() - extension.size());
path.push_back('/');
mArcOverlaysPath = std::move(path);
return true;
}
void JKRArchive::buildIndexToPathMap(u32 dirIndex, const std::string& currentPath) const {
const SDIDirEntry& dir = mNodes[dirIndex];
for (int i = 0; i < dir.num_entries; i++) {
const SDIFileEntry& entry = mFiles[dir.first_file_index + i];
std::string entryName{&mStringTable[entry.getNameOffset()]};
if (entryName == "." || entryName == "..") {
continue;
}
if (entry.isDirectory()) {
buildIndexToPathMap(entry.data_offset, currentPath + entryName + "/");
} else {
mIdxToPathMap[entry.index] = currentPath + entryName;
}
}
}
bool JKRArchive::getOverlayPath(SDIFileEntry* entry, std::string& path) const {
if (entry == nullptr || !buildArcOverlaysPath()) {
return false;
}
if (mIdxToPathMap.empty()) {
buildIndexToPathMap(0, std::string{&mStringTable[mNodes[0].name_offset]} + "/");
}
const auto pathIt = mIdxToPathMap.find(entry->index);
if (pathIt == mIdxToPathMap.end()) {
return false;
}
path = mArcOverlaysPath + pathIt->second;
return true;
}
void* JKRArchive::getActiveOverlayData(SDIFileEntry* entry, u32* outSize) const {
const auto activeIt = mActiveArcOverlayResources.find(entry->index);
if (activeIt == mActiveArcOverlayResources.end()) {
return nullptr;
}
const auto resourceIt = mArcOverlayResources.find(activeIt->second);
const u64 generation = sArcOverlayGeneration.load(std::memory_order_acquire);
if (resourceIt == mArcOverlayResources.end() || resourceIt->second.generation != generation) {
// Keep the allocation owned so raw pointers returned by earlier fetches remain valid.
mActiveArcOverlayResources.erase(activeIt);
return nullptr;
}
if (outSize != nullptr) {
*outSize = resourceIt->second.size;
}
return resourceIt->first;
}
void* JKRArchive::getOverlayData(SDIFileEntry* entry, u32* outSize) {
if (entry == nullptr) {
return nullptr;
}
if (void* data = getActiveOverlayData(entry, outSize)) {
return data;
}
std::string path;
if (!getOverlayPath(entry, path)) {
return nullptr;
}
constexpr u32 alignmentMask = 0x1f;
const u64 generation = sArcOverlayGeneration.load(std::memory_order_acquire);
DVDFileInfo fileInfo{};
if (!DVDOpen(path.c_str(), &fileInfo)) {
return nullptr;
}
const u32 logicalSize = fileInfo.length;
if (logicalSize > static_cast<u32>(std::numeric_limits<s32>::max()) - alignmentMask) {
DVDClose(&fileInfo);
return nullptr;
}
const u32 readSize = ALIGN_NEXT(logicalSize, 0x20);
const u32 allocationSize = readSize == 0 ? 1 : readSize;
void* data = alloc_overlay_buffer(allocationSize);
if (data == nullptr) {
DVDClose(&fileInfo);
return nullptr;
}
const s32 status = DVDReadPrio(&fileInfo, data, readSize, 0, 2);
DVDClose(&fileInfo);
if (status < DVD_RESULT_GOOD || static_cast<u32>(status) != logicalSize) {
free_overlay_buffer(data);
return nullptr;
}
mArcOverlayResources.emplace(data, ArcOverlayResource{
.entryIndex = entry->index,
.size = logicalSize,
.generation = generation,
});
mActiveArcOverlayResources[entry->index] = data;
if (outSize != nullptr) {
*outSize = logicalSize;
}
return data;
}
bool JKRArchive::copyOverlayData(void* buffer, u32 bufferSize, SDIFileEntry* entry, u32* outSize) {
u32 overlaySize;
const void* overlayData = getOverlayData(entry, &overlaySize);
if (overlayData == nullptr) {
return false;
}
const u32 copySize = overlaySize < bufferSize ? overlaySize : bufferSize;
if (copySize != 0) {
memcpy(buffer, overlayData, copySize);
}
if (outSize != nullptr) {
*outSize = copySize;
}
return true;
}
bool JKRArchive::getOverlayResourceSize(const void* data, u32* outSize) const {
const auto resourceIt = mArcOverlayResources.find(const_cast<void*>(data));
if (resourceIt == mArcOverlayResources.end()) {
return false;
}
if (outSize != nullptr) {
*outSize = resourceIt->second.size;
}
return true;
}
bool JKRArchive::getOverlayFileSize(SDIFileEntry* entry, u32* outSize) const {
if (entry == nullptr) {
return false;
}
u32 activeSize;
if (getActiveOverlayData(entry, &activeSize) != nullptr) {
if (outSize != nullptr) {
*outSize = activeSize;
}
return true;
}
std::string path;
if (!getOverlayPath(entry, path)) {
return false;
}
DVDFileInfo fileInfo{};
if (!DVDOpen(path.c_str(), &fileInfo)) {
return false;
}
if (outSize != nullptr) {
*outSize = fileInfo.length;
}
DVDClose(&fileInfo);
return true;
}
u32 JKRArchive::getFileSize(SDIFileEntry* entry) const {
u32 size;
if (getOverlayFileSize(entry, &size)) {
return size;
}
return entry != nullptr ? entry->getSize() : 0;
}
bool JKRArchive::removeOverlayResource(void* resource, bool freeResource) {
const auto resourceIt = mArcOverlayResources.find(resource);
if (resourceIt == mArcOverlayResources.end()) {
return false;
}
const auto activeIt = mActiveArcOverlayResources.find(resourceIt->second.entryIndex);
if (activeIt != mActiveArcOverlayResources.end() && activeIt->second == resource) {
mActiveArcOverlayResources.erase(activeIt);
}
if (freeResource) {
free_overlay_buffer(resource);
}
mArcOverlayResources.erase(resourceIt);
return true;
}
void JKRArchive::removeAllOverlayResources() {
mActiveArcOverlayResources.clear();
for (const auto& key : mArcOverlayResources | std::views::keys) {
free_overlay_buffer(key);
}
mArcOverlayResources.clear();
}
#endif
@@ -246,6 +246,7 @@ u32 JKRArchive::readResource(void* buffer, u32 bufferSize, u16 id) {
void JKRArchive::removeResourceAll() {
if (mArcInfoBlock && mMountMode != MOUNT_MEM) {
IF_DUSK(removeAllOverlayResources();)
SDIFileEntry* fileEntry = mFiles;
for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
if (JKAR_DATA(fileEntry)) {
@@ -259,6 +260,13 @@ void JKRArchive::removeResourceAll() {
bool JKRArchive::removeResource(void* resource) {
JUT_ASSERT(678, resource != NULL);
#if TARGET_PC
if (removeOverlayResource(resource, true)) {
return true;
}
#endif
SDIFileEntry* fileEntry = findPtrResource(resource);
if (fileEntry == NULL)
return false;
@@ -270,6 +278,13 @@ bool JKRArchive::removeResource(void* resource) {
bool JKRArchive::detachResource(void* resource) {
JUT_ASSERT(707, resource != NULL);
#if TARGET_PC
if (removeOverlayResource(resource, false)) {
return true;
}
#endif
SDIFileEntry* fileEntry = findPtrResource(resource);
if (fileEntry == NULL)
return false;
@@ -280,6 +295,13 @@ bool JKRArchive::detachResource(void* resource) {
u32 JKRArchive::getResSize(const void* resource) const {
JUT_ASSERT(732, resource != NULL);
#if TARGET_PC
if (u32 size; getOverlayResourceSize(resource, &size)) {
return size;
}
#endif
SDIFileEntry* fileEntry = findPtrResource(resource);
if (fileEntry == NULL)
return -1;
@@ -226,6 +226,13 @@ bool JKRCompArchive::open(s32 entryNum) {
void* JKRCompArchive::fetchResource(SDIFileEntry *fileEntry, u32 *pSize) {
JUT_ASSERT(597, isMounted());
#if TARGET_PC
if (void* data = getOverlayData(fileEntry, pSize); data != nullptr) {
return data;
}
#endif
u32 ptrSize;
u32 size = fileEntry->data_size;
int compression = JKRConvertAttrToCompressionType(u8(fileEntry->type_flags_and_name_offset >> 0x18));
@@ -274,6 +281,13 @@ void *JKRCompArchive::fetchResource(void *data, u32 compressedSize, SDIFileEntry
{
u32 size = 0;
JUT_ASSERT(708, isMounted());
#if TARGET_PC
if (copyOverlayData(data, compressedSize, fileEntry, pSize)) {
return data;
}
#endif
u32 fileSize = fileEntry->data_size;
u32 alignedSize = ALIGN_NEXT(fileSize, 32);
u32 fileFlag = fileEntry->type_flags_and_name_offset >> 0x18;
@@ -319,6 +333,7 @@ void *JKRCompArchive::fetchResource(void *data, u32 compressedSize, SDIFileEntry
void JKRCompArchive::removeResourceAll() {
if (mArcInfoBlock != NULL && mMountMode != MOUNT_MEM) {
IF_DUSK(removeAllOverlayResources();)
SDIFileEntry* fileEntry = mFiles;
for (int i = 0; i < mArcInfoBlock->num_file_entries; i++) {
int tmp = fileEntry->type_flags_and_name_offset >> 0x18;
@@ -336,6 +351,12 @@ void JKRCompArchive::removeResourceAll() {
}
bool JKRCompArchive::removeResource(void* resource) {
#if TARGET_PC
if (removeOverlayResource(resource, true)) {
return true;
}
#endif
SDIFileEntry* fileEntry = findPtrResource(resource);
if (!fileEntry)
return false;
@@ -356,6 +377,12 @@ u32 JKRCompArchive::getExpandedResSize(const void *resource) const
return getResSize(resource);
}
#if TARGET_PC
if (u32 size; getOverlayResourceSize(resource, &size)) {
return size;
}
#endif
SDIFileEntry *fileEntry = findPtrResource(resource);
if(!fileEntry) {
return 0xffffffff;
@@ -147,6 +147,13 @@ cleanup:
void* JKRDvdArchive::fetchResource(SDIFileEntry* fileEntry, u32* returnSize) {
JUT_ASSERT(428, isMounted());
#if TARGET_PC
if (void* data = getOverlayData(fileEntry, returnSize); data != nullptr) {
return data;
}
#endif
u32 tempReturnSize;
if (returnSize == NULL) {
returnSize = &tempReturnSize;
@@ -181,6 +188,13 @@ void* JKRDvdArchive::fetchResource(SDIFileEntry* fileEntry, u32* returnSize) {
void* JKRDvdArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* fileEntry,
u32* returnSize) {
JUT_ASSERT(504, isMounted());
#if TARGET_PC
if (copyOverlayData(buffer, bufferSize, fileEntry, returnSize)) {
return buffer;
}
#endif
u32 size = fileEntry->data_size;
JKRCompression fileCompression = JKRConvertAttrToCompressionType(u8(fileEntry->type_flags_and_name_offset >> 24));
@@ -344,6 +358,12 @@ u32 JKRDvdArchive::getExpandedResSize(const void* resource) const {
return getResSize(resource);
}
#if TARGET_PC
if (u32 size; getOverlayResourceSize(resource, &size)) {
return size;
}
#endif
SDIFileEntry* fileEntry = findPtrResource(resource);
if (!fileEntry) {
return -1;
@@ -135,6 +135,13 @@ bool JKRMemArchive::open(void* buffer, u32 bufferSize, JKRMemBreakFlag flag) {
void* JKRMemArchive::fetchResource(SDIFileEntry* fileEntry, u32* resourceSize) {
JUT_ASSERT(555, isMounted());
#if TARGET_PC
if (void* data = getOverlayData(fileEntry, resourceSize); data != nullptr) {
return data;
}
#endif
if (!JKAR_DATA(fileEntry)) {
JKAR_DATA(fileEntry) = mArchiveData + fileEntry->data_offset;
}
@@ -149,6 +156,13 @@ void* JKRMemArchive::fetchResource(SDIFileEntry* fileEntry, u32* resourceSize) {
void* JKRMemArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* fileEntry,
u32* resourceSize) {
JUT_ASSERT(595, isMounted());
#if TARGET_PC
if (copyOverlayData(buffer, bufferSize, fileEntry, resourceSize)) {
return buffer;
}
#endif
u32 srcLength = fileEntry->data_size;
if (srcLength > bufferSize) {
srcLength = bufferSize;
@@ -173,6 +187,8 @@ void* JKRMemArchive::fetchResource(void* buffer, u32 bufferSize, SDIFileEntry* f
void JKRMemArchive::removeResourceAll(void) {
JUT_ASSERT(642, isMounted());
IF_DUSK(removeAllOverlayResources();)
if (mArcInfoBlock == NULL)
return;
if (mMountMode == MOUNT_MEM)
@@ -192,6 +208,12 @@ void JKRMemArchive::removeResourceAll(void) {
bool JKRMemArchive::removeResource(void* resource) {
JUT_ASSERT(673, isMounted());
#if TARGET_PC
if (removeOverlayResource(resource, true)) {
return true;
}
#endif
SDIFileEntry* fileEntry = findPtrResource(resource);
if (!fileEntry)
return false;
@@ -231,6 +253,12 @@ u32 JKRMemArchive::fetchResource_subroutine(u8* src, u32 srcLength, u8* dst, u32
}
u32 JKRMemArchive::getExpandedResSize(const void* resource) const {
#if TARGET_PC
if (u32 overlaySize; getOverlayResourceSize(resource, &overlaySize)) {
return overlaySize;
}
#endif
SDIFileEntry* fileEntry = findPtrResource(resource);
if (fileEntry == NULL)
return -1;
+53 -3
View File
@@ -7,6 +7,10 @@
#include "JSystem/JMessage/control.h"
#if TARGET_PC
#include "dusk/mods/svc/flow.hpp"
#endif
JMessage::TControl::TControl()
: pSequenceProcessor_(NULL),
pRenderingProcessor_(NULL),
@@ -19,9 +23,16 @@ JMessage::TControl::TControl()
pMessageText_current_(NULL)
{}
JMessage::TControl::~TControl() {}
JMessage::TControl::~TControl() {
#if TARGET_PC
dusk::flow::release_message_control(this);
#endif
}
void JMessage::TControl::reset() {
#if TARGET_PC
dusk::flow::release_message_control(this);
#endif
pEntry_ = NULL;
pMessageText_begin_ = NULL;
pszText_update_current_ = NULL;
@@ -78,18 +89,57 @@ int JMessage::TControl::setMessageID(u32 uMsgID, u32 param_1, bool* pbValid) {
}
bool JMessage::TControl::setMessageCode_inSequence_(JMessage::TProcessor const* pProcessor, u16 u16GroupID, u16 u16Index) {
#if TARGET_PC
const TResource* resource = NULL;
const void* resolvedEntry = NULL;
const char* resolvedText = NULL;
if (u16Index >= dusk::flow::kCustomMessageMin) {
if (!dusk::flow::custom_message_for_processor(
this, pProcessor, u16Index, resource, resolvedEntry, resolvedText))
{
return false;
}
const_cast<TProcessor*>(pProcessor)->setResourceCache(const_cast<TResource*>(resource));
} else {
resource = pProcessor->getResource_groupID(u16GroupID);
if (resource == NULL) {
return false;
}
const void* nativeEntry = resource->getMessageEntry_messageIndex(u16Index);
const char* nativeText =
nativeEntry != NULL ? resource->getMessageText_messageEntry(nativeEntry) : NULL;
resolvedEntry = nativeEntry;
resolvedText = nativeText;
dusk::flow::resolve_message_for_control(this, resource->oParse_THeader_.getRaw(), u16Index,
nativeEntry, nativeText, resolvedEntry, resolvedText);
}
pEntry_ = const_cast<void*>(resolvedEntry);
if (pEntry_ == NULL || resolvedText == NULL) {
return false;
}
#else
pEntry_ = pProcessor->getMessageEntry_messageCode(u16GroupID, u16Index);
if (pEntry_ == NULL) {
return false;
}
#endif
#if TARGET_PC
uMessageGroupID_ = resource->getGroupID();
pResourceCache_ = resource;
#else
uMessageGroupID_ = u16GroupID;
uMessageID_ = u16Index;
pResourceCache_ = pProcessor->getResourceCache();
#endif
uMessageID_ = u16Index;
JUT_ASSERT(155, pResourceCache_!=NULL);
#if TARGET_PC
pMessageText_begin_ = resolvedText;
#else
pMessageText_begin_ = pResourceCache_->getMessageText_messageEntry(pEntry_);
#endif
pMessageText_current_ = pMessageText_begin_;
oStack_renderingProcessor_.clear();
return true;
+89 -1
View File
@@ -5,15 +5,26 @@
#include "JSystem/JUtility/JUTAssert.h"
#include <cstdint>
#if TARGET_PC
#include "dusk/mods/svc/flow.hpp"
#endif
JMessage::TReference::~TReference() {}
const char* JMessage::TReference::do_word(u32 param_0) const {
return NULL;
}
JMessage::TProcessor::~TProcessor() {}
JMessage::TProcessor::~TProcessor() {
#if TARGET_PC
dusk::flow::release_message_processor(this);
#endif
}
void JMessage::TProcessor::reset() {
#if TARGET_PC
dusk::flow::release_message_processor(this);
#endif
on_resetStatus_(NULL);
do_reset();
}
@@ -44,6 +55,15 @@ const JMessage::TResource* JMessage::TProcessor::getResource_groupID(u16 u16Grou
}
u32 JMessage::TProcessor::toMessageCode_messageID(u32 uMsgID, u32 param_1, bool* pbValid) const {
#if TARGET_PC
u32 customCode = 0;
if (dusk::flow::message_code_for_id(this, uMsgID, customCode)) {
if (pbValid != NULL) {
*pbValid = true;
}
return customCode;
}
#endif
const TResource* pResourceCache = (const TResource*)getResourceCache();
if (pResourceCache != NULL) {
u16 u16Index = pResourceCache->toMessageIndex_messageID(uMsgID, param_1, pbValid);
@@ -73,6 +93,74 @@ u32 JMessage::TProcessor::toMessageCode_messageID(u32 uMsgID, u32 param_1, bool*
return 0xFFFFFFFF;
}
#if TARGET_PC
void* JMessage::TProcessor::getMessageEntry_messageCode(u16 groupId, u16 index) const {
if (index >= dusk::flow::kCustomMessageMin) {
const TResource* customResource = NULL;
const void* customEntry = NULL;
const char* customText = NULL;
if (!dusk::flow::custom_message_for_processor(
NULL, this, index, customResource, customEntry, customText))
{
return NULL;
}
const_cast<TProcessor*>(this)->setResourceCache(const_cast<TResource*>(customResource));
return const_cast<void*>(customEntry);
}
const TResource* resource = getResource_groupID(groupId);
if (resource == NULL) {
return NULL;
}
void* nativeEntry = resource->getMessageEntry_messageIndex(index);
const char* nativeText =
nativeEntry != NULL ? resource->getMessageText_messageEntry(nativeEntry) : NULL;
const void* resolvedEntry = nativeEntry;
const char* resolvedText = nativeText;
dusk::flow::resolve_message(this, resource->oParse_THeader_.getRaw(), index, nativeEntry,
nativeText, resolvedEntry, resolvedText);
return const_cast<void*>(resolvedEntry);
}
const char* JMessage::TProcessor::getMessageText_messageCode(u16 groupId, u16 index) const {
if (index >= dusk::flow::kCustomMessageMin) {
const TResource* customResource = NULL;
const void* customEntry = NULL;
const char* customText = NULL;
if (!dusk::flow::custom_message_for_processor(
NULL, this, index, customResource, customEntry, customText))
{
return NULL;
}
const_cast<TProcessor*>(this)->setResourceCache(const_cast<TResource*>(customResource));
return customText;
}
const TResource* resource = getResource_groupID(groupId);
if (resource == NULL) {
return NULL;
}
const void* nativeEntry = resource->getMessageEntry_messageIndex(index);
const char* nativeText =
nativeEntry != NULL ? resource->getMessageText_messageEntry(nativeEntry) : NULL;
const void* resolvedEntry = nativeEntry;
const char* resolvedText = nativeText;
dusk::flow::resolve_message(this, resource->oParse_THeader_.getRaw(), index, nativeEntry,
nativeText, resolvedEntry, resolvedText);
return resolvedText;
}
const char* JMessage::TProcessor::on_message_limited(u16 index) const {
JUT_ASSERT(482, pResourceCache_!=NULL);
const void* nativeEntry = pResourceCache_->getMessageEntry_messageIndex(index);
const char* nativeText =
nativeEntry != NULL ? pResourceCache_->getMessageText_messageEntry(nativeEntry) : NULL;
const void* resolvedEntry = nativeEntry;
const char* resolvedText = nativeText;
dusk::flow::resolve_message(this, pResourceCache_->oParse_THeader_.getRaw(), index,
nativeEntry, nativeText, resolvedEntry, resolvedText);
return resolvedText;
}
#endif
void JMessage::TProcessor::on_select_begin(char const* (*pfn)(JMessage::TProcessor*),
void const* pOffset, char const* pcBase, u32 uNumber) {
JUT_ASSERT(191, uNumber>0);
+1 -1
View File
@@ -71,7 +71,7 @@ void JUTResFont::initJoinedTexture() {
int pageCount = 0;
u32 pageNumCells = block.numRows * block.numColumns;
if (dusk::version::getGameVersion() == dusk::version::GameVersion::GcnJpn) {
if (dusk::version::isRegionJpn()) {
pageCount = 1;
if (pageNumCells > 0 && block.endCode > block.startCode) {
pageCount = (block.endCode - block.startCode + pageNumCells - 1) / pageNumCells;
+7 -2
View File
@@ -42,7 +42,7 @@ JUTVideo::JUTVideo(GXRenderModeObj const* param_0) {
mRetraceCount = VIGetRetraceCount();
field_0x10 = 1;
field_0x18 = 0;
sVideoLastTick = OSGetTick();
sVideoLastTick = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
sVideoInterval = 670000;
mPreRetraceCallback = VISetPreRetraceCallback(preRetraceProc);
mPostRetraceCallback = VISetPostRetraceCallback(postRetraceProc);
@@ -68,8 +68,13 @@ void JUTVideo::preRetraceProc(u32 retrace_count) {
(*sManager->mPreCallback)(retrace_count);
}
OSTick tick = OSGetTick();
OSTick tick = DUSK_IF_ELSE(static_cast<OSTick>(OSGetNativeTime()), OSGetTick());
sVideoInterval = tick - sVideoLastTick;
#if TARGET_PC
if (sVideoInterval <= 0) {
sVideoInterval = 1;
}
#endif
sVideoLastTick = tick;
JUTXfb* xfb = JUTXfb::getManager();
+12 -10
View File
@@ -53,6 +53,7 @@ ResourceBuffer g_denoiseSource = RESOURCE_BUFFER_INIT;
ResourceBuffer g_compositeSource = RESOURCE_BUFFER_INIT;
GfxDeviceInfo g_deviceInfo = GFX_DEVICE_INFO_INIT;
GfxRenderTargetLayout g_sceneTargetLayout = GFX_RENDER_TARGET_LAYOUT_INIT;
WGPUComputePipeline g_preprocessPipeline = nullptr;
WGPUComputePipeline g_mip4Pipeline = nullptr;
WGPUComputePipeline g_gtaoPipeline = nullptr;
@@ -226,19 +227,17 @@ bool build_composite_pipeline(
.dstFactor = WGPUBlendFactor_One,
},
};
WGPUColorTargetState colorTarget = WGPU_COLOR_TARGET_STATE_INIT;
colorTarget.format = g_deviceInfo.color_format;
if (blend) {
colorTarget.blend = &blendState;
}
WGPUColorTargetState colorTargets[GFX_MAX_COLOR_ATTACHMENTS];
const uint32_t colorTargetCount = gfx_init_color_target_states(
&g_sceneTargetLayout, colorTargets, blend ? &blendState : nullptr, WGPUColorWriteMask_All);
WGPUFragmentState fragment = WGPU_FRAGMENT_STATE_INIT;
fragment.module = module;
fragment.entryPoint = {"fs_main", WGPU_STRLEN};
fragment.targetCount = 1;
fragment.targets = &colorTarget;
fragment.targetCount = colorTargetCount;
fragment.targets = colorTargets;
// Depth state must match the EFB pass despite never touching depth.
WGPUDepthStencilState depthStencil = WGPU_DEPTH_STENCIL_STATE_INIT;
depthStencil.format = g_deviceInfo.depth_format;
depthStencil.format = g_sceneTargetLayout.depth_stencil_format;
depthStencil.depthWriteEnabled = WGPUOptionalBool_False;
depthStencil.depthCompare = WGPUCompareFunction_Always;
@@ -248,7 +247,7 @@ bool build_composite_pipeline(
pipelineDesc.vertex.entryPoint = {"vs_main", WGPU_STRLEN};
pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
pipelineDesc.depthStencil = &depthStencil;
pipelineDesc.multisample.count = g_deviceInfo.sample_count;
pipelineDesc.multisample.count = g_sceneTargetLayout.sample_count;
pipelineDesc.fragment = &fragment;
outPipeline = wgpuDeviceCreateRenderPipeline(g_deviceInfo.device, &pipelineDesc);
wgpuShaderModuleRelease(module);
@@ -504,7 +503,7 @@ void on_compute(
// Render worker thread: composite the AO over the scene (or show it, in debug view).
void on_draw(
ModContext*, const GfxDrawContext* ctx, const void* payload, size_t payloadSize, void*) {
if (payloadSize != sizeof(CompositePayload)) {
if (payloadSize != sizeof(CompositePayload) || ctx->layout.key != g_sceneTargetLayout.key) {
return;
}
CompositePayload data;
@@ -816,6 +815,9 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) {
return mods::set_error(error, MOD_ERROR, "failed to query device info");
}
if (svc_gfx->get_scene_target_layout(mod_ctx, &g_sceneTargetLayout) != MOD_OK) {
return mods::set_error(error, MOD_ERROR, "failed to query scene target layout");
}
if (!build_compute_pipeline("AO preprocess depth", g_preprocessSource, "preprocess_depth",
g_preprocessPipeline, g_preprocessLayout) ||
!build_compute_pipeline("AO downsample mip4", g_preprocessSource, "downsample_mip4",
+19
View File
@@ -0,0 +1,19 @@
cmake_minimum_required(VERSION 3.25)
project(flow_demo CXX)
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root")
option(DUSK_MOD_USE_FULL_TREE "Use full build instead of the minimal mod SDK" OFF)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
if (DUSK_MOD_USE_FULL_TREE)
add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL)
else ()
add_subdirectory("${DUSK_DIR}/sdk" dusk-sdk EXCLUDE_FROM_ALL)
endif ()
endif ()
add_mod(flow_demo
FEATURES fmt
SOURCES src/mod.cpp
MOD_JSON mod.json
)
+7
View File
@@ -0,0 +1,7 @@
{
"id": "dev.twilitrealm.flow_demo",
"name": "[Demo] Flow & Messages",
"version": "1.0.0",
"author": "Twilit Realm",
"description": "Adds a FlowService demonstration submenu to Midna's menu."
}
+300
View File
@@ -0,0 +1,300 @@
#include "mods/service.hpp"
#include "mods/svc/flow.hpp"
#include "mods/svc/log.hpp"
#include <array>
#include <cstdint>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
DEFINE_MOD();
IMPORT_SERVICE(LogService, svc_log);
IMPORT_SERVICE(FlowService, svc_flow);
IMPORT_SERVICE(MessageService, svc_message);
namespace {
// The Midna flow lives in BMG group 0.
constexpr uint16_t kMessageGroup = 0;
// Midna's "speaker" value for messages.
constexpr uint16_t kMidnaSpeaker = 21;
// Vanilla flow node/branch/edge IDs that we reference or override.
constexpr uint16_t kMidnaPromptHumanNode = 0x018c;
constexpr uint16_t kMidnaPromptWolfNode = 0x018d;
constexpr uint16_t kMidnaHumanBranch = 0x0190;
constexpr uint16_t kMidnaWolfBranch = 0x0193;
constexpr uint16_t kMidnaTalkNode = 0x018f;
constexpr uint16_t kMidnaHumanTalkEdge = 0x0113;
constexpr uint16_t kMidnaWolfTalkEdge = 0x0119;
// For vanilla messages, we need both the entry index and the message ID.
// For registered custom messages, they're the same.
constexpr uint16_t kMidnaMenuPromptEntry = 3003; // message index used in flow message data
constexpr uint16_t kMidnaMenuPromptId = 2042; // message ID used to override
constexpr std::array kAllLanguages{
MESSAGE_LANGUAGE_ENGLISH,
MESSAGE_LANGUAGE_GERMAN,
MESSAGE_LANGUAGE_FRENCH,
MESSAGE_LANGUAGE_SPANISH,
MESSAGE_LANGUAGE_ITALIAN,
MESSAGE_LANGUAGE_JAPANESE,
};
constexpr mods::flow::MessageStyle kResponseStyle =
mods::flow::MessageStyle{}.speaker(kMidnaSpeaker).box_kind(MESSAGE_BOX_MIDNA);
constexpr mods::flow::MessageStyle kPromptStyle =
kResponseStyle.draw_type(MESSAGE_DRAW_INSTANT).talk_anim(31).face_anim(31);
mods::flow::Query g_demoQuery;
mods::flow::Event g_demoEvent;
mods::flow::Graph g_graph;
std::vector<mods::flow::RegisteredMessage> g_messages;
std::vector<mods::flow::MessageOverride> g_overrides;
std::vector<uint8_t> g_visitedPromptText;
uint32_t g_queryExecutions = 0;
uint32_t g_eventExecutions = 0;
mods::flow::MessageBuilder build_prompt(std::string_view suffix) {
return mods::flow::MessageBuilder{kPromptStyle}
.text("What is it, ")
.text_color(MESSAGE_COLOR_RED)
.player_name()
.text_color(MESSAGE_COLOR_DEFAULT)
.text("?\n")
.text_scale(85)
.text(suffix)
.text_scale(100)
.await_choice();
}
// Registers a single message for all languages.
mods::flow::RegisteredMessage register_message(const mods::flow::MessageBuilder& builder) {
std::vector<mods::flow::MessageVariant> variants;
variants.reserve(kAllLanguages.size());
for (const MessageLanguage language : kAllLanguages) {
variants.push_back(builder.build(language));
}
return mods::flow::register_message(kMessageGroup, variants);
}
ModResult add_message(const mods::flow::MessageBuilder& builder, MessageId& outId) {
auto message = register_message(builder);
if (!message) {
return message.result();
}
outId = message.id();
g_messages.push_back(std::move(message));
return MOD_OK;
}
ModResult add_fixed_override(
uint16_t messageId, const std::vector<uint8_t>& text, bool addCallback) {
for (const MessageLanguage language : kAllLanguages) {
auto fixed =
mods::flow::override_message(kMessageGroup, messageId, language, std::span{text});
if (!fixed) {
return fixed.result();
}
g_overrides.push_back(std::move(fixed));
if (!addCallback) {
continue;
}
auto callback = mods::flow::override_message_fn(kMessageGroup, messageId, language,
[](ModContext*, const MessageOverrideContext*, MessageTextData* outText,
void*) -> bool {
if (g_eventExecutions == 0 || outText == nullptr) {
return false;
}
outText->text = g_visitedPromptText.data();
outText->text_size = g_visitedPromptText.size();
return true;
});
if (!callback) {
return callback.result();
}
g_overrides.push_back(std::move(callback));
}
return MOD_OK;
}
uint16_t demo_query(ModContext*, const FlowQueryContext* query, void*) {
if (query == nullptr || query->parameter != 1234 || query->result_count < 3) {
return 0;
}
const auto result = static_cast<uint16_t>(g_queryExecutions % 3);
if (query->phase == FLOW_QUERY_PHASE_EXECUTE) {
++g_queryExecutions;
mods::log::info("Flow demo query selected result {}", result);
}
return result;
}
void demo_event(ModContext*, const FlowEventContext* event, void*) {
++g_eventExecutions;
const uint8_t path = event != nullptr ? event->parameters[3] : 0;
mods::log::info("Flow demo event {} executed (activation {})", path, g_eventExecutions);
}
} // namespace
extern "C" {
MOD_EXPORT ModResult mod_initialize(ModError* outError) {
g_queryExecutions = 0;
g_eventExecutions = 0;
MessageId humanSelectionId = 0;
MessageId wolfSelectionId = 0;
MessageId labPromptId = 0;
MessageId labSelectionId = 0;
MessageId formattedId = 0;
MessageId evenId = 0;
MessageId oddId = 0;
MessageId timeoutId = 0;
auto result = add_message(mods::flow::MessageBuilder{}
.speaker(kMidnaSpeaker)
.options("Transform into human", "Warp", "Flow demo"),
humanSelectionId);
if (result == MOD_OK) {
result = add_message(mods::flow::MessageBuilder{}
.speaker(kMidnaSpeaker)
.options("Transform into wolf", "Warp", "Flow demo"),
wolfSelectionId);
}
if (result == MOD_OK) {
result = add_message(build_prompt("Choose a FlowService test."), labPromptId);
}
if (result == MOD_OK) {
result = add_message(mods::flow::MessageBuilder{}
.speaker(kMidnaSpeaker)
.options("Formatted message", "Callback branch", "Talk to Midna"),
labSelectionId);
}
if (result == MOD_OK) {
result = add_message(mods::flow::MessageBuilder{kResponseStyle}
.text_color(MESSAGE_COLOR_DEFAULT)
.text("Hello from ")
.text_color(MESSAGE_COLOR_RED)
.text("FlowService")
.text_color(MESSAGE_COLOR_DEFAULT)
.text(", ")
.player_name()
.text("!\n")
.character_delay(3)
.text("This text is slow. ")
.character_delay(0)
.pause(12)
.text_scale(125)
.text("Big")
.text_scale(100)
.text(" text too.")
.input_after_delay(15),
formattedId);
}
if (result == MOD_OK) {
result = add_message(mods::flow::MessageBuilder{kResponseStyle}
.text("Query returned ")
.text_color(MESSAGE_COLOR_GREEN)
.text("zero")
.text_color(MESSAGE_COLOR_DEFAULT)
.text(".")
.auto_advance(75),
evenId);
}
if (result == MOD_OK) {
result = add_message(mods::flow::MessageBuilder{kResponseStyle}
.box_kind(MESSAGE_BOX_LIGHT_SPIRIT)
.text("Query returned ")
.text_color(MESSAGE_COLOR_RED)
.text("one")
.text_color(MESSAGE_COLOR_DEFAULT)
.text(".")
.auto_advance(75),
oddId);
}
if (result == MOD_OK) {
result = add_message(mods::flow::MessageBuilder{kResponseStyle}
.text("Query returned ")
.text_color(MESSAGE_COLOR_YELLOW)
.text("three")
.text_color(MESSAGE_COLOR_DEFAULT)
.text(".\nThis will timeout after three seconds.")
.input_or_timeout(90),
timeoutId);
}
if (result != MOD_OK) {
return mods::set_error(outError, result, "failed to register custom demo messages");
}
g_visitedPromptText =
build_prompt("A custom event has run.").build(MESSAGE_LANGUAGE_ENGLISH).text();
result = add_fixed_override(kMidnaMenuPromptId,
build_prompt("Flow demo is installed.").build(MESSAGE_LANGUAGE_ENGLISH).text(), true);
if (result != MOD_OK) {
return mods::set_error(outError, result, "failed to register demo message overrides");
}
g_demoQuery = mods::flow::register_query("flow_demo cycling branch", demo_query);
g_demoEvent = mods::flow::register_event("flow_demo activation", demo_event);
if (!g_demoQuery || !g_demoEvent) {
const auto callbackResult = !g_demoQuery ? g_demoQuery.result() : g_demoEvent.result();
return mods::set_error(outError, callbackResult, "failed to register flow callbacks");
}
// Set up our demo graph
mods::flow::GraphBuilder graph{kMessageGroup};
const auto demoSetup = graph.add_event(FLOW_EVENT_SELECT_VERTICAL, {0, 0, 0, 4});
const auto formattedMessage = graph.add_message(formattedId).next(demoSetup);
const auto formattedEvent =
graph.add_event(g_demoEvent.id(), {0, 0, 0, 1}).next(formattedMessage);
const auto evenMessage = graph.add_message(evenId).next(demoSetup);
const auto evenEvent = graph.add_event(g_demoEvent.id(), {0, 0, 0, 2}).next(evenMessage);
const auto oddMessage = graph.add_message(oddId).next(demoSetup);
const auto oddEvent = graph.add_event(g_demoEvent.id(), {0, 0, 0, 3}).next(oddMessage);
const auto timeoutMessage = graph.add_message(timeoutId).next(demoSetup);
const auto timeoutEvent = graph.add_event(g_demoEvent.id(), {0, 0, 0, 4}).next(timeoutMessage);
const auto callbackBranch =
graph.add_branch(g_demoQuery.id(), 1234).results({evenEvent, oddEvent, timeoutEvent});
const auto choiceBranch =
graph.add_branch(FLOW_QUERY_SELECT_3_CANCEL, 0)
.results({formattedEvent, callbackBranch, kMidnaTalkNode, mods::flow::kEnd});
const auto demoSelection = graph.add_message(labSelectionId).next(choiceBranch);
const auto demoPrompt = graph.add_message(labPromptId).next(demoSelection);
demoSetup.next(demoPrompt);
// Patch original nodes and edges so they flow into our custom graph
const auto humanSelection = graph.add_message(humanSelectionId).next(kMidnaHumanBranch);
const auto wolfSelection = graph.add_message(wolfSelectionId).next(kMidnaWolfBranch);
graph.patch_node(
kMidnaPromptHumanNode, mods::flow::message(0, kMidnaMenuPromptEntry, humanSelection));
graph.patch_node(
kMidnaPromptWolfNode, mods::flow::message(0, kMidnaMenuPromptEntry, wolfSelection));
graph.patch_edge(kMidnaHumanTalkEdge, demoSetup);
graph.patch_edge(kMidnaWolfTalkEdge, demoSetup);
g_graph = graph.commit();
if (!g_graph) {
return mods::set_error(outError, g_graph.result(), "failed to commit the flow demo graph");
}
mods::log::info("Flow demo ready: {} custom messages", g_messages.size());
return MOD_OK;
}
MOD_EXPORT ModResult mod_update(ModError*) {
return MOD_OK;
}
MOD_EXPORT ModResult mod_shutdown(ModError*) {
mods::log::info("Flow demo unloaded after {} events", g_eventExecutions);
g_graph.reset();
g_overrides.clear();
g_messages.clear();
g_visitedPromptText.clear();
return MOD_OK;
}
}
+13 -11
View File
@@ -20,12 +20,12 @@
#include "dolphin/gx/GXPixel.h"
#include "dolphin/gx/GXTransform.h"
#include "m_Do/m_Do_mtx.h"
#include "mods/svc/hook.hpp"
#include "mods/service.hpp"
#include "mods/svc/camera.h"
#include "mods/svc/config.h"
#include "mods/svc/gfx.h"
#include "mods/svc/hook.h"
#include "mods/svc/hook.hpp"
#include "mods/svc/log.h"
#include "mods/svc/resource.h"
#include "mods/svc/ui.h"
@@ -69,6 +69,7 @@ GfxStageHookHandle g_frameBeforeHudHook = 0;
UiWindowHandle g_controlsWindow = 0;
ResourceBuffer g_shaderSource = RESOURCE_BUFFER_INIT;
GfxDeviceInfo g_deviceInfo = GFX_DEVICE_INFO_INIT;
GfxRenderTargetLayout g_sceneTargetLayout = GFX_RENDER_TARGET_LAYOUT_INIT;
WGPURenderPipeline g_compositePipeline = nullptr; // multiply blend
WGPURenderPipeline g_compositeDebugPipeline = nullptr; // no blend (debug views)
WGPUBindGroupLayout g_compositeLayout = nullptr;
@@ -399,18 +400,16 @@ bool build_composite_pipeline(
.srcFactor = WGPUBlendFactor_Zero,
.dstFactor = WGPUBlendFactor_One},
};
WGPUColorTargetState colorTarget = WGPU_COLOR_TARGET_STATE_INIT;
colorTarget.format = g_deviceInfo.color_format;
if (blend) {
colorTarget.blend = &blendState;
}
WGPUColorTargetState colorTargets[GFX_MAX_COLOR_ATTACHMENTS];
const uint32_t colorTargetCount = gfx_init_color_target_states(
&g_sceneTargetLayout, colorTargets, blend ? &blendState : nullptr, WGPUColorWriteMask_All);
WGPUFragmentState fragment = WGPU_FRAGMENT_STATE_INIT;
fragment.module = module;
fragment.entryPoint = {"fs_main", WGPU_STRLEN};
fragment.targetCount = 1;
fragment.targets = &colorTarget;
fragment.targetCount = colorTargetCount;
fragment.targets = colorTargets;
WGPUDepthStencilState depthStencil = WGPU_DEPTH_STENCIL_STATE_INIT;
depthStencil.format = g_deviceInfo.depth_format;
depthStencil.format = g_sceneTargetLayout.depth_stencil_format;
depthStencil.depthWriteEnabled = WGPUOptionalBool_False;
depthStencil.depthCompare = WGPUCompareFunction_Always;
@@ -420,7 +419,7 @@ bool build_composite_pipeline(
pipelineDesc.vertex.entryPoint = {"vs_main", WGPU_STRLEN};
pipelineDesc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
pipelineDesc.depthStencil = &depthStencil;
pipelineDesc.multisample.count = g_deviceInfo.sample_count;
pipelineDesc.multisample.count = g_sceneTargetLayout.sample_count;
pipelineDesc.fragment = &fragment;
outPipeline = wgpuDeviceCreateRenderPipeline(g_deviceInfo.device, &pipelineDesc);
wgpuShaderModuleRelease(module);
@@ -518,7 +517,7 @@ WGPUBindGroup create_composite_bind_group(WGPUDevice device, WGPUBindGroupLayout
// Render worker thread: fullscreen deferred-shadow composite.
void on_draw(
ModContext*, const GfxDrawContext* ctx, const void* payload, size_t payloadSize, void*) {
if (payloadSize != sizeof(DrawPayload)) {
if (payloadSize != sizeof(DrawPayload) || ctx->layout.key != g_sceneTargetLayout.key) {
return;
}
DrawPayload data;
@@ -1243,6 +1242,9 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
if (svc_gfx->get_device_info(mod_ctx, &g_deviceInfo) != MOD_OK) {
return mods::set_error(error, MOD_ERROR, "failed to query device info");
}
if (svc_gfx->get_scene_target_layout(mod_ctx, &g_sceneTargetLayout) != MOD_OK) {
return mods::set_error(error, MOD_ERROR, "failed to query scene target layout");
}
if (!build_composite_pipeline(true, g_compositePipeline, g_compositeLayout) ||
!build_composite_pipeline(false, g_compositeDebugPipeline, g_compositeDebugLayout))
{
-6
View File
@@ -189,12 +189,6 @@ MOD_EXPORT ModResult mod_initialize(ModError* error) {
return mods::set_error(error, MOD_ERROR, "failed to register mod panel");
}
if (open_window() != MOD_OK) {
svc_gfx->unregister_stage_hook(mod_ctx, g_stageHook);
g_stageHook = 0;
return mods::set_error(error, MOD_ERROR, "failed to open auxiliary window");
}
mods::log::info("auxiliary WebGPU window ready");
return MOD_OK;
}
+68
View File
@@ -143,6 +143,74 @@ eyebrow span {
transition: decorator color opacity 0.1s linear-in-out;
}
#menu-list button.game-mode-button {
position: relative;
padding: 0;
overflow: visible;
}
#menu-list game-mode-label-viewport {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
border-radius: 8dp;
pointer-events: none;
z-index: 0;
}
#menu-list game-mode-label {
display: block;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
padding: 8dp 16dp;
opacity: 0;
text-overflow: ellipsis;
white-space: nowrap;
transition: opacity 0.24s cubic-in-out;
}
#menu-list game-mode-label.active {
opacity: 1;
}
#menu-list game-mode-previous,
#menu-list game-mode-next {
display: none;
position: absolute;
top: 0;
width: 48dp;
height: 54dp;
align-items: center;
justify-content: center;
color: #FFFFFF;
font-family: "Material Symbols Rounded";
font-weight: normal;
font-size: 30dp;
z-index: 1;
}
#menu-list game-mode-previous {
left: -48dp;
decorator: text("&#xe5cb;" center center);
}
#menu-list game-mode-next {
right: -48dp;
decorator: text("&#xe5cc;" center center);
}
#menu-list button.game-mode-button.can-cycle game-mode-previous,
#menu-list button.game-mode-button.can-cycle game-mode-next {
display: flex;
}
#menu-list button:hover,
#menu-list button:focus-visible {
color: black;
+10
View File
@@ -519,3 +519,13 @@ progress.verification-progress-bar {
flex: 0 0 auto;
padding-top: 4dp;
}
.modal-actions-vertical {
flex-direction: column;
align-items: stretch;
}
.modal-actions-vertical button.modal-btn {
flex: 0 0 auto;
width: 100%;
}
+6
View File
@@ -0,0 +1,6 @@
#pragma once
// Really lazy way to avoid duplicating these helpers
#define dusk mods
#include "../../../include/helpers/bits.hpp"
#undef dusk
+182
View File
@@ -0,0 +1,182 @@
#pragma once
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#define FLOW_SERVICE_ID "dev.twilitrealm.dusklight.flow"
#define FLOW_SERVICE_MAJOR 1u
#define FLOW_SERVICE_MINOR 0u
typedef uint64_t FlowGraphHandle;
typedef uint16_t FlowQueryId;
typedef uint8_t FlowEventId;
/* FLW1 node data */
typedef struct FlowNodeData {
uint8_t bytes[8];
} FlowNodeData;
static_assert(sizeof(FlowNodeData) == 8);
/* Built-in branch queries. The result selects the edge slot next_node_index + result;
* [param] is the branch node's 16-bit parameter. */
typedef enum FlowGameQuery {
FLOW_QUERY_SELECT_2 = 0, /* two-way selection result: 0 first, 1 second */
FLOW_QUERY_EVENT_FLAG = 1, /* 0 if event flag [param] is set */
FLOW_QUERY_PLAYER_FORM = 2, /* 0 human, 1 wolf, 2 riding */
FLOW_QUERY_RANDOM = 3, /* random result in [0, param) */
FLOW_QUERY_SELECT_3 = 4, /* three-way selection result: 0/1/2 */
FLOW_QUERY_TALK_DISTANCE = 5, /* player within talk range; param overrides max distance */
FLOW_QUERY_RUPEES = 6, /* 0 if rupees >= param; param 0 checks wallet max */
FLOW_QUERY_SWORD_TUTORIAL_STEP = 7, /* 0 if the scarecrow tutorial step matches param */
FLOW_QUERY_SWORD_TUTORIAL_RESULT = 8, /* 0 on tutorial success */
FLOW_QUERY_SWORD_TUTORIAL_COUNT = 9, /* 0 if first success */
FLOW_QUERY_TEMP_FLAG = 10, /* 0 if temporary event flag [param] is set */
FLOW_QUERY_CHEST_FLAG = 11, /* 0 if treasure chest flag [param] is set */
FLOW_QUERY_SAVE_SWITCH = 12, /* 0 if save switch [param] is set */
FLOW_QUERY_SAVE_ITEM_FLAG = 13,
FLOW_QUERY_DUNGEON_SWITCH = 14,
FLOW_QUERY_DUNGEON_ITEM_FLAG = 15,
FLOW_QUERY_ZONE_SWITCH = 16,
FLOW_QUERY_ZONE_ITEM_FLAG = 17,
FLOW_QUERY_ONE_ZONE_SWITCH = 18,
FLOW_QUERY_ONE_ZONE_ITEM_FLAG = 19,
FLOW_QUERY_EQUIPPED = 20, /* 1 if item [param] is equipped or on an item slot */
FLOW_QUERY_ITEM_OWNED = 21, /* 0 if item [param] is owned */
FLOW_QUERY_BOMB_BAG_COUNT = 22, /* number of bomb bags owned: 0-3 */
FLOW_QUERY_ARROWS = 23, /* 0 if arrows >= param */
FLOW_QUERY_EMPTY_BOTTLES = 24, /* 0 if empty bottles >= param */
FLOW_QUERY_SHOP_CLERK = 25, /* shop system conversation flag */
FLOW_QUERY_TEARS_OF_LIGHT = 26, /* 0 if tears >= param; param 0 uses the required count */
/* 0 if goat-herding time <= param seconds; publishes the time for display */
FLOW_QUERY_HERDING_TIME = 27,
FLOW_QUERY_LANTERN_OIL = 28, /* 0 full, 1 partial, 2 empty */
FLOW_QUERY_REGISTER = 29, /* flow scratch register value */
FLOW_QUERY_GOATS_CAUGHT = 30, /* 0 if caught runaway goats >= param */
FLOW_QUERY_HEARTS = 31, /* 0 if life >= param */
FLOW_QUERY_HOLDING_LANTERN = 32, /* 0 if the player has the lantern out */
FLOW_QUERY_TIME_OF_DAY = 33, /* current hour of game time (0-23) */
FLOW_QUERY_MAGIC = 34, /* 0 if magic >= param */
FLOW_QUERY_SELECT_2_CANCEL = 35, /* 0/1 choice, 2 on B cancel */
FLOW_QUERY_SELECT_3_CANCEL = 36, /* 0/1/2 choice, 3 on B cancel */
FLOW_QUERY_BOMB_BAG_CONTENTS = 37, /* 0 empty, 1 bombs, 2 water bombs, 3 bomblings */
FLOW_QUERY_BOMBS_FIT = 38, /* 1 if param more bombs fit in the bag, 0 if over max */
FLOW_QUERY_BOMB_BAG_FILL = 39, /* 0 empty, 1 partial, 2 full */
FLOW_QUERY_WATER_BOMBS_FIT = 40,
/* 0 clear, 1 NPC near, 2 NPC far, 3 environment, 4 Sacred Grove */
FLOW_QUERY_TRANSFORM_BLOCKED = 41,
FLOW_QUERY_BOMBLINGS_FIT = 42,
FLOW_QUERY_WARP_ALLOWED = 43, /* 0 if a dungeon warp is accepted here */
FLOW_QUERY_GOLDEN_BUGS = 44, /* 0 none, 1 1-11, 2 12-22, 3 23, 4 all 24 */
FLOW_QUERY_UNDELIVERED_BUG = 45, /* 1 if carrying a golden bug not yet delivered to Agitha */
FLOW_QUERY_UNUSED_46 = 46, /* asserts; do not use */
FLOW_QUERY_NEW_LETTERS = 47, /* 0 none, 1 one (& stores its name for the tag), 2 more */
FLOW_QUERY_POE_SOULS = 48, /* 0 none, 1 <20, 2 <40, 3 <60, 4 60+ */
FLOW_QUERY_DONATION_TOTAL = 49, /* 0 if donations >= param */
FLOW_QUERY_BALLOON_SCORE = 50, /* 0 zero, 1 <1000, 2 <10000, 3 <61454, 4 max */
FLOW_QUERY_IN_WATER = 51, /* 1 if the player is swimming */
FLOW_QUERY_IRON_BOOTS = 52, /* 1 if iron boots are equipped */
FLOW_QUERY_BUILTIN_COUNT,
} FlowGameQuery;
/* Built-in event actions. Params (big-endian): one u32, two u16 (p0, p1), or four u8. */
typedef enum FlowGameEvent {
FLOW_EVENT_SET_EVENT_FLAG = 0, /* sets event flags [p0] and [p1]; 0 = none */
FLOW_EVENT_CLEAR_EVENT_FLAG = 1,
FLOW_EVENT_ADD_RUPEES = 2,
FLOW_EVENT_REMOVE_RUPEES = 3,
FLOW_EVENT_ADD_HEARTS = 4,
FLOW_EVENT_REMOVE_HEARTS = 5,
FLOW_EVENT_ADD_MAGIC = 6,
FLOW_EVENT_REMOVE_MAGIC = 7,
FLOW_EVENT_START_EVENT = 8, /* publish (p0 event id, p1 item id) for the speaker to poll */
FLOW_EVENT_JUMP_FLOW = 9, /* continue at flow [p]; 0 jumps to the stage/Midna flow */
FLOW_EVENT_SET_TEMP_FLAG = 10,
FLOW_EVENT_CLEAR_TEMP_FLAG = 11,
FLOW_EVENT_OPEN_DOOR = 12, /* marks the flow as a door unlock path (probe only) */
FLOW_EVENT_SELECT_VERTICAL = 13, /* vertical selection; p = result index chosen on B cancel */
FLOW_EVENT_SET_SWITCH = 14, /* p0 scope: 0 save, 1 dungeon, 2 zone, 3 one-zone; p1 bit */
FLOW_EVENT_CLEAR_SWITCH = 15,
FLOW_EVENT_SHOP_SELECT = 16, /* start shop item selection; four u8 shop params */
FLOW_EVENT_GIVE_ITEM = 17, /* p0 item number, p1 count */
/* four u8 direction values for the speaker; p3 plays a sound */
FLOW_EVENT_STAGE_DIRECTION = 18,
FLOW_EVENT_SET_SPEAKER = 19, /* point the box at talk partner [p1] */
FLOW_EVENT_WARP_PLAYER = 20, /* move the player to the room spawn tagged [p] */
FLOW_EVENT_WAIT = 21, /* close the box and wait [p] frames */
FLOW_EVENT_FILL_LANTERN = 22, /* refill oil to [p] percent; 0 = full */
/* p: 1-3 red/green/blue potion, 4 milk, 5 half milk, 6 oil, 7 hot spring water */
FLOW_EVENT_FILL_BOTTLE = 23,
FLOW_EVENT_SHOP_SOLD_OUT = 24,
FLOW_EVENT_SET_REGISTER = 25, /* set the flow scratch register (see FLOW_QUERY_REGISTER) */
FLOW_EVENT_TENT_PURCHASE = 26, /* unattended stand purchase; marks the item sold out */
FLOW_EVENT_FILL_BOMBS = 27, /* u8 p0 bag select, u8 p1 operation; u16 p1 count */
FLOW_EVENT_SELL_BOMBS = 28, /* empty the selected bag and pay out */
/* horizontal selection; p = result index chosen on B cancel */
FLOW_EVENT_SELECT_HORIZONTAL = 29,
FLOW_EVENT_FILL_ARROWS = 30, /* p1 count, 0 = max; p0 nonzero defers the refill */
FLOW_EVENT_RETURN_RENTAL_BOMB_BAG = 31,
FLOW_EVENT_FADE_IN = 32, /* p0: 0 black, 1 white; p1 frames */
FLOW_EVENT_FADE_OUT = 33,
FLOW_EVENT_SET_TRADE_ITEM = 34, /* set the trade-quest item */
FLOW_EVENT_REMOVE_ITEM = 35,
FLOW_EVENT_SET_SAVE_SWITCH = 36, /* set save switch (p0 area, p1 bit) */
FLOW_EVENT_CLEAR_SAVE_SWITCH = 37,
FLOW_EVENT_RECEIVE_LETTER = 38,
FLOW_EVENT_UNLOCK_MAP_REGION = 39,
FLOW_EVENT_EMPTY_BOTTLE = 40, /* p as in FLOW_EVENT_FILL_BOTTLE */
FLOW_EVENT_ADD_DONATION = 41,
FLOW_EVENT_UNUSED_42 = 42, /* no-op */
FLOW_EVENT_BUILTIN_COUNT,
} FlowGameEvent;
typedef enum FlowQueryPhase {
FLOW_QUERY_PHASE_PROBE = 0,
FLOW_QUERY_PHASE_EXECUTE = 1,
} FlowQueryPhase;
typedef struct FlowQueryContext {
const void* speaker_actor;
uint16_t parameter;
uint8_t result_count;
uint8_t phase; /* FlowQueryPhase */
} FlowQueryContext;
typedef uint16_t (*FlowQueryFn)(ModContext* ctx, const FlowQueryContext* query, void* user_data);
typedef struct FlowEventContext {
const void* speaker_actor;
uint8_t parameters[4];
} FlowEventContext;
typedef void (*FlowEventFn)(ModContext* ctx, const FlowEventContext* event, void* user_data);
typedef struct FlowService {
ServiceHeader header;
ModResult (*begin_graph)(ModContext* ctx, uint16_t group, FlowGraphHandle* out_handle);
/* Allocates one node ID. Fill it with fill_node before commit_graph. */
ModResult (*allocate_node)(ModContext* ctx, FlowGraphHandle handle, uint16_t* out_id);
/* Adds a series of edges with the given targets. */
ModResult (*add_edges)(ModContext* ctx, FlowGraphHandle handle, const uint16_t* targets,
uint16_t count, uint16_t* out_first);
/* May be called again to replace a node until commit_graph. */
ModResult (*fill_node)(
ModContext* ctx, FlowGraphHandle handle, uint16_t node_index, const FlowNodeData* node);
/* Replace a native node or edge when the graph commits; reverted when it is removed. */
ModResult (*patch_node)(
ModContext* ctx, FlowGraphHandle handle, uint16_t node_index, const FlowNodeData* node);
ModResult (*patch_edge)(
ModContext* ctx, FlowGraphHandle handle, uint16_t edge_index, uint16_t target_node);
ModResult (*commit_graph)(ModContext* ctx, FlowGraphHandle handle);
ModResult (*remove_graph)(ModContext* ctx, FlowGraphHandle handle);
ModResult (*register_query)(ModContext* ctx, const char* debug_name, FlowQueryFn fn,
void* user_data, FlowQueryId* out_id);
ModResult (*register_event)(ModContext* ctx, const char* debug_name, FlowEventFn fn,
void* user_data, FlowEventId* out_id);
} FlowService;
MOD_DECLARE_SERVICE(FlowService, svc_flow, FLOW_SERVICE_ID, FLOW_SERVICE_MAJOR, FLOW_SERVICE_MINOR);
+633
View File
@@ -0,0 +1,633 @@
#pragma once
#include <mods/bits.hpp>
#include <mods/svc/flow.h>
#include <mods/svc/message.h>
#include <array>
#include <cstddef>
#include <cstdint>
#include <initializer_list>
#include <span>
#include <string_view>
#include <utility>
#include <vector>
namespace mods::flow {
static_assert(sizeof(FlowNodeData) == 8);
static_assert(sizeof(MessageEntryData) == 20);
inline constexpr uint16_t kCustomNodeMin = 0x8000;
inline constexpr uint16_t kCustomEdgeMin = 0x8000;
inline constexpr uint16_t kCustomMessageMin = 0x8000;
inline constexpr uint16_t kEnd = 0xffff;
/* entryIndex is the INF1 entry index, not the message ID stored inside the entry.
* For custom messages the entry index equals the MessageId. */
constexpr FlowNodeData message(
uint8_t subtype, uint16_t entryIndex, uint16_t nextNode, uint16_t unknown = 0) {
FlowNodeData node{};
node.bytes[0] = 1;
node.bytes[1] = subtype;
write_bits(node.bytes + 2, entryIndex);
write_bits(node.bytes + 4, nextNode);
write_bits(node.bytes + 6, unknown);
return node;
}
constexpr FlowNodeData branch(
uint8_t resultCount, FlowQueryId query, uint16_t parameter, uint16_t firstEdge) {
FlowNodeData node{};
node.bytes[0] = 2;
node.bytes[1] = resultCount;
write_bits(node.bytes + 2, query);
write_bits(node.bytes + 4, parameter);
write_bits(node.bytes + 6, firstEdge);
return node;
}
constexpr FlowNodeData event(FlowEventId eventId, uint16_t edge, std::array<uint8_t, 4> params) {
FlowNodeData node{};
node.bytes[0] = 3;
node.bytes[1] = eventId;
write_bits(node.bytes + 2, edge);
for (size_t i = 0; i < params.size(); ++i) {
node.bytes[4 + i] = params[i];
}
return node;
}
class Graph {
public:
Graph() = default;
Graph(FlowGraphHandle handle, ModResult result) : mHandle{handle}, mResult{result} {}
Graph(const Graph&) = delete;
Graph& operator=(const Graph&) = delete;
Graph(Graph&& other) noexcept { *this = std::move(other); }
Graph& operator=(Graph&& other) noexcept {
if (this != &other) {
reset();
mHandle = std::exchange(other.mHandle, 0);
mResult = other.mResult;
}
return *this;
}
~Graph() { reset(); }
explicit operator bool() const { return mResult == MOD_OK; }
ModResult result() const { return mResult; }
FlowGraphHandle handle() const { return mHandle; }
void reset() {
if (mHandle != 0 && svc_flow != nullptr) {
svc_flow->remove_graph(mod_ctx, mHandle);
mHandle = 0;
}
}
private:
FlowGraphHandle mHandle{};
ModResult mResult = MOD_UNAVAILABLE;
};
class GraphBuilder;
class NodeRef {
public:
NodeRef() = default;
/* Set the successor of a message or event node: a node ID or kEnd */
NodeRef next(uint16_t target) const;
/* Set a branch node's result targets */
NodeRef results(std::initializer_list<uint16_t> targets) const;
uint16_t id() const { return mId; }
operator uint16_t() const { return mId; }
private:
friend class GraphBuilder;
NodeRef(GraphBuilder* builder, uint16_t id) : mBuilder{builder}, mId{id} {}
GraphBuilder* mBuilder = nullptr;
uint16_t mId = 0;
};
class GraphBuilder {
public:
explicit GraphBuilder(uint16_t group) {
if (svc_flow == nullptr) {
mResult = MOD_UNAVAILABLE;
return;
}
mResult = svc_flow->begin_graph(mod_ctx, group, &mHandle);
}
GraphBuilder(const GraphBuilder&) = delete;
GraphBuilder& operator=(const GraphBuilder&) = delete;
~GraphBuilder() {
if (mHandle != 0 && svc_flow != nullptr) {
svc_flow->remove_graph(mod_ctx, mHandle);
}
}
/* entryIndex is an INF1 entry index for native messages, or a registered MessageId */
NodeRef add_message(uint16_t entryIndex, uint8_t subtype = 0) {
return append(message(subtype, entryIndex, 0), true);
}
NodeRef add_branch(FlowQueryId query, uint16_t parameter) {
return append(branch(0, query, parameter, 0), true);
}
NodeRef add_event(FlowEventId eventId, std::array<uint8_t, 4> params) {
return append(event(eventId, 0, params), true);
}
NodeRef add_node(const FlowNodeData& node) { return append(node, false); }
GraphBuilder& patch_node(uint16_t nodeIndex, const FlowNodeData& node) {
if (mResult == MOD_OK) {
mResult = svc_flow->patch_node(mod_ctx, mHandle, nodeIndex, &node);
}
return *this;
}
GraphBuilder& patch_edge(uint16_t edgeIndex, uint16_t targetNode) {
if (mResult == MOD_OK) {
mResult = svc_flow->patch_edge(mod_ctx, mHandle, edgeIndex, targetNode);
}
return *this;
}
Graph commit() {
for (const auto& node : mNodes) {
if (node.typed && !node.wired) {
mResult = MOD_INVALID_ARGUMENT;
}
}
for (const auto& node : mNodes) {
if (mResult != MOD_OK) {
break;
}
mResult = svc_flow->fill_node(mod_ctx, mHandle, node.id, &node.data);
}
if (mResult == MOD_OK) {
mResult = svc_flow->commit_graph(mod_ctx, mHandle);
}
if (mResult != MOD_OK) {
return {0, mResult}; // the builder destructor removes the graph
}
return {std::exchange(mHandle, 0), MOD_OK};
}
private:
friend class NodeRef;
struct BuilderNode {
uint16_t id;
FlowNodeData data;
bool typed;
bool wired;
};
NodeRef append(const FlowNodeData& node, bool typed) {
uint16_t id = 0;
if (mResult == MOD_OK) {
mResult = svc_flow->allocate_node(mod_ctx, mHandle, &id);
}
if (mResult == MOD_OK) {
mNodes.push_back({id, node, typed, false});
}
return {this, id};
}
BuilderNode* find(uint16_t id) {
for (auto& node : mNodes) {
if (node.id == id) {
return &node;
}
}
return nullptr;
}
void set_next(uint16_t id, uint16_t target) {
if (mResult != MOD_OK) {
return;
}
auto* node = find(id);
if (node == nullptr || node->wired) {
mResult = MOD_INVALID_ARGUMENT;
return;
}
node->wired = true;
switch (node->data.bytes[0]) {
case 1:
write_bits(node->data.bytes + 4, target);
break;
case 3: {
uint16_t first = 0;
mResult = svc_flow->add_edges(mod_ctx, mHandle, &target, 1, &first);
if (mResult == MOD_OK) {
write_bits(node->data.bytes + 2, first);
}
break;
}
default:
mResult = MOD_INVALID_ARGUMENT;
}
}
void set_results(uint16_t id, std::initializer_list<uint16_t> targets) {
if (mResult != MOD_OK) {
return;
}
auto* node = find(id);
if (node == nullptr || node->wired || node->data.bytes[0] != 2 || targets.size() == 0 ||
targets.size() > 0xff)
{
mResult = MOD_INVALID_ARGUMENT;
return;
}
node->wired = true;
uint16_t first = 0;
mResult = svc_flow->add_edges(
mod_ctx, mHandle, targets.begin(), static_cast<uint16_t>(targets.size()), &first);
if (mResult == MOD_OK) {
node->data.bytes[1] = static_cast<uint8_t>(targets.size());
write_bits(node->data.bytes + 6, first);
}
}
FlowGraphHandle mHandle{};
std::vector<BuilderNode> mNodes;
ModResult mResult = MOD_OK;
};
inline NodeRef NodeRef::next(uint16_t target) const {
if (mBuilder != nullptr) {
mBuilder->set_next(mId, target);
}
return *this;
}
inline NodeRef NodeRef::results(std::initializer_list<uint16_t> targets) const {
if (mBuilder != nullptr) {
mBuilder->set_results(mId, targets);
}
return *this;
}
/* Single-patch convenience functions */
inline Graph patch_node(uint16_t group, uint16_t nodeIndex, const FlowNodeData& node) {
GraphBuilder builder{group};
builder.patch_node(nodeIndex, node);
return builder.commit();
}
inline Graph patch_edge(uint16_t group, uint16_t edgeIndex, uint16_t targetNode) {
GraphBuilder builder{group};
builder.patch_edge(edgeIndex, targetNode);
return builder.commit();
}
class Query {
public:
Query() = default;
Query(FlowQueryId id, ModResult result) : mId{id}, mResult{result} {}
explicit operator bool() const { return mResult == MOD_OK; }
FlowQueryId id() const { return mId; }
ModResult result() const { return mResult; }
private:
FlowQueryId mId{};
ModResult mResult = MOD_UNAVAILABLE;
};
inline Query register_query(const char* debugName, FlowQueryFn fn, void* userData = nullptr) {
FlowQueryId id{};
const ModResult result = svc_flow != nullptr ?
svc_flow->register_query(mod_ctx, debugName, fn, userData, &id) :
MOD_UNAVAILABLE;
return {id, result};
}
class Event {
public:
Event() = default;
Event(FlowEventId id, ModResult result) : mId{id}, mResult{result} {}
explicit operator bool() const { return mResult == MOD_OK; }
FlowEventId id() const { return mId; }
ModResult result() const { return mResult; }
private:
FlowEventId mId{};
ModResult mResult = MOD_UNAVAILABLE;
};
inline Event register_event(const char* debugName, FlowEventFn fn, void* userData = nullptr) {
FlowEventId id{};
const ModResult result = svc_flow != nullptr ?
svc_flow->register_event(mod_ctx, debugName, fn, userData, &id) :
MOD_UNAVAILABLE;
return {id, result};
}
/* Message presentation/style attributes (INF1) */
class MessageStyle {
public:
constexpr MessageStyle() { mData.bytes[12] = 0xff; }
constexpr explicit MessageStyle(MessageEntryData data) : mData{data} {}
/* saveBitLabels index set when the message displays. */
[[nodiscard]] constexpr MessageStyle event_label_id(uint16_t value) const {
return set_u16(6, value);
}
/* Z2SpeechMgr2 voice bank ID */
[[nodiscard]] constexpr MessageStyle speaker(uint8_t value) const { return set_u8(8, value); }
[[nodiscard]] constexpr MessageStyle box_kind(MessageBoxKind value) const {
return set_u8(9, static_cast<uint8_t>(value));
}
[[nodiscard]] constexpr MessageStyle draw_type(MessageDrawType value) const {
return set_u8(10, static_cast<uint8_t>(value));
}
[[nodiscard]] constexpr MessageStyle box_position(MessageBoxPosition value) const {
return set_u8(11, static_cast<uint8_t>(value));
}
/* 0 centered (JP builds only), 1 left */
[[nodiscard]] constexpr MessageStyle line_alignment(uint8_t value) const {
return set_u8(13, value);
}
/* Grunt emotion index for the voice bank */
[[nodiscard]] constexpr MessageStyle speaker_mood(uint8_t value) const {
return set_u8(14, value);
}
/* 1-10 focus talk-actor slot, >=11 talk-camera style, 0 none */
[[nodiscard]] constexpr MessageStyle camera_attr(uint8_t value) const {
return set_u8(15, value);
}
/* NPC talk motion attribute */
[[nodiscard]] constexpr MessageStyle talk_anim(uint8_t value) const {
return set_u8(16, value);
}
/* NPC talk face attribute */
[[nodiscard]] constexpr MessageStyle face_anim(uint8_t value) const {
return set_u8(17, value);
}
[[nodiscard]] constexpr const MessageEntryData& data() const { return mData; }
private:
[[nodiscard]] constexpr MessageStyle set_u8(size_t offset, uint8_t value) const {
MessageStyle style = *this;
style.mData.bytes[offset] = value;
return style;
}
[[nodiscard]] constexpr MessageStyle set_u16(size_t offset, uint16_t value) const {
MessageStyle style = *this;
write_bits(style.mData.bytes + offset, value);
return style;
}
MessageEntryData mData{};
};
class MessageVariant {
public:
MessageVariant() = default;
MessageVariant(MessageLanguage language, MessageEntryData entry, std::vector<uint8_t> text,
ModResult result = MOD_OK)
: mLanguage{language}, mEntry{entry}, mText{std::move(text)}, mResult{result} {}
explicit operator bool() const { return mResult == MOD_OK; }
ModResult result() const { return mResult; }
MessageLanguage language() const { return mLanguage; }
const MessageEntryData& entry() const { return mEntry; }
const std::vector<uint8_t>& text() const { return mText; }
MessageVariantData data() const {
return {static_cast<uint8_t>(mLanguage), mEntry, mText.data(), mText.size()};
}
private:
MessageLanguage mLanguage = MESSAGE_LANGUAGE_ENGLISH;
MessageEntryData mEntry{};
std::vector<uint8_t> mText;
ModResult mResult = MOD_UNAVAILABLE;
};
class MessageBuilder {
public:
explicit MessageBuilder(MessageStyle style = {}) : mStyle{style} { mText.push_back(0); }
/* Style setters forwarded to MessageStyle */
MessageBuilder& event_label_id(uint16_t value) {
return style(&MessageStyle::event_label_id, value);
}
MessageBuilder& speaker(uint8_t value) { return style(&MessageStyle::speaker, value); }
MessageBuilder& box_kind(MessageBoxKind value) { return style(&MessageStyle::box_kind, value); }
MessageBuilder& draw_type(MessageDrawType value) {
return style(&MessageStyle::draw_type, value);
}
MessageBuilder& box_position(MessageBoxPosition value) {
return style(&MessageStyle::box_position, value);
}
MessageBuilder& line_alignment(uint8_t value) {
return style(&MessageStyle::line_alignment, value);
}
MessageBuilder& speaker_mood(uint8_t value) {
return style(&MessageStyle::speaker_mood, value);
}
MessageBuilder& camera_attr(uint8_t value) { return style(&MessageStyle::camera_attr, value); }
MessageBuilder& talk_anim(uint8_t value) { return style(&MessageStyle::talk_anim, value); }
MessageBuilder& face_anim(uint8_t value) { return style(&MessageStyle::face_anim, value); }
/* Content builder functions */
MessageBuilder& text(std::string_view value) {
if (!mText.empty()) {
mText.pop_back();
}
mText.insert(mText.end(), value.begin(), value.end());
mText.push_back(0);
return *this;
}
MessageBuilder& raw_tag(uint8_t group, uint16_t type, std::span<const uint8_t> arguments) {
if (arguments.size() > 250) {
mResult = MOD_INVALID_ARGUMENT;
return *this;
}
mText.pop_back();
mText.push_back(0x1a);
mText.push_back(static_cast<uint8_t>(5 + arguments.size()));
mText.push_back(group);
mText.push_back(static_cast<uint8_t>(type >> 8));
mText.push_back(static_cast<uint8_t>(type));
mText.insert(mText.end(), arguments.begin(), arguments.end());
mText.push_back(0);
return *this;
}
MessageBuilder& text_color(MessageTextColor color) {
const auto index = static_cast<uint8_t>(color);
return raw_tag(255, 0, {&index, 1});
}
MessageBuilder& text_scale(uint16_t percent) { return timed_tag(255, 1, percent); }
MessageBuilder& character_delay(uint16_t frames) { return timed_tag(0, 6, frames); }
MessageBuilder& pause(uint16_t frames) { return timed_tag(0, 7, frames); }
MessageBuilder& auto_advance(uint16_t frames) { return timed_tag(0, 4, frames); }
MessageBuilder& auto_advance_alternate(uint16_t frames) { return timed_tag(0, 3, frames); }
MessageBuilder& input_or_timeout(uint16_t frames) { return timed_tag(0, 5, frames); }
MessageBuilder& input_after_delay(uint16_t frames) { return timed_tag(0, 54, frames); }
/* Insert the player's name. */
MessageBuilder& player_name() { return raw_tag(0, 0, {}); }
/* End a prompt that presents a selection; the options live in the target flow node. */
MessageBuilder& await_choice() { return raw_tag(0, 32, {}); }
/* Option list for a vertical selection message. Only two or three options are supported.
* `initial` configures the highlighted option on open. */
MessageBuilder& options(std::string_view first, std::string_view second, uint8_t initial = 0) {
option(8, 0, initial, first);
return option(8, 1, initial, second);
}
MessageBuilder& options(std::string_view first, std::string_view second, std::string_view third,
uint8_t initial = 0) {
option(9, 0, initial, first);
option(9, 1, initial, second);
return option(9, 2, initial, third);
}
MessageVariant build(MessageLanguage language) const {
return {language, mStyle.data(), mText, mResult};
}
private:
MessageBuilder& option(
uint16_t type, uint8_t position, uint8_t initial, std::string_view value) {
if (position > 0) {
text("\n");
}
uint8_t marker = static_cast<uint8_t>(position + 1);
if (position == initial) {
marker = 1;
} else if (position == 0) {
marker = static_cast<uint8_t>(initial + 1);
}
raw_tag(0, type, {&marker, 1});
return text(value);
}
template <typename Setter, typename Value>
MessageBuilder& style(Setter setter, Value value) {
mStyle = (mStyle.*setter)(value);
return *this;
}
MessageBuilder& timed_tag(uint8_t group, uint16_t type, uint16_t value) {
return raw_tag(
group, type, std::array{static_cast<uint8_t>(value >> 8), static_cast<uint8_t>(value)});
}
MessageStyle mStyle;
std::vector<uint8_t> mText;
ModResult mResult = MOD_OK;
};
class MessageOverride {
public:
MessageOverride() = default;
MessageOverride(MessageOverrideHandle handle, ModResult result)
: mHandle{handle}, mResult{result} {}
MessageOverride(const MessageOverride&) = delete;
MessageOverride& operator=(const MessageOverride&) = delete;
MessageOverride(MessageOverride&& other) noexcept { *this = std::move(other); }
MessageOverride& operator=(MessageOverride&& other) noexcept {
if (this != &other) {
reset();
mHandle = std::exchange(other.mHandle, 0);
mResult = other.mResult;
}
return *this;
}
~MessageOverride() { reset(); }
explicit operator bool() const { return mResult == MOD_OK; }
ModResult result() const { return mResult; }
MessageOverrideHandle handle() const { return mHandle; }
void reset() {
if (mHandle != 0 && svc_message != nullptr) {
svc_message->remove_override(mod_ctx, mHandle);
mHandle = 0;
}
}
private:
MessageOverrideHandle mHandle{};
ModResult mResult = MOD_UNAVAILABLE;
};
inline MessageOverride override_message(
uint16_t group, uint16_t messageId, MessageLanguage language, std::span<const uint8_t> text) {
MessageOverrideHandle handle{};
const ModResult result = svc_message != nullptr ? svc_message->override_message(mod_ctx, group,
messageId, static_cast<uint8_t>(language),
text.data(), text.size(), &handle) :
MOD_UNAVAILABLE;
return {handle, result};
}
inline MessageOverride override_message_fn(uint16_t group, uint16_t messageId,
MessageLanguage language, MessageOverrideFn fn, void* userData = nullptr) {
MessageOverrideHandle handle{};
const ModResult result = svc_message != nullptr ?
svc_message->override_message_fn(mod_ctx, group, messageId,
static_cast<uint8_t>(language), fn, userData, &handle) :
MOD_UNAVAILABLE;
return {handle, result};
}
class RegisteredMessage {
public:
RegisteredMessage() = default;
RegisteredMessage(MessageId id, MessageHandle handle, ModResult result)
: mId{id}, mHandle{handle}, mResult{result} {}
RegisteredMessage(const RegisteredMessage&) = delete;
RegisteredMessage& operator=(const RegisteredMessage&) = delete;
RegisteredMessage(RegisteredMessage&& other) noexcept { *this = std::move(other); }
RegisteredMessage& operator=(RegisteredMessage&& other) noexcept {
if (this != &other) {
reset();
mId = other.mId;
mHandle = std::exchange(other.mHandle, 0);
mResult = other.mResult;
}
return *this;
}
~RegisteredMessage() { reset(); }
explicit operator bool() const { return mResult == MOD_OK; }
MessageId id() const { return mId; }
ModResult result() const { return mResult; }
void reset() {
if (mHandle != 0 && svc_message != nullptr) {
svc_message->remove_message(mod_ctx, mHandle);
mHandle = 0;
}
}
private:
MessageId mId{};
MessageHandle mHandle{};
ModResult mResult = MOD_UNAVAILABLE;
};
inline RegisteredMessage register_message(
uint16_t group, std::span<const MessageVariant> variants) {
std::vector<MessageVariantData> data;
data.reserve(variants.size());
for (const auto& variant : variants) {
if (!variant) {
return {0, 0, variant.result()};
}
data.push_back(variant.data());
}
MessageId id{};
MessageHandle handle{};
const ModResult result = svc_message != nullptr ? svc_message->register_message(mod_ctx, group,
data.data(), data.size(), &id, &handle) :
MOD_UNAVAILABLE;
return {id, handle, result};
}
inline RegisteredMessage register_message(
uint16_t group, std::initializer_list<MessageVariant> variants) {
return register_message(group, std::span{variants.begin(), variants.size()});
}
} // namespace mods::flow
+1 -1
View File
@@ -16,7 +16,7 @@
* of letting them corrupt memory.
*/
#define GAME_SERVICE_ID "dev.twilitrealm.dusklight.game"
#define GAME_SERVICE_MAJOR 1u
#define GAME_SERVICE_MAJOR 2u
#define GAME_SERVICE_MINOR 0u
typedef struct GameService {
+50
View File
@@ -0,0 +1,50 @@
#pragma once
#include <mods/api.h>
#include <mods/svc/config.h>
#define GAME_MODE_SERVICE_ID "dev.twilitrealm.dusklight.gamemode"
#define GAME_MODE_SERVICE_MAJOR 1u
#define GAME_MODE_SERVICE_MINOR 0u
/* MOD_OK on success; failures disable the mod. */
typedef ModResult (*GameModeCallback)(void* user_data, ModError* out_error);
typedef enum GameModeNewSaveState {
GAME_MODE_STATE_PENDING = 0,
GAME_MODE_STATE_PROCEED,
GAME_MODE_STATE_RETURN,
} GameModeNewSaveState;
/* Set state to PROCEED after custom UI completes, or RETURN to cancel.
* State pointer is valid until selection completes. */
typedef ModResult (*GameModeNewSaveSelectCallback)(
void* user_data, GameModeNewSaveState* state, ModError* out_error);
typedef struct {
uint32_t struct_size;
const char* game_mode_id;
const char* full_name;
const char save_name[32]; // Empty uses default (gczelda2); max 31 chars
void* user_data; // Pointer will be passed to all callbacks
GameModeCallback on_activated; // Called when the game mode is selected
GameModeCallback on_deactivated; // Called when the game mode is deselected
GameModeCallback on_play; // Called when play is pressed
GameModeCallback on_save_loaded; // Called whenever a save file is loaded
GameModeCallback on_new_save; // Called when a new save is created
GameModeNewSaveSelectCallback on_new_save_select;
GameModeCallback on_game_reset; // Called when the game is reset
GameModeCallback on_tick; // Called on every game tick while active
} GameModeDesc;
#define GAME_MODE_DESC_INIT {sizeof(GameModeDesc)}
typedef struct GameModeService {
ServiceHeader header;
ModResult (*register_game_mode)(ModContext* ctx, const GameModeDesc* desc);
ModResult (*unregister_game_mode)(ModContext* ctx, const char* id);
ModResult (*is_active)(ModContext* ctx, const char* game_mode_id, bool* out_active);
} GameModeService;
MOD_DECLARE_SERVICE(GameModeService, svc_game_mode, GAME_MODE_SERVICE_ID, GAME_MODE_SERVICE_MAJOR,
GAME_MODE_SERVICE_MINOR);
+71 -3
View File
@@ -33,10 +33,68 @@
#define GFX_SERVICE_ID "dev.twilitrealm.dusklight.gfx"
#define GFX_SERVICE_MAJOR 1u
#define GFX_SERVICE_MINOR 1u
#define GFX_SERVICE_MINOR 2u
/* Maximum size for push_draw payload */
#define GFX_INLINE_DRAW_PAYLOAD_SIZE 128u
#define GFX_MAX_COLOR_ATTACHMENTS 8u
#define GFX_SCENE_COLOR_ATTACHMENT_INDEX 0u
typedef enum GfxAttachmentSemantic {
GFX_ATTACHMENT_SCENE_COLOR,
GFX_ATTACHMENT_NORMAL,
GFX_ATTACHMENT_AUXILIARY,
} GfxAttachmentSemantic;
typedef struct GfxColorAttachmentLayout {
GfxAttachmentSemantic semantic;
WGPUTextureFormat format;
uint32_t width;
uint32_t height;
} GfxColorAttachmentLayout;
typedef struct GfxRenderTargetLayout {
uint32_t struct_size;
uint64_t key;
/* At least one; scene color is at GFX_SCENE_COLOR_ATTACHMENT_INDEX. */
uint32_t color_attachment_count;
GfxColorAttachmentLayout color_attachments[GFX_MAX_COLOR_ATTACHMENTS];
WGPUTextureFormat depth_stencil_format;
uint32_t sample_count;
} GfxRenderTargetLayout;
#define GFX_RENDER_TARGET_LAYOUT_INIT \
{sizeof(GfxRenderTargetLayout), 0u, 0u, \
{{GFX_ATTACHMENT_AUXILIARY, WGPUTextureFormat_Undefined}}, WGPUTextureFormat_Undefined, \
1u}
/*
* Initializes pipeline color targets for a render-target layout. Only scene color is writable;
* callers that write another semantic should override that target afterward.
*/
static uint32_t gfx_init_color_target_states(const GfxRenderTargetLayout* layout,
WGPUColorTargetState targets[GFX_MAX_COLOR_ATTACHMENTS], const WGPUBlendState* scene_blend,
WGPUColorWriteMask scene_write_mask) {
if (layout == NULL || targets == NULL) {
return 0;
}
const uint32_t count = layout->color_attachment_count < GFX_MAX_COLOR_ATTACHMENTS ?
layout->color_attachment_count :
GFX_MAX_COLOR_ATTACHMENTS;
for (uint32_t i = 0; i < GFX_MAX_COLOR_ATTACHMENTS; ++i) {
WGPUColorTargetState target = WGPU_COLOR_TARGET_STATE_INIT;
if (i < count) {
target.format = layout->color_attachments[i].format;
target.writeMask = WGPUColorWriteMask_None;
}
targets[i] = target;
}
if (count != 0) {
targets[GFX_SCENE_COLOR_ATTACHMENT_INDEX].blend = scene_blend;
targets[GFX_SCENE_COLOR_ATTACHMENT_INDEX].writeMask = scene_write_mask;
}
return count;
}
/* 0 is never a valid handle. */
typedef uint64_t GfxDrawTypeHandle;
@@ -51,8 +109,8 @@ typedef struct GfxRange {
} 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.
* Device and legacy primary scene-pass configuration. Use get_scene_target_layout when creating
* scene pipelines. Offscreen passes from create_pass are always single-sample.
*/
typedef struct GfxDeviceInfo {
uint32_t struct_size;
@@ -83,12 +141,18 @@ typedef struct GfxDrawContext {
WGPUBuffer index_buffer;
WGPUBuffer uniform_buffer;
WGPUBuffer storage_buffer;
/* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].format */
WGPUTextureFormat color_format;
/* deprecated: use layout.depth_stencil_format */
WGPUTextureFormat depth_format;
/* deprecated: use layout.sample_count */
uint32_t sample_count;
/* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].width */
uint32_t target_width;
/* deprecated: use layout.color_attachments[GFX_SCENE_COLOR_ATTACHMENT_INDEX].height */
uint32_t target_height;
bool uses_reversed_z;
GfxRenderTargetLayout layout; /* added in GfxService 1.2 */
} GfxDrawContext;
typedef void (*GfxDrawFn)(ModContext* ctx, const GfxDrawContext* draw_ctx, const void* payload,
@@ -269,6 +333,10 @@ typedef struct GfxService {
*/
ModResult (*push_present)(
ModContext* ctx, GfxPresentTargetHandle handle, const void* payload, size_t payload_size);
/* Minor version 2 */
ModResult (*get_scene_target_layout)(ModContext* ctx, GfxRenderTargetLayout* out_layout);
} GfxService;
MOD_DECLARE_SERVICE(GfxService, svc_gfx, GFX_SERVICE_ID, GFX_SERVICE_MAJOR, GFX_SERVICE_MINOR);
+94
View File
@@ -0,0 +1,94 @@
#pragma once
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#define ITEM_SERVICE_ID "dev.twilitrealm.dusklight.item"
#define ITEM_SERVICE_MAJOR 2u
#define ITEM_SERVICE_MINOR 0u
/* 0 is never a valid handle. */
typedef uint64_t ItemCheckHandle;
typedef uint64_t ItemGiveHandle;
/*
* Item check resolution and inventory grants.
*
* Check names are case-sensitive. Resolvers must be free of side effects because the game may
* resolve a check more than once, including once for display and again when granting the item.
* Registrations and pending grants are removed when the calling mod is detached. Callbacks run
* on the game thread.
*/
/* Host-owned callback data, valid only for the duration of the callback. */
typedef struct ItemCheckInfo {
const char* name;
const void* giver_actor; /* fopAc_ac_c*, or NULL when unavailable */
uint8_t vanilla_item;
uint8_t current_item;
} ItemCheckInfo;
/* Return true and write out_item to replace current_item, or false to leave it unchanged. */
typedef bool (*ItemCheckResolveFn)(
ModContext* ctx, const ItemCheckInfo* info, uint8_t* out_item, void* user_data);
typedef enum ItemGiveOrigin {
ITEM_GIVE_ORIGIN_GAME = 0,
ITEM_GIVE_ORIGIN_QUEUE = 1,
ITEM_GIVE_ORIGIN_QUEUE_SILENT = 2,
} ItemGiveOrigin;
/* Host-owned callback data, valid only for the duration of the callback. */
typedef struct ItemGiveInfo {
const char* check_name; /* NULL when the grant is not attributed to a check */
const void* giver_actor; /* fopAc_ac_c*, or NULL when unavailable */
uint8_t item;
uint8_t origin; /* ItemGiveOrigin */
} ItemGiveInfo;
typedef void (*ItemGiveObserveFn)(ModContext* ctx, const ItemGiveInfo* info, void* user_data);
enum {
/* Apply the inventory change without a get-item demo. */
ITEM_GIVE_SILENT = 1u << 0,
/* Resolve item_no as the named check's vanilla item when the queue entry is dispatched. */
ITEM_GIVE_RESOLVE = 1u << 1,
};
typedef struct ItemService {
ServiceHeader header;
/* Set or replace the calling mod's fixed override for name. */
ModResult (*set_check_override)(ModContext* ctx, const char* name, uint8_t item_no);
ModResult (*clear_check_override)(ModContext* ctx, const char* name);
/* Register for name, or for every check when name is NULL. out_handle may be NULL. */
ModResult (*set_check_resolver)(ModContext* ctx, const char* name, ItemCheckResolveFn fn,
void* user_data, ItemCheckHandle* out_handle);
ModResult (*clear_check_resolver)(ModContext* ctx, ItemCheckHandle handle);
/* Resolve without granting an item or notifying give observers. */
ModResult (*resolve_check)(
ModContext* ctx, const char* name, uint8_t vanilla_item, uint8_t* out_item);
/*
* Add a grant to the global FIFO. check_name may be NULL unless ITEM_GIVE_RESOLVE is set.
* Returns MOD_UNAVAILABLE when the queue is full. Entries wait until gameplay is in a safe
* state and are cleared when the active save slot changes.
*/
ModResult (*give_item)(
ModContext* ctx, const char* check_name, uint8_t item_no, uint32_t flags);
/* Observe inventory grants. out_handle may be NULL. */
ModResult (*observe_gives)(
ModContext* ctx, ItemGiveObserveFn fn, void* user_data, ItemGiveHandle* out_handle);
ModResult (*unobserve_gives)(ModContext* ctx, ItemGiveHandle handle);
} ItemService;
MOD_DECLARE_SERVICE(ItemService, svc_item, ITEM_SERVICE_ID, ITEM_SERVICE_MAJOR, ITEM_SERVICE_MINOR);
+126
View File
@@ -0,0 +1,126 @@
#pragma once
#include <mods/api.h>
#ifdef __cplusplus
#include <mods/service.hpp>
#endif
#define MESSAGE_SERVICE_ID "dev.twilitrealm.dusklight.message"
#define MESSAGE_SERVICE_MAJOR 1u
#define MESSAGE_SERVICE_MINOR 0u
typedef uint16_t MessageId;
typedef uint64_t MessageHandle;
typedef uint64_t MessageOverrideHandle;
/* INF1 entry data */
typedef struct MessageEntryData {
uint8_t bytes[20];
} MessageEntryData;
static_assert(sizeof(MessageEntryData) == 20);
/* INF1 box kind (offset 0x09): message screen class */
typedef enum MessageBoxKind {
MESSAGE_BOX_TALK = 0, /* ordinary dialogue box */
MESSAGE_BOX_DEMO_CAPTION = 1, /* boxless cutscene caption */
MESSAGE_BOX_SIGN = 2, /* signs and posted notices */
MESSAGE_BOX_PLAIN = 5, /* boxless system text */
MESSAGE_BOX_KANBAN = 6, /* signboard screen class; unused */
MESSAGE_BOX_STAFF_ROLL = 7,
MESSAGE_BOX_LIGHT_SPIRIT = 8, /* spirit text window and glow */
MESSAGE_BOX_ITEM_GET = 9, /* centered item-get box */
MESSAGE_BOX_ITEM_NAME = 11, /* UI string fetch, no box */
MESSAGE_BOX_PLACE_NAME = 12, /* area intro banner */
MESSAGE_BOX_MIDNA = 13, /* Midna dialogue colors and glow */
MESSAGE_BOX_ANIMAL = 14, /* wolf-form animal speech glow */
MESSAGE_BOX_NOTICE = 15, /* floating gameplay notice; can't be used during dialogue */
MESSAGE_BOX_SAVE = 16, /* save and memory card prompts */
MESSAGE_BOX_HOWL = 17, /* howling stone UI */
MESSAGE_BOX_BOSS_NAME = 19, /* boss intro banner */
} MessageBoxKind;
/* INF1 draw type (offset 0x0A): text pacing */
typedef enum MessageDrawType {
MESSAGE_DRAW_TYPED = 0, /* types per-character; A skips typing */
MESSAGE_DRAW_INSTANT = 1, /* whole page at once (menus, prompts) */
MESSAGE_DRAW_TYPED_NO_SKIP = 2, /* types per-character; A does not skip */
MESSAGE_DRAW_FADE = 3, /* page fades in */
MESSAGE_DRAW_UI_NAME = 4, /* UI string fetch (item names), no box pacing */
MESSAGE_DRAW_TYPED_SLOW = 5, /* weighted slow typing (light spirit speech) */
MESSAGE_DRAW_UI_ACTION = 7, /* UI string fetch (action button labels) */
MESSAGE_DRAW_FADE_SLOW = 9, /* slow page fade (staff credits) */
} MessageDrawType;
/* INF1 box position (offset 0x0B) */
typedef enum MessageBoxPosition {
MESSAGE_POSITION_BOTTOM = 0,
MESSAGE_POSITION_TOP = 1,
MESSAGE_POSITION_MIDDLE = 2,
MESSAGE_POSITION_AUTO = 3, /* top or bottom, avoiding the speaker on screen */
} MessageBoxPosition;
/* Applies to both the text and gradient colors. */
typedef enum MessageTextColor {
MESSAGE_COLOR_DEFAULT = 0, /* box default */
MESSAGE_COLOR_RED = 1, /* 0xF07878 */
MESSAGE_COLOR_GREEN = 2, /* 0xAADC8C */
MESSAGE_COLOR_BLUE = 3, /* 0xA0B4DC */
MESSAGE_COLOR_YELLOW = 4, /* 0xDCDC82 */
MESSAGE_COLOR_SKY = 5, /* 0xB4C8E6 */
MESSAGE_COLOR_PURPLE = 6, /* 0xC8A0DC */
MESSAGE_COLOR_WHITE = 7, /* 0xFFFFFF */
MESSAGE_COLOR_ORANGE = 8, /* 0xDCAA78 */
} MessageTextColor;
/* Message text language. 5 (Dutch) is unused. */
typedef enum MessageLanguage {
MESSAGE_LANGUAGE_ENGLISH = 0,
MESSAGE_LANGUAGE_GERMAN = 1,
MESSAGE_LANGUAGE_FRENCH = 2,
MESSAGE_LANGUAGE_SPANISH = 3,
MESSAGE_LANGUAGE_ITALIAN = 4,
MESSAGE_LANGUAGE_JAPANESE = 6,
} MessageLanguage;
typedef struct MessageVariantData {
uint8_t language; /* MessageLanguage */
MessageEntryData entry;
const uint8_t* text;
size_t text_size;
} MessageVariantData;
typedef struct MessageTextData {
const uint8_t* text;
size_t text_size;
} MessageTextData;
typedef struct MessageOverrideContext {
uint16_t group;
uint16_t message_id;
uint8_t language; /* MessageLanguage */
const uint8_t* original_text;
size_t original_text_size;
} MessageOverrideContext;
/* Return true with a valid out_text to override, or false to try the next registration. */
typedef bool (*MessageOverrideFn)(ModContext* ctx, const MessageOverrideContext* message,
MessageTextData* out_text, void* user_data);
typedef struct MessageService {
ServiceHeader header;
ModResult (*override_message)(ModContext* ctx, uint16_t group, uint16_t message_id,
uint8_t language, const uint8_t* text, size_t text_size, MessageOverrideHandle* out_handle);
ModResult (*override_message_fn)(ModContext* ctx, uint16_t group, uint16_t message_id,
uint8_t language, MessageOverrideFn fn, void* user_data, MessageOverrideHandle* out_handle);
ModResult (*remove_override)(ModContext* ctx, MessageOverrideHandle handle);
ModResult (*register_message)(ModContext* ctx, uint16_t group,
const MessageVariantData* variants, size_t variant_count, MessageId* out_id,
MessageHandle* out_handle);
ModResult (*remove_message)(ModContext* ctx, MessageHandle handle);
} MessageService;
MOD_DECLARE_SERVICE(
MessageService, svc_message, MESSAGE_SERVICE_ID, MESSAGE_SERVICE_MAJOR, MESSAGE_SERVICE_MINOR);
+5 -1
View File
@@ -9,7 +9,7 @@
#define UI_SERVICE_ID "dev.twilitrealm.dusklight.ui"
#define UI_SERVICE_MAJOR 1u
#define UI_SERVICE_MINOR 1u
#define UI_SERVICE_MINOR 2u
/*
* UI primitives: a panel inside the host Mods window, mod-owned windows, dialogs, toasts,
@@ -291,6 +291,10 @@ typedef struct UiService {
/* Enqueue a toast notification. */
ModResult (*push_toast)(ModContext* ctx, const UiToastDesc* desc);
/* Minor version 2 */
ModResult (*get_clipboard_text)(ModContext* ctx, char* buffer, size_t bufferSize, size_t* outLength);
ModResult (*set_clipboard_text)(ModContext* ctx, const char* text);
} UiService;
MOD_DECLARE_SERVICE(UiService, svc_ui, UI_SERVICE_ID, UI_SERVICE_MAJOR, UI_SERVICE_MINOR);
+47
View File
@@ -0,0 +1,47 @@
#pragma once
#include <mods/svc/ui.h>
#include <string>
#include <utility>
namespace mods::ui {
inline ModResult get_clipboard_text(std::string& outText) {
outText.clear();
size_t textLength = 0;
auto result = svc_ui->get_clipboard_text(mod_ctx, nullptr, 0, &textLength);
if (result != MOD_OK) {
return result;
}
for (int attempt = 0; attempt < 3; ++attempt) {
std::string buffer(textLength + 1, '\0');
size_t actualLength = 0;
result = svc_ui->get_clipboard_text(
mod_ctx, buffer.data(), buffer.size(), &actualLength);
if (result == MOD_OK) {
buffer.resize(actualLength);
outText = std::move(buffer);
return MOD_OK;
}
// Retry if the clipboard grew between the two calls.
if (result != MOD_INVALID_ARGUMENT || actualLength <= textLength) {
return result;
}
textLength = actualLength;
}
return MOD_ERROR;
}
inline ModResult set_clipboard_text(const std::string& text) {
return svc_ui->set_clipboard_text(mod_ctx, text.c_str());
}
} // namespace mods::ui
+4 -1
View File
@@ -11,7 +11,10 @@
#include "Z2AudioLib/SpotName.h"
#include "os_report.h"
#if TARGET_PC
#include "dusk/audio.h"
#include "dusk/version.hpp"
#endif
Z2SeqMgr::Z2SeqMgr() : JASGlobalInstance<Z2SeqMgr>(true) {
mMainBgmMaster.forceIn();
@@ -584,7 +587,7 @@ void Z2SeqMgr::bgmStreamPlay() {
}
#if !PLATFORM_SHIELD
else if (getStreamBgmID() == 0x2000000) {
else if (getStreamBgmID() == 0x2000000 IF_DUSK(&& dusk::version::isGcn())) {
if (mStreamBgmHandle) {
mStreamBgmHandle->stop();
}
+1 -1
View File
@@ -551,7 +551,7 @@ void daAlink_c::setBowSight() {
getArrowFlyData(&dist, &speed, TRUE);
checkSightLine(dist, &sight_pos);
mSight.setPos(&sight_pos);
mSight.offDrawFlg();
DUSK_IF_ELSE(dusk::getSettings().game.aimingReticle ? mSight.onDrawFlg() : mSight.offDrawFlg(), mSight.offDrawFlg());
} else {
mSight.offDrawFlg();
}
+13 -4
View File
@@ -23,10 +23,13 @@
#include "d/actor/d_a_npc_tkc.h"
#include <cstring>
#ifdef TARGET_PC
#include "dusk/game_mode.hpp"
#include "dusk/imgui/ImGuiConsole.hpp"
#include "dusk/settings.h"
#include "dusk/speedrun.h"
#include "dusk/version.hpp"
#endif
BOOL daAlink_c::checkEventRun() const {
return dComIfGp_event_runCheck() || checkPlayerDemoMode();
@@ -2260,6 +2263,12 @@ int daAlink_c::procCoGetItemInit() {
s16 var_r22 = 0;
BOOL var_r31 = FALSE;
BOOL var_r30 = FALSE;
#if TARGET_PC
// Queued grants have no event partner, so create the demo item from the event's GtItm.
if (dusk::mods::item_give_queue_dispatching()) {
mDemo.setParam0(0x100);
}
#endif
if (mProcID == PROC_GET_ITEM || mProcID == PROC_INSECT_CATCH ||
(mProcID == PROC_PREACTION_UNEQUIP && !checkNoUpperAnime()))
{
@@ -2299,7 +2308,7 @@ int daAlink_c::procCoGetItemInit() {
}
fpc_ProcID item_partner_id = fopAcM_createItemForPresentDemo(&current.pos, item_no, 0, -1,
fopAcM_GetRoomNo(this), NULL, NULL);
fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(dusk::mods::item_give_queue_take_tag()));
if (item_partner_id != fpcM_ERROR_PROCESS_ID_e) {
dComIfGp_event_setItemPartnerId(item_partner_id);
}
@@ -4010,9 +4019,9 @@ int daAlink_c::procGanonFinishInit() {
onEndResetFlg1(ERFLG1_SHIELD_BACKBONE);
#if TARGET_PC
if (dusk::getSettings().game.speedrunMode) {
if (dusk::m_speedrunInfo.m_isRunStarted) {
dusk::m_speedrunInfo.stopRun();
if (dusk::speedrun::isActive()) {
if (dusk::speedrun::g_speedrunInfo.m_isRunStarted) {
dusk::speedrun::g_speedrunInfo.stopRun();
}
}
#endif
+4
View File
@@ -4084,6 +4084,10 @@ void daB_DS_c::executeBattle2Dead() {
camera->mCamera.SetTrimSize(0);
dComIfGp_event_reset();
dComIfGs_onStageBossEnemy(0x13);
#if TARGET_PC
// This reward has no original grant at this point in the cutscene.
dusk::mods::item_check_enqueue("Arbiters Grounds Dungeon Reward", dItemNo_NONE_e);
#endif
/* dSv_event_flag_c::F_0265 - Arbiter's Grounds - Arbiter's Grounds clear */
dComIfGs_onEventBit(0x2010);
fopAcM_delete(this);
+1 -1
View File
@@ -170,7 +170,7 @@ void daDitem_c::actionEvent() {
if (chkDead()) {
if (!chkArgFlag(0x1)) {
execItemGet(m_itemNo);
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
}
if (m_itemNo == dItemNo_KAKERA_HEART_e) {
+12 -5
View File
@@ -726,11 +726,18 @@ void daE_HP_c::executeDead() {
fopAcM_onSwitch(this, bitSw);
}
dComIfGs_addPohSpiritNum();
#if TARGET_PC
mItemCheckOverridden =
dusk::mods::item_check_poe(bitSw, dItemNo_POU_SPIRIT_e, this) != dItemNo_POU_SPIRIT_e;
if (mItemCheckOverridden) {
dusk::mods::item_check_enqueue_poe(bitSw, dItemNo_POU_SPIRIT_e);
} else
#endif
dComIfGs_addPohSpiritNum();
field_0x784 = -1;
if (dComIfGs_getPohSpiritNum() == 20) {
if (dComIfGs_getPohSpiritNum() == 20 IF_DUSK(&&!mItemCheckOverridden)) {
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[0x1c9]);
}
@@ -752,13 +759,13 @@ void daE_HP_c::executeDead() {
field_0x788 = 1;
}
}
} else if (field_0x788 != 0) {
} else if (field_0x788 != 0 IF_DUSK(|| mItemCheckOverridden)) {
fopAcM_createDisappear(this, &current.pos, 8, 3, 0xff);
fopAcM_delete(this);
} else {
if (field_0x784 == -1) {
field_0x784 = fopAcM_createItemForPresentDemo(&current.pos, dItemNo_POU_SPIRIT_e, 0, -1,
-1, 0, 0);
field_0x784 = fopAcM_createItemForPresentDemo(&current.pos, dItemNo_POU_SPIRIT_e, 0,
-1, -1, 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag_poe(bitSw)));
}
if (fopAcM_IsExecuting(field_0x784) != FALSE) {
+16 -1
View File
@@ -1132,12 +1132,20 @@ static void e_po_dead(e_po_class* i_this) {
camera_player->mCamera.Start();
camera_player->mCamera.SetTrimSize(0);
dComIfGp_event_reset();
dComIfGs_addPohSpiritNum();
#if TARGET_PC
if (dusk::mods::item_check_poe(i_this->BitSW, dItemNo_POU_SPIRIT_e, a_this) ==
dItemNo_POU_SPIRIT_e)
{
#endif
dComIfGs_addPohSpiritNum();
#if !PLATFORM_SHIELD
if (dComIfGs_getPohSpiritNum() == 0x14) {
/* dSv_event_flag_c::F_0457 - Castle Town - Revived cat */
dComIfGs_onEventBit(dSv_event_flag_c::saveBitLabels[457]);
}
#endif
#if TARGET_PC
}
#endif
daPy_getPlayerActorClass()->cancelOriginalDemo();
} else if (mArg0Check(i_this, 0) != 0) {
@@ -1265,8 +1273,15 @@ static void e_po_dead(e_po_class* i_this) {
}
} else {
if (i_this->field_0x75C == -1) {
#if TARGET_PC
const u8 itemNo =
dusk::mods::item_check_poe(i_this->BitSW, dItemNo_POU_SPIRIT_e, a_this);
i_this->field_0x75C = fopAcM_createItemForPresentDemo(&a_this->current.pos, itemNo,
0, -1, -1, NULL, NULL, dusk::mods::item_give_tag_poe(i_this->BitSW));
#else
i_this->field_0x75C = fopAcM_createItemForPresentDemo(&a_this->current.pos, 0xE0, 0,
-1, -1, NULL, NULL);
#endif
}
if (fopAcM_IsExecuting(i_this->field_0x75C)) {
i_this->field_0x762 =
+3 -1
View File
@@ -1250,7 +1250,9 @@ static void demo_camera(e_rdb_class* i_this) {
}
if (iVar1 != 0) {
daPy_getPlayerActorClass()->changeDemoMode(11, 32, 0, 0);
daPy_getPlayerActorClass()->changeDemoMode(11,
DUSK_ITEM_CHECK_EXPR("bulblin_key:D_MN09", dItemNo_SMALL_KEY_e, &i_this->enemy), 0,
0);
i_this->mDemoMode = 12;
i_this->field_0x10aa = 0;
i_this->field_0xfe5 = 1;
+3 -1
View File
@@ -931,7 +931,9 @@ static void get_demo(e_th_ball_class* i_this) {
case 0:
break;
case 1:
demo_id = fopAcM_createItemForTrBoxDemo(&i_this->current.pos, dItemNo_IRONBALL_e, -1, fopAcM_GetRoomNo(i_this), NULL, NULL);
demo_id = fopAcM_createItemForTrBoxDemo(&i_this->current.pos,
DUSK_ITEM_CHECK_EXPR("ball_and_chain:D_MN11", dItemNo_IRONBALL_e, i_this), -1,
fopAcM_GetRoomNo(i_this), NULL, NULL DUSK_GIVE_TAG("ball_and_chain:D_MN11"));
JUT_ASSERT(1670, demo_id != fpcM_ERROR_PROCESS_ID_e);
i_this->mDemoMode = 2;
break;
+74 -18
View File
@@ -15,12 +15,50 @@
#include "dusk/dvd_asset.hpp"
#include "dusk/frame_interpolation.h"
using GameVersion = dusk::version::GameVersion;
#include <type_traits>
using namespace dusk::version;
#define MANT_REL_PATH platformSelect<const char*>("/rel/Final/Release/d_a_mant.rel", "/rel/Rfinal/Release/d_a_mant.rel")
#define DEFINE_MANT_ASSET(name, type, count, ...) \
static type* name##_get() { \
alignas(32) static std::remove_cv_t<type> buf[count]; \
static bool _ = (dusk::LoadRelAsset(buf, MANT_REL_PATH, __VA_ARGS__), true); \
return buf; \
}
// keep the original version of the cape texture const so we don't need to reload the file
static u8 const * l_Egnd_mantTEX_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x1C00}, {GameVersion::GcnPal, 0x1C00}, {GameVersion::GcnJpn, 0x1C00}}, 0x4000), true); return buf; }
static u8* l_Egnd_mantTEX_U_get() { alignas(32) static u8 buf[0x4000]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x5C00}, {GameVersion::GcnPal, 0x5C00}, {GameVersion::GcnJpn, 0x5C00}}, 0x4000), true); return buf; }
static u8* l_Egnd_mantPAL_get() { alignas(32) static u8 buf[0x60]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x9C00}, {GameVersion::GcnPal, 0x9C00}, {GameVersion::GcnJpn, 0x9C00}}, 0x60), true); return buf; }
DEFINE_MANT_ASSET(l_Egnd_mantTEX, u8 const, 0x4000, {
{GameVersion::GcnUsa, 0x1C00},
{GameVersion::GcnPal, 0x1C00},
{GameVersion::GcnJpn, 0x1C00},
{GameVersion::WiiUsaRev0, 0x1B00},
{GameVersion::WiiUsa, 0x1900},
{GameVersion::WiiPal, 0x1900},
{GameVersion::WiiJpn, 0x1900},
});
DEFINE_MANT_ASSET(l_Egnd_mantTEX_U, u8, 0x4000, {
{GameVersion::GcnUsa, 0x5C00},
{GameVersion::GcnPal, 0x5C00},
{GameVersion::GcnJpn, 0x5C00},
{GameVersion::WiiUsaRev0, 0x5B00},
{GameVersion::WiiUsa, 0x5900},
{GameVersion::WiiPal, 0x5900},
{GameVersion::WiiJpn, 0x5900},
});
DEFINE_MANT_ASSET(l_Egnd_mantPAL, u8, 0x60, {
{GameVersion::GcnUsa, 0x9C00},
{GameVersion::GcnPal, 0x9C00},
{GameVersion::GcnJpn, 0x9C00},
{GameVersion::WiiUsaRev0, 0x9B00},
{GameVersion::WiiUsa, 0x9900},
{GameVersion::WiiPal, 0x9900},
{GameVersion::WiiJpn, 0x9900},
});
#define l_Egnd_mantTEX (l_Egnd_mantTEX_get())
#define l_Egnd_mantTEX_U (l_Egnd_mantTEX_U_get())
#define l_Egnd_mantPAL (l_Egnd_mantPAL_get())
@@ -35,14 +73,39 @@ static TGXTexObj mainTexObj;
static TGXTexObj undersideTexObj;
// l_pos is unused
//static f32* l_pos_get() { alignas(32) static f32 buf[507]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0xA44C}, {GameVersion::GcnPal, 0xA44C}}, sizeof(buf)), true); return buf; }
static f32* l_normal_get() { alignas(32) static f32 buf[3]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0x9C60}, {GameVersion::GcnPal, 0x9C60}, {GameVersion::GcnJpn, 0x9C60}}, sizeof(buf)), true); return buf; }
static f32* l_texCoord_get() { alignas(32) static f32 buf[338]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0xA458}, {GameVersion::GcnPal, 0xA458}, {GameVersion::GcnJpn, 0xA458}}, sizeof(buf)), true); return buf; }
//#define l_pos (l_pos_get())
#define l_normal (l_normal_get())
// DEFINE_MANT_ASSET(l_pos, f32, 507, {
// {GameVersion::GcnUsa, 0x9C60},
// {GameVersion::GcnPal, 0x9C60},
// });
alignas(32) static f32 const l_normal[3] = {0.0f, 1.0f, 0.0f};
DEFINE_MANT_ASSET(l_texCoord, f32, 338, {
{GameVersion::GcnUsa, 0xA458},
{GameVersion::GcnPal, 0xA458},
{GameVersion::GcnJpn, 0xA458},
{GameVersion::WiiUsaRev0, 0x9B60},
{GameVersion::WiiUsa, 0x9960},
{GameVersion::WiiPal, 0x9960},
{GameVersion::WiiJpn, 0x9960},
});
// #define l_pos (l_pos_get())
#define l_texCoord (l_texCoord_get())
static bool l_Egnd_mantTEX_hasReplacement = false;
DEFINE_MANT_ASSET(l_Egnd_mantDL, u8, 0x3EC, {
{GameVersion::GcnUsa, 0xA9A0},
{GameVersion::GcnPal, 0xA9A0},
{GameVersion::GcnJpn, 0xA9A0},
{GameVersion::WiiUsaRev0, 0xA0C0},
{GameVersion::WiiUsa, 0x9EC0},
{GameVersion::WiiPal, 0x9EC0},
{GameVersion::WiiJpn, 0x9EC0},
});
#define l_Egnd_mantDL (l_Egnd_mantDL_get())
#else
#include "assets/l_Egnd_mantTEX.h"
@@ -52,16 +115,9 @@ static bool l_Egnd_mantTEX_hasReplacement = false;
#endif
#include "d/d_s_play.h"
#if TARGET_PC
using GameVersion = dusk::version::GameVersion;
static u8* l_Egnd_mantDL_get() { alignas(32) static u8 buf[0x3EC]; static bool _ = (dusk::LoadRelAsset(buf, "/rel/Final/Release/d_a_mant.rel", {{GameVersion::GcnUsa, 0xA9A0}, {GameVersion::GcnPal, 0xA9A0}, {GameVersion::GcnJpn, 0xA9A0}}, 0x3EC), true); return buf; }
#define l_Egnd_mantDL (l_Egnd_mantDL_get())
#else
#include "assets/l_Egnd_mantDL.h"
#endif
#if !TARGET_PC
#include "assets/l_Egnd_mantDL.h"
static void* pal_d = (void*)&l_Egnd_mantPAL;
static void* tex_d[2] = {
+2 -2
View File
@@ -5874,8 +5874,8 @@ static int dmg_rod_Execute(dmg_rod_class* i_this) {
#if TARGET_PC
if (dusk::getSettings().game.buttonFishing) {
if ((item_any_fishing_rod(dComIfGp_getSelectItem(0)) && mDoCPd_c::getHoldX(PAD_1)) ||
(item_any_fishing_rod(dComIfGp_getSelectItem(1)) && mDoCPd_c::getHoldY(PAD_1)))
{
(item_any_fishing_rod(dComIfGp_getSelectItem(1)) && mDoCPd_c::getHoldY(PAD_1)) ||
(i_this->action == ACTION_LURE_STANDBY && mDoCPd_c::getTrigB(PAD_1))) {
i_this->rod_stick_y = -1.0f;
i_this->rod_substick_y = -1.0f;
}
+3 -1
View File
@@ -1744,7 +1744,9 @@ int daNpc_Aru_c::cutSpeakTo(int i_staffID) {
switch (eventId) {
case 1:
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1, -1, NULL, NULL);
DUSK_ITEM_CHECK("goats_reward", itemNo, this);
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, itemNo,
0, -1, -1, NULL, NULL DUSK_GIVE_TAG("goats_reward"));
}
if (fopAcM_IsExecuting(mItemPartnerId)) {
+3 -2
View File
@@ -1001,8 +1001,9 @@ BOOL daNpcAshB_c::EvCut_Appear(int i_staffID) {
case '0008':
local_30[0] = 0;
if (mFlow.getEventId(local_30) == 1) {
mItemPartnerId =
fopAcM_createItemForPresentDemo(&current.pos, local_30[0], 0, -1, -1, 0, 0);
DUSK_ITEM_CHECK("ashei_sketch", local_30[0], this);
mItemPartnerId = fopAcM_createItemForPresentDemo(
&current.pos, local_30[0], 0, -1, -1, 0, 0 DUSK_GIVE_TAG("ashei_sketch"));
dComIfGp_event_setItemPartnerId(mItemPartnerId);
mItemPartnerId = -1;
}
+12 -1
View File
@@ -1804,8 +1804,19 @@ int daNpcChin_c::_Evt_GameSucceed_CutMain(const int& param_0) {
itemId1 = 0;
}
#if TARGET_PC
const char* itemCheckName = nullptr;
if (itemId1 == dItemNo_ARROW_LV2_e) {
itemCheckName = "star_reward_1";
} else if (itemId1 == dItemNo_ARROW_LV3_e) {
itemCheckName = "star_reward_2";
}
if (itemCheckName != nullptr) {
itemId1 = dusk::mods::item_check(itemCheckName, itemId1, this);
}
#endif
fpc_ProcID itemId2 = fopAcM_createItemForPresentDemo(&current.pos, itemId1, 0, -1, -1,
0, 0);
0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
if (itemId2 != -1) {
dComIfGp_event_setItemPartnerId(itemId2);
}
+3 -1
View File
@@ -1333,7 +1333,9 @@ void daNpc_Fairy_c::PresentDemoCall() {
item_no = 0;
}
fpc_ProcID id = fopAcM_createItemForPresentDemo(&current.pos, item_no, 0, -1, -1, NULL, NULL);
DUSK_ITEM_CHECK("fairy_reward:D_SB01", item_no, this);
fpc_ProcID id = fopAcM_createItemForPresentDemo(&current.pos, item_no, 0, -1, -1, NULL,
NULL DUSK_GIVE_TAG("fairy_reward:D_SB01"));
if (id != fpcM_ERROR_PROCESS_ID_e) {
dComIfGp_event_setItemPartnerId(id);
}
+9 -2
View File
@@ -4018,8 +4018,15 @@ BOOL daNpc_grA_c::talk(void*) {
}
if (r26 && talkProc(NULL, TRUE, NULL)) {
if (mFlow.getEventId(&sp8) == 1) {
field_0x1480 =
fopAcM_createItemForPresentDemo(&current.pos, sp8, 0, -1, -1, NULL, NULL);
#if TARGET_PC
const char* itemCheckName = nullptr;
if (sp8 == dItemNo_BOMB_IN_BAG_e) {
itemCheckName = "goron_reward:F_SP113";
sp8 = dusk::mods::item_check(itemCheckName, sp8, this);
}
#endif
field_0x1480 = fopAcM_createItemForPresentDemo(&current.pos, sp8, 0, -1, -1, NULL,
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
if (field_0x1480 != fpcM_ERROR_PROCESS_ID_e) {
s16 r25 = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xff);
dComIfGp_getEvent()->reset(this);
+3 -1
View File
@@ -1681,7 +1681,9 @@ int daNpc_grO_c::talk(void* param_1) {
if (facePlayerFlag && talkProc(NULL, TRUE, NULL)) {
if (mType == TYPE_MINES) {
if (mFlow.getEventId(&itemId) == 1) {
mItemID = fopAcM_createItemForPresentDemo(&current.pos, itemId, 0, -1, -1, NULL, NULL);
DUSK_ITEM_CHECK("key_shard_2:D_MN04", itemId, this);
mItemID = fopAcM_createItemForPresentDemo(&current.pos, itemId, 0, -1, -1,
NULL, NULL DUSK_GIVE_TAG("key_shard_2:D_MN04"));
if (mItemID != fpcM_ERROR_PROCESS_ID_e) {
s16 eventIdx = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xFF);
dComIfGp_getEvent()->reset(this);
+3 -1
View File
@@ -1338,7 +1338,9 @@ int daNpc_grR_c::talk(void* param_1) {
if (bVar1 && talkProc(NULL, TRUE, NULL)) {
if (mType == TYPE_0) {
if (mFlow.getEventId(&i_itemNo) == 1) {
mItemID = fopAcM_createItemForPresentDemo(&current.pos, i_itemNo, 0, -1, -1, NULL, NULL);
DUSK_ITEM_CHECK("key_shard_3:D_MN04", i_itemNo, this);
mItemID = fopAcM_createItemForPresentDemo(&current.pos, i_itemNo, 0, -1, -1,
NULL, NULL DUSK_GIVE_TAG("key_shard_3:D_MN04"));
if (mItemID != fpcM_ERROR_PROCESS_ID_e) {
s16 i_eventID = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xFF);
+3 -2
View File
@@ -1190,8 +1190,9 @@ int daNpc_grS_c::talk(void* param_0) {
if (unkFlag1 && talkProc(NULL, 1, NULL)) {
if (mType == 0) {
if (mFlow.getEventId(&unkInt2) == 1) {
mPresentItemId =
fopAcM_createItemForPresentDemo(&current.pos, unkInt2, 0, -1, -1, 0, 0);
DUSK_ITEM_CHECK("key_shard_1:D_MN04", unkInt2, this);
mPresentItemId = fopAcM_createItemForPresentDemo(&current.pos, unkInt2, 0, -1,
-1, 0, 0 DUSK_GIVE_TAG("key_shard_1:D_MN04"));
if (mPresentItemId != fpcM_ERROR_PROCESS_ID_e) {
s16 eventIdx =
+6 -4
View File
@@ -971,8 +971,9 @@ BOOL daNpcImpal_c::EvCut_ImpalAppear1(int i_cut_index) {
if (talkProc(NULL, 1, NULL)) {
int evt_id = 0;
if (mFlow.getEventId(&evt_id) == 1) {
mItemPartnerId =
fopAcM_createItemForPresentDemo(&current.pos, evt_id, 0, -1, -1, 0, 0);
DUSK_ITEM_CHECK("ilia_charm", evt_id, this);
mItemPartnerId = fopAcM_createItemForPresentDemo(
&current.pos, evt_id, 0, -1, -1, 0, 0 DUSK_GIVE_TAG("ilia_charm"));
if (mItemPartnerId != 0xffffffff) {
s16 evt_idx =
dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xff);
@@ -1060,8 +1061,9 @@ BOOL daNpcImpal_c::EvCut_CopyRod(int i_cut_index) {
case '0003':
int evt_id = 0;
if (mFlow.getEventId(&evt_id) == 1) {
mItemPartnerId =
fopAcM_createItemForPresentDemo(&current.pos, evt_id, 0, -1, -1, 0, 0);
DUSK_ITEM_CHECK("skybook", evt_id, this);
mItemPartnerId = fopAcM_createItemForPresentDemo(
&current.pos, evt_id, 0, -1, -1, 0, 0 DUSK_GIVE_TAG("skybook"));
dComIfGp_event_setItemPartnerId(mItemPartnerId);
mItemPartnerId = -1;
}
+16 -1
View File
@@ -11,6 +11,10 @@
#include "d/d_msg_object.h"
#include <cstring>
#if TARGET_PC
static u8 s_givenInsectId = dItemNo_NONE_e;
#endif
enum Ins_RES_File_ID {
/* BCK */
/* 0x06 */ BCK_INS_F_HAPPY = 0x6,
@@ -1259,6 +1263,7 @@ int daNpcIns_c::waitPresent(void* param_1) {
daPy_py_c* player = daPy_getPlayerActorClass();
player->changeOriginalDemo();
player->changeDemoMode(0x25, 2, type, 0);
IF_DUSK(s_givenInsectId = type;)
} else {
mInsectMsgNo = 0x719;
}
@@ -1487,7 +1492,17 @@ int daNpcIns_c::talk(void* param_1) {
OS_REPORT("会話終了時 イベントID=%d アイテムNo=%d\n", eventID, itemNo);
if (eventID == 1) {
mItemID = fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1, -1, NULL, NULL);
#if TARGET_PC
u32 itemGiveTag = 0;
if (s_givenInsectId != dItemNo_NONE_e) {
itemNo = dusk::mods::item_check_bug(
s_givenInsectId, itemNo & 0xFF, this);
itemGiveTag = dusk::mods::item_give_tag_bug(s_givenInsectId);
s_givenInsectId = dItemNo_NONE_e;
}
#endif
mItemID = fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1,
-1, NULL, NULL IF_DUSK_ARG(itemGiveTag));
if (mItemID != fpcM_ERROR_PROCESS_ID_e) {
daPy_getPlayerActorClass()->cancelOriginalDemo();
+10 -1
View File
@@ -1181,7 +1181,16 @@ int daNpc_Kkri_c::talk(void*) {
switch (eventId) {
case 1:
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, item_no, 0, -1, -1, NULL, NULL);
#if TARGET_PC
const char* itemCheckName = nullptr;
if (item_no == dItemNo_OIL_BOTTLE3_e) {
itemCheckName = "coro_bottle";
item_no = dusk::mods::item_check(itemCheckName, item_no, this);
}
#endif
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, item_no,
0, -1, -1, NULL,
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
}
if (fopAcM_IsExecuting(mItemPartnerId)) {
+2 -1
View File
@@ -1236,8 +1236,9 @@ int daNpc_Len_c::talk(void* param_0) {
switch (evt_id) {
case 1:
if (mItemPartnerId == -1) {
DUSK_ITEM_CHECK("renado_letter", local_18, this);
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, local_18,
0, -1, -1, 0, 0);
0, -1, -1, 0, 0 DUSK_GIVE_TAG("renado_letter"));
}
if (fopAcM_IsExecuting(mItemPartnerId)) {
mEvtNo = 1;
+4 -1
View File
@@ -2393,7 +2393,10 @@ int daNpc_Maro_c::cutArrowTutorial(int arg0) {
switch (evt_ret) {
case 1: {
if (mItemPartnerId == -1) {
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, evt_id, 0, -1, -1, NULL, NULL);
DUSK_ITEM_CHECK("archery_reward:F_SP109", evt_id, this);
mItemPartnerId =
fopAcM_createItemForPresentDemo(&current.pos, evt_id, 0, -1, -1,
NULL, NULL DUSK_GIVE_TAG("archery_reward:F_SP109"));
}
if (fopAcM_IsExecuting(mItemPartnerId)) {
+9 -1
View File
@@ -1191,7 +1191,15 @@ int daNpc_myna2_c::ECut_gameGoalSuccess(int i_staffId) {
case 20: {
int itemNo = 0;
if (mFlow.getEventId(&itemNo) == 1) {
mItemPid = fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1, -1, NULL, NULL);
#if TARGET_PC
const char* itemCheckName = nullptr;
if (itemNo == dItemNo_KAKERA_HEART_e) {
itemCheckName = "plumm_minigame_reward";
itemNo = dusk::mods::item_check(itemCheckName, itemNo, this);
}
#endif
mItemPid = fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1, -1, NULL,
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
}
break;
}
+12 -1
View File
@@ -958,8 +958,19 @@ int daNpc_Pouya_c::cutHaveFavorToAsk(int param_0) {
switch (evt_id) {
case 1:
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
#if TARGET_PC
const char* itemCheckName = nullptr;
if (local_64 == dItemNo_DROP_BOTTLE_e) {
itemCheckName = "jovani_reward_1";
} else if (local_64 == dItemNo_SILVER_RUPEE_e) {
itemCheckName = "jovani_reward_2";
}
if (itemCheckName != nullptr) {
local_64 = dusk::mods::item_check(itemCheckName, local_64, this);
}
#endif
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, local_64, 0,
-1, -1, 0, 0);
-1, -1, 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
}
if (fopAcM_IsExecuting(mItemPartnerId)) {
field_0xfce = 1;
+2 -1
View File
@@ -720,7 +720,8 @@ fpc_ProcID daNpcPray_c::createHeart() {
mDoMtx_stack_c::ZXYrotS(rot);
mDoMtx_stack_c::multVec(&offset, &offset);
pos += offset;
return fopAcM_createItemForBoss(&pos, dItemNo_KAKERA_HEART_e, fopAcM_GetRoomNo(this), &rot, &size, 0.0f, 0.0f, 0);
return fopAcM_createItemForBoss(&pos, dItemNo_KAKERA_HEART_e, fopAcM_GetRoomNo(this), &rot,
&size, 0.0f, 0.0f, 0 IF_DUSK_ARG("prayer_reward"));
}
BOOL daNpcPray_c::_Evt_GetHeart(int i_staffID) {
+6 -2
View File
@@ -1310,7 +1310,9 @@ bool daNpcRafrel_c::talk(void* param_0) {
OS_REPORT("会話終了時 イベントID=%d アイテムNo=%d\n", eventId, itemNo);
if (eventId == 1) {
field_0xe00 = fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1, -1, NULL, NULL);
DUSK_ITEM_CHECK("auru_memo", itemNo, this);
field_0xe00 = fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1, -1,
NULL, NULL DUSK_GIVE_TAG("auru_memo"));
if (field_0xe00 != fpcM_ERROR_PROCESS_ID_e) {
s16 eventIdx = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xFF);
dComIfGp_getEvent()->reset(this);
@@ -1562,7 +1564,9 @@ int daNpcRafrel_c::EvCut_Appear(int i_staffId) {
int itemNo = 0;
u16 eventId = mFlow.getEventId(&itemNo);
if (eventId == 1) {
field_0xe00 = fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1, -1, NULL, NULL);
DUSK_ITEM_CHECK("auru_memo", itemNo, this);
field_0xe00 = fopAcM_createItemForPresentDemo(
&current.pos, itemNo, 0, -1, -1, NULL, NULL DUSK_GIVE_TAG("auru_memo"));
dComIfGp_event_setItemPartnerId(field_0xe00);
field_0xe00 = fpcM_ERROR_PROCESS_ID_e;
}
+2 -1
View File
@@ -823,8 +823,9 @@ BOOL daNpcThe_c::talk(void* param_0) {
}
int item_no = 0;
if (mFlow.getEventId(&item_no) == 1) {
DUSK_ITEM_CHECK("telma_invoice", item_no, this);
mItemID = fopAcM_createItemForPresentDemo(&current.pos, item_no, 0, -1, -1,
NULL, NULL);
NULL, NULL DUSK_GIVE_TAG("telma_invoice"));
if (mItemID != -1) {
s16 event_id = dComIfGp_getEventManager().getEventIdx(
this, "DEFAULT_GETITEM", 0xff);
+3 -2
View File
@@ -1165,8 +1165,9 @@ int daNpc_Uri_c::cutEndCarryTutorial(int param_1) {
(s32)mFlow.getEventId(&local_48) == 1)
{
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
mItemPartnerId =
fopAcM_createItemForPresentDemo(&current.pos, local_48, 0, -1, -1, NULL, NULL);
DUSK_ITEM_CHECK("uli_cradle_reward", local_48, this);
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, local_48, 0, -1, -1,
NULL, NULL DUSK_GIVE_TAG("uli_cradle_reward"));
}
if (fopAcM_IsExecuting(mItemPartnerId)) {
/* T_0007 - Ordon Village - During Uli's pick-up tutorial */
+6 -4
View File
@@ -2213,8 +2213,9 @@ int daNpc_ykW_c::cutEndSnowboardRace(int param_0) {
switch (eventId) {
case 1:
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, itemId, 0,
-1, -1, 0, 0);
DUSK_ITEM_CHECK("snowboard_race_reward", itemId, this);
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, itemId, 0, -1,
-1, 0, 0 DUSK_GIVE_TAG("snowboard_race_reward"));
}
if (fopAcM_IsExecuting(mItemPartnerId)) {
@@ -2911,8 +2912,9 @@ int daNpc_ykW_c::talk(void* param_0) {
switch (eventId) {
case 1:
if (mItemPartnerId == fpcM_ERROR_PROCESS_ID_e) {
mItemPartnerId =
fopAcM_createItemForPresentDemo(&current.pos, itemNo, 0, -1, -1, 0, 0);
DUSK_ITEM_CHECK("dungeon_map:D_MN11", itemNo, this);
mItemPartnerId = fopAcM_createItemForPresentDemo(&current.pos, itemNo,
0, -1, -1, 0, 0 DUSK_GIVE_TAG("dungeon_map:D_MN11"));
}
if (fopAcM_IsExecuting(mItemPartnerId)) {
+13 -2
View File
@@ -2609,8 +2609,19 @@ BOOL daNpc_zrA_c::ECut_thanksBlast(int i_staffID) {
case 31: {
int item_id = 0;
if (mFlow.getEventId(&item_id) == 1) {
mItemID = fopAcM_createItemForPresentDemo(&current.pos, item_id,
0, -1, -1, NULL, NULL);
#if TARGET_PC
const char* itemCheckName = nullptr;
if (item_id == dItemNo_BOMB_IN_BAG_e) {
itemCheckName = "iza_reward_1";
} else if (item_id == dItemNo_BOMB_BAG_LV2_e) {
itemCheckName = "iza_reward_2";
}
if (itemCheckName != nullptr) {
item_id = dusk::mods::item_check(itemCheckName, item_id, this);
}
#endif
mItemID = fopAcM_createItemForPresentDemo(&current.pos, item_id, 0, -1, -1, NULL,
NULL IF_DUSK_ARG(dusk::mods::item_give_tag(itemCheckName)));
}
field_0x9eb = true;
break;
+3 -2
View File
@@ -1681,8 +1681,9 @@ BOOL daNpc_zrC_c::ECut_earringGet(int i_staffID) {
case 40: {
int item_no = 0;
if (mFlow.getEventId(&item_no) == 1) {
mItemID = fopAcM_createItemForPresentDemo(&current.pos, item_no,
0, -1, -1, NULL, NULL);
DUSK_ITEM_CHECK("coral_earring", item_no, this);
mItemID = fopAcM_createItemForPresentDemo(
&current.pos, item_no, 0, -1, -1, NULL, NULL DUSK_GIVE_TAG("coral_earring"));
}
break;
}
+3 -2
View File
@@ -1729,8 +1729,9 @@ BOOL daNpc_zrZ_c::ECut_clothesGet(int i_staffID) {
}
item_no = 0;
if (mFlow.getEventId(&item_no) == 1) {
mItemID = fopAcM_createItemForPresentDemo(&current.pos, item_no,
0, -1, -1, NULL, NULL);
DUSK_ITEM_CHECK("zora_armor", item_no, this);
mItemID = fopAcM_createItemForPresentDemo(
&current.pos, item_no, 0, -1, -1, NULL, NULL DUSK_GIVE_TAG("zora_armor"));
}
break;
+65 -14
View File
@@ -206,7 +206,7 @@ void daItem_c::CreateInit() {
initBaseMtx();
animPlay(1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f);
if (m_itemNo == dItemNo_BOOMERANG_e) {
if (m_itemNo == dItemNo_BOOMERANG_e IF_DUSK(&&!mItemOverridden)) {
itemGetNextExecute();
} else if ((m_itemNo == dItemNo_ORANGE_RUPEE_e || m_itemNo == dItemNo_SILVER_RUPEE_e) &&
mSparkleEmtr.getEmitter() == NULL)
@@ -264,6 +264,17 @@ int daItem_c::_daItem_create() {
shape_angle.z = 0;
shape_angle.x = 0;
#if TARGET_PC
const u32 params = fopAcM_GetParam(this);
mOriginalItemNo = params & 0xFF;
const u8 resolvedItem = dusk::mods::item_check_freestanding(
daItem_prm::getItemBitNo(this), mOriginalItemNo, this);
mItemOverridden = resolvedItem != mOriginalItemNo;
mItemGiveTag = dusk::mods::item_give_tag_freestanding(daItem_prm::getItemBitNo(this));
if (mItemOverridden) {
fopAcM_SetParam(this, (params & 0xFFFFFF00) | resolvedItem);
}
#endif
field_0x95d = true;
}
@@ -507,9 +518,18 @@ void daItem_c::procInitGetDemoEvent() {
fopAcM_orderItemEvent(this, 0, 0);
eventInfo.onCondition(dEvtCnd_CANGETITEM_e);
m_item_id = fopAcM_createItemForTrBoxDemo(&current.pos, m_itemNo, -1, fopAcM_GetRoomNo(this),
NULL, NULL);
#if TARGET_PC
const u8 displayItemNo = m_itemNo;
if (mItemOverridden) {
m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this);
}
#endif
m_item_id = fopAcM_createItemForTrBoxDemo(
&current.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(mItemGiveTag));
JUT_ASSERT(0, m_item_id != fpcM_ERROR_PROCESS_ID_e);
#if TARGET_PC
m_itemNo = displayItemNo;
#endif
setStatus(STATUS_WAIT_GET_DEMO_EVENT_e);
}
@@ -521,7 +541,7 @@ void daItem_c::procWaitGetDemoEvent() {
dComIfGp_event_setItemPartnerId(m_item_id);
}
} else {
if (m_itemNo == dItemNo_BOOMERANG_e) {
if (m_itemNo == dItemNo_BOOMERANG_e IF_DUSK(&&!mItemOverridden)) {
fopAcM_orderItemEvent(this, 0, 0);
eventInfo.onCondition(dEvtCnd_CANGETITEM_e);
return;
@@ -537,7 +557,9 @@ void daItem_c::procWaitGetDemoEvent() {
procInitSimpleGetDemo();
itemGet();
if (!haveItem) {
if (!haveItem IF_DUSK(&&(!mItemOverridden || (m_itemNo >= dItemNo_GREEN_RUPEE_e &&
m_itemNo <= dItemNo_SILVER_RUPEE_e))))
{
dComIfGs_offItemFirstBit(m_itemNo);
}
} else {
@@ -864,6 +886,12 @@ void daItem_c::itemGetNextExecute() {
procInitGetDemoEvent();
break;
default:
#if TARGET_PC
if (mItemOverridden) {
procInitGetDemoEvent();
break;
}
#endif
// "[daItem_c] Get process not defined[%d]\n"
OS_REPORT_ERROR("[daItem_c]ゲット処理が定義されていません[%d]\n", m_itemNo);
}
@@ -877,38 +905,48 @@ void daItem_c::itemGetNextExecute() {
}
void daItem_c::itemGet() {
#if TARGET_PC
const u8 displayItemNo = m_itemNo;
if (mItemOverridden) {
m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this);
}
#endif
switch (m_itemNo) {
#if TARGET_PC
case dItemNo_UTAWA_HEART_e:
case dItemNo_KAKERA_HEART_e:
#endif
case dItemNo_HEART_e:
mDoAud_seStart(Z2SE_HEART_PIECE_GET, NULL, 0, 0);
execItemGet(m_itemNo);
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
break;
case dItemNo_GREEN_RUPEE_e:
mDoAud_seStart(Z2SE_GREEN_LUPY_GET, NULL, 0, 0);
execItemGet(m_itemNo);
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
break;
case dItemNo_BLUE_RUPEE_e:
mDoAud_seStart(Z2SE_BLUE_LUPY_GET, NULL, 0, 0);
execItemGet(m_itemNo);
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
break;
case dItemNo_YELLOW_RUPEE_e:
mDoAud_seStart(Z2SE_BLUE_LUPY_GET, NULL, 0, 0);
execItemGet(m_itemNo);
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
break;
case dItemNo_RED_RUPEE_e:
mDoAud_seStart(Z2SE_RED_LUPY_GET, NULL, 0, 0);
execItemGet(m_itemNo);
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
break;
case dItemNo_PURPLE_RUPEE_e:
mDoAud_seStart(Z2SE_RED_LUPY_GET, NULL, 0, 0);
execItemGet(m_itemNo);
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
break;
case dItemNo_ORANGE_RUPEE_e:
mDoAud_seStart(Z2SE_RED_LUPY_GET, NULL, 0, 0);
execItemGet(m_itemNo);
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
break;
case dItemNo_SILVER_RUPEE_e:
mDoAud_seStart(Z2SE_RED_LUPY_GET, NULL, 0, 0);
execItemGet(m_itemNo);
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
break;
case dItemNo_BOOMERANG_e:
break;
@@ -918,12 +956,25 @@ void daItem_c::itemGet() {
case dItemNo_ARROW_1_e:
case dItemNo_PACHINKO_SHOT_e:
mDoAud_seStart(Z2SE_CONSUMP_ITEM_GET, NULL, 0, 0);
execItemGet(m_itemNo);
execItemGet(m_itemNo IF_DUSK_ARG(mItemGiveTag) IF_DUSK_ARG(this));
#if TARGET_PC
break;
#endif
default:
#if TARGET_PC
if (mItemOverridden) {
mDoAud_seStart(Z2SE_CONSUMP_ITEM_GET, NULL, 0, 0);
execItemGet(m_itemNo, mItemGiveTag, this);
break;
}
#endif
// "[daItem_c] Get process not defined[%d]\n"
OS_REPORT_ERROR("[daItem_c]ゲット処理が定義されていません[%d]\n", m_itemNo);
break;
}
#if TARGET_PC
m_itemNo = displayItemNo;
#endif
}
BOOL daItem_c::checkCountTimer() {
+39 -2
View File
@@ -97,6 +97,12 @@ int daObjLife_c::Create() {
field_0x94c = 0.7f;
mRotateSpeed = 7000;
#if TARGET_PC
if (mOverrideHover) {
fopAcM_SetGravity(this, 0.0f);
mRotateSpeed = 550;
}
#endif
setEffect();
mSound.init(&current.pos, 1);
return 1;
@@ -140,6 +146,27 @@ int daObjLife_c::create() {
home.angle.x = home.angle.z = 0;
current.angle.x = current.angle.z = 0;
shape_angle.x = shape_angle.z = 0;
#if TARGET_PC
const u32 params = fopAcM_GetParam(this);
const u8 parameterItemNo = params & 0xFF;
if (mItemGiveOriginalNo == dItemNo_NONE_e) {
mOriginalItemNo = parameterItemNo;
const u8 resolvedItem =
dusk::mods::item_check_freestanding(getSaveBitNo(), mOriginalItemNo, this);
mItemGiveTag = dusk::mods::item_give_tag_freestanding(getSaveBitNo());
mItemOverridden = resolvedItem != mOriginalItemNo;
if (mItemOverridden) {
fopAcM_SetParam(this, (params & 0xFFFFFF00) | resolvedItem);
}
} else {
mOriginalItemNo = mItemGiveOriginalNo;
mItemOverridden = parameterItemNo != mItemGiveOriginalNo;
}
mOverrideHover =
mItemOverridden &&
(mOriginalItemNo == dItemNo_UTAWA_HEART_e ||
(mOriginalItemNo >= dItemNo_M_BEETLE_e && mOriginalItemNo <= dItemNo_F_MAYFLY_e));
#endif
mIsPrmsInit = true;
}
@@ -153,7 +180,7 @@ int daObjLife_c::create() {
return cPhs_ERROR_e;
}
if (m_itemNo == dItemNo_UTAWA_HEART_e && dComIfGs_isStageLife()) {
if (m_itemNo == dItemNo_UTAWA_HEART_e && dComIfGs_isStageLife() IF_DUSK(&&!mItemOverridden)) {
return cPhs_ERROR_e;
}
@@ -296,8 +323,18 @@ int daObjLife_c::initActionOrderGetDemo() {
fopAcM_orderItemEvent(this, 0, 0);
eventInfo.onCondition(dEvtCnd_CANGETITEM_e);
mItemId = fopAcM_createItemForTrBoxDemo(&current.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL);
#if TARGET_PC
const u8 displayItemNo = m_itemNo;
if (mItemOverridden) {
m_itemNo = dusk::mods::item_check_tagged(mItemGiveTag, mOriginalItemNo, this);
}
#endif
mItemId = fopAcM_createItemForTrBoxDemo(
&current.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), NULL, NULL IF_DUSK_ARG(mItemGiveTag));
JUT_ASSERT(699, mItemId != fpcM_ERROR_PROCESS_ID_e);
#if TARGET_PC
m_itemNo = displayItemNo;
#endif
setStatus(STATUS_ORDER_GET_DEMO_e);
return 1;
+4 -2
View File
@@ -109,6 +109,7 @@ int daItemShield_c::__CreateHeap() {
int daItemShield_c::create() {
fopAcM_ct(this, daItemShield_c);
m_itemNo = dItemNo_WOOD_SHIELD_e;
DUSK_ITEM_CHECK("ordon_shield", m_itemNo, this);
if (fopAcM_isSwitch(this, getSwBit2())) {
OS_REPORT("木の盾:もう取ったので出ません\n");
return cPhs_ERROR_e;
@@ -240,8 +241,9 @@ int daItemShield_c::initActionOrderGetDemo() {
daItemBase_c::hide();
fopAcM_orderItemEvent(this, 0, 0);
eventInfo.onCondition(dEvtCnd_CANGETITEM_e);
mItemId =
fopAcM_createItemForTrBoxDemo(&current.pos, m_itemNo, -1, fopAcM_GetRoomNo(this), 0, 0);
mItemId = fopAcM_createItemForTrBoxDemo(&current.pos,
DUSK_ITEM_CHECK_EXPR("ordon_shield", dItemNo_WOOD_SHIELD_e, this), -1,
fopAcM_GetRoomNo(this), 0, 0 DUSK_GIVE_TAG("ordon_shield"));
JUT_ASSERT(682, mItemId != fpcM_ERROR_PROCESS_ID_e)
setStatus(STATUS_ORDERGETDEMO);
return 1;
+4 -2
View File
@@ -37,6 +37,7 @@ int daObjSword_c::Create() {
cPhs_Step daObjSword_c::create() {
fopAcM_ct(this, daObjSword_c);
m_itemNo = 0x28;
DUSK_ITEM_CHECK("ordon_sword", m_itemNo, this);
if (fopAcM_isItem(this, getItemBit())) {
return cPhs_ERROR_e;
}
@@ -71,8 +72,9 @@ int daObjSword_c::initActionOrderGetDemo() {
hide();
fopAcM_orderItemEvent(this, 0, 0);
eventInfo.onCondition(8);
mProcID = fopAcM_createItemForTrBoxDemo(&current.pos, m_itemNo, -1, fopAcM_GetRoomNo(this),
NULL, NULL);
mProcID =
fopAcM_createItemForTrBoxDemo(&current.pos, DUSK_ITEM_CHECK_EXPR("ordon_sword", 0x28, this),
-1, fopAcM_GetRoomNo(this), NULL, NULL DUSK_GIVE_TAG("ordon_sword"));
setStatus(1);
return 1;
}
+3 -2
View File
@@ -255,8 +255,9 @@ int daObjWStatue_c::initActionOrderGetDemo() {
s16 eventIdx = dComIfGp_getEventManager().getEventIdx(this, "DEFAULT_GETITEM", 0xff);
dComIfGp_getEvent()->reset(this);
fopAcM_orderChangeEventId(this, eventIdx, 1, 0xffff);
mItemId = fopAcM_createItemForTrBoxDemo(&current.pos, m_itemNo, 0xffffffff,
fopAcM_GetRoomNo(this), 0, 0);
mItemId = fopAcM_createItemForTrBoxDemo(&current.pos,
DUSK_ITEM_CHECK_EXPR("wood_statue", m_itemNo, this), 0xffffffff, fopAcM_GetRoomNo(this),
0, 0 DUSK_GIVE_TAG("wood_statue"));
JUT_ASSERT(544, mItemId != fpcM_ERROR_PROCESS_ID_e);
setStatus(STATUS_ORDER_GET_DEMO);
return 1;
+1
View File
@@ -32,6 +32,7 @@ int daObjZCloth_c::Create() {
int daObjZCloth_c::create() {
fopAcM_ct(this, daObjZCloth_c);
m_itemNo = 0x31;
DUSK_ITEM_CHECK("zora_armor", m_itemNo, this);
int phase = dComIfG_resLoad(&mPhase, dItem_data::getFieldArc(m_itemNo));
if (phase == cPhs_COMPLEATE_e) {
if (!fopAcM_entrySolidHeap(this, (heapCallbackFunc)CheckFieldItemCreateHeap, 0x2fb0)) {
+8 -4
View File
@@ -375,10 +375,14 @@ static const u8* l_sightDL_get() {
static bool _ = (
dusk::LoadDolAsset(
buf,
{
{GameVersion::GcnUsa, 0x803BA0C0},
{GameVersion::GcnPal, 0x803BBDA0},
{GameVersion::GcnJpn, 0x803B4220}
{
{GameVersion::GcnUsa, 0x803BA0C0},
{GameVersion::GcnPal, 0x803BBDA0},
{GameVersion::GcnJpn, 0x803B4220},
{GameVersion::WiiUsaRev0, 0x803F63C0},
{GameVersion::WiiUsa, 0x803E1640},
{GameVersion::WiiPal, 0x803E23A0},
{GameVersion::WiiJpn, 0x803DF600}
},
0x89
),
+59 -16
View File
@@ -11,6 +11,20 @@
#include "m_Do/m_Do_lib.h"
#include <cstring>
#if TARGET_PC
#include "d/d_item_data.h"
const ResourceData& daShopItem_c::getResourceData() const {
if (mItemOverridden) {
return mOverrideData;
}
return mData[mShopItemID];
}
#define SHOP_RESOURCE_DATA getResourceData()
#else
#define SHOP_RESOURCE_DATA mData[mShopItemID]
#endif
const char* daShopItem_c::getShopArcname() {
switch (m_itemNo) {
case dItemNo_NONE_e:
@@ -88,7 +102,29 @@ const char* daShopItem_c::getShopArcname() {
return NULL;
}
return mData[mShopItemID].get_arcName();
#if TARGET_PC
if (m_itemNo != dItemNo_NONE_e && mItemGiveOriginalNo == dItemNo_NONE_e) {
mItemGiveOriginalNo = m_itemNo;
const u8 resolvedItem = dusk::mods::item_check_shop(mItemGiveOriginalNo, this);
mItemOverridden = resolvedItem != mItemGiveOriginalNo;
if (mItemOverridden) {
mOverrideData = mData[mShopItemID];
mOverrideData.mArcName = dItem_data::getArcName(resolvedItem);
mOverrideData.mBmdName = dItem_data::getBmdName(resolvedItem);
mOverrideData.mBtkName = dItem_data::getBtkName(resolvedItem);
mOverrideData.mBckName = dItem_data::getBckName(resolvedItem);
mOverrideData.mBrkName = dItem_data::getBrkName(resolvedItem);
mOverrideData.mBtpName = dItem_data::getBtpName(resolvedItem);
mOverrideData.mTevFrm = dItem_data::getTevFrm(resolvedItem);
mOverrideData.mBtpFrm = -1;
mOverrideData.mFlag = static_cast<u32>(-1);
mOverrideData.mOffsetY = mShopItemID == SHOP_ITEMNO_ARMOR ? 60.0f : 15.0f;
mOverrideData.mScale = 1.0f;
}
}
#endif
return SHOP_RESOURCE_DATA.get_arcName();
}
DUSK_GAME_DATA const f32 daShopItem_c::m_cullfar_max = 5000.0f;
@@ -104,6 +140,11 @@ u16 daShopItem_c::getHeapSize() {
OS_REPORT("ShopItemID [%u]\n", a_ShopItemID);
ASSERT(a_ShopItemID < SHOP_ITEMNO_MAX);
#if TARGET_PC
if (mItemOverridden) {
return 0x8000;
}
#endif
return HeapSizeTbl[a_ShopItemID];
}
@@ -119,11 +160,11 @@ void daShopItem_c::CreateInit() {
if (strcmp("R_SP109", dComIfGp_getStartStageName()) == 0 && dComIfGp_getStartStageRoomNo() == 1)
{
scale.set(mData[mShopItemID].get_scale() * 0.8f, mData[mShopItemID].get_scale() * 0.8f,
mData[mShopItemID].get_scale() * 0.8f);
scale.set(SHOP_RESOURCE_DATA.get_scale() * 0.8f, SHOP_RESOURCE_DATA.get_scale() * 0.8f,
SHOP_RESOURCE_DATA.get_scale() * 0.8f);
} else {
scale.set(mData[mShopItemID].get_scale(), mData[mShopItemID].get_scale(),
mData[mShopItemID].get_scale());
scale.set(SHOP_RESOURCE_DATA.get_scale(), SHOP_RESOURCE_DATA.get_scale(),
SHOP_RESOURCE_DATA.get_scale());
}
home.pos = current.pos;
@@ -137,8 +178,8 @@ void daShopItem_c::set_mtx() {
if (daShopItem_prm::getGroup(this) == 15) {
mDoMtx_stack_c::transS(current.pos.x, current.pos.y, current.pos.z);
} else {
mDoMtx_stack_c::transS(current.pos.x, current.pos.y + mData[mShopItemID].get_offsetY(),
current.pos.z);
mDoMtx_stack_c::transS(
current.pos.x, current.pos.y + SHOP_RESOURCE_DATA.get_offsetY(), current.pos.z);
}
MTXCopy(mDoMtx_stack_c::get(), mMtx);
@@ -147,8 +188,8 @@ void daShopItem_c::set_mtx() {
if (daShopItem_prm::getGroup(this) == 15) {
mDoMtx_stack_c::ZXYrotM(-11300, 32700, 7300);
} else {
mDoMtx_stack_c::ZXYrotM(mAngleX + mData[mShopItemID].get_angleX(),
mData[mShopItemID].get_angleY(), mData[mShopItemID].get_angleZ());
mDoMtx_stack_c::ZXYrotM(mAngleX + SHOP_RESOURCE_DATA.get_angleX(),
SHOP_RESOURCE_DATA.get_angleY(), SHOP_RESOURCE_DATA.get_angleZ());
}
mDoMtx_stack_c::ZXYrotM(current.angle.x, current.angle.y, current.angle.z);
@@ -156,7 +197,7 @@ void daShopItem_c::set_mtx() {
if (daShopItem_prm::getGroup(this) == 15) {
mDoMtx_stack_c::XrotM(0);
} else {
mDoMtx_stack_c::XrotM(mData[mShopItemID].get_angleOffsetX());
mDoMtx_stack_c::XrotM(SHOP_RESOURCE_DATA.get_angleOffsetX());
}
mpModel->setBaseTRMtx(mDoMtx_stack_c::get());
@@ -190,29 +231,31 @@ void daShopItem_c::setShadow() {
}
BOOL daShopItem_c::chkFlag(int i_flag) {
return mData[mShopItemID].get_flag() & i_flag;
return SHOP_RESOURCE_DATA.get_flag() & i_flag;
}
s8 daShopItem_c::getTevFrm() {
return mData[mShopItemID].get_tevfrm();
return SHOP_RESOURCE_DATA.get_tevfrm();
}
s8 daShopItem_c::getBtpFrm() {
return mData[mShopItemID].get_btpfrm();
return SHOP_RESOURCE_DATA.get_btpfrm();
}
u8 daShopItem_c::getShadowSize() {
return mData[mShopItemID].get_shadowSize();
return SHOP_RESOURCE_DATA.get_shadowSize();
}
u8 daShopItem_c::getCollisionH() {
return mData[mShopItemID].get_collisionH();
return SHOP_RESOURCE_DATA.get_collisionH();
}
u8 daShopItem_c::getCollisionR() {
return mData[mShopItemID].get_collisionR();
return SHOP_RESOURCE_DATA.get_collisionR();
}
#undef SHOP_RESOURCE_DATA
int daShopItem_c::_create() {
fopAcM_ct(this, daShopItem_c);
+5 -3
View File
@@ -362,9 +362,11 @@ int daTagStatue_c::demoProc() {
item = dItemNo_AIR_LETTER_e;
}
mItemId =
fopAcM_createItemForTrBoxDemo(&current.pos, item, -1,
fopAcM_GetRoomNo(this), 0, 0);
#if TARGET_PC
item = dusk::mods::item_check_sky_character(item & 0xFF, this);
#endif
mItemId = fopAcM_createItemForTrBoxDemo(&current.pos, item, -1,
fopAcM_GetRoomNo(this), 0, 0 IF_DUSK_ARG(dusk::mods::item_give_tag_sky_character()));
JUT_ASSERT(580, mItemId != fpcM_ERROR_PROCESS_ID_e);

Some files were not shown because too many files have changed in this diff Show More