// worldTimeMgr.cpp
mIsTimeFlowingNormally = false;
This is at 0x14f
, or byte 35 of
firstItem.mIngredients.mWork[1].elem.mBuffer
. Since the longest item actor
name is Get_TwnObj_DLC_MemorialPicture_A_01
, which is 35 characters long, this
is guaranteed to be 00
already. This is a no-op.
float &time = mTime;
gdm->getF32(mTimeFlag, &time);
// gdtManager.h
GDT_GET_(getF32, f32)
#define GDT_GET(NAME, T) \
bool NAME( \
FlagHandle handle, T *value, bool debug = false, \
bool ignore_trigger_param_result = false \
) { return unwrapHandle<false>(handle, debug, [&]( \
u32 idx, TriggerParamRef &ref \
) { \
const bool result = ref.get().NAME(value, idx); \
return ignore_trigger_param_result || result; \
}); } \
\
// ...
template <bool Write, typename Fn>
KSYS_ALWAYS_INLINE bool unwrapHandle(
FlagHandle handle, bool debug, const Fn &fn
) { return debug ? unwrapHandle<Write, true>(
handle, fn
) : unwrapHandle<Write, false>(handle, fn); }
template <bool Write, bool BypassPerm, typename Fn>
KSYS_ALWAYS_INLINE bool unwrapHandle(FlagHandle handle, const Fn &fn) {
const u32 idx = std::to_underlying(handle);
auto &ref = BypassPerm ? getParamBypassPerm() : getParam();
const auto check = [&] { return !Write || !ref.shouldChangeOnlyOnce(); };
if (mBitFlags.isOff(BitFlag::_8000) && handle != InvalidHandle)
return check() && fn(idx, ref);
return idx >> 24 == mCurrentFlagHandlePrefix && check() && fn(
idx & 0xffffff, ref
);
}
TriggerParamRef &getParam() { return mParam; }
TriggerParamRef mParam{&mFlagBuffer1, &mFlagBuffer, true, false, false};
TriggerParamRef(
TriggerParam **param_1, TriggerParam **param, bool check_permissions,
bool propagate_param_1_changes, bool change_only_once
) : mParam1(param_1), mParam(param), mCheckPermissions(check_permissions),
mPropagateParam1Changes(propagate_param_1_changes),
mChangeOnlyOnce(change_only_once) {}
TriggerParam **mParam1;
TriggerParam **mParam;
bool mCheckPermissions;
bool mPropagateParam1Changes;
bool mChangeOnlyOnce;
Proxy get() const { return Proxy(*this, false); }
Proxy(
const TriggerParamRef &ref, bool param1
) : mUseParam1(param1), mRef(ref) {}
bool mUseParam1;
const TriggerParamRef &mRef;
PROXY_GET_SET_IMPL_(getF32, setF32, f32)
#define PROXY_GET_SET_IMPL_(GET_NAME, SET_NAME, TYPE) \
bool GET_NAME(TYPE *value, s32 index) const { \
return getBuffer()->GET_NAME(value, index, mRef.mCheckPermissions); \
} \
\
// ...
TriggerParam *getBuffer() const {
return mUseParam1 ? *mRef.mParam1 : *mRef.mParam;
}
// gdtTriggerParam.cpp
bool TriggerParam::getF32(f32 *value, s32 index, bool check_permissions) const {
return getFlagValue(mF32Flags, value, index, check_permissions);
}
template <typename T, typename FlagValueType = T>
inline bool getFlagValue(
const sead::PtrArray<FlagBase> &array, T *out_value, s32 index,
bool check_permissions
) {
static_assert(isValidFlagValueType<FlagValueType>());
const auto *flag = getFlagByIndex<FlagValueType>(array, index);
if (!flag) return false;
if (check_permissions && !flag->isProgramReadable()) return false;
if constexpr (std::is_same<T, const char *>())
*out_value = flag->getValueRef().cstr();
else *out_value = flag->getValue();
return true;
}
template <typename T>
inline Flag<T> *getFlagByIndex(
const sead::PtrArray<FlagBase> &flags, s32 index
) { return static_cast<Flag<T> *>(getFlagByIndexBase(flags, index)); }
inline FlagBase *getFlagByIndexBase(
const sead::PtrArray<FlagBase> &flags, s32 index
) { return index < 0 || index >= flags.size() ? nullptr : flags[index]; }
// seadPtrArray.h
T *operator[](s32 pos) const { return at(pos); }
T *at(s32 pos) const { return static_cast<T *>(PtrArrayImpl::at(pos)); }
void *at(s32 idx) const { return static_cast<u32>(mPtrNum) <= static_cast<u32>(
idx
) ? nullptr : mPtrs[idx]; }
s32 mPtrNum = 0;
void **mPtrs = nullptr;
// gdtTriggerParam.cpp
void TriggerParam::copyFromGameDataRes(res::GameData *gdata, sead::Heap *heap) {
if (!gdata) return;
copyGameDataResBuffer(gdata->getBoolFlags(), mBoolFlags, heap);
copyGameDataResBuffer(gdata->getS32Flags(), mS32Flags, heap);
copyGameDataResBuffer(gdata->getF32Flags(), mF32Flags, heap);
copyGameDataResBuffer(gdata->getStringFlags(), mStringFlags, heap);
copyGameDataResBuffer(gdata->getString64Flags(), mString64Flags, heap);
copyGameDataResBuffer(gdata->getString256Flags(), mString256Flags, heap);
copyGameDataResBuffer(gdata->getVector2fFlags(), mVector2fFlags, heap);
copyGameDataResBuffer(gdata->getVector3fFlags(), mVector3fFlags, heap);
copyGameDataResBuffer(gdata->getVector4fFlags(), mVector4fFlags, heap);
copyGameDataResBuffer(gdata->getBoolArrayFlags(), mBoolArrayFlags, heap);
copyGameDataResBuffer(gdata->getS32ArrayFlags(), mS32ArrayFlags, heap);
copyGameDataResBuffer(gdata->getF32ArrayFlags(), mF32ArrayFlags, heap);
copyGameDataResBuffer(gdata->getStringArrayFlags(), mStringArrayFlags, heap);
copyGameDataResBuffer(
gdata->getString64ArrayFlags(), mString64ArrayFlags, heap
);
copyGameDataResBuffer(
gdata->getString256ArrayFlags(), mString256ArrayFlags, heap
);
copyGameDataResBuffer(
gdata->getVector2fArrayFlags(), mVector2fArrayFlags, heap
);
copyGameDataResBuffer(
gdata->getVector3fArrayFlags(), mVector3fArrayFlags, heap
);
copyGameDataResBuffer(
gdata->getVector4fArrayFlags(), mVector4fArrayFlags, heap
);
mResourceFlags = gdata->getField14();
}
// resResourceGameData.cpp
sead::Buffer<gdt::FlagF32> &getF32Flags() { return mF32Flags; }
sead::Buffer<gdt::FlagF32> mF32Flags;
void GameData::doCreate_(
u8 *data, [[maybe_unused]] u32 unused1, [[maybe_unused]] sead::Heap *unused2
) {
auto *heap = gdt::Manager::instance()->getSaveAreaHeap();
const al::ByamlIter root_iter{data};
const char *key = nullptr;
root_iter.getKeyName(&key, 0);
const sead::SafeString data_type = key;
// ...
loadFlags(
data_type, root_iter, heap, mF32Flags, "f32_data", gdt::FlagType::F32, [](
gdt::FlagF32::ConfigType *config, const al::ByamlIter &iter
) {
f32 init_value{};
f32 min_value{};
f32 max_value{};
tryGetFloatOrInt(iter, "InitValue", &init_value);
tryGetFloatOrInt(iter, "MinValue", &min_value);
tryGetFloatOrInt(iter, "MaxValue", &max_value);
config->initial_value = init_value;
config->min_value = min_value;
config->max_value = max_value;
}
);
// ...
}
idx == handle
, which is mTimeFlag
, which is at 0xd4
.
mCurrentFlagHandlePrefix == 0x00
, so this code is checking if the most
significant byte of idx
is 00
. Only if it is will it call fn
; otherwise,
this is a no-op. 0xd4
in newItem.mName
is
firstItem.mIngredients.mWork[0].elem.mBuffer[0x8:0xc]
, which has a 00
as its
most significant byte only if firstItem
is not a meal or its first ingredient
has an actor name shorter than 12 (0xc
) bytes long. As mTimeFlag
is a handle
for an f32
flag, and there are 46 (0x2e
) f32
flags, the 3 most significant
bytes of the handle must all be 00
in order for this to refer to a valid flag;
otherwise, this is a no-op. Hence, the actor name must also be shorter than 10
(0xa
) bytes long. All such items are listed below, along with which flag they
correspond to.
Actor name | Handle | Flag | Success? |
---|---|---|---|
BeeHome (Courser Bee Honey) |
0x00000000 |
dummy_float |
❌ |
IceArrow (Ice Arrow) |
0x00000000 |
dummy_float |
❌ |
FireArrow (Fire Arrow) |
0x00000077 |
❌ |
As no items with actor names shorter than 10 bytes long refer to valid and readable flags, this line is a no-op.
// worldTimeMgr.cpp
gdm->getS32(mNumberOfDaysFlag, &mNumberOfDays);
gdm->getS32(mTimeDivisionFlag, &mTimeDivision);
gdm->getBool(mIsDaytimeFlag, &is_daytime_flag_set);
gdm->getF32(mBloodyMoonTimerFlag, &mBloodMoonTimer);
if (mBloodyEndReserveTimerFlag != gdt::InvalidHandle)
gdm->getS32(mBloodyEndReserveTimerFlag, &mBloodyEndReserveTimer);
if (!mPlayedDemo103Or997) {
mTime = DefaultTime;
gdm->getBool(mIsPlayedDemo103Flag, &mPlayedDemo103Or997, true);
if (!mPlayedDemo103Or997)
gdm->getBool(mDemo997Flag, &mPlayedDemo103Or997, true);
}
if (!mFindDungeonActivated)
gdm->getBool(mFindDungeonActivatedFlag, &mFindDungeonActivated, true);
Similarly, these lines write a flag of a particular type at an index to a variable. The following table describes all flag reads performed here, in order.
Prefix | Type | Handle/Offset | PouchItem Handle |
Member/Offset | PouchItem Flag |
---|---|---|---|---|---|
?? |
f32 |
mTimeFlag (0xd4 ) |
firstItem.mIngredients.mWork[0].elem.mBuffer[0x8:0xc] |
||
?? |
s32 |
mNumberOfDaysFlag (0xd8 ) |
firstItem.mIngredients.mWork[0].elem.mBuffer[0xc:0x10] Table 1 |
mNumberOfDays (0x13c ) |
firstItem.mIngredients.mWork[1].elem.mBuffer[0x10:0x14] |
?? |
s32 |
mTimeDivisionFlag (0xdc ) |
firstItem.mIngredients.mWork[0].elem.mBuffer[0x10:0x14] Table 2 |
mTimeDivision (0x28 ) |
Least significant dword of firstItem.mName.mStringTop |
?? |
bool |
mIsDaytimeFlag (0xe0 ) |
firstItem.mIngredients.mWork[0].elem.mBuffer[0x14:0x18] Table 3 |
||
?? |
f32 |
mBloodyMoonTimerFlag (0x120 ) |
Least significant dword of firstItem.mIngredients.mWork[1].elem.mStringTop |
mBloodMoonTimer (0xc8 ) |
firstItem.mIngredients.mWork[0].elem.mBufferSize |
00 |
s32 |
mBloodyEndReserveTimerFlag (0x124 ) |
Most significant dword of firstItem.mIngredients.mWork[1].elem.mStringTop (0x00-0x7f ) |
mBloodyEndReserveTimer (0x144 ) |
firstItem.mIngredients.mWork[1].elem.mBuffer[0x18:0x1c] |
?? |
bool |
mIsPlayedDemo103Flag (0xe8 ) |
firstItem.mIngredients.mWork[0].elem.mBuffer[0x1c:0x20] |
||
00 |
bool |
mDemo997Flag (0xec ) |
firstItem.mIngredients.mWork[0].elem.mBuffer[0x20:0x24] |
||
00 |
bool |
mFindDungeonActivatedFlag (0xf0 ) |
firstItem.mIngredients.mWork[0].elem.mBuffer[0x24:0x28] |
Click to expand
Actor name | Handle | Flag |
---|---|---|
ElectricArrow (Shock Arrow) |
0x00000077 |
EquipTime_Armor_004_Head 1 |
Item_Enemy_00 (Bokoblin Horn) |
0x00000030 |
LizarfosSeries_Counter 2 |
Item_Enemy_01 (Bokoblin Fang) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_Enemy_02 (Bokoblin Guts) |
0x00000032 |
RinelSeries_Counter 2 |
Item_Enemy_03 (Lizalfos Horn) |
0x00000033 |
Location_RemainsWind 3 |
Item_Enemy_04 (Lizalfos Talon) |
0x00000034 |
Location_RemainsWater 4 |
Item_Enemy_05 (Lizalfos Tail) |
0x00000035 |
Location_RemainsFire 5 |
Item_Enemy_06 (Moblin Horn) |
0x00000036 |
Location_RemainsElectric 6 |
Item_Enemy_07 (Moblin Fang) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Item_Enemy_08 (Moblin Guts) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Item_Enemy_12 (Lynel Horn) |
0x00000032 |
RinelSeries_Counter 2 |
Item_Enemy_13 (Lynel Hoof) |
0x00000033 |
Location_RemainsWind 3 |
Item_Enemy_14 (Lynel Guts) |
0x00000034 |
Location_RemainsWater 4 |
Item_Enemy_15 (Red Chuchu Jelly) |
0x00000035 |
Location_RemainsFire 5 |
Item_Enemy_16 (Yellow Chuchu Jelly) |
0x00000036 |
Location_RemainsElectric 6 |
Item_Enemy_17 (White Chuchu Jelly) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Item_Enemy_18 (Keese Wing) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Item_Enemy_19 (Keese Eyeball) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Item_Enemy_20 (Octorok Tentacle) |
0x00000030 |
LizarfosSeries_Counter 2 |
Item_Enemy_21 (Octorok Eyeball) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_Enemy_24 (Molduga Fin) |
0x00000034 |
Location_RemainsWater 4 |
Item_Enemy_25 (Modluga Guts) |
0x00000035 |
Location_RemainsFire 5 |
Item_Enemy_26 (Ancient Gear) |
0x00000036 |
Location_RemainsElectric 6 |
Item_Enemy_27 (Ancient Screw) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Item_Enemy_28 (Ancient Spring) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Item_Enemy_29 (Ancient Shaft) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Item_Enemy_30 (Ancient Core) |
0x00000030 |
LizarfosSeries_Counter 2 |
Item_Enemy_31 (Giant Ancient Core) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_Enemy_32 (Hinox Toenail) |
0x00000032 |
RinelSeries_Counter 2 |
Item_Enemy_33 (Hinox Tooth) |
0x00000033 |
Location_RemainsWind 3 |
Item_Enemy_34 (Hinox Guts) |
0x00000034 |
Location_RemainsWater 4 |
Item_Enemy_38 (Dinraal's Scale) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Item_Enemy_39 (Dinraal's Claw) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Item_Enemy_40 (Chuchu Jelly) |
0x00000030 |
LizarfosSeries_Counter 2 |
Item_Enemy_41 (Red Lizalfos Tail) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_Enemy_42 (Icy Lizalfos Tail) |
0x00000032 |
RinelSeries_Counter 2 |
Item_Enemy_43 (Yellow Lizalfos Tail) |
0x00000033 |
Location_RemainsWind 3 |
Item_Enemy_44 (Fire Keese Wing) |
0x00000034 |
Location_RemainsWater 4 |
Item_Enemy_45 (Electric Keese Wing) |
0x00000035 |
Location_RemainsFire 5 |
Item_Enemy_46 (Ice Keese Wing) |
0x00000036 |
Location_RemainsElectric 6 |
Item_Enemy_47 (Shard of Dinraal's Fang) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Item_Enemy_48 (Shard of Dinraal's Horn) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Item_Enemy_49 (Naydra's Scale) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Item_Enemy_50 (Naydra's Claw) |
0x00000030 |
LizarfosSeries_Counter 2 |
Item_Enemy_51 (Shard of Naydra's Fang) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_Enemy_52 (Shard of Naydra's Horn) |
0x00000032 |
RinelSeries_Counter 2 |
Item_Enemy_53 (Farosh's Scale) |
0x00000033 |
Location_RemainsWind 3 |
Item_Enemy_54 (Farosh's Claw) |
0x00000034 |
Location_RemainsWater 4 |
Item_Enemy_55 (Shard of Farosh's Fang) |
0x00000035 |
Location_RemainsFire 5 |
Item_Enemy_56 (Shard of Farosh's Horn) |
0x00000036 |
Location_RemainsElectric 6 |
Item_Enemy_57 (Octo Balloon) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Item_Roast_01 (Seared Steak) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_Roast_02 (Roasted Bird Drumstick) |
0x00000032 |
RinelSeries_Counter 2 |
Item_Roast_03 (Baked Apple) |
0x00000033 |
Location_RemainsWind 3 |
Item_Roast_04 (Toasty Stamella Shroom) |
0x00000034 |
Location_RemainsWater 4 |
Item_Roast_05 (Toasted Hearty Truffle) |
0x00000035 |
Location_RemainsFire 5 |
Item_Roast_06 (Toasty Hylian Shroom) |
0x00000036 |
Location_RemainsElectric 6 |
Item_Roast_07 (Roasted Wildberry) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Item_Roast_08 (Roasted Voltfruit) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Item_Roast_09 (Roasted Hearty Durian) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Item_Roast_10 (Baked Palm Fruit) |
0x00000030 |
LizarfosSeries_Counter 2 |
Item_Roast_11 (Roasted Mighty Bananas) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_Roast_12 (Roasted Hydromelon) |
0x00000032 |
RinelSeries_Counter 2 |
Item_Roast_13 (Charred Pepper) |
0x00000033 |
Location_RemainsWind 3 |
Item_Roast_15 (Baked Foritifed Pumpkin) |
0x00000035 |
Location_RemainsFire 5 |
Item_Roast_16 (Roasted Lotus Seeds) |
0x00000036 |
Location_RemainsElectric 6 |
Item_Roast_18 (Roasted Radish) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Item_Roast_19 (Roasted Big Radish) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Item_Roast_24 (Roasted Swift Carrot) |
0x00000034 |
Location_RemainsWater 4 |
Item_Roast_27 (Roasted Mighty Thistle) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Item_Roast_28 (Roasted Armoranth) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Item_Roast_31 (Toasty Chillshroom) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_Roast_32 (Toasty Sunshroom) |
0x00000032 |
RinelSeries_Counter 2 |
Item_Roast_33 (Toasty Zapshroom) |
0x00000033 |
Location_RemainsWind 3 |
Item_Roast_36 (Toasty Rushroom) |
0x00000036 |
Location_RemainsElectric 6 |
Item_Roast_37 (Toasty Razorshroom) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Item_Roast_38 (Toasty Ironshroom) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Item_Roast_39 (Toasty Silent Shroom) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Item_Roast_40 (Seared Prime Steak) |
0x00000030 |
LizarfosSeries_Counter 2 |
Item_Roast_41 (Roasted Bird Thigh) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_Roast_45 (Seared Gourmet Steak) |
0x00000035 |
Location_RemainsFire 5 |
Item_Roast_46 (Roasted Whole Bird) |
0x00000036 |
Location_RemainsElectric 6 |
Item_Roast_48 (Roasted Acorn) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Item_Roast_49 (Toasted Big Hearty Truffle) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Item_Roast_50 (Roasted Endura Carrot) |
0x00000030 |
LizarfosSeries_Counter 2 |
Item_Roast_51 (Campfire Egg) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_Roast_52 (Roasted Tree Nut) |
0x00000032 |
RinelSeries_Counter 2 |
Item_Roast_53 (Toasty Endura Shroom) |
0x00000033 |
Location_RemainsWind 3 |
Obj_KorokNuts (Korok Seed) |
0x00000073 |
EquipTime_Armor_001_Head 7 |
Obj_ProofBook (Classified Envelope) |
0x0000006b |
Item_DefenceRate 2 |
Click to expand
Actor name | Handle | Flag |
---|---|---|
Item_RoastFish_01 (Roasted Bass) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_RoastFish_02 (Roasted Hearty Bass) |
0x00000032 |
RinelSeries_Counter 2 |
Item_RoastFish_03 (Roasted Trout) |
0x00000033 |
Location_RemainsWind 3 |
Item_RoastFish_04 (Roasted Hearty Salmon) |
0x00000034 |
Location_RemainsWater 4 |
Item_RoastFish_07 (Roasted Carp) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Item_RoastFish_09 (Roasted Porgy) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Item_RoastFish_11 (Blueshell Escargot) |
0x00000031 |
TartnackSeries_Counter 2 |
Item_RoastFish_13 (Sneaky River Escargot) |
0x00000033 |
Location_RemainsWind 3 |
Item_RoastFish_15 (Blackened Crab) |
0x00000035 |
Location_RemainsFire 5 |
Obj_HeroSoul_Rito (Revali's Gale) |
0x0000006f |
Item_ElectricLevelAdd 2 |
Obj_HeroSoul_Zora (Mipha's Grace) |
0x00000061 |
Counter_TerminalElectric 8 |
Weapon_Lsword_001 (Traveler's Claymore) |
0x00000031 |
TartnackSeries_Counter 2 |
Weapon_Lsword_002 (Soldier's Claymore) |
0x00000032 |
RinelSeries_Counter 2 |
Weapon_Lsword_003 (Knight's Claymore) |
0x00000033 |
Location_RemainsWind 3 |
Weapon_Lsword_004 (Boko Bat) |
0x00000034 |
Location_RemainsWater 4 |
Weapon_Lsword_005 (Spiked Boko Bat) |
0x00000035 |
Location_RemainsFire 5 |
Weapon_Lsword_006 (Dragonbone Boko Bat) |
0x00000036 |
Location_RemainsElectric 6 |
Weapon_Lsword_010 (Moblin Club) |
0x00000030 |
LizarfosSeries_Counter 2 |
Weapon_Lsword_011 (Spiked Moblin Club) |
0x00000031 |
TartnackSeries_Counter 2 |
Weapon_Lsword_012 (Dragonbone Moblin Club) |
0x00000032 |
RinelSeries_Counter 2 |
Weapon_Lsword_013 (Ancient Battle Axe) |
0x00000033 |
Location_RemainsWind 3 |
Weapon_Lsword_014 (Ancient Battle Axe+) |
0x00000034 |
Location_RemainsWater 4 |
Weapon_Lsword_015 (Ancient Battle Axe++) |
0x00000035 |
Location_RemainsFire 5 |
Weapon_Lsword_016 (Lynel Crusher) |
0x00000036 |
Location_RemainsElectric 6 |
Weapon_Lsword_017 (Mighty Lynel Crusher) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Weapon_Lsword_018 (Savage Lynel Crusher) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Weapon_Lsword_019 (Moblin Arm) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Weapon_Lsword_020 (Rusty Claymore) |
0x00000030 |
LizarfosSeries_Counter 2 |
Weapon_Lsword_023 (Ancient Bladesaw) |
0x00000033 |
Location_RemainsWind 3 |
Weapon_Lsword_024 (Royal Claymore) |
0x00000034 |
Location_RemainsWater 4 |
Weapon_Lsword_027 (Silver Longsword) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Weapon_Lsword_029 (Golden Claymore) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Weapon_Lsword_030 (Double Axe) |
0x00000030 |
LizarfosSeries_Counter 2 |
Weapon_Lsword_031 (Iron Sledgehammer) |
0x00000031 |
TartnackSeries_Counter 2 |
Weapon_Lsword_032 (Woodcutter's Axe) |
0x00000032 |
RinelSeries_Counter 2 |
Weapon_Lsword_033 (Great Flameblade) |
0x00000033 |
Location_RemainsWind 3 |
Weapon_Lsword_034 (Great Frostblade) |
0x00000034 |
Location_RemainsWater 4 |
Weapon_Lsword_035 (Great Thunderblade) |
0x00000035 |
Location_RemainsFire 5 |
Weapon_Lsword_036 (Cobble Crusher) |
0x00000036 |
Location_RemainsElectric 6 |
Weapon_Lsword_037 (Stone Smasher) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Weapon_Lsword_038 (Boat Oar) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Weapon_Lsword_041 (Eightfold Longblade) |
0x00000031 |
TartnackSeries_Counter 2 |
Weapon_Lsword_045 (Farming Hoe) |
0x00000035 |
Location_RemainsFire 5 |
Weapon_Lsword_047 (Royal Guard's Claymore) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Weapon_Lsword_051 (Giant Boomerang) |
0x00000031 |
TartnackSeries_Counter 2 |
Weapon_Lsword_054 (Boulder Breaker) |
0x00000034 |
Location_RemainsWater 4 |
Weapon_Lsword_055 (Edge of Duality) |
0x00000035 |
Location_RemainsFire 5 |
Weapon_Lsword_056 (Korok Leaf) |
0x00000036 |
Location_RemainsElectric 6 |
Weapon_Lsword_057 (Sword of the Six Sages) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Weapon_Lsword_059 (Biggoron's Sword) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Weapon_Lsword_060 (Fierce Deity Sword) |
0x00000030 |
LizarfosSeries_Counter 2 |
Weapon_Lsword_074 (Windcleaver) |
0x00000034 |
Location_RemainsWater 4 |
Weapon_Lsword_097 (Biggoron's Sword) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Weapon_Shield_001 (Wooden Shield) |
0x00000031 |
TartnackSeries_Counter 2 |
Weapon_Shield_002 (Soldier's Shield) |
0x00000032 |
RinelSeries_Counter 2 |
Weapon_Shield_003 (Knight's Shield) |
0x00000033 |
Location_RemainsWind 3 |
Weapon_Shield_004 (Boko Shield) |
0x00000034 |
Location_RemainsWater 4 |
Weapon_Shield_005 (Spiked Boko Shield) |
0x00000035 |
Location_RemainsFire 5 |
Weapon_Shield_006 (Dragonbone Boko Shield) |
0x00000036 |
Location_RemainsElectric 6 |
Weapon_Shield_007 (Lizal Shield) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Weapon_Shield_008 (Reinforced Lizal Shield) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Weapon_Shield_009 (Steel Lizal Shield) |
0x00000039 |
RemainsWind_SmallKeyNum 2 |
Weapon_Shield_013 (Guardian Shield) |
0x00000033 |
Location_RemainsWind 3 |
Weapon_Shield_014 (Guardian Shield+) |
0x00000034 |
Location_RemainsWater 4 |
Weapon_Shield_015 (Guardian Shield++) |
0x00000035 |
Location_RemainsFire 5 |
Weapon_Shield_016 (Lynel Shield) |
0x00000036 |
Location_RemainsElectric 6 |
Weapon_Shield_017 (Mighty Lynel Shield) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Weapon_Shield_018 (Savage Lynel Shield) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Weapon_Shield_021 (Rusty Shield) |
0x00000031 |
TartnackSeries_Counter 2 |
Weapon_Shield_022 (Royal Shield) |
0x00000032 |
RinelSeries_Counter 2 |
Weapon_Shield_023 (Forest Dweller's Shield) |
0x00000033 |
Location_RemainsWind 3 |
Weapon_Shield_025 (Silver Shield) |
0x00000035 |
Location_RemainsFire 5 |
Weapon_Shield_026 (Gerudo Shield) |
0x00000036 |
Location_RemainsElectric 6 |
Weapon_Shield_030 (Hylian Shield) |
0x00000030 |
LizarfosSeries_Counter 2 |
Weapon_Shield_031 (Hunter's Shield) |
0x00000031 |
TartnackSeries_Counter 2 |
Weapon_Shield_032 (Fisherman's Shield) |
0x00000032 |
RinelSeries_Counter 2 |
Weapon_Shield_033 (Royal Guard's Shield) |
0x00000033 |
Location_RemainsWind 3 |
Weapon_Shield_034 (Emblazoned Shield) |
0x00000034 |
Location_RemainsWater 4 |
Weapon_Shield_035 (Traveler's Shield) |
0x00000035 |
Location_RemainsFire 5 |
Weapon_Shield_036 (Radiant Shield) |
0x00000036 |
Location_RemainsElectric 6 |
Weapon_Shield_037 (Daybreaker) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Weapon_Shield_038 (Ancient Shield) |
0x00000038 |
RemainsFire_SmallKeyNum 2 |
Weapon_Shield_040 (Pot Lid) |
0x00000030 |
LizarfosSeries_Counter 2 |
Weapon_Shield_041 (Shield of the Mind's Eye) |
0x00000031 |
TartnackSeries_Counter 2 |
Weapon_Shield_042 (Kite Shield) |
0x00000032 |
RinelSeries_Counter 2 |
Weapon_Shield_057 (Hero's Shield) |
0x00000037 |
RemainsWater_SmallKeyNum 2 |
Click to expand
Actor name | Handle | Flag |
---|---|---|
Obj_DLC_HeroSeal_Rito (Medoh's Emblem) |
0x0000006f |
Clear_Dungeon059 9 |
Obj_DLC_HeroSeal_Zora (Ruta's Emblem) |
0x00000061 |
Clear_Dungeon045 10 |
Obj_DLC_HeroSoul_Rito (Revali's Gale +) |
0x0000006f |
Clear_Dungeon059 9 |
Obj_DLC_HeroSoul_Zora (Mipha's Grace +) |
0x00000061 |
Clear_Dungeon045 10 |
Some of these flag reads are conditional, and their conditions include member
variables. These members corresponding to the following members of PouchItem
:
Member/Offset | PouchItem Member |
Result |
---|---|---|
mBloodyEndReserveTimerFlag (0x124 ) |
Most significant dword of firstItem.mIngredients.mWork[1].elem.mStringTop |
true |
mPlayedDemo103Or997 (0x14d ) |
firstItem.mIngredients.mWork[1].elem.mBuffer[0x21] |
true if a true flag hasn't been written here yet and firstItem 's 2nd ingredient, if any, is not the Picture of the Champions, otherwise false |
mFindDungeonActivated (0x14e ) |
firstItem.mIngredients.mWork[1].elem.mBuffer[0x22] |
Whether firstItem 's 2nd ingredient, if any, is not the Picture of the Champions |
if (mNewTime >= 0.0 && (
mTimeUpdateMode == TimeUpdateMode::Forced
|| mTimeUpdateMode == TimeUpdateMode::Normal
)) {
mTime = mNewTime;
mNewTime = -1.0;
wm->getSkyMgr()->onTimeUpdate();
}
mNewTime
is at offset0xc0
, which corresponds to the least significant dword offirstItem.mIngredients.mWork[0].elem.mStringTop
. Since>= 0.0
is a sign check, both0.0
and-0.0
are equal to0.0
, and the sign is the most significant bit of a float, this is essentially a<= 0x80000000
check. Due to ASLR, this is unpredictable, but has a slightly higher chance of beingfalse
due to addresses0x0000000000000000-0x0000000007ffffff
being outside the ASLR range.mTimeUpdateMode
is at offset0x149
, which corresponds tofirstItem.mIngredients.mWork[1].elem.mBuffer[0x1d]
. Actor names will never contain the byte0xff
(TimeUpdateMode::Forced
), so the only option is for this to be0x00
(TimeUpdateMode::Normal
), meaning the name must be at most0x1d
(29) bytes long for the condition to betrue
. This is true for every item except the Picture of the Champions (Get_TwnObj_DLC_MemorialPicture_A_01
), which has the longest actor name of any item at 35 bytes. The condition will also betrue
iffirstItem
is not a meal or only has 1 ingredient.
If the condition is true
, the following will happen:
mNewTime
is read from offset0xc0
, which corresponds to the least significant dword offirstItem.mIngredients.mWork[0].elem.mStringTop
, and copied tomTime
, which is at offset0xb8
and corresponds to the least significant dword offirstItem.mIngredients.mWork[0].elem
's vptr.mNewTime
(least significant dword offirstItem.mIngredients.mWork[0].elem.mStringTop
) is set to-1.0f
, or0xbf800000
.
if (mNeedToHandleNewDay) {
handleNewDay(HandleNewDayMode::Forced);
mNeedToHandleNewDay = false;
}
mNeedToHandleNewDay
is at offset 0x148
, which corresponds to
firstItem.mIngredients.mWork[1].elem.mBuffer[0x1c]
. Since a
bool
being anything other than true
or false
is UB, checking the
executable shows that this specific check uses a cbz
(Compare and Branch on
Zero) instruction. Similarly to the mTimeUpdateMode
check, the name must be
longer than 0x1c
(28) bytes for this byte to be nonzero, and, again, the only
item with an actor name this long is the Picture of the Champions, so this will
only be true
if firstItem
is a meal with at least 2 ingredients and its
second ingredient is the Picture of the Champions.
// void TimeMgr::handleNewDay(HandleNewDayMode mode)
++mNumberOfDays;
mNumberOfDays
is at offset 0x13c
, which corresponds to
firstItem.mIngredients.mWork[1].elem.mBuffer[0x10:0x14]
, so mBuffer[0x10]
will be incremented, either changing the ingredient, invalidating it, or adding
a 01
byte to the end (also invalidating it).
if (mode == HandleNewDayMode::Normal && wm->getEnvMgr()->isBloodMoonNight()) {
// ...
}
mode
is HandleNewDayMode::Forced
, so the condition is false
.
if (gdm) {
gdm->getBool(mBloodyDayFlag, &mWasBloodyDay);
if (playerHasLeftPlateau() && mBloodMoonTimer > 7_days) {
mBloodMoonTimer = 0.0;
gdm->setBool(true, mBloodyDayFlag);
}
else mBloodyEndReserveTimer = 150;
}
// worldTimeMgr.cpp
static bool playerHasLeftPlateau() {
bool left_plateau = false;
gdt::Manager::instance()->getParamBypassPerm().get().getBool(
&left_plateau, "FirstTouchdown"
);
return left_plateau;
}
// worldTimeMgr.h
constexpr float operator""_days(unsigned long long days) {
return timeToFloat(static_cast<int>(24 * days), 0);
}
constexpr float timeToFloat(int h, int m) {
return float(h) * 15.0f + float(m) * (15.0f / 60.0f);
}
mBloodyDayFlag
is at offset 0x11c
, which corresponds to the most significant
dword of firstItem.mIngredients.mWork[1].elem
's vptr. This allows reading any
boolean flag in the range 0x00-0x7f
into mWasBloodyDay
, which is at offset
0x152
and corresponds to firstItem.mIngredients.mWork[1].elem.mBuffer[0x26]
.
Since no item actor names are longer than or equal to 0x26
(38) bytes, this is
effectively a no-op.
mBloodMoonTimer
is at offset 0xc8
, which corresponds to
firstItem.mIngredients.mWork[0].elem.mBufferSize
. Since this is always 0x40
,
interpreting it as an f32
gives 8.96831017167882925391e-44
. This is not
greater than 2520.0f
, so mBloodyEndReserveTimer
(offset 0x144
,
firstItem.mIngredients.mWork[1].elem.mBuffer[0x18:0x1c]
) is set to 150
(0x96
), invalidating the actor name if it was the Picture of the Champions.
// worldTimeMgr.cpp
NewDayEvent event;
if (mNewDaySignal.getNumSlots() > 0) mNewDaySignal.emit(event);
sead::DelegateEvent<const NewDayEvent &> mNewDaySignal;
// seadDelegateEventSlot.h
int getNumSlots() const { return mList.size(); }
SlotList mList;
using SlotList = TList<Slot *>;
// seadTList.h
template <typename T>
class TList : public ListImpl {
// ...
}
// seadListImpl.h
s32 size() const { return mCount; }
s32 mCount;
// seadDelegateEventSlot.h
void emit(T arg) {
for (auto &slot_node : mList.robustRange()) slot_node.mData->invoke_(arg);
}
// seadTList.h
RobustRange robustRange() const { return {*this}; }
struct RobustRange {
auto begin() const { return mList.robustBegin(); }
auto end() const { return mList.robustEnd(); }
const TList &mList;
}
robustIterator robustBegin() const {
return robustIterator(static_cast<TListNode<T> *>(mStartEnd.next()));
}
robustIterator robustEnd() const {
return robustIterator(static_cast<TListNode<T> *>(
const_cast<ListNode *>(&mStartEnd)
));
}
class robustIterator {
public:
explicit robustIterator(TListNode<T> *ptr) : mPtr(ptr) {
mPtrNext = static_cast<TListNode<T> *>(mPtr->next());
}
robustIterator &operator++() {
mPtr = mPtrNext;
mPtrNext = static_cast<TListNode<T> *>(mPtrNext->next());
return *this;
}
robustIterator operator++() {
robustIterator copy = *this;
mPtr = mPtrNext;
mPtrNext = static_cast<TListNode<T> *>(mPtrNext->next());
return copy;
}
TListNode<T> &operator*() const { return *mPtr; }
TListNode<T> *operator->() const { return mPtr; }
friend bool operator==(robustIterator it1, robustIterator it2) {
return it1.mPtr == it2.mPtr;
}
friend bool operator!=(robustIterator it1, robustIterator it2) {
return !(it1 == it2);
}
private:
TListNode<T> *mPtr;
TListNode<T> *mPtrNext;
};
// seadListImpl.h
ListNode mStartEnd;
ListNode *next() const { return mNext; }
ListNode *mNext = nullptr;
// seadTList.h
T mData;
// seadDelegateEventSlot.h
void invoke_(T arg) { if (mDelegatePtr) (*mDelegatePtr)(arg); }
IDelegate1<T> *mDelegatePtr = nullptr;
// seadDelegate.h
template <typename A1>
class IDelegate1 {
virtual void invoke(A1 a1) = 0;
// ...
void operator()(A1 a1) { return invoke(a1); }
};
mNewDaySignal
is at offset 0x80
, which corresponds to
firstItem.mData.mSellPrice
/firstItem.mData.mModifier
,
firstItem.mData.mEffect
, firstItem.mData.mEffectLevel
, padding,
firstItem.mIngredients.mPtrNum
, firstItem.mIngredients.mPtrNumMax
, and
firstItem.mIngredients.mPtrs
. mList
is at offset 0x8
in
sead::DelegateEvent<T>
, and mCount
is at offset 0x10
in sead::ListImpl
,
so getNumSlots()
returns the s32
at offset 0x18
from mNewDaySignal
,
which is the least significant dword of firstItem.mIngredients.mPtrs
. This
will never be 0
, so the only requirement is for it to be positive, or for the
sign bit to be 0
. This is unpredictable due to ASLR. If the condition is
true
, the following will happen:
- The
for
loop begins iteration atmList.robustBegin()
, which isrobustIterator(static_cast<TListNode<T> *>(mStartEnd.next()))
.mStartEnd
is at offset0x0
insead::ListImpl
, andmNext
is at offset0x8
insead::ListNode
, so a pointer is read from offset0x10
frommNewDaySignal
, which ismPtrNum
andmPtrNumMax
.mPtrNumMax
will always be5
, and there is very little control overmPtrNum
, being0-5
, so this will not point to a valid pointer. It is immediately dereferenced with->
inexplicit robustIterator(TListNode<T> *ptr)
, crashing the game.
Footnotes
-
Time you have worn the Hylian Hood ★★★ for, in seconds ↩
-
Unused, always 0 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19 ↩20 ↩21 ↩22 ↩23 ↩24 ↩25 ↩26 ↩27 ↩28 ↩29 ↩30 ↩31 ↩32 ↩33 ↩34 ↩35 ↩36 ↩37 ↩38 ↩39 ↩40 ↩41 ↩42 ↩43 ↩44 ↩45 ↩46 ↩47 ↩48 ↩49 ↩50 ↩51 ↩52 ↩53 ↩54 ↩55 ↩56 ↩57 ↩58 ↩59 ↩60 ↩61 ↩62 ↩63 ↩64 ↩65 ↩66 ↩67 ↩68 ↩69 ↩70 ↩71 ↩72 ↩73 ↩74 ↩75 ↩76 ↩77 ↩78 ↩79 ↩80 ↩81 ↩82 ↩83 ↩84 ↩85 ↩86 ↩87 ↩88 ↩89 ↩90 ↩91 ↩92 ↩93 ↩94 ↩95 ↩96 ↩97 ↩98 ↩99 ↩100 ↩101
-
Times you have visited Divine Beast Vah Medoh ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18 ↩19
-
Times you have visited Divine Beast Vah Ruta ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18
-
Times you have visited Divine Beast Vah Rudania ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17 ↩18
-
Times you have visited Divine Beast Vah Naboris ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11 ↩12 ↩13 ↩14 ↩15 ↩16 ↩17
-
Time you have worn the Hylian Hood for, in seconds ↩
-
Number of activated terminals in Divine Beast Vah Naboris ↩