Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
40d073e
bugfix(w3d): Skip rivers that overflow the dynamic index buffer (#3489)
tintinhamans Oct 13, 2026
92d94b5
fix(worldheightmap): Free the legacy cliff state buffer when loading …
tintinhamans Oct 13, 2026
7f3233f
bugfix(worldheightmap): Reject map sizes that overflow the cell array…
tintinhamans Oct 15, 2026
e985a3e
fix(lanapi): Fix GameInfo memory leaks in LANAPI::OnGameStart(), LANA…
tintinhamans Oct 10, 2026
d8fee7d
bugfix(network): Close sockets that fail to bind (#3488)
tintinhamans Oct 12, 2026
062fc89
fix(network): Release the disconnect chat message after sending it (#…
tintinhamans Oct 15, 2026
a9169a3
fix(lanapi): Delete the lobby player when they leave the lobby (#3499)
tintinhamans Oct 15, 2026
ea4afdb
fix(addoncompat): Close fullviewport.dat file handle after reading it…
tintinhamans Oct 11, 2026
cc48398
fix: Close leaked system handles in GameWindowManager, ImageCollectio…
tintinhamans Oct 13, 2026
76e46fe
fix(maputil): Close the file and free the buffer when a map preview c…
tintinhamans Oct 15, 2026
5da510c
perf(displaystring): Rebuild static text only when its hotkey setting…
tintinhamans Oct 9, 2026
3982944
bugfix(w3d): Fix leaked surface copy in screenshot and movie capture …
tintinhamans Oct 12, 2026
bc3d781
bugfix(gamewindow): Remove destroyed windows from the modal stack and…
tintinhamans Sep 9, 2026
dd4a958
perf(script): Reduce cost of ScriptEngine::findScript() by around 85%…
Mauller Aug 14, 2026
649743a
bugfix(pathfinder): Prevent a retail crash where the head of the clos…
Mauller Aug 3, 2026
87993aa
bugfix(pathfinder): Prevent a retail crash where a pathfindCell has a…
Mauller Aug 3, 2026
19ab074
bugfix: Prevent the game from freezing during scripted camera movemen…
tintinhamans Oct 25, 2026
5ea0282
refactor(particlesys): Cleanup retail smudge particle type identifica…
Mauller Aug 18, 2026
b57020c
unify(particlesys): Move W3DParticleSys.h to Core (#3172)
stephanmeesters Aug 19, 2026
7132a19
tweak(particlesys): Cleanup volume particle depth handling and add IN…
Mauller Aug 26, 2026
05f25bc
perf(particlesys): Batch same type particles to reduce cost of partic…
Mauller Sep 6, 2026
e07e8df
bugfix(particlesys): Render lone streak type particles as regular par…
Mauller Sep 20, 2026
2275bf0
bugfix(texturefilter): Fix blocky shroud edges from misconfigured tex…
mirelle7 Oct 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Core/GameEngine/Include/Common/GameDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,10 @@
#define PRESERVE_RETAIL_SCRIPTED_CAMERA (1) // Retain scripted camera behavior present in retail Generals 1.08 and Zero Hour 1.04
#endif

#ifndef PRESERVE_RETAIL_PARTICLES
#define PRESERVE_RETAIL_PARTICLES (1) // Preserve original look of particles present in retail Generals 1.08 and Zero Hour 1.04
#endif

#ifndef RETAIL_COMPATIBLE_CRC
#if defined(GENERALS_ONLINE)
#define RETAIL_COMPATIBLE_CRC (0)
Expand Down
28 changes: 17 additions & 11 deletions Core/GameEngine/Include/GameClient/ParticleSys.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,11 @@ enum ParticleSystemID CPP_11(: Int)
INVALID_PARTICLE_SYSTEM_ID = 0
};

#define MAX_VOLUME_PARTICLE_DEPTH ( 16 )
#define DEFAULT_VOLUME_PARTICLE_DEPTH ( 0 )//The Default is not to do the volume thing!
#define OPTIMUM_VOLUME_PARTICLE_DEPTH ( 6 )
constexpr const UnsignedInt INVALID_VOLUME_PARTICLE_DEPTH = 0; //The volume depth is not initialized
constexpr const UnsignedInt DEFAULT_VOLUME_PARTICLE_DEPTH = 1; //The Default is not to do the volume thing!
constexpr const UnsignedInt MIN_VOLUME_PARTICLE_DEPTH = 2;
constexpr const UnsignedInt OPTIMUM_VOLUME_PARTICLE_DEPTH = 6;
constexpr const UnsignedInt MAX_VOLUME_PARTICLE_DEPTH = 16;

// TheSuperHackers @info The X and Y angles are not necessary for particles because there are only 2 placement modes:
// Billboard (always facing camera) and Ground Aligned, which overwrite any rotations on the X and Y axis by design.
Expand Down Expand Up @@ -511,7 +513,9 @@ class ParticleSystemTemplate : public MemoryPoolObject, protected ParticleSystem
public:
ParticleSystemTemplate( const AsciiString &name );

AsciiString getName() const { return m_name; }
void validate();

const AsciiString& getName() const { return m_name; }

// This function was made const because of update modules' module data being all const.
ParticleSystem *createSlaveSystem( Bool createSlaves = TRUE ) const ; ///< if returns non-null, it is a slave system for use
Expand Down Expand Up @@ -602,15 +606,17 @@ class ParticleSystem : public MemoryPoolObject,

void setInitialDelay( UnsignedInt delay ) { m_delayLeft = delay; }

AsciiString getParticleTypeName() { return m_particleTypeName; } ///< return the name of the particles
Bool isUsingDrawables() { return (m_particleType == DRAWABLE) ? true : false; }
Bool isUsingStreak() { return (m_particleType == STREAK) ? true : false; }
Bool isUsingSmudge() { return (m_particleType == SMUDGE) ? true : false; }
UnsignedInt getVolumeParticleDepth() { return ( m_particleType == VOLUME_PARTICLE ) ? OPTIMUM_VOLUME_PARTICLE_DEPTH : 0; }
const AsciiString& getParticleTypeName() const { return m_particleTypeName; } ///< return the name of the particles
Bool isUsingParticles() const { return m_particleType == PARTICLE; }
Bool isUsingDrawables() const { return m_particleType == DRAWABLE; }
Bool isUsingStreak() const { return m_particleType == STREAK; }
Bool isUsingSmudge() const { return m_particleType == SMUDGE; }
Bool isUsingVolumeParticles() const { return m_particleType == VOLUME_PARTICLE; }
UnsignedInt getVolumeParticleDepth() const { return m_volumeParticleDepth; }

Bool shouldBillboard() { return !m_isGroundAligned; }
Bool shouldBillboard() const { return !m_isGroundAligned; }

ParticleShaderType getShaderType() { return m_shaderType; }
ParticleShaderType getShaderType() const { return m_shaderType; }

void setSlave( ParticleSystem *slave ); ///< set a slave system for us
ParticleSystem *getSlave() { return m_slaveSystem; }
Expand Down
1 change: 1 addition & 0 deletions Core/GameEngine/Include/GameNetwork/GameInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ class SkirmishGameInfo : public GameInfo, public Snapshot
for (Int i = 0; i< MAX_SLOTS; ++i)
setSlotPointer(i, &m_skirmishSlot[i]);
}
virtual ~SkirmishGameInfo() override {}
};

extern SkirmishGameInfo *TheSkirmishGameInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ class GameSpyStagingRoom : public GameInfo

public:
GameSpyStagingRoom();
virtual ~GameSpyStagingRoom() override {}
virtual void reset() override;

void cleanUpSlotPointers();
Expand Down
1 change: 1 addition & 0 deletions Core/GameEngine/Include/GameNetwork/LANGameInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class LANGameInfo : public GameInfo

public:
LANGameInfo();
virtual ~LANGameInfo() override {}
void setSlot( Int slotNum, LANGameSlot slotInfo ); ///< Set the slot state (human, open, AI, etc)
LANGameSlot* getLANSlot( Int slotNum ); ///< Get the slot
const LANGameSlot* getConstLANSlot( Int slotNum ) const; ///< Get the slot
Expand Down
1 change: 1 addition & 0 deletions Core/GameEngine/Source/Common/AddonCompat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Bool HasFullviewportDat()
if (File* file = TheFileSystem->openFile("GenTool/fullviewport.dat", File::READ | File::BINARY))
{
file->read(&value, 1);
file->close();
}
return value != '0';
}
Expand Down
1 change: 1 addition & 0 deletions Core/GameEngine/Source/Common/INI/INIParticleSys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,5 @@ void INI::parseParticleSystemDefinition( INI* ini )

// parse the ini definition
ini->initFromINI( sysTemplate, sysTemplate->getFieldParse() );
sysTemplate->validate();
}
58 changes: 17 additions & 41 deletions Core/GameEngine/Source/GameClient/MapUtil.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1142,53 +1142,38 @@ const MapMetaData *MapCache::findMap(AsciiString mapName)
// ------------------------------------------------------------------------------------------------
/** Embed the pristine map into the xfer stream */
// ------------------------------------------------------------------------------------------------
static void copyFromBigToDir( const AsciiString& infile, const AsciiString& outfile )
static Bool copyFromBigToDir( const AsciiString& infile, const AsciiString& outfile )
{
// open the map file

File *file = TheFileSystem->openFile( infile.str(), File::READ | File::BINARY );
if( file == nullptr )
{
DEBUG_CRASH(( "copyFromBigToDir - Error opening source file '%s'", infile.str() ));
throw SC_INVALID_DATA;
return FALSE;
}

// how big is the map file
Int fileSize = file->seek( 0, File::END );


// rewind to beginning of file
const Int fileSize = file->seek( 0, File::END );
file->seek( 0, File::START );

// allocate buffer big enough to hold the entire map file
char *buffer = NEW char[ fileSize ];
if( buffer == nullptr )
{
DEBUG_CRASH(( "copyFromBigToDir - Unable to allocate buffer for file '%s'", infile.str() ));
throw SC_INVALID_DATA;
}

// copy the file to the buffer
if( file->read( buffer, fileSize ) < fileSize )
{
DEBUG_CRASH(( "copyFromBigToDir - Error reading from file '%s'", infile.str() ));
throw SC_INVALID_DATA;
}
// close the BIG file
const Bool readOk = file->read( buffer, fileSize ) == fileSize;
file->close();
DEBUG_ASSERTCRASH( readOk, ( "copyFromBigToDir - Error reading from file '%s'", infile.str() ) );

File *filenew = TheFileSystem->openFile( outfile.str(), File::WRITE | File::CREATE | File::BINARY );

if( !filenew || filenew->write(buffer, fileSize) < fileSize)
Bool writeOk = FALSE;
if( readOk )
{
DEBUG_CRASH(( "copyFromBigToDir - Error writing to file '%s'", outfile.str() ));
throw SC_INVALID_DATA;
File *filenew = TheFileSystem->openFile( outfile.str(), File::WRITE | File::CREATE | File::BINARY );
if( filenew != nullptr )
{
writeOk = filenew->write( buffer, fileSize ) == fileSize;
filenew->close();
}
DEBUG_ASSERTCRASH( writeOk, ( "copyFromBigToDir - Error writing to file '%s'", outfile.str() ) );
}

filenew->close();

// delete the buffer
delete [] buffer;

return writeOk;
}

