From c3fe8e62140ea11056262e1fd6110502e84a1f4a Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 18 Oct 2026 12:28:25 +0200 Subject: [PATCH 1/8] fix(hlod): Keep the aggregate and proxy arrays of HLod definitions HLodDefClass::Load_W3D reads the aggregate and proxy arrays of a W3D_CHUNK_HLOD, but Save wrote neither, so W3DView's Export LOD dropped them. Save_Aggregate_Array was never called, and if it had been, SubObjectArrayClass::Save_W3D would have nested the aggregates in a W3D_CHUNK_HLOD_LOD_ARRAY where Load_W3D cannot read them. There was no writer for proxies at all. Initialize, which W3DView uses after Record Screen Area and Include NULL LOD, copied only the LOD arrays. SubObjectArrayClass::Save_W3D now writes only the array contents, as its Load_W3D reads them and as max2w3d writes them. Save_Lod_Array wraps each LOD array in its chunk, and Save writes the aggregate array and the new proxy array after the LOD arrays, in max2w3d's order. Initialize copies the additional models as aggregates and shares the proxy array, and Free now also releases the aggregates. No HLod in the retail archives of either game has an aggregate or proxy array. A Google Test round-trips an HLod chunk with both arrays and one with only a LOD array, and saves a definition built from an HLod with an additional model. --- .../Libraries/Source/WWVegas/WW3D2/hlod.cpp | 133 +++++++++--- .../Libraries/Source/WWVegas/WW3D2/hlod.h | 3 + .../Libraries/Source/WWVegas/WW3D2/hlod.cpp | 133 +++++++++--- .../Libraries/Source/WWVegas/WW3D2/hlod.h | 3 + Tests/Google/Core/CMakeLists.txt | 1 + .../Libraries/WWVegas/WW3D2/hlod_test.cpp | 205 ++++++++++++++++++ 6 files changed, 408 insertions(+), 70 deletions(-) create mode 100644 Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 60de88bfb66..79ee1127787 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -43,6 +43,7 @@ * HLodDefClass::Save_Header -- writes the HLodDef header * * HLodDefClass::Save_Lod_Array -- Saves the lod array * * HLodDefClass::Save_Aggregate_Array -- Save the array of aggregate models * + * HLodDefClass::Save_Proxy_Array -- Save the array of proxies * * HLodDefClass::Load_W3D -- Loads this HLodDef from a W3d File * * HLodDefClass::read_header -- loads the HLodDef header from a W3d file * * HLodDefClass::read_proxy_array -- load the proxy names * @@ -344,6 +345,7 @@ void HLodDefClass::Free() Lod = nullptr; LodCount = 0; + Aggregates.Reset(); REF_PTR_RELEASE(ProxyArray); } @@ -408,6 +410,21 @@ void HLodDefClass::Initialize(HLodClass &src_lod) Lod[index].BoneIndex = bone_indicies; } } + + // TheSuperHackers @bugfix Also copy the aggregates and proxies, so that saving this definition keeps them. + // The screen size of the aggregates is 0, as max2w3d writes it. + Aggregates.ModelCount = src_lod.Get_Additional_Model_Count (); + if (Aggregates.ModelCount > 0) { + Aggregates.MaxScreenSize = 0.0f; + Aggregates.ModelName = W3DNEWARRAY char *[Aggregates.ModelCount]; + Aggregates.BoneIndex = W3DNEWARRAY int[Aggregates.ModelCount]; + for (int model_index = 0; model_index < Aggregates.ModelCount; model_index ++) { + Aggregates.ModelName[model_index] = ::strdup (src_lod.Peek_Additional_Model (model_index)->Get_Name ()); + Aggregates.BoneIndex[model_index] = src_lod.Get_Additional_Model_Bone (model_index); + } + } + + REF_PTR_SET (ProxyArray, src_lod.ProxyArray); } @@ -432,8 +449,11 @@ WW3DErrorType HLodDefClass::Save(ChunkSaveClass & csave) if (csave.Begin_Chunk (W3D_CHUNK_HLOD) == TRUE) { // Attempt to save the different sections of the aggregate definition + // TheSuperHackers @bugfix Also save the aggregate and proxy arrays, which Load_W3D reads, in the order max2w3d writes them. if ((Save_Header (csave) == WW3D_ERROR_OK) && - (Save_Lod_Array (csave) == WW3D_ERROR_OK)) { + (Save_Lod_Array (csave) == WW3D_ERROR_OK) && + (Save_Aggregate_Array (csave) == WW3D_ERROR_OK) && + (Save_Proxy_Array (csave) == WW3D_ERROR_OK)) { // Success! ret_val = WW3D_ERROR_OK; @@ -516,7 +536,11 @@ WW3DErrorType HLodDefClass::Save_Lod_Array(ChunkSaveClass &csave) for (int lod_index = 0; (lod_index < LodCount) && success; lod_index ++) { - success = Lod[lod_index].Save_W3D (csave); + success = (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE); + if (success) { + success = Lod[lod_index].Save_W3D (csave); + csave.End_Chunk (); + } } // Return the WW3DErrorType return code @@ -550,6 +574,49 @@ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) } +/*********************************************************************************************** + * HLodDefClass::Save_Proxy_Array -- Save the array of proxies * + * * + * The screen size in the array header is 0, as max2w3d writes it. * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + *=============================================================================================*/ +WW3DErrorType HLodDefClass::Save_Proxy_Array(ChunkSaveClass & csave) +{ + if ((ProxyArray != nullptr) && (ProxyArray->Length() > 0)) { + csave.Begin_Chunk(W3D_CHUNK_HLOD_PROXY_ARRAY); + + csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER); + W3dHLodArrayHeaderStruct header = { 0 }; + header.ModelCount = ProxyArray->Length(); + header.MaxScreenSize = 0.0f; + csave.Write(&header,sizeof(header)); + csave.End_Chunk(); + + for (int imodel=0; imodelLength(); ++imodel) { + ProxyRecordClass & proxy = (*ProxyArray)[imodel]; + W3dHLodSubObjectStruct subobjdef = { 0 }; + subobjdef.BoneIndex = proxy.Get_Bone_Index(); + ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); + subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; + + csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); + csave.Write(&subobjdef,sizeof(subobjdef)); + csave.End_Chunk(); + } + + csave.End_Chunk(); + } + return WW3D_ERROR_OK; +} + + /*********************************************************************************************** * HLodDefClass::Load_W3D -- Loads this HLodDef from a W3d File * * * @@ -840,52 +907,48 @@ bool HLodDefClass::SubObjectArrayClass::Save_W3D(ChunkSaveClass &csave) // Assume error bool ret_val = false; - // Begin a chunk that identifies the LOD array - if (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE) { + // TheSuperHackers @bugfix Write only the array contents, as Load_W3D reads them. The caller wraps them in + // W3D_CHUNK_HLOD_LOD_ARRAY or W3D_CHUNK_HLOD_AGGREGATE_ARRAY. - // Begin a chunk that identifies the LOD header - if (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) == TRUE) { + // Begin a chunk that identifies the LOD header + if (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) == TRUE) { - W3dHLodArrayHeaderStruct header = { 0 }; - header.ModelCount = ModelCount; - header.MaxScreenSize = MaxScreenSize; + W3dHLodArrayHeaderStruct header = { 0 }; + header.ModelCount = ModelCount; + header.MaxScreenSize = MaxScreenSize; - // Write the LOD header structure out to the chunk - ret_val = (csave.Write (&header, sizeof (header)) == sizeof (header)); + // Write the LOD header structure out to the chunk + ret_val = (csave.Write (&header, sizeof (header)) == sizeof (header)); - // End the header chunk - csave.End_Chunk (); - } + // End the header chunk + csave.End_Chunk (); + } - if (ret_val) { + if (ret_val) { - // Write all of this LOD's models to the file - for (int index = 0; - (index < ModelCount) && ret_val; - index ++) { + // Write all of this LOD's models to the file + for (int index = 0; + (index < ModelCount) && ret_val; + index ++) { - // Save this LOD sub-obj to the chunk - ret_val &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT) == TRUE); - if (ret_val) { + // Save this LOD sub-obj to the chunk + ret_val &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT) == TRUE); + if (ret_val) { - W3dHLodSubObjectStruct info = { 0 }; - info.BoneIndex = BoneIndex[index]; + W3dHLodSubObjectStruct info = { 0 }; + info.BoneIndex = BoneIndex[index]; - // Copy this model name into the structure - ::lstrcpyn (info.Name, ModelName[index], sizeof (info.Name)); - info.Name[sizeof (info.Name) - 1] = 0; + // Copy this model name into the structure + ::lstrcpyn (info.Name, ModelName[index], sizeof (info.Name)); + info.Name[sizeof (info.Name) - 1] = 0; - // Write the LOD sub-obj structure out to the chunk - ret_val &= (csave.Write (&info, sizeof (info)) == sizeof (info)); + // Write the LOD sub-obj structure out to the chunk + ret_val &= (csave.Write (&info, sizeof (info)) == sizeof (info)); - // End the sub-obj chunk - csave.End_Chunk (); - } + // End the sub-obj chunk + csave.End_Chunk (); } } - - // End the HLOD-Array chunk - csave.End_Chunk (); } // Return the true/false result code diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h index 02cc810ec9c..6149e53519a 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h @@ -257,6 +257,8 @@ class HLodClass : public Animatable3DObjClass // Current LOD Bias (affects recalculation of the Value array) float LODBias; + + friend class HLodDefClass; }; @@ -298,6 +300,7 @@ class HLodDefClass WW3DErrorType Save_Header (ChunkSaveClass &csave); WW3DErrorType Save_Lod_Array (ChunkSaveClass &csave); WW3DErrorType Save_Aggregate_Array(ChunkSaveClass & csave); + WW3DErrorType Save_Proxy_Array(ChunkSaveClass & csave); private: diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 9705de5602f..b496029d46c 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -43,6 +43,7 @@ * HLodDefClass::Save_Header -- writes the HLodDef header * * HLodDefClass::Save_Lod_Array -- Saves the lod array * * HLodDefClass::Save_Aggregate_Array -- Save the array of aggregate models * + * HLodDefClass::Save_Proxy_Array -- Save the array of proxies * * HLodDefClass::Load_W3D -- Loads this HLodDef from a W3d File * * HLodDefClass::read_header -- loads the HLodDef header from a W3d file * * HLodDefClass::read_proxy_array -- load the proxy names * @@ -344,6 +345,7 @@ void HLodDefClass::Free() Lod = nullptr; LodCount = 0; + Aggregates.Reset(); REF_PTR_RELEASE(ProxyArray); } @@ -408,6 +410,21 @@ void HLodDefClass::Initialize(HLodClass &src_lod) Lod[index].BoneIndex = bone_indicies; } } + + // TheSuperHackers @bugfix Also copy the aggregates and proxies, so that saving this definition keeps them. + // The screen size of the aggregates is 0, as max2w3d writes it. + Aggregates.ModelCount = src_lod.Get_Additional_Model_Count (); + if (Aggregates.ModelCount > 0) { + Aggregates.MaxScreenSize = 0.0f; + Aggregates.ModelName = W3DNEWARRAY char *[Aggregates.ModelCount]; + Aggregates.BoneIndex = W3DNEWARRAY int[Aggregates.ModelCount]; + for (int model_index = 0; model_index < Aggregates.ModelCount; model_index ++) { + Aggregates.ModelName[model_index] = ::strdup (src_lod.Peek_Additional_Model (model_index)->Get_Name ()); + Aggregates.BoneIndex[model_index] = src_lod.Get_Additional_Model_Bone (model_index); + } + } + + REF_PTR_SET (ProxyArray, src_lod.ProxyArray); } @@ -432,8 +449,11 @@ WW3DErrorType HLodDefClass::Save(ChunkSaveClass & csave) if (csave.Begin_Chunk (W3D_CHUNK_HLOD) == TRUE) { // Attempt to save the different sections of the aggregate definition + // TheSuperHackers @bugfix Also save the aggregate and proxy arrays, which Load_W3D reads, in the order max2w3d writes them. if ((Save_Header (csave) == WW3D_ERROR_OK) && - (Save_Lod_Array (csave) == WW3D_ERROR_OK)) { + (Save_Lod_Array (csave) == WW3D_ERROR_OK) && + (Save_Aggregate_Array (csave) == WW3D_ERROR_OK) && + (Save_Proxy_Array (csave) == WW3D_ERROR_OK)) { // Success! ret_val = WW3D_ERROR_OK; @@ -516,7 +536,11 @@ WW3DErrorType HLodDefClass::Save_Lod_Array(ChunkSaveClass &csave) for (int lod_index = 0; (lod_index < LodCount) && success; lod_index ++) { - success = Lod[lod_index].Save_W3D (csave); + success = (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE); + if (success) { + success = Lod[lod_index].Save_W3D (csave); + csave.End_Chunk (); + } } // Return the WW3DErrorType return code @@ -550,6 +574,49 @@ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) } +/*********************************************************************************************** + * HLodDefClass::Save_Proxy_Array -- Save the array of proxies * + * * + * The screen size in the array header is 0, as max2w3d writes it. * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + *=============================================================================================*/ +WW3DErrorType HLodDefClass::Save_Proxy_Array(ChunkSaveClass & csave) +{ + if ((ProxyArray != nullptr) && (ProxyArray->Length() > 0)) { + csave.Begin_Chunk(W3D_CHUNK_HLOD_PROXY_ARRAY); + + csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER); + W3dHLodArrayHeaderStruct header = { 0 }; + header.ModelCount = ProxyArray->Length(); + header.MaxScreenSize = 0.0f; + csave.Write(&header,sizeof(header)); + csave.End_Chunk(); + + for (int imodel=0; imodelLength(); ++imodel) { + ProxyRecordClass & proxy = (*ProxyArray)[imodel]; + W3dHLodSubObjectStruct subobjdef = { 0 }; + subobjdef.BoneIndex = proxy.Get_Bone_Index(); + ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); + subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; + + csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); + csave.Write(&subobjdef,sizeof(subobjdef)); + csave.End_Chunk(); + } + + csave.End_Chunk(); + } + return WW3D_ERROR_OK; +} + + /*********************************************************************************************** * HLodDefClass::Load_W3D -- Loads this HLodDef from a W3d File * * * @@ -840,52 +907,48 @@ bool HLodDefClass::SubObjectArrayClass::Save_W3D(ChunkSaveClass &csave) // Assume error bool ret_val = false; - // Begin a chunk that identifies the LOD array - if (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE) { + // TheSuperHackers @bugfix Write only the array contents, as Load_W3D reads them. The caller wraps them in + // W3D_CHUNK_HLOD_LOD_ARRAY or W3D_CHUNK_HLOD_AGGREGATE_ARRAY. - // Begin a chunk that identifies the LOD header - if (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) == TRUE) { + // Begin a chunk that identifies the LOD header + if (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) == TRUE) { - W3dHLodArrayHeaderStruct header = { 0 }; - header.ModelCount = ModelCount; - header.MaxScreenSize = MaxScreenSize; + W3dHLodArrayHeaderStruct header = { 0 }; + header.ModelCount = ModelCount; + header.MaxScreenSize = MaxScreenSize; - // Write the LOD header structure out to the chunk - ret_val = (csave.Write (&header, sizeof (header)) == sizeof (header)); + // Write the LOD header structure out to the chunk + ret_val = (csave.Write (&header, sizeof (header)) == sizeof (header)); - // End the header chunk - csave.End_Chunk (); - } + // End the header chunk + csave.End_Chunk (); + } - if (ret_val) { + if (ret_val) { - // Write all of this LOD's models to the file - for (int index = 0; - (index < ModelCount) && ret_val; - index ++) { + // Write all of this LOD's models to the file + for (int index = 0; + (index < ModelCount) && ret_val; + index ++) { - // Save this LOD sub-obj to the chunk - ret_val &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT) == TRUE); - if (ret_val) { + // Save this LOD sub-obj to the chunk + ret_val &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT) == TRUE); + if (ret_val) { - W3dHLodSubObjectStruct info = { 0 }; - info.BoneIndex = BoneIndex[index]; + W3dHLodSubObjectStruct info = { 0 }; + info.BoneIndex = BoneIndex[index]; - // Copy this model name into the structure - ::lstrcpyn (info.Name, ModelName[index], sizeof (info.Name)); - info.Name[sizeof (info.Name) - 1] = 0; + // Copy this model name into the structure + ::lstrcpyn (info.Name, ModelName[index], sizeof (info.Name)); + info.Name[sizeof (info.Name) - 1] = 0; - // Write the LOD sub-obj structure out to the chunk - ret_val &= (csave.Write (&info, sizeof (info)) == sizeof (info)); + // Write the LOD sub-obj structure out to the chunk + ret_val &= (csave.Write (&info, sizeof (info)) == sizeof (info)); - // End the sub-obj chunk - csave.End_Chunk (); - } + // End the sub-obj chunk + csave.End_Chunk (); } } - - // End the HLOD-Array chunk - csave.End_Chunk (); } // Return the true/false result code diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h index f48d74d0f2a..cb8e389197a 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h @@ -258,6 +258,8 @@ class HLodClass : public Animatable3DObjClass // Current LOD Bias (affects recalculation of the Value array) float LODBias; + + friend class HLodDefClass; }; @@ -299,6 +301,7 @@ class HLodDefClass WW3DErrorType Save_Header (ChunkSaveClass &csave); WW3DErrorType Save_Lod_Array (ChunkSaveClass &csave); WW3DErrorType Save_Aggregate_Array(ChunkSaveClass & csave); + WW3DErrorType Save_Proxy_Array(ChunkSaveClass & csave); private: diff --git a/Tests/Google/Core/CMakeLists.txt b/Tests/Google/Core/CMakeLists.txt index 8681be68f0f..b6b929b805e 100644 --- a/Tests/Google/Core/CMakeLists.txt +++ b/Tests/Google/Core/CMakeLists.txt @@ -5,6 +5,7 @@ set(COREI_GOOGLETEST_SRC GameEngine/Common/INITest.cpp GameEngineDevice/StdDevice/Common/StdLocalFileSystemTest.cpp Libraries/Compression/CompressionManagerTest.cpp + Libraries/WWVegas/WW3D2/hlod_test.cpp Libraries/WWVegas/WW3D2/soundrobj_test.cpp Libraries/WWVegas/WWMath/WWMathCalls.h Libraries/WWVegas/WWMath/WWMathDumps.h diff --git a/Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp b/Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp new file mode 100644 index 00000000000..c8d32c6b7b9 --- /dev/null +++ b/Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp @@ -0,0 +1,205 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2026 TheSuperHackers +** +** This program is free software: you can redistribute it and/or modify +** it under the terms of the GNU General Public License as published by +** the Free Software Foundation, either version 3 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU General Public License for more details. +** +** You should have received a copy of the GNU General Public License +** along with this program. If not, see . +*/ + +#include +#include + +#include "WW3D2/hlod.h" +#include "WW3D2/nullrobj.h" +#include "WWLib/RAMFILE.h" +#include "WWLib/chunkio.h" + +// W3DView's Export LOD saves HLod definitions as W3D_CHUNK_HLOD. These arrays hold the bytes that ChunkSaveClass writes: +// a chunk header is the uint32 ID and the uint32 size, whose top bit marks a chunk that contains chunks. +namespace +{ +// clang-format off + +// Two levels of detail, an aggregate and a proxy, as max2w3d writes them. +const unsigned char FullChunk[] = +{ + 0x00, 0x07, 0x00, 0x00, 0x6C, 0x01, 0x00, 0x80, // W3D_CHUNK_HLOD, 364 bytes, contains chunks + 0x01, 0x07, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_HEADER, 40 bytes + // W3dHLodHeaderStruct: version 1.0, 2 LODs, name "ProbeHLod", hierarchy "ProbeTree" + 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x48, 0x4C, 0x6F, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_LOD_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size 300 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x96, 0x43, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 1, name "ProbeTree.Low" + 0x01, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, 0x65, 0x2E, 0x4C, 0x6F, + 0x77, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x02, 0x07, 0x00, 0x00, 0x68, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_LOD_ARRAY, 104 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 2 models, max screen size FLT_MAX + 0x02, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0x7F, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 1, name "ProbeTree.High" + 0x01, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, 0x65, 0x2E, 0x48, 0x69, + 0x67, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 2, name "ProbeTree.Turret" + 0x02, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, 0x65, 0x2E, 0x54, 0x75, + 0x72, 0x72, 0x65, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x05, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_AGGREGATE_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size 0 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 3, name "ProbeEmitter" + 0x03, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x45, 0x6D, 0x69, 0x74, 0x74, 0x65, 0x72, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x06, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_PROXY_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size 0 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 4, name "ProbeProxy" + 0x04, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x50, 0x72, 0x6F, 0x78, 0x79, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +// One level of detail and nothing else, like every HLod in the retail archives. +const unsigned char LodOnlyChunk[] = +{ + 0x00, 0x07, 0x00, 0x00, 0x74, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD, 116 bytes, contains chunks + 0x01, 0x07, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_HEADER, 40 bytes + // W3dHLodHeaderStruct: version 1.0, 1 LOD, name "ProbeHLod", hierarchy "ProbeTree" + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x48, 0x4C, 0x6F, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, + 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_LOD_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size FLT_MAX + 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0x7F, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 0, name "ProbeTree.Mesh" + 0x00, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x54, 0x72, 0x65, 0x65, 0x2E, 0x4D, 0x65, + 0x73, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + +// An HLod built from one null object with a second null object on its root bone. +const unsigned char InitializedChunk[] = +{ + 0x00, 0x07, 0x00, 0x00, 0xB8, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD, 184 bytes, contains chunks + 0x01, 0x07, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_HEADER, 40 bytes + // W3dHLodHeaderStruct: version 1.0, 1 LOD, name "ProbeHLod", hierarchy "" + 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x48, 0x4C, 0x6F, + 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x02, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_LOD_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size FLT_MAX + 0x01, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x7F, 0x7F, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 0, name "ProbeLod" + 0x00, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x4C, 0x6F, 0x64, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x05, 0x07, 0x00, 0x00, 0x3C, 0x00, 0x00, 0x80, // W3D_CHUNK_HLOD_AGGREGATE_ARRAY, 60 bytes, contains chunks + 0x03, 0x07, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER, 8 bytes + // W3dHLodArrayHeaderStruct: 1 model, max screen size 0 + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x04, 0x07, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, // W3D_CHUNK_HLOD_SUB_OBJECT, 36 bytes + // W3dHLodSubObjectStruct: bone 0, name "ProbeAggregate" + 0x00, 0x00, 0x00, 0x00, 0x50, 0x72, 0x6F, 0x62, 0x65, 0x41, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, + 0x74, 0x65, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, +}; + + +// clang-format on + +template +std::vector toBytes(const unsigned char (&data)[Size]) +{ + return std::vector(data, data + Size); +} + +std::vector saveDefinition(HLodDefClass &definition) +{ + std::vector buffer(1024); + RAMFileClass file(buffer.data(), static_cast(buffer.size())); + file.Open(FileClass::WRITE); + ChunkSaveClass csave(&file); + EXPECT_EQ(definition.Save(csave), WW3D_ERROR_OK); + buffer.resize(file.Size()); + file.Close(); + return buffer; +} + +// Loads the chunk with the loader that the asset manager uses and saves the loaded definition again. +std::vector loadAndSave(std::vector input) +{ + RAMFileClass file(input.data(), static_cast(input.size())); + file.Open(FileClass::READ); + ChunkLoadClass cload(&file); + + std::vector output; + if (cload.Open_Chunk()) + { + EXPECT_EQ(cload.Cur_Chunk_ID(), static_cast(W3D_CHUNK_HLOD)); + PrototypeClass *prototype = _HLodLoader.Load_W3D(cload); + cload.Close_Chunk(); + EXPECT_NE(prototype, nullptr); + if (prototype != nullptr) + { + output = saveDefinition(*static_cast(prototype)->Get_Definition()); + prototype->DeleteSelf(); + } + } + file.Close(); + return output; +} + +} // namespace + +TEST(HLodDefClass, SavesLoadedChunkUnchanged) +{ + EXPECT_EQ(loadAndSave(toBytes(FullChunk)), toBytes(FullChunk)); +} + +TEST(HLodDefClass, SavesLodOnlyChunkUnchanged) +{ + EXPECT_EQ(loadAndSave(toBytes(LodOnlyChunk)), toBytes(LodOnlyChunk)); +} + +// W3DView rebuilds the definition from the displayed HLod after Record Screen Area and Include NULL LOD. +TEST(HLodDefClass, InitializeKeepsAdditionalModels) +{ + RenderObjClass *lod = NEW_REF(Null3DObjClass, ("ProbeLod")); + HLodClass *hlod = NEW_REF(HLodClass, ("ProbeHLod", &lod, 1)); + RenderObjClass *aggregate = NEW_REF(Null3DObjClass, ("ProbeAggregate")); + hlod->Add_Sub_Object_To_Bone(aggregate, 0); + + HLodDefClass definition(*hlod); + EXPECT_EQ(saveDefinition(definition), toBytes(InitializedChunk)); + + REF_PTR_RELEASE(aggregate); + REF_PTR_RELEASE(hlod); + REF_PTR_RELEASE(lod); +} From 012fbd988f7e43f2be3cf26fae94b3835606b104 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:33:27 +0200 Subject: [PATCH 2/8] Polish comments --- .../Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp | 12 ++++++------ .../Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 79ee1127787..2f5889e7478 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -412,7 +412,6 @@ void HLodDefClass::Initialize(HLodClass &src_lod) } // TheSuperHackers @bugfix Also copy the aggregates and proxies, so that saving this definition keeps them. - // The screen size of the aggregates is 0, as max2w3d writes it. Aggregates.ModelCount = src_lod.Get_Additional_Model_Count (); if (Aggregates.ModelCount > 0) { Aggregates.MaxScreenSize = 0.0f; @@ -449,7 +448,7 @@ WW3DErrorType HLodDefClass::Save(ChunkSaveClass & csave) if (csave.Begin_Chunk (W3D_CHUNK_HLOD) == TRUE) { // Attempt to save the different sections of the aggregate definition - // TheSuperHackers @bugfix Also save the aggregate and proxy arrays, which Load_W3D reads, in the order max2w3d writes them. + // TheSuperHackers @fix Also save the aggregate and proxy arrays, which Load_W3D reads. if ((Save_Header (csave) == WW3D_ERROR_OK) && (Save_Lod_Array (csave) == WW3D_ERROR_OK) && (Save_Aggregate_Array (csave) == WW3D_ERROR_OK) && @@ -577,7 +576,7 @@ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) /*********************************************************************************************** * HLodDefClass::Save_Proxy_Array -- Save the array of proxies * * * - * The screen size in the array header is 0, as max2w3d writes it. * + * The screen size in the array header is 0. * * * * INPUT: * * * @@ -586,6 +585,7 @@ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) * WARNINGS: * * * * HISTORY: * + * 10/18/2026 TheSuperHackers : Created. * *=============================================================================================*/ WW3DErrorType HLodDefClass::Save_Proxy_Array(ChunkSaveClass & csave) { @@ -901,15 +901,15 @@ bool HLodDefClass::SubObjectArrayClass::Load_W3D(ChunkLoadClass & cload) * * * HISTORY: * * 1/26/00 gth : Created. * + * 10/18/2026 TheSuperHackers : Write only the array contents, as Load_W3D reads them. * + * The caller wraps them in W3D_CHUNK_HLOD_LOD_ARRAY or * + * W3D_CHUNK_HLOD_AGGREGATE_ARRAY * *=============================================================================================*/ bool HLodDefClass::SubObjectArrayClass::Save_W3D(ChunkSaveClass &csave) { // Assume error bool ret_val = false; - // TheSuperHackers @bugfix Write only the array contents, as Load_W3D reads them. The caller wraps them in - // W3D_CHUNK_HLOD_LOD_ARRAY or W3D_CHUNK_HLOD_AGGREGATE_ARRAY. - // Begin a chunk that identifies the LOD header if (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) == TRUE) { diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index b496029d46c..d5fa9255a5f 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -412,7 +412,6 @@ void HLodDefClass::Initialize(HLodClass &src_lod) } // TheSuperHackers @bugfix Also copy the aggregates and proxies, so that saving this definition keeps them. - // The screen size of the aggregates is 0, as max2w3d writes it. Aggregates.ModelCount = src_lod.Get_Additional_Model_Count (); if (Aggregates.ModelCount > 0) { Aggregates.MaxScreenSize = 0.0f; @@ -449,7 +448,7 @@ WW3DErrorType HLodDefClass::Save(ChunkSaveClass & csave) if (csave.Begin_Chunk (W3D_CHUNK_HLOD) == TRUE) { // Attempt to save the different sections of the aggregate definition - // TheSuperHackers @bugfix Also save the aggregate and proxy arrays, which Load_W3D reads, in the order max2w3d writes them. + // TheSuperHackers @fix Also save the aggregate and proxy arrays, which Load_W3D reads. if ((Save_Header (csave) == WW3D_ERROR_OK) && (Save_Lod_Array (csave) == WW3D_ERROR_OK) && (Save_Aggregate_Array (csave) == WW3D_ERROR_OK) && @@ -577,7 +576,7 @@ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) /*********************************************************************************************** * HLodDefClass::Save_Proxy_Array -- Save the array of proxies * * * - * The screen size in the array header is 0, as max2w3d writes it. * + * The screen size in the array header is 0. * * * * INPUT: * * * @@ -586,6 +585,7 @@ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) * WARNINGS: * * * * HISTORY: * + * 10/18/2026 TheSuperHackers : Created. * *=============================================================================================*/ WW3DErrorType HLodDefClass::Save_Proxy_Array(ChunkSaveClass & csave) { @@ -901,15 +901,15 @@ bool HLodDefClass::SubObjectArrayClass::Load_W3D(ChunkLoadClass & cload) * * * HISTORY: * * 1/26/00 gth : Created. * + * 10/18/2026 TheSuperHackers : Write only the array contents, as Load_W3D reads them. * + * The caller wraps them in W3D_CHUNK_HLOD_LOD_ARRAY or * + * W3D_CHUNK_HLOD_AGGREGATE_ARRAY * *=============================================================================================*/ bool HLodDefClass::SubObjectArrayClass::Save_W3D(ChunkSaveClass &csave) { // Assume error bool ret_val = false; - // TheSuperHackers @bugfix Write only the array contents, as Load_W3D reads them. The caller wraps them in - // W3D_CHUNK_HLOD_LOD_ARRAY or W3D_CHUNK_HLOD_AGGREGATE_ARRAY. - // Begin a chunk that identifies the LOD header if (csave.Begin_Chunk (W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER) == TRUE) { From e4a302d7c65afb39fc8aa7cded880e2708c7ef47 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:41:53 +0200 Subject: [PATCH 3/8] Move aggregate copying to new function SubObjectArrayClass::CopyFrom --- .../Libraries/Source/WWVegas/WW3D2/hlod.cpp | 39 +++++++++++++------ .../Libraries/Source/WWVegas/WW3D2/hlod.h | 1 + .../Libraries/Source/WWVegas/WW3D2/hlod.cpp | 39 +++++++++++++------ .../Libraries/Source/WWVegas/WW3D2/hlod.h | 1 + 4 files changed, 58 insertions(+), 22 deletions(-) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 2f5889e7478..76842ab4431 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -412,17 +412,7 @@ void HLodDefClass::Initialize(HLodClass &src_lod) } // TheSuperHackers @bugfix Also copy the aggregates and proxies, so that saving this definition keeps them. - Aggregates.ModelCount = src_lod.Get_Additional_Model_Count (); - if (Aggregates.ModelCount > 0) { - Aggregates.MaxScreenSize = 0.0f; - Aggregates.ModelName = W3DNEWARRAY char *[Aggregates.ModelCount]; - Aggregates.BoneIndex = W3DNEWARRAY int[Aggregates.ModelCount]; - for (int model_index = 0; model_index < Aggregates.ModelCount; model_index ++) { - Aggregates.ModelName[model_index] = ::strdup (src_lod.Peek_Additional_Model (model_index)->Get_Name ()); - Aggregates.BoneIndex[model_index] = src_lod.Get_Additional_Model_Bone (model_index); - } - } - + Aggregates.CopyFrom(src_lod); REF_PTR_SET (ProxyArray, src_lod.ProxyArray); } @@ -839,6 +829,33 @@ void HLodDefClass::SubObjectArrayClass::Reset() } +/*********************************************************************************************** + * HLodDefClass::SubObjectArrayClass::CopyFrom * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 10/18/2026 TheSuperHackers : Created. * + *=============================================================================================*/ +void HLodDefClass::SubObjectArrayClass::CopyFrom(const HLodClass & hlod) +{ + ModelCount = hlod.Get_Additional_Model_Count (); + if (ModelCount > 0) { + MaxScreenSize = 0.0f; + ModelName = W3DNEWARRAY char *[ModelCount]; + BoneIndex = W3DNEWARRAY int[ModelCount]; + for (int model_index = 0; model_index < ModelCount; model_index ++) { + ModelName[model_index] = ::strdup (hlod.Peek_Additional_Model (model_index)->Get_Name ()); + BoneIndex[model_index] = hlod.Get_Additional_Model_Bone (model_index); + } + } +} + + /*********************************************************************************************** * HLodDefClass::SubObjectArrayClass::Load_W3D -- LodArray load function * * * diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h index 6149e53519a..6f2b29176d0 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h @@ -316,6 +316,7 @@ class HLodDefClass SubObjectArrayClass(); ~SubObjectArrayClass(); void Reset(); + void CopyFrom(const HLodClass & hlod); void operator = (const SubObjectArrayClass & that); bool Load_W3D(ChunkLoadClass & cload); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index d5fa9255a5f..433e8cf4940 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -412,17 +412,7 @@ void HLodDefClass::Initialize(HLodClass &src_lod) } // TheSuperHackers @bugfix Also copy the aggregates and proxies, so that saving this definition keeps them. - Aggregates.ModelCount = src_lod.Get_Additional_Model_Count (); - if (Aggregates.ModelCount > 0) { - Aggregates.MaxScreenSize = 0.0f; - Aggregates.ModelName = W3DNEWARRAY char *[Aggregates.ModelCount]; - Aggregates.BoneIndex = W3DNEWARRAY int[Aggregates.ModelCount]; - for (int model_index = 0; model_index < Aggregates.ModelCount; model_index ++) { - Aggregates.ModelName[model_index] = ::strdup (src_lod.Peek_Additional_Model (model_index)->Get_Name ()); - Aggregates.BoneIndex[model_index] = src_lod.Get_Additional_Model_Bone (model_index); - } - } - + Aggregates.CopyFrom(src_lod); REF_PTR_SET (ProxyArray, src_lod.ProxyArray); } @@ -839,6 +829,33 @@ void HLodDefClass::SubObjectArrayClass::Reset() } +/*********************************************************************************************** + * HLodDefClass::SubObjectArrayClass::CopyFrom * + * * + * INPUT: * + * * + * OUTPUT: * + * * + * WARNINGS: * + * * + * HISTORY: * + * 10/18/2026 TheSuperHackers : Created. * + *=============================================================================================*/ +void HLodDefClass::SubObjectArrayClass::CopyFrom(const HLodClass & hlod) +{ + ModelCount = hlod.Get_Additional_Model_Count (); + if (ModelCount > 0) { + MaxScreenSize = 0.0f; + ModelName = W3DNEWARRAY char *[ModelCount]; + BoneIndex = W3DNEWARRAY int[ModelCount]; + for (int model_index = 0; model_index < ModelCount; model_index ++) { + ModelName[model_index] = ::strdup (hlod.Peek_Additional_Model (model_index)->Get_Name ()); + BoneIndex[model_index] = hlod.Get_Additional_Model_Bone (model_index); + } + } +} + + /*********************************************************************************************** * HLodDefClass::SubObjectArrayClass::Load_W3D -- LodArray load function * * * diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h index cb8e389197a..8cb8de9d7f3 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h @@ -317,6 +317,7 @@ class HLodDefClass SubObjectArrayClass(); ~SubObjectArrayClass(); void Reset(); + void CopyFrom(const HLodClass & hlod); void operator = (const SubObjectArrayClass & that); bool Load_W3D(ChunkLoadClass & cload); From 25613aa00ff6985df9dc997051eaa39cc0b44647 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:52:41 +0200 Subject: [PATCH 4/8] Remove superfluous comments --- Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp | 2 -- GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp | 2 -- 2 files changed, 4 deletions(-) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 76842ab4431..f6f6afd7c58 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -411,7 +411,6 @@ void HLodDefClass::Initialize(HLodClass &src_lod) } } - // TheSuperHackers @bugfix Also copy the aggregates and proxies, so that saving this definition keeps them. Aggregates.CopyFrom(src_lod); REF_PTR_SET (ProxyArray, src_lod.ProxyArray); } @@ -438,7 +437,6 @@ WW3DErrorType HLodDefClass::Save(ChunkSaveClass & csave) if (csave.Begin_Chunk (W3D_CHUNK_HLOD) == TRUE) { // Attempt to save the different sections of the aggregate definition - // TheSuperHackers @fix Also save the aggregate and proxy arrays, which Load_W3D reads. if ((Save_Header (csave) == WW3D_ERROR_OK) && (Save_Lod_Array (csave) == WW3D_ERROR_OK) && (Save_Aggregate_Array (csave) == WW3D_ERROR_OK) && diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 433e8cf4940..c187815efc9 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -411,7 +411,6 @@ void HLodDefClass::Initialize(HLodClass &src_lod) } } - // TheSuperHackers @bugfix Also copy the aggregates and proxies, so that saving this definition keeps them. Aggregates.CopyFrom(src_lod); REF_PTR_SET (ProxyArray, src_lod.ProxyArray); } @@ -438,7 +437,6 @@ WW3DErrorType HLodDefClass::Save(ChunkSaveClass & csave) if (csave.Begin_Chunk (W3D_CHUNK_HLOD) == TRUE) { // Attempt to save the different sections of the aggregate definition - // TheSuperHackers @fix Also save the aggregate and proxy arrays, which Load_W3D reads. if ((Save_Header (csave) == WW3D_ERROR_OK) && (Save_Lod_Array (csave) == WW3D_ERROR_OK) && (Save_Aggregate_Array (csave) == WW3D_ERROR_OK) && From 922026b9e360ee43d4ef061595f420ca27c326b1 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 18 Oct 2026 14:42:48 +0200 Subject: [PATCH 5/8] Polish code, have HLodDefClass::Save_Proxy_Array return error on failure --- .../Libraries/Source/WWVegas/WW3D2/hlod.cpp | 68 +++++++++++-------- .../Libraries/Source/WWVegas/WW3D2/hlod.h | 2 +- .../Libraries/Source/WWVegas/WW3D2/hlod.cpp | 68 +++++++++++-------- .../Libraries/Source/WWVegas/WW3D2/hlod.h | 2 +- 4 files changed, 84 insertions(+), 56 deletions(-) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index f6f6afd7c58..701710f17d3 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -52,6 +52,7 @@ * HLodDefClass::SubObjectArrayClass::Save_W3D -- saves a w3d file for this HLodDef * * HLodDefClass::SubObjectArrayClass::Load_W3D -- LodArray load function * * HLodDefClass::SubObjectArrayClass::Reset -- release the contents of this array * + * HLodDefClass::SubObjectArrayClass::Copy_Additional_Models -- copies the additional models * * HLodClass::HLodClass -- Constructor * * HLodClass::HLodClass -- copy constructor * * HLodClass::HLodClass -- Constructor * @@ -411,7 +412,7 @@ void HLodDefClass::Initialize(HLodClass &src_lod) } } - Aggregates.CopyFrom(src_lod); + Aggregates.Copy_Additional_Models(src_lod); REF_PTR_SET (ProxyArray, src_lod.ProxyArray); } @@ -523,10 +524,10 @@ WW3DErrorType HLodDefClass::Save_Lod_Array(ChunkSaveClass &csave) for (int lod_index = 0; (lod_index < LodCount) && success; lod_index ++) { - success = (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE); + success &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE); if (success) { - success = Lod[lod_index].Save_W3D (csave); - csave.End_Chunk (); + success &= Lod[lod_index].Save_W3D (csave); + success &= csave.End_Chunk (); } } @@ -577,31 +578,42 @@ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) *=============================================================================================*/ WW3DErrorType HLodDefClass::Save_Proxy_Array(ChunkSaveClass & csave) { - if ((ProxyArray != nullptr) && (ProxyArray->Length() > 0)) { - csave.Begin_Chunk(W3D_CHUNK_HLOD_PROXY_ARRAY); + bool success = true; - csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER); - W3dHLodArrayHeaderStruct header = { 0 }; - header.ModelCount = ProxyArray->Length(); - header.MaxScreenSize = 0.0f; - csave.Write(&header,sizeof(header)); - csave.End_Chunk(); + if ((ProxyArray != nullptr) && (ProxyArray->Length() > 0)) { + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_PROXY_ARRAY); - for (int imodel=0; imodelLength(); ++imodel) { - ProxyRecordClass & proxy = (*ProxyArray)[imodel]; - W3dHLodSubObjectStruct subobjdef = { 0 }; - subobjdef.BoneIndex = proxy.Get_Bone_Index(); - ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); - subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; + if (success) { + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER); + + if (success) { + W3dHLodArrayHeaderStruct header = { 0 }; + header.ModelCount = ProxyArray->Length(); + header.MaxScreenSize = 0.0f; + success &= (csave.Write(&header,sizeof(header)) == sizeof(header)); + success &= csave.End_Chunk(); + } - csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); - csave.Write(&subobjdef,sizeof(subobjdef)); - csave.End_Chunk(); + if (success) { + for (int imodel=0; imodelLength(); ++imodel) { + ProxyRecordClass & proxy = (*ProxyArray)[imodel]; + W3dHLodSubObjectStruct subobjdef = { 0 }; + subobjdef.BoneIndex = proxy.Get_Bone_Index(); + ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); + subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; + + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); + if (success) { + success &= (csave.Write(&subobjdef,sizeof(subobjdef)) == sizeof(subobjdef)); + success &= csave.End_Chunk(); + } + } + } + success &= csave.End_Chunk(); } - - csave.End_Chunk(); } - return WW3D_ERROR_OK; + + return success ? WW3D_ERROR_OK : WW3D_ERROR_SAVE_FAILED; } @@ -828,7 +840,7 @@ void HLodDefClass::SubObjectArrayClass::Reset() /*********************************************************************************************** - * HLodDefClass::SubObjectArrayClass::CopyFrom * + * HLodDefClass::SubObjectArrayClass::Copy_Additional_Models -- copies the additional models * * * * INPUT: * * * @@ -839,8 +851,10 @@ void HLodDefClass::SubObjectArrayClass::Reset() * HISTORY: * * 10/18/2026 TheSuperHackers : Created. * *=============================================================================================*/ -void HLodDefClass::SubObjectArrayClass::CopyFrom(const HLodClass & hlod) +void HLodDefClass::SubObjectArrayClass::Copy_Additional_Models(const HLodClass & hlod) { + Reset(); + ModelCount = hlod.Get_Additional_Model_Count (); if (ModelCount > 0) { MaxScreenSize = 0.0f; @@ -918,7 +932,7 @@ bool HLodDefClass::SubObjectArrayClass::Load_W3D(ChunkLoadClass & cload) * 1/26/00 gth : Created. * * 10/18/2026 TheSuperHackers : Write only the array contents, as Load_W3D reads them. * * The caller wraps them in W3D_CHUNK_HLOD_LOD_ARRAY or * - * W3D_CHUNK_HLOD_AGGREGATE_ARRAY * + * W3D_CHUNK_HLOD_AGGREGATE_ARRAY. * *=============================================================================================*/ bool HLodDefClass::SubObjectArrayClass::Save_W3D(ChunkSaveClass &csave) { diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h index 6f2b29176d0..d2dc923ba30 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.h @@ -316,7 +316,7 @@ class HLodDefClass SubObjectArrayClass(); ~SubObjectArrayClass(); void Reset(); - void CopyFrom(const HLodClass & hlod); + void Copy_Additional_Models(const HLodClass & hlod); void operator = (const SubObjectArrayClass & that); bool Load_W3D(ChunkLoadClass & cload); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index c187815efc9..6f57be0543b 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -52,6 +52,7 @@ * HLodDefClass::SubObjectArrayClass::Save_W3D -- saves a w3d file for this HLodDef * * HLodDefClass::SubObjectArrayClass::Load_W3D -- LodArray load function * * HLodDefClass::SubObjectArrayClass::Reset -- release the contents of this array * + * HLodDefClass::SubObjectArrayClass::Copy_Additional_Models -- copies the additional models * * HLodClass::HLodClass -- Constructor * * HLodClass::HLodClass -- copy constructor * * HLodClass::HLodClass -- Constructor * @@ -411,7 +412,7 @@ void HLodDefClass::Initialize(HLodClass &src_lod) } } - Aggregates.CopyFrom(src_lod); + Aggregates.Copy_Additional_Models(src_lod); REF_PTR_SET (ProxyArray, src_lod.ProxyArray); } @@ -523,10 +524,10 @@ WW3DErrorType HLodDefClass::Save_Lod_Array(ChunkSaveClass &csave) for (int lod_index = 0; (lod_index < LodCount) && success; lod_index ++) { - success = (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE); + success &= (csave.Begin_Chunk (W3D_CHUNK_HLOD_LOD_ARRAY) == TRUE); if (success) { - success = Lod[lod_index].Save_W3D (csave); - csave.End_Chunk (); + success &= Lod[lod_index].Save_W3D (csave); + success &= csave.End_Chunk (); } } @@ -577,31 +578,42 @@ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) *=============================================================================================*/ WW3DErrorType HLodDefClass::Save_Proxy_Array(ChunkSaveClass & csave) { - if ((ProxyArray != nullptr) && (ProxyArray->Length() > 0)) { - csave.Begin_Chunk(W3D_CHUNK_HLOD_PROXY_ARRAY); + bool success = true; - csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER); - W3dHLodArrayHeaderStruct header = { 0 }; - header.ModelCount = ProxyArray->Length(); - header.MaxScreenSize = 0.0f; - csave.Write(&header,sizeof(header)); - csave.End_Chunk(); + if ((ProxyArray != nullptr) && (ProxyArray->Length() > 0)) { + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_PROXY_ARRAY); - for (int imodel=0; imodelLength(); ++imodel) { - ProxyRecordClass & proxy = (*ProxyArray)[imodel]; - W3dHLodSubObjectStruct subobjdef = { 0 }; - subobjdef.BoneIndex = proxy.Get_Bone_Index(); - ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); - subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; + if (success) { + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT_ARRAY_HEADER); + + if (success) { + W3dHLodArrayHeaderStruct header = { 0 }; + header.ModelCount = ProxyArray->Length(); + header.MaxScreenSize = 0.0f; + success &= (csave.Write(&header,sizeof(header)) == sizeof(header)); + success &= csave.End_Chunk(); + } - csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); - csave.Write(&subobjdef,sizeof(subobjdef)); - csave.End_Chunk(); + if (success) { + for (int imodel=0; imodelLength(); ++imodel) { + ProxyRecordClass & proxy = (*ProxyArray)[imodel]; + W3dHLodSubObjectStruct subobjdef = { 0 }; + subobjdef.BoneIndex = proxy.Get_Bone_Index(); + ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); + subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; + + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); + if (success) { + success &= (csave.Write(&subobjdef,sizeof(subobjdef)) == sizeof(subobjdef)); + success &= csave.End_Chunk(); + } + } + } + success &= csave.End_Chunk(); } - - csave.End_Chunk(); } - return WW3D_ERROR_OK; + + return success ? WW3D_ERROR_OK : WW3D_ERROR_SAVE_FAILED; } @@ -828,7 +840,7 @@ void HLodDefClass::SubObjectArrayClass::Reset() /*********************************************************************************************** - * HLodDefClass::SubObjectArrayClass::CopyFrom * + * HLodDefClass::SubObjectArrayClass::Copy_Additional_Models -- copies the additional models * * * * INPUT: * * * @@ -839,8 +851,10 @@ void HLodDefClass::SubObjectArrayClass::Reset() * HISTORY: * * 10/18/2026 TheSuperHackers : Created. * *=============================================================================================*/ -void HLodDefClass::SubObjectArrayClass::CopyFrom(const HLodClass & hlod) +void HLodDefClass::SubObjectArrayClass::Copy_Additional_Models(const HLodClass & hlod) { + Reset(); + ModelCount = hlod.Get_Additional_Model_Count (); if (ModelCount > 0) { MaxScreenSize = 0.0f; @@ -918,7 +932,7 @@ bool HLodDefClass::SubObjectArrayClass::Load_W3D(ChunkLoadClass & cload) * 1/26/00 gth : Created. * * 10/18/2026 TheSuperHackers : Write only the array contents, as Load_W3D reads them. * * The caller wraps them in W3D_CHUNK_HLOD_LOD_ARRAY or * - * W3D_CHUNK_HLOD_AGGREGATE_ARRAY * + * W3D_CHUNK_HLOD_AGGREGATE_ARRAY. * *=============================================================================================*/ bool HLodDefClass::SubObjectArrayClass::Save_W3D(ChunkSaveClass &csave) { diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h index 8cb8de9d7f3..4c61a85a149 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.h @@ -317,7 +317,7 @@ class HLodDefClass SubObjectArrayClass(); ~SubObjectArrayClass(); void Reset(); - void CopyFrom(const HLodClass & hlod); + void Copy_Additional_Models(const HLodClass & hlod); void operator = (const SubObjectArrayClass & that); bool Load_W3D(ChunkLoadClass & cload); From e6f05b38a7f541946e283da44208da30edb89006 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 18 Oct 2026 15:03:35 +0200 Subject: [PATCH 6/8] Bail loop --- .../Libraries/Source/WWVegas/WW3D2/hlod.cpp | 24 +++++++++---------- .../Libraries/Source/WWVegas/WW3D2/hlod.cpp | 24 +++++++++---------- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 701710f17d3..bd004904123 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -594,19 +594,17 @@ WW3DErrorType HLodDefClass::Save_Proxy_Array(ChunkSaveClass & csave) success &= csave.End_Chunk(); } - if (success) { - for (int imodel=0; imodelLength(); ++imodel) { - ProxyRecordClass & proxy = (*ProxyArray)[imodel]; - W3dHLodSubObjectStruct subobjdef = { 0 }; - subobjdef.BoneIndex = proxy.Get_Bone_Index(); - ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); - subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; - - success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); - if (success) { - success &= (csave.Write(&subobjdef,sizeof(subobjdef)) == sizeof(subobjdef)); - success &= csave.End_Chunk(); - } + for (int imodel=0; success && imodelLength(); ++imodel) { + ProxyRecordClass & proxy = (*ProxyArray)[imodel]; + W3dHLodSubObjectStruct subobjdef = { 0 }; + subobjdef.BoneIndex = proxy.Get_Bone_Index(); + ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); + subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; + + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); + if (success) { + success &= (csave.Write(&subobjdef,sizeof(subobjdef)) == sizeof(subobjdef)); + success &= csave.End_Chunk(); } } success &= csave.End_Chunk(); diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 6f57be0543b..9a636668499 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -594,19 +594,17 @@ WW3DErrorType HLodDefClass::Save_Proxy_Array(ChunkSaveClass & csave) success &= csave.End_Chunk(); } - if (success) { - for (int imodel=0; imodelLength(); ++imodel) { - ProxyRecordClass & proxy = (*ProxyArray)[imodel]; - W3dHLodSubObjectStruct subobjdef = { 0 }; - subobjdef.BoneIndex = proxy.Get_Bone_Index(); - ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); - subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; - - success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); - if (success) { - success &= (csave.Write(&subobjdef,sizeof(subobjdef)) == sizeof(subobjdef)); - success &= csave.End_Chunk(); - } + for (int imodel=0; success && imodelLength(); ++imodel) { + ProxyRecordClass & proxy = (*ProxyArray)[imodel]; + W3dHLodSubObjectStruct subobjdef = { 0 }; + subobjdef.BoneIndex = proxy.Get_Bone_Index(); + ::lstrcpyn(subobjdef.Name, proxy.Get_Name(), sizeof(subobjdef.Name)); + subobjdef.Name[sizeof(subobjdef.Name) - 1] = 0; + + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_SUB_OBJECT); + if (success) { + success &= (csave.Write(&subobjdef,sizeof(subobjdef)) == sizeof(subobjdef)); + success &= csave.End_Chunk(); } } success &= csave.End_Chunk(); From 3660229ffcc60ba7fea325850bd2ab015b399670 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 18 Oct 2026 15:06:40 +0200 Subject: [PATCH 7/8] return error in HLodDefClass::Save_Aggregate_Array --- Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp | 11 +++++++---- .../Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp | 11 +++++++---- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index bd004904123..e8cc3a81e65 100644 --- a/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/Generals/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -553,12 +553,15 @@ WW3DErrorType HLodDefClass::Save_Lod_Array(ChunkSaveClass &csave) *=============================================================================================*/ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) { + bool success = true; if (Aggregates.ModelCount > 0) { - csave.Begin_Chunk(W3D_CHUNK_HLOD_AGGREGATE_ARRAY); - Aggregates.Save_W3D(csave); - csave.End_Chunk(); + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_AGGREGATE_ARRAY); + if (success) { + success &= Aggregates.Save_W3D(csave); + success &= csave.End_Chunk(); + } } - return WW3D_ERROR_OK; + return success ? WW3D_ERROR_OK : WW3D_ERROR_SAVE_FAILED; } diff --git a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp index 9a636668499..901bc4b2401 100644 --- a/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp +++ b/GeneralsMD/Code/Libraries/Source/WWVegas/WW3D2/hlod.cpp @@ -553,12 +553,15 @@ WW3DErrorType HLodDefClass::Save_Lod_Array(ChunkSaveClass &csave) *=============================================================================================*/ WW3DErrorType HLodDefClass::Save_Aggregate_Array(ChunkSaveClass & csave) { + bool success = true; if (Aggregates.ModelCount > 0) { - csave.Begin_Chunk(W3D_CHUNK_HLOD_AGGREGATE_ARRAY); - Aggregates.Save_W3D(csave); - csave.End_Chunk(); + success &= csave.Begin_Chunk(W3D_CHUNK_HLOD_AGGREGATE_ARRAY); + if (success) { + success &= Aggregates.Save_W3D(csave); + success &= csave.End_Chunk(); + } } - return WW3D_ERROR_OK; + return success ? WW3D_ERROR_OK : WW3D_ERROR_SAVE_FAILED; } From 9edb482c8662da2840b8bbb98d999a55d1e0fabe Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Thu, 22 Oct 2026 19:33:22 +0200 Subject: [PATCH 8/8] Remove max2w3d reference --- Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp b/Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp index c8d32c6b7b9..d858f6837fe 100644 --- a/Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp +++ b/Tests/Google/Core/Libraries/WWVegas/WW3D2/hlod_test.cpp @@ -30,7 +30,7 @@ namespace { // clang-format off -// Two levels of detail, an aggregate and a proxy, as max2w3d writes them. +// Two levels of detail: an aggregate and a proxy. const unsigned char FullChunk[] = { 0x00, 0x07, 0x00, 0x00, 0x6C, 0x01, 0x00, 0x80, // W3D_CHUNK_HLOD, 364 bytes, contains chunks @@ -131,7 +131,6 @@ const unsigned char InitializedChunk[] = 0x00, 0x00, 0x00, 0x00, }; - // clang-format on template