From 7f71b169ddd6dc09f08063001dcc0a07b2fdb0a6 Mon Sep 17 00:00:00 2001 From: Goober5000 Date: Sun, 4 Oct 2026 13:08:30 -0400 Subject: [PATCH] map appended-submodel textures before changing the virtual POF When appending submodels would take the combined POF past MAX_MODEL_TEXTURES, the limit was only noticed partway through, after the submodel array had been grown, subsystems copied, and n_textures incremented past the limit, which later loops over maps[] then overran. Work out the texture mapping first, and give up before touching the model. Also skip the -1 texture id of untextured flat polygons, which was being assigned a texture slot, and which replace_textures_used then used to index Num_verts and Num_polies. Co-Authored-By: Claude Opus 5.5 --- code/model/modelreplace.cpp | 38 +++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/code/model/modelreplace.cpp b/code/model/modelreplace.cpp index e7e1d2e756..f29858c1df 100644 --- a/code/model/modelreplace.cpp +++ b/code/model/modelreplace.cpp @@ -379,6 +379,26 @@ void VirtualPOFOperationAddSubmodel::process(polymodel* pm, model_read_deferred_ } if (!has_name_collision) { + //Assign new texture indices before changing anything, so that running out of textures leaves the model intact + SCP_vector> texturesUsed; + SCP_map textureIDReplace; + int new_n_textures = pm->n_textures; + for (int submodel : to_copy_submodels) { + texturesUsed.emplace_back(model_get_textures_used(appendingPM, submodel)); + for (int textureID : texturesUsed.back()) { + //Flat polygons are untextured + if (textureID < 0) + continue; + if (textureIDReplace.find(textureID) == textureIDReplace.end()) + textureIDReplace.emplace(textureID, new_n_textures++); + } + } + if (new_n_textures > MAX_MODEL_TEXTURES) { + Warning(LOCATION, "Failed to add submodel %s of POF %s to virtual POF %s, combined POF has too many (over %d) textures.", subobjNameSrc.c_str(), appendingPOF.c_str(), virtualPof.name.c_str(), MAX_MODEL_TEXTURES); + return; + } + pm->n_textures = new_n_textures; + int old_n_submodel = reallocate_and_copy_array(pm->submodel, pm->n_models, to_copy_submodels.size()); SCP_unordered_map replaceSubobjNo; @@ -397,8 +417,6 @@ void VirtualPOFOperationAddSubmodel::process(polymodel* pm, model_read_deferred_ int deltaDepth = (pm->submodel[dest_subobj_no].depth + 1) - appendingPM->submodel[src_subobj_no].depth; - SCP_map textureIDReplace; - //Copy over new data. This one needs to be fully free'd afterwards, so make sure to nullptr the respective pointers before freeing later for (int i = 0; i < (int)to_copy_submodels.size(); i++) { auto& newSubmodel = pm->submodel[i + old_n_submodel]; @@ -408,18 +426,10 @@ void VirtualPOFOperationAddSubmodel::process(polymodel* pm, model_read_deferred_ newSubmodel.depth += deltaDepth; //Store texture replacement indices - const auto& textureIDs = model_get_textures_used(appendingPM, to_copy_submodels[i]); - for (const auto& textureID : textureIDs) { - auto it = textureIDReplace.find(textureID); - if (it == textureIDReplace.end()) { - int newID = pm->n_textures++; - if (pm->n_textures > MAX_MODEL_TEXTURES) { - Warning(LOCATION, "Failed to add submodel %s of POF %s to virtual POF %s, combined POF has too many (over %d) textures.", subobjNameSrc.c_str(), appendingPOF.c_str(), virtualPof.name.c_str(), MAX_MODEL_TEXTURES); - return; - } - it = textureIDReplace.emplace(textureID, newID).first; - } - deferredTasks.texture_replacements[i + old_n_submodel].replacementIds[textureID] = it->second; + for (int textureID : texturesUsed[i]) { + if (textureID < 0) + continue; + deferredTasks.texture_replacements[i + old_n_submodel].replacementIds[textureID] = textureIDReplace.at(textureID); } //Clear old pointer to data