Image *getMapPreviewImage( AsciiString mapName )
Expand Down Expand Up @@ -1233,16 +1218,7 @@ Image *getMapPreviewImage( AsciiString mapName )

mapPreviewDir.concat(name);

Bool success = false;
try
{
copyFromBigToDir(tgaName, mapPreviewDir);
success = true;
}
catch (...)
{
success = false; // no rethrow
}
const Bool success = copyFromBigToDir(tgaName, mapPreviewDir);

if (success)
{
Expand Down
36 changes: 32 additions & 4 deletions Core/GameEngine/Source/GameClient/System/ParticleSys.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ ParticleSystemInfo::ParticleSystemInfo()
m_windMotionEndAngleMax = TWO_PI;
m_windMotionEndAngle = m_windMotionEndAngleMin;
m_windMotionMovingToEndAngle = TRUE;
m_volumeParticleDepth = DEFAULT_VOLUME_PARTICLE_DEPTH;
m_volumeParticleDepth = INVALID_VOLUME_PARTICLE_DEPTH;

}

Expand Down Expand Up @@ -1122,7 +1122,7 @@ ParticleSystem::ParticleSystem( const ParticleSystemTemplate *sysTemplate,


///@todo: further formalize this parameter with an UnsignedInt field in the editor
m_volumeParticleDepth = DEFAULT_VOLUME_PARTICLE_DEPTH;
m_volumeParticleDepth = sysTemplate->m_volumeParticleDepth;


m_driftVelocity = sysTemplate->m_driftVelocity;
Expand Down Expand Up @@ -2741,6 +2741,9 @@ const FieldParse ParticleSystemTemplate::m_fieldParseTable[] =
{ "SizeRate", INI::parseGameClientRandomVariable, nullptr, offsetof( ParticleSystemTemplate, m_sizeRate ) },
{ "SizeRateDamping", INI::parseGameClientRandomVariable, nullptr, offsetof( ParticleSystemTemplate, m_sizeRateDamping ) },

// TheSuperHackers @feature Volume particle depth is now exposed for configuration
{ "VolParticleDepth", INI::parseUnsignedInt, nullptr, offsetof(ParticleSystemTemplate, m_volumeParticleDepth) },

{ "Alpha1", ParticleSystemTemplate::parseRandomKeyframe, nullptr, offsetof( ParticleSystemTemplate, m_alphaKey[0] ) },
{ "Alpha2", ParticleSystemTemplate::parseRandomKeyframe, nullptr, offsetof( ParticleSystemTemplate, m_alphaKey[1] ) },
{ "Alpha3", ParticleSystemTemplate::parseRandomKeyframe, nullptr, offsetof( ParticleSystemTemplate, m_alphaKey[2] ) },
Expand Down Expand Up @@ -2910,6 +2913,31 @@ ParticleSystemTemplate::~ParticleSystemTemplate()
}

