From 8e4430b82a5888d42a775a99347759ba5ebd25ea Mon Sep 17 00:00:00 2001 From: Omar Aglan Date: Fri, 9 Oct 2026 18:51:11 +0300 Subject: [PATCH] bugfix(worldbuilder): Reject lossy retail cliff saves --- .../WorldBuilder/include/WHeightMapEdit.h | 1 + .../Tools/WorldBuilder/src/WHeightMapEdit.cpp | 19 +++++++++++++++++++ .../WorldBuilder/src/WorldBuilderDoc.cpp | 9 +++++++++ .../WorldBuilder/include/WHeightMapEdit.h | 1 + .../Tools/WorldBuilder/src/WHeightMapEdit.cpp | 19 +++++++++++++++++++ .../WorldBuilder/src/WorldBuilderDoc.cpp | 9 +++++++++ 6 files changed, 58 insertions(+) diff --git a/Generals/Code/Tools/WorldBuilder/include/WHeightMapEdit.h b/Generals/Code/Tools/WorldBuilder/include/WHeightMapEdit.h index 7f554481882..c702792d593 100644 --- a/Generals/Code/Tools/WorldBuilder/include/WHeightMapEdit.h +++ b/Generals/Code/Tools/WorldBuilder/include/WHeightMapEdit.h @@ -98,6 +98,7 @@ class WorldHeightMapEdit : public WorldHeightMap WorldHeightMapEdit(ChunkInputStream *pStrm); ///< read from file. virtual ~WorldHeightMapEdit() override; ///< destroy. + Bool canSaveCliffState() const; void saveToFile(DataChunkOutput &chunkWriter); WorldHeightMapEdit *duplicate(); diff --git a/Generals/Code/Tools/WorldBuilder/src/WHeightMapEdit.cpp b/Generals/Code/Tools/WorldBuilder/src/WHeightMapEdit.cpp index 09bbaf42c5b..2d7438a923a 100644 --- a/Generals/Code/Tools/WorldBuilder/src/WHeightMapEdit.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/WHeightMapEdit.cpp @@ -556,6 +556,21 @@ void WorldHeightMapEdit::getTexClassNeighbors(Int xIndex, Int yIndex, Int textur +Bool WorldHeightMapEdit::canSaveCliffState() const +{ +#if RTS_GENERALS && RETAIL_COMPATIBLE_DATA + const Int byteWidth = (m_width + 1) / 8; + if (byteWidth < m_flipStateWidth) { + for (Int j = 0; j < m_height; j++) { + if (m_cellCliffState[j * m_flipStateWidth + byteWidth] != 0) { + return false; + } + } + } +#endif + return true; +} + // /// SaveToFile - saves a height map to a file. // Format is @@ -572,6 +587,10 @@ void WorldHeightMapEdit::getTexClassNeighbors(Int xIndex, Int yIndex, Int textur // void WorldHeightMapEdit::saveToFile(DataChunkOutput &chunkWriter) { + if (!canSaveCliffState()) { + throw(ERROR_CORRUPT_FILE_FORMAT); + } + // This is the chunk writer stuff. int i; diff --git a/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp b/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp index 2b9866d0324..7e93c4e8d2a 100644 --- a/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp @@ -666,6 +666,11 @@ BOOL CWorldBuilderDoc::DoSave(LPCTSTR lpszPathName, BOOL bReplace) // if 'bReplace' is TRUE will change file name if successful (SaveAs) // if 'bReplace' is FALSE will not change path name (SaveCopyAs) { + if (m_heightMap && !m_heightMap->canSaveCliffState()) { + ::AfxMessageBox("Cannot save: the retail Generals map format cannot store cliff flags at the right edge of this map. Adjust the map width before saving."); + return FALSE; + } + CString newName = lpszPathName; if (newName.IsEmpty()) { @@ -796,6 +801,10 @@ Bool CWorldBuilderDoc::ParseWaypointData(DataChunkInput &file, DataChunkInfo *in void CWorldBuilderDoc::autoSave() { + if (m_heightMap && !m_heightMap->canSaveCliffState()) { + return; + } + // srj sez: put autosave into our user data folder, not the ap dir AsciiString autosave1 = TheGlobalData->getPath_UserData(); AsciiString autosave2 = TheGlobalData->getPath_UserData(); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/include/WHeightMapEdit.h b/GeneralsMD/Code/Tools/WorldBuilder/include/WHeightMapEdit.h index c51ffb3a4d0..edf5aaa23a4 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/include/WHeightMapEdit.h +++ b/GeneralsMD/Code/Tools/WorldBuilder/include/WHeightMapEdit.h @@ -98,6 +98,7 @@ class WorldHeightMapEdit : public WorldHeightMap WorldHeightMapEdit(ChunkInputStream *pStrm); ///< read from file. virtual ~WorldHeightMapEdit() override; ///< destroy. + Bool canSaveCliffState() const; void saveToFile(DataChunkOutput &chunkWriter); WorldHeightMapEdit *duplicate(); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WHeightMapEdit.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WHeightMapEdit.cpp index e409b2a42f2..fcc353e609d 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WHeightMapEdit.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WHeightMapEdit.cpp @@ -556,6 +556,21 @@ void WorldHeightMapEdit::getTexClassNeighbors(Int xIndex, Int yIndex, Int textur +Bool WorldHeightMapEdit::canSaveCliffState() const +{ +#if RTS_GENERALS && RETAIL_COMPATIBLE_DATA + const Int byteWidth = (m_width + 1) / 8; + if (byteWidth < m_flipStateWidth) { + for (Int j = 0; j < m_height; j++) { + if (m_cellCliffState[j * m_flipStateWidth + byteWidth] != 0) { + return false; + } + } + } +#endif + return true; +} + // /// SaveToFile - saves a height map to a file. // Format is @@ -572,6 +587,10 @@ void WorldHeightMapEdit::getTexClassNeighbors(Int xIndex, Int yIndex, Int textur // void WorldHeightMapEdit::saveToFile(DataChunkOutput &chunkWriter) { + if (!canSaveCliffState()) { + throw(ERROR_CORRUPT_FILE_FORMAT); + } + // This is the chunk writer stuff. int i; diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp index 9de7548202f..5f11cbcd08b 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/WorldBuilderDoc.cpp @@ -722,6 +722,11 @@ BOOL CWorldBuilderDoc::DoSave(LPCTSTR lpszPathName, BOOL bReplace) // if 'bReplace' is TRUE will change file name if successful (SaveAs) // if 'bReplace' is FALSE will not change path name (SaveCopyAs) { + if (m_heightMap && !m_heightMap->canSaveCliffState()) { + ::AfxMessageBox("Cannot save: the retail Generals map format cannot store cliff flags at the right edge of this map. Adjust the map width before saving."); + return FALSE; + } + CString newName = lpszPathName; if (newName.IsEmpty()) { @@ -852,6 +857,10 @@ Bool CWorldBuilderDoc::ParseWaypointData(DataChunkInput &file, DataChunkInfo *in void CWorldBuilderDoc::autoSave() { + if (m_heightMap && !m_heightMap->canSaveCliffState()) { + return; + } + // srj sez: put autosave into our user data folder, not the ap dir AsciiString autosave1 = TheGlobalData->getPath_UserData(); AsciiString autosave2 = TheGlobalData->getPath_UserData();