From 6c3db3837ec6acb086ed6eba93706a7448a38b11 Mon Sep 17 00:00:00 2001 From: Omar Aglan Date: Mon, 26 Oct 2026 13:56:25 +0300 Subject: [PATCH 1/2] bugfix(scripts): Read Zero Hour script action and condition chunks in Generals --- .../Source/GameLogic/ScriptEngine/Scripts.cpp | 55 ++++++++++++++++--- 1 file changed, 47 insertions(+), 8 deletions(-) diff --git a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp index 8e50e281ee4..6a211778aa9 100644 --- a/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp +++ b/Generals/Code/GameEngine/Source/GameLogic/ScriptEngine/Scripts.cpp @@ -152,6 +152,10 @@ enum { K_SCRIPT_LIST_DATA_VERSION_1 = 1, K_SCRIPT_CONDITION_VERSION_2 = 2, K_SCRIPT_CONDITION_VERSION_3 = 3, K_SCRIPTS_DATA_VERSION_1, + + // Added in Zero Hour + K_SCRIPT_ACTION_VERSION_2 = 2, + K_SCRIPT_CONDITION_VERSION_4 = 4, end_of_the_enumeration }; @@ -1658,7 +1662,18 @@ Bool Condition::ParseConditionDataChunk(DataChunkInput &file, DataChunkInfo *inf Condition *pCondition = newInstance(Condition); OrCondition *pOr = (OrCondition *)userData; pCondition->m_conditionType = (enum ConditionType)file.readInt(); - pCondition->m_numParms =file.readInt(); + // TheSuperHackers @bugfix Skip the name key that Zero Hour writes after the type. See readActionType. + if (info->version >= K_SCRIPT_CONDITION_VERSION_4) { + file.readNameKey(); + } + const Bool unknownType = pCondition->m_conditionType < 0 || pCondition->m_conditionType >= NUM_ITEMS; + if (unknownType) { + // The parameters of an unknown type are not read. Closing the chunk skips them. + DEBUG_LOG(("Unknown script condition %d. Making it false.", pCondition->m_conditionType)); + pCondition->m_conditionType = CONDITION_FALSE; + } else { + pCondition->m_numParms =file.readInt(); + } Int i; for (i=0; im_numParms; i++) { @@ -1695,7 +1710,7 @@ Bool Condition::ParseConditionDataChunk(DataChunkInput &file, DataChunkInfo *inf } else { pOr->setFirstAndCondition(pCondition); } - DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Unexpected data left over.")); + DEBUG_ASSERTCRASH(unknownType || file.atEndOfChunk(), ("Unexpected data left over.")); return true; } @@ -2341,6 +2356,24 @@ void ScriptAction::WriteActionDataChunk(DataChunkOutput &chunkWriter, ScriptActi } } +// TheSuperHackers @bugfix Read the name key that Zero Hour writes after the script action type. +// The Generals types are the first entries of the Zero Hour lists, so the type number alone identifies them. +// Types past the end of the Generals list only exist in Zero Hour and become a no-op. +// The parameters of an unknown type are not read. Closing the chunk skips them. +static ScriptAction::ScriptActionType readActionType(DataChunkInput &file, DataChunkInfo *info, Script *pScript, Bool &unknownType) +{ + Int type = file.readInt(); + if (info->version >= K_SCRIPT_ACTION_VERSION_2) { + file.readNameKey(); + } + unknownType = type < 0 || type >= ScriptAction::NUM_ITEMS; + if (unknownType) { + DEBUG_LOG(("Unknown script action %d in script '%s'. Making it a no-op.", type, pScript->getName().str())); + return ScriptAction::NO_OP; + } + return (ScriptAction::ScriptActionType)type; +} + /** * ScriptAction::ParseActionDataChunk - read an action chunk in a script list. * Format is the newer CHUNKY format. @@ -2354,7 +2387,8 @@ Bool ScriptAction::ParseActionDataChunk(DataChunkInput &file, DataChunkInfo *inf ScriptAction *pScriptAction = newInstance(ScriptAction); - pScriptAction->m_actionType = (enum ScriptActionType)file.readInt(); + Bool unknownType; + pScriptAction->m_actionType = readActionType(file, info, pScript, unknownType); #if defined(RTS_DEBUG) const ActionTemplate* at = TheScriptEngine->getActionTemplate(pScriptAction->m_actionType); @@ -2363,7 +2397,9 @@ Bool ScriptAction::ParseActionDataChunk(DataChunkInput &file, DataChunkInfo *inf } #endif - pScriptAction->m_numParms =file.readInt(); + if (!unknownType) { + pScriptAction->m_numParms =file.readInt(); + } Int i; for (i=0; im_numParms; i++) { @@ -2449,7 +2485,7 @@ Bool ScriptAction::ParseActionDataChunk(DataChunkInput &file, DataChunkInfo *inf { pScript->setAction(pScriptAction); } - DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Unexpected data left over.")); + DEBUG_ASSERTCRASH(unknownType || file.atEndOfChunk(), ("Unexpected data left over.")); return true; } @@ -2489,8 +2525,11 @@ Bool ScriptAction::ParseActionFalseDataChunk(DataChunkInput &file, DataChunkInfo ScriptAction *pScriptAction = newInstance(ScriptAction); - pScriptAction->m_actionType = (enum ScriptActionType)file.readInt(); - pScriptAction->m_numParms =file.readInt(); + Bool unknownType; + pScriptAction->m_actionType = readActionType(file, info, pScript, unknownType); + if (!unknownType) { + pScriptAction->m_numParms =file.readInt(); + } Int i; for (i=0; im_numParms; i++) { pScriptAction->m_parms[i] = Parameter::ReadParameter(file); @@ -2505,7 +2544,7 @@ Bool ScriptAction::ParseActionFalseDataChunk(DataChunkInput &file, DataChunkInfo } else { pScript->setFalseAction(pScriptAction); } - DEBUG_ASSERTCRASH(file.atEndOfChunk(), ("Unexpected data left over.")); + DEBUG_ASSERTCRASH(unknownType || file.atEndOfChunk(), ("Unexpected data left over.")); return true; } From d9296979a9d5e688773afdbcc098d543be04e5d4 Mon Sep 17 00:00:00 2001 From: Omar Aglan Date: Sun, 8 Nov 2026 14:51:06 +0300 Subject: [PATCH 2/2] bugfix(worldbuilder): Read the layer name of version 4 polygon triggers when importing scripts --- .../Code/Tools/WorldBuilder/src/ScriptDialog.cpp | 8 ++++++++ .../Code/Tools/WorldBuilder/src/ScriptDialog.cpp | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp b/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp index 73dcb95bdbb..832454c921a 100644 --- a/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp +++ b/Generals/Code/Tools/WorldBuilder/src/ScriptDialog.cpp @@ -1656,6 +1656,7 @@ Bool ScriptDialog::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChunk Int triggerID; // Int maxTriggerId = 0; AsciiString triggerName; + AsciiString layerName; // Remove any existing polygon triggers, if any. ScriptDialog *pThis = (ScriptDialog *)userData; pThis->m_firstTrigger = nullptr; @@ -1667,6 +1668,10 @@ Bool ScriptDialog::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChunk count--; Bool isWater = false; triggerName = file.readAsciiString(); + // TheSuperHackers @bugfix Read the layer name of version 4 triggers. + if (info->version >= K_TRIGGERS_VERSION_4) { + layerName = file.readAsciiString(); + } triggerID = file.readInt(); if (info->version >= K_TRIGGERS_VERSION_2) { isWater = file.readByte(); @@ -1680,6 +1685,9 @@ Bool ScriptDialog::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChunk numPoints = file.readInt(); PolygonTrigger *pTrig = newInstance(PolygonTrigger)(numPoints+1); pTrig->setTriggerName(triggerName); + if (info->version >= K_TRIGGERS_VERSION_4) { + pTrig->setLayerName(layerName); + } pTrig->setWaterArea(isWater); pTrig->setRiver(isRiver); pTrig->setRiverStart(riverStart); diff --git a/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp b/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp index 982b5746d50..546851c3b76 100644 --- a/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp +++ b/GeneralsMD/Code/Tools/WorldBuilder/src/ScriptDialog.cpp @@ -38,6 +38,7 @@ #include "EditGroup.h" #include "EditParameter.h" #include "ExportScriptsOptions.h" +#include "LayersList.h" #include "Common/ThingFactory.h" #include "WaypointOptions.h" #include "Common/UnicodeString.h" @@ -1560,11 +1561,14 @@ void ScriptDialog::OnLoad() } PolygonTrigger *pTrig; PolygonTrigger *pNextTrig; + TheLayersList->disableUpdates(); for (pTrig=m_firstTrigger; pTrig; pTrig = pNextTrig) { pNextTrig = pTrig->getNext(); pTrig->setNextPoly(nullptr); PolygonTrigger::addPolygonTrigger(pTrig); + TheLayersList->addPolygonTriggerToLayersList(pTrig, pTrig->getLayerName()); } + TheLayersList->enableUpdates(); ScriptList *scripts[MAX_PLAYER_COUNT]; Int count = ScriptList::getReadScripts(scripts); @@ -1863,6 +1867,7 @@ Bool ScriptDialog::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChunk Int triggerID; // Int maxTriggerId = 0; AsciiString triggerName; + AsciiString layerName; // Remove any existing polygon triggers, if any. ScriptDialog *pThis = (ScriptDialog *)userData; pThis->m_firstTrigger = nullptr; @@ -1874,6 +1879,10 @@ Bool ScriptDialog::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChunk count--; Bool isWater = false; triggerName = file.readAsciiString(); + // TheSuperHackers @bugfix Read the layer name of version 4 triggers. + if (info->version >= K_TRIGGERS_VERSION_4) { + layerName = file.readAsciiString(); + } triggerID = file.readInt(); if (info->version >= K_TRIGGERS_VERSION_2) { isWater = file.readByte(); @@ -1887,6 +1896,9 @@ Bool ScriptDialog::ParsePolygonTriggersDataChunk(DataChunkInput &file, DataChunk numPoints = file.readInt(); PolygonTrigger *pTrig = newInstance(PolygonTrigger)(numPoints+1); pTrig->setTriggerName(triggerName); + if (info->version >= K_TRIGGERS_VERSION_4) { + pTrig->setLayerName(layerName); + } pTrig->setWaterArea(isWater); pTrig->setRiver(isRiver); pTrig->setRiverStart(riverStart);