// ------------------------------------------------------------------------------------------------
void ParticleSystemTemplate::validate()
{
// TheSuperHackers @info Initialise all volume particles that lack ini configuration to the optimum depth of 6
// In retail, volume particle depth was not configurable through ini and was hard coded to a particle depth of 6
if (m_particleType == ParticleSystemInfo::VOLUME_PARTICLE)
{
if (m_volumeParticleDepth == INVALID_VOLUME_PARTICLE_DEPTH)
m_volumeParticleDepth = OPTIMUM_VOLUME_PARTICLE_DEPTH;
}
else
{
m_volumeParticleDepth = DEFAULT_VOLUME_PARTICLE_DEPTH;
}

#if PRESERVE_RETAIL_PARTICLES
// TheSuperHackers @info Hack to allow isUsingSmudge() functionality with retail smudge particles
// The retail data template for smudge particles is not correctly configured with the smudge particle type
if (m_particleType != ParticleSystemInfo::SMUDGE && m_particleTypeName.startsWithNoCase("SMUDGE."))
{
m_particleType = ParticleSystemInfo::SMUDGE;
}
#endif
}

// ------------------------------------------------------------------------------------------------
/** If returns non-null, it is a slave system for use ... the create slaves parameter
* tells *this* slave system whether or not it should create any slaves itself
* automatically during its own constructor */
Expand Down Expand Up @@ -3082,8 +3110,8 @@ void ParticleSystemManager::update()
if (sys->isUsingDrawables())
continue;

