Fix anchor issues (#7362)

Avoid use-after-free with dummy players,

Sync silver rupees, sync autoplanted beans
This commit is contained in:
Philip Dubé
2026-10-11 21:54:54 +00:00
committed by GitHub
parent c2325bd751
commit 88f98c63b6
3 changed files with 42 additions and 0 deletions
@@ -11,6 +11,7 @@
#include "soh/Enhancements/randomizer/fishsanity.h"
#include "soh/Enhancements/randomizer/static_data.h"
#include "soh/Enhancements/game-interactor/GameInteractor.h"
#include "soh/Enhancements/game-interactor/GameInteractor_Hooks.h"
#include "soh/SohGui/ImGuiUtils.h"
#include "soh/Notification/Notification.h"
#include "soh/SaveManager.h"
@@ -478,6 +479,9 @@ static void PlantAllBeans() {
gSaveContext.sceneFlags[patch.scene].swch |= 1 << patch.swchFlag;
if (gPlayState->sceneNum == patch.scene) {
Flags_SetSwitch(gPlayState, patch.swchFlag);
} else {
// Flags_SetSwitch fires this for the current scene, other scenes need it so Anchor syncs them
GameInteractor_ExecuteOnSceneFlagSet(patch.scene, FLAG_SCENE_SWITCH, patch.swchFlag);
}
}
ObjBean* bean = (ObjBean*)Actor_Find(&gPlayState->actorCtx, ACTOR_OBJ_BEAN, ACTORCAT_BG);
+6
View File
@@ -254,4 +254,10 @@ void DummyPlayer_Destroy(Actor* actor, PlayState* play) {
// asserts. Set the id back to ACTOR_PLAYER so that `numLoaded` will be decremented
// correctly.
actor->id = ACTOR_PLAYER;
// Drop client's pointer so packet handlers don't use after free
uint32_t clientId = Anchor::Instance->GetDummyPlayerClientId(actor);
if (Anchor::Instance->clients.contains(clientId) && Anchor::Instance->clients[clientId].player == (Player*)actor) {
Anchor::Instance->clients[clientId].player = nullptr;
}
}
@@ -104,18 +104,50 @@ inline void from_json(const json& j, SohStats& sohStats) {
j.at("fileCreatedAt").get_to(sohStats.fileCreatedAt);
}
#define SILVER_RUPEE_FIELDS(X) \
X(silverShadowBlades) \
X(silverShadowPit) \
X(silverShadowSpikes) \
X(silverSpiritChild) \
X(silverSpiritSun) \
X(silverSpiritBoulders) \
X(silverBotw) \
X(silverIceCavernBlades) \
X(silverIceCavernBlock) \
X(silverGtgSlope) \
X(silverGtgLava) \
X(silverGtgWater) \
X(silverGanonLight) \
X(silverGanonForest) \
X(silverGanonFire) \
X(silverGanonSpirit) \
X(silverMqDodongosCavern) \
X(silverMqShadowInvisibleBlades) \
X(silverMqSpiritLobby) \
X(silverMqSpiritBigWall) \
X(silverMqGanonWater) \
X(silverMqGanonShadow)
inline void to_json(json& j, const ShipRandomizerSaveContextData& shipRandomizerSaveContextData) {
j = json{
{ "triforcePiecesCollected", shipRandomizerSaveContextData.triforcePiecesCollected },
{ "bombchuUpgradeLevel", shipRandomizerSaveContextData.bombchuUpgradeLevel },
};
#define SILVER_TO_JSON(field) j[#field] = shipRandomizerSaveContextData.field;
SILVER_RUPEE_FIELDS(SILVER_TO_JSON)
#undef SILVER_TO_JSON
}
inline void from_json(const json& j, ShipRandomizerSaveContextData& shipRandomizerSaveContextData) {
j.at("triforcePiecesCollected").get_to(shipRandomizerSaveContextData.triforcePiecesCollected);
j.at("bombchuUpgradeLevel").get_to(shipRandomizerSaveContextData.bombchuUpgradeLevel);
#define SILVER_FROM_JSON(field) shipRandomizerSaveContextData.field = j.value(#field, (s8)0);
SILVER_RUPEE_FIELDS(SILVER_FROM_JSON)
#undef SILVER_FROM_JSON
}
#undef SILVER_RUPEE_FIELDS
inline void to_json(json& j, const ShipQuestSpecificSaveContextData& shipQuestSpecificSaveContextData) {
j = json{
{ "randomizer", shipQuestSpecificSaveContextData.randomizer },