diff --git a/data/dynos.c.h b/data/dynos.c.h index 7d00447af..fb40b2f1b 100644 --- a/data/dynos.c.h +++ b/data/dynos.c.h @@ -79,6 +79,7 @@ struct GraphNode* dynos_model_load_dl(u32* aId, enum ModelPool aModelPool, u8 aL struct GraphNode* dynos_model_store_geo(u32* aId, enum ModelPool aModelPool, void* aAsset, struct GraphNode* aGraphNode); struct GraphNode* dynos_model_get_geo(u32 aId); void dynos_model_overwrite_slot(u32 srcSlot, u32 dstSlot); +Gfx *dynos_model_duplicate_displaylist(Gfx* gfx); u32 dynos_model_get_id_from_asset(void* aAsset); u32 dynos_model_get_id_from_graph_node(struct GraphNode* aGraphNode); void dynos_model_clear_pool(enum ModelPool aModelPool); diff --git a/data/dynos.cpp.h b/data/dynos.cpp.h index a498cf3e1..a4da960df 100644 --- a/data/dynos.cpp.h +++ b/data/dynos.cpp.h @@ -972,6 +972,7 @@ struct GraphNode* DynOS_Model_GetGeo(u32 aId); u32 DynOS_Model_GetIdFromAsset(void* asset); u32 DynOS_Model_GetIdFromGraphNode(struct GraphNode* aNode); void DynOS_Model_OverwriteSlot(u32 srcSlot, u32 dstSlot); +Gfx *DynOS_Model_Duplicate_DisplayList(Gfx* aGfx); void DynOS_Model_ClearPool(enum ModelPool aModelPool); // diff --git a/data/dynos_c.cpp b/data/dynos_c.cpp index 758f46b2c..5e7a0811b 100644 --- a/data/dynos_c.cpp +++ b/data/dynos_c.cpp @@ -270,6 +270,10 @@ void dynos_model_overwrite_slot(u32 srcSlot, u32 dstSlot) { DynOS_Model_OverwriteSlot(srcSlot, dstSlot); } +Gfx *dynos_model_duplicate_displaylist(Gfx* gfx) { + return DynOS_Model_Duplicate_DisplayList(gfx); +} + // -- other -- // void dynos_mod_shutdown(void) { diff --git a/data/dynos_mgr_models.cpp b/data/dynos_mgr_models.cpp index 90386fac2..9d8cfeed3 100644 --- a/data/dynos_mgr_models.cpp +++ b/data/dynos_mgr_models.cpp @@ -7,6 +7,7 @@ extern "C" { #include "engine/graph_node.h" #include "model_ids.h" #include "pc/lua/utils/smlua_model_utils.h" +#include "engine/display_list.h" } enum ModelLoadType { @@ -20,6 +21,7 @@ struct ModelInfo { void* asset; struct GraphNode* graphNode; enum ModelPool modelPool; + std::vector *duplicates; }; static struct DynamicPool* sModelPools[MODEL_POOL_MAX] = { 0 }; @@ -28,6 +30,10 @@ static std::map sAssetMap[MODEL_POOL_MAX]; static std::map> sIdMap; static std::map sOverwriteMap; +// An array of display list and/or vertex buffer duplicates for the current model processed in process_geo_layout +static std::vector *sCurrModelDuplicates = nullptr; +static std::vector sScheduledFree[MODEL_POOL_MAX]; + static u32 find_empty_id(bool aIsPermanent) { u32 id = aIsPermanent ? 9999 : VANILLA_ID_END + 1; s8 dir = aIsPermanent ? -1 : 1; @@ -90,6 +96,8 @@ static struct GraphNode* DynOS_Model_LoadCommonInternal(u32* aId, enum ModelPool return found.graphNode; } + sCurrModelDuplicates = new std::vector(); + // load geo struct GraphNode* node = NULL; switch (mlt) { @@ -103,7 +111,14 @@ static struct GraphNode* DynOS_Model_LoadCommonInternal(u32* aId, enum ModelPool node = aGraphNode; break; } - if (!node) { return NULL; } + if (!node) { + for (auto &duplicate : *sCurrModelDuplicates) { + free(duplicate); + } + delete sCurrModelDuplicates; + sCurrModelDuplicates = nullptr; + return NULL; + } // figure out id if (!*aId) { *aId = find_empty_id(aModelPool == MODEL_POOL_PERMANENT); } @@ -113,8 +128,10 @@ static struct GraphNode* DynOS_Model_LoadCommonInternal(u32* aId, enum ModelPool .id = *aId, .asset = aAsset, .graphNode = node, - .modelPool = aModelPool + .modelPool = aModelPool, + .duplicates = sCurrModelDuplicates, }; + sCurrModelDuplicates = nullptr; // store in maps sIdMap[*aId].push_back(info); @@ -224,12 +241,53 @@ void DynOS_Model_OverwriteSlot(u32 srcSlot, u32 dstSlot) { sOverwriteMap[srcSlot] = dstSlot; } +// Display lists need to be duplicated so they can be modified by mods +// also to prevent trying to write to read only memory for vanilla display lists +Gfx *DynOS_Model_Duplicate_DisplayList(Gfx* aGfx) { + if (!aGfx) { return nullptr; } + + u32 size = gfx_get_size(aGfx) * sizeof(Gfx); + Gfx *gfxDuplicate = (Gfx *) malloc(size); + memcpy(gfxDuplicate, aGfx, size); + + // Look for other display lists or vertices + for (u32 i = 0; i < size / sizeof(Gfx); i++) { + Gfx *cmd = gfxDuplicate + i; + u32 op = cmd->words.w0 >> 24; + + // Duplicate referenced display lists + if (op == G_DL) { + cmd->words.w1 = (uintptr_t) DynOS_Model_Duplicate_DisplayList((Gfx *) cmd->words.w1); + if (C0(cmd, 16, 1) == G_DL_NOPUSH) { break; } // This is a branch (jump), end of display list + } + + // Duplicate referenced vertices + if (op == G_VTX) { + u32 size = C0(cmd, 12, 8) * sizeof(Vtx); + Vtx *vtxDuplicate = (Vtx *) malloc(size); + memcpy(vtxDuplicate, (Vtx *) cmd->words.w1, size); + cmd->words.w1 = (uintptr_t) vtxDuplicate; + sCurrModelDuplicates->push_back(vtxDuplicate); + } + } + + sCurrModelDuplicates->push_back(gfxDuplicate); + + return gfxDuplicate; +} + void DynOS_Model_ClearPool(enum ModelPool aModelPool) { if (!sModelPools[aModelPool]) { return; } // schedule pool to be freed dynamic_pool_free_pool(sModelPools[aModelPool]); + // free scheduled duplicates + for (auto &duplicate : sScheduledFree[aModelPool]) { + free(duplicate); + } + sScheduledFree[aModelPool].clear(); + // clear overwrite if (aModelPool == MODEL_POOL_LEVEL) { sOverwriteMap.clear(); @@ -257,6 +315,15 @@ void DynOS_Model_ClearPool(enum ModelPool aModelPool) { info2++; } } + + // schedule duplicates to be freed + if (info.duplicates) { + for (auto &duplicate : *info.duplicates) { + sScheduledFree[aModelPool].push_back(duplicate); + } + delete info.duplicates; + info.duplicates = nullptr; + } } assetMap.clear(); diff --git a/src/engine/display_list.c b/src/engine/display_list.c new file mode 100644 index 000000000..dbc9c911d --- /dev/null +++ b/src/engine/display_list.c @@ -0,0 +1,25 @@ +#include "display_list.h" + +// Get the size of a display list by iterating +// until gsSPEndDisplayList or gsSPBranchList is found +u32 gfx_get_size(const Gfx* gfx) { + for (u32 i = 0;;) { + u32 op = (gfx + i)->words.w0 >> 24; + u32 cmdSize = 1; + switch (op) { + case G_DL: + if (C0(gfx + i, 16, 1) == G_DL_NOPUSH) { return i + 1; } // For displaylists that end with branches (jumps) + break; + case G_ENDDL: + return i + 1; + case G_TEXRECT: + case G_TEXRECTFLIP: + cmdSize = 3; + break; + case G_FILLRECT: + cmdSize = 2; + break; + } + i += cmdSize; + } +} diff --git a/src/engine/display_list.h b/src/engine/display_list.h new file mode 100644 index 000000000..c663b5949 --- /dev/null +++ b/src/engine/display_list.h @@ -0,0 +1,5 @@ +#include + +#define C0(cmd, pos, width) (((cmd)->words.w0 >> (pos)) & ((1U << width) - 1)) + +u32 gfx_get_size(const Gfx* gfx); diff --git a/src/engine/graph_node.c b/src/engine/graph_node.c index 992255cd4..d1eb67207 100644 --- a/src/engine/graph_node.c +++ b/src/engine/graph_node.c @@ -235,7 +235,7 @@ init_graph_node_translation_rotation(struct DynamicPool *pool, vec3s_copy(graphNode->translation, translation); vec3s_copy(graphNode->rotation, rotation); graphNode->node.flags = (drawingLayer << 8) | (graphNode->node.flags & 0xFF); - graphNode->displayList = displayList; + graphNode->displayList = dynos_model_duplicate_displaylist(displayList); } return graphNode; @@ -257,7 +257,7 @@ struct GraphNodeTranslation *init_graph_node_translation(struct DynamicPool *poo vec3s_copy(graphNode->translation, translation); graphNode->node.flags = (drawingLayer << 8) | (graphNode->node.flags & 0xFF); - graphNode->displayList = displayList; + graphNode->displayList = dynos_model_duplicate_displaylist(displayList); } return graphNode; @@ -278,7 +278,7 @@ struct GraphNodeRotation *init_graph_node_rotation(struct DynamicPool *pool, init_scene_graph_node_links(&graphNode->node, GRAPH_NODE_TYPE_ROTATION); vec3s_copy(graphNode->rotation, rotation); graphNode->node.flags = (drawingLayer << 8) | (graphNode->node.flags & 0xFF); - graphNode->displayList = displayList; + graphNode->displayList = dynos_model_duplicate_displaylist(displayList); } return graphNode; @@ -299,7 +299,7 @@ struct GraphNodeScale *init_graph_node_scale(struct DynamicPool *pool, graphNode->node.flags = (drawingLayer << 8) | (graphNode->node.flags & 0xFF); graphNode->scale = scale; graphNode->prevScale = scale; - graphNode->displayList = displayList; + graphNode->displayList = dynos_model_duplicate_displaylist(displayList); } return graphNode; @@ -369,7 +369,7 @@ struct GraphNodeAnimatedPart *init_graph_node_animated_part(struct DynamicPool * init_scene_graph_node_links(&graphNode->node, GRAPH_NODE_TYPE_ANIMATED_PART); vec3s_copy(graphNode->translation, translation); graphNode->node.flags = (drawingLayer << 8) | (graphNode->node.flags & 0xFF); - graphNode->displayList = displayList; + graphNode->displayList = dynos_model_duplicate_displaylist(displayList); } return graphNode; @@ -390,7 +390,7 @@ struct GraphNodeBillboard *init_graph_node_billboard(struct DynamicPool *pool, init_scene_graph_node_links(&graphNode->node, GRAPH_NODE_TYPE_BILLBOARD); vec3s_copy(graphNode->translation, translation); graphNode->node.flags = (drawingLayer << 8) | (graphNode->node.flags & 0xFF); - graphNode->displayList = displayList; + graphNode->displayList = dynos_model_duplicate_displaylist(displayList); } return graphNode; @@ -409,7 +409,7 @@ struct GraphNodeDisplayList *init_graph_node_display_list(struct DynamicPool *po if (graphNode != NULL) { init_scene_graph_node_links(&graphNode->node, GRAPH_NODE_TYPE_DISPLAY_LIST); graphNode->node.flags = (drawingLayer << 8) | (graphNode->node.flags & 0xFF); - graphNode->displayList = displayList; + graphNode->displayList = dynos_model_duplicate_displaylist(displayList); } return graphNode; diff --git a/src/pc/lua/utils/smlua_gfx_utils.c b/src/pc/lua/utils/smlua_gfx_utils.c index f944f4393..3bfa7a6d1 100644 --- a/src/pc/lua/utils/smlua_gfx_utils.c +++ b/src/pc/lua/utils/smlua_gfx_utils.c @@ -3,6 +3,7 @@ #include "game/rendering_graph_node.h" #include "game/skybox.h" #include "geo_commands.h" +#include "engine/display_list.h" void set_override_fov(f32 fov) { gOverrideFOV = fov; @@ -109,8 +110,6 @@ void set_skybox_color(u8 index, u8 value) { /// -#define C0(pos, width) ((cmd->words.w0 >> (pos)) & ((1U << width) - 1)) - // Assumes the current microcode is Fast3DEX2 Extended (default for pc port) void gfx_parse(Gfx* cmd, LuaFunction func) { if (!cmd) { return; } @@ -121,7 +120,7 @@ void gfx_parse(Gfx* cmd, LuaFunction func) { u32 op = cmd->words.w0 >> 24; switch (op) { case G_DL: - if (C0(16, 1) == 0) { + if (C0(cmd, 16, 1) == G_DL_PUSH) { gfx_parse((Gfx *) cmd->words.w1, func); } else { cmd = (Gfx *) cmd->words.w1; @@ -160,7 +159,7 @@ Vtx *gfx_get_vtx(Gfx* cmd, u16 offset) { if (op != G_VTX) { return NULL; } if (cmd->words.w1 == 0) { return NULL; } - u16 numVertices = C0(12, 8); + u16 numVertices = C0(cmd, 12, 8); if (offset >= numVertices) { return NULL; } return &((Vtx *) cmd->words.w1)[offset];