// temporary hack that checks if texture name starts with "SMUD" - if so, we can assume it's a smudge type
if (/*sys->isUsingSmudge()*/ *((DWORD *)sys->getParticleTypeName().str()) == 0x44554D53)
// Handle smudge type particles
if (sys->isUsingSmudge())
{
for (Particle *p = sys->getFirstParticle(); p; p = p->m_systemNext)
{
Expand Down
23 changes: 22 additions & 1 deletion Core/GameEngine/Source/GameLogic/AI/AIPathfind.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1125,9 +1125,11 @@ void PathfindCellInfo::forceCleanPathFindCellInfos()

void Pathfinder::forceCleanCells()
{
UnicodeString pathfinderFailoverMessage = TheGameText->FETCH_OR_SUBSTITUTE("GUI:PathfindingCrashPrevented", L"A pathfinding crash was prevented, now switching to the crash fixed pathfinding.");
UnicodeString pathfinderFailoverMessage = TheGameText->FETCH_OR_SUBSTITUTE_FORMAT("GUI:PathfindingCrashPrevented", L"A pathfinding crash was prevented at frame %u, now switching to the crash fixed pathfinding.", TheGameLogic->getFrame());
TheInGameUI->message(pathfinderFailoverMessage);

printf("%ls\n", pathfinderFailoverMessage.str());

TheAudio->addAudioEvent(&TheAudio->getMiscAudio()->m_allCheerSound);

PathfindCellInfo::forceCleanPathFindCellInfos();
Expand Down Expand Up @@ -1727,6 +1729,13 @@ void PathfindCell::forwardInsertionSortRetailCompatible(PathfindCellList& list)
UnsignedInt cellCount = 0;
while (currentCell && cellCount < PATHFIND_CELLS_PER_FRAME && currentCell->m_info->m_totalCost <= m_info->m_totalCost)
{
// Prevent a retail crash where a pathfindCell has an m_info with a dangling nextOpen pointer
if (currentCell->m_info->m_nextOpen && !currentCell->m_info->m_nextOpen->m_cell->m_info)
{
currentCell->m_info->m_nextOpen->m_cell = nullptr;
currentCell->m_info->m_nextOpen = nullptr;
}

cellCount++;
previousCell = currentCell;
currentCell = currentCell->getNextOpen();
Expand Down Expand Up @@ -1976,7 +1985,19 @@ void PathfindCell::putOnClosedList( PathfindCellList &list )
m_info->m_prevOpen = nullptr;
m_info->m_nextOpen = list.m_head ? list.m_head->m_info : nullptr;
if (list.m_head)
#if RETAIL_COMPATIBLE_PATHFINDING
// TheSuperHackers @info This is only here to catch a crash point in the retail compatible pathfinding
// This crash mode occurs due to the closed list head not having an m_info associated with it
// A node cannot be put onto the closed list without an m_info under normal conditions
{
if (list.m_head->m_info)
{
list.m_head->m_info->m_prevOpen = this->m_info;
}
}
#else
list.m_head->m_info->m_prevOpen = this->m_info;
#endif

list.m_head = this;
}
Expand Down
2 changes: 2 additions & 0 deletions Core/GameEngine/Source/GameNetwork/ConnectionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2509,6 +2509,8 @@ void ConnectionManager::sendDisconnectChat(UnicodeString text) {

sendLocalCommandDirect(msg, 0xff ^ (1 << m_localSlot));
processDisconnectChat(msg);

msg->detach();
}

UnsignedShort ConnectionManager::sendFileAnnounce(AsciiString path, UnsignedByte playerMask)
Expand Down
2 changes: 2 additions & 0 deletions Core/GameEngine/Source/GameNetwork/FirewallHelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1529,6 +1529,8 @@ Bool FirewallHelperClass::openSpareSocket(UnsignedShort port) {

if (m_spareSockets[i].udp->Bind((UnsignedInt)0, port) != 0) {
DEBUG_CRASH(("FirewallHelperClass::openSpareSocket - Failed to init spare socket"));
delete m_spareSockets[i].udp;
m_spareSockets[i].udp = nullptr;
return FALSE;
}

Expand Down
2 changes: 2 additions & 0 deletions Core/GameEngine/Source/GameNetwork/GameSpy/MainMenuUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -776,6 +776,7 @@ int asyncGethostbyname(char * szName)
/* Thread finished */
stat = 0;
s_asyncDNSLookupInProgress = FALSE;
CloseHandle(s_asyncDNSThreadHandle);
s_asyncDNSThreadHandle = nullptr;
return( (s_asyncDNSThreadSucceeded)?LOOKUP_SUCCEEDED:LOOKUP_FAILED );
}
Expand Down Expand Up @@ -833,6 +834,7 @@ void StopAsyncDNSCheck()
MAYBE_UNUSED Int res = TerminateThread(s_asyncDNSThreadHandle, 0);
(void)res;
DEBUG_ASSERTCRASH(res, ("Could not terminate the Async DNS Lookup thread!")); // Thread still not killed!
CloseHandle(s_asyncDNSThreadHandle);
}
s_asyncDNSThreadHandle = nullptr;
s_asyncDNSLookupInProgress = FALSE;
Expand Down
1 change: 1 addition & 0 deletions Core/GameEngine/Source/GameNetwork/LANAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -693,6 +693,7 @@ void LANAPI::RequestGameLeave()
// Exit out immediately if we're hosting
OnPlayerLeave(m_name);
removeGame(m_currentGame);
delete m_currentGame;
m_currentGame = nullptr;
m_inLobby = true;
}
Expand Down
1 change: 1 addition & 0 deletions Core/GameEngine/Source/GameNetwork/LANAPICallbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ void LANAPI::OnGameStart()
DEBUG_LOG(("After transfer, we didn't really have the map. Bailing..."));
OnPlayerLeave(m_name);
removeGame(m_currentGame);
delete m_currentGame;
m_currentGame = nullptr;
m_inLobby = TRUE;

Expand Down
1 change: 1 addition & 0 deletions Core/GameEngine/Source/GameNetwork/LANAPIhandlers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,7 @@ void LANAPI::handleRequestLobbyLeave( LANMessage *msg, UnsignedInt senderIP )
if (player->getIP() == senderIP)
{
removePlayer(player);
delete player;
OnPlayerList(m_lobbyPlayers);
break;
}
Expand Down
2 changes: 2 additions & 0 deletions Core/GameEngine/Source/GameNetwork/udp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,8 @@ Int UDP::Bind(UnsignedInt IP,UnsignedShort Port)
{
status=GetStatus();
//CERR("Bind failure (" << status << ") IP " << IP << " PORT " << Port )
closesocket(fd);
fd=0;
return(status);
}

Expand Down
2 changes: 1 addition & 1 deletion Core/GameEngineDevice/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ set(GAMEENGINEDEVICE_SRC
# Include/W3DDevice/GameClient/W3DInGameUI.h
# Include/W3DDevice/GameClient/W3DMirror.h
Include/W3DDevice/GameClient/W3DMouse.h
# Include/W3DDevice/GameClient/W3DParticleSys.h
Include/W3DDevice/GameClient/W3DParticleSys.h
# Include/W3DDevice/GameClient/W3DPoly.h
Include/W3DDevice/GameClient/W3DProfilerFrameCapture.h
# Include/W3DDevice/GameClient/W3DProjectedShadow.h
Expand Down
Loading
Loading