From ea5f74e058c8549b83ed6ff718d3094a40ebcdd1 Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 6 Jul 2025 22:10:28 +0200 Subject: [PATCH 1/2] [ZH] Fix Memory Manager initialization issues --- .../Source/WWVegas/WWLib/CMakeLists.txt | 1 + .../Source/WWVegas/WWLib/MallocAllocator.h | 129 ++++++++++++++++++ .../GameEngine/Include/Common/CommandLine.h | 1 + .../GameEngine/Include/Common/STLTypedefs.h | 8 ++ .../Include/GameClient/ClientInstance.h | 1 + .../GameEngine/Source/Common/CommandLine.cpp | 53 +++++-- .../GameEngine/Source/Common/System/Debug.cpp | 5 +- .../Source/Common/System/GameMemory.cpp | 2 + .../Source/GameClient/ClientInstance.cpp | 4 +- GeneralsMD/Code/Main/WinMain.cpp | 13 +- 10 files changed, 196 insertions(+), 21 deletions(-) create mode 100644 Core/Libraries/Source/WWVegas/WWLib/MallocAllocator.h diff --git a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt index 88a0c0b3ab..76995a52dc 100644 --- a/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt +++ b/Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt @@ -66,6 +66,7 @@ set(WWLIB_SRC #lzostraw.cpp #lzostraw.h #lzo_conf.h + MallocAllocator.h #md5.cpp #md5.h mempool.h diff --git a/Core/Libraries/Source/WWVegas/WWLib/MallocAllocator.h b/Core/Libraries/Source/WWVegas/WWLib/MallocAllocator.h new file mode 100644 index 0000000000..e465b0ad5d --- /dev/null +++ b/Core/Libraries/Source/WWVegas/WWLib/MallocAllocator.h @@ -0,0 +1,129 @@ +/* +** Command & Conquer Generals Zero Hour(tm) +** Copyright 2025 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 . +*/ + +#pragma once + +#include // malloc, free +#include // std::size_t, std::ptrdiff_t +#include // std::bad_alloc + + +namespace stl +{ + +// STL allocator that uses malloc and free. Useful if allocations are meant to bypass new and delete. + +template +class malloc_allocator +{ +public: + + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + template + struct rebind + { + typedef malloc_allocator other; + }; + + malloc_allocator() throw() {} + +#if !(defined(_MSC_VER) && _MSC_VER < 1300) + malloc_allocator(const malloc_allocator&) throw() {} +#endif + + template + malloc_allocator(const malloc_allocator&) throw() {} + + ~malloc_allocator() throw() {} + + pointer address(reference x) const { return &x; } + const_pointer address(const_reference x) const { return &x; } + + pointer allocate(size_type n, const void* = 0) + { + if (n > max_size()) + throw std::bad_alloc(); + + void* p = ::malloc(n * sizeof(T)); + if (!p) + throw std::bad_alloc(); + return static_cast(p); + } + + void deallocate(pointer p, size_type) + { + ::free(p); + } + + void construct(pointer p, const T& val) + { + new (static_cast(p)) T(val); + } + + void destroy(pointer p) + { + p->~T(); + } + + size_type max_size() const throw() + { + return ~size_type(0) / sizeof(T); + } +}; + +// Allocators of same type are always equal +template +bool operator==(const malloc_allocator&, const malloc_allocator&) throw() { + return true; +} + +template +bool operator!=(const malloc_allocator&, const malloc_allocator&) throw() { + return false; +} + +} // namespace stl + + +#if defined(USING_STLPORT) + +// This tells STLport how to rebind malloc_allocator +namespace std +{ + template + struct __stl_alloc_rebind_helper; + + template + inline stl::malloc_allocator& __stl_alloc_rebind(stl::malloc_allocator& a, const Tp2*) { + return *reinterpret_cast*>(&a); + } + + template + inline const stl::malloc_allocator& __stl_alloc_rebind(const stl::malloc_allocator& a, const Tp2*) { + return *reinterpret_cast*>(&a); + } +} + +#endif diff --git a/GeneralsMD/Code/GameEngine/Include/Common/CommandLine.h b/GeneralsMD/Code/GameEngine/Include/Common/CommandLine.h index fe458850ba..c0e8988118 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/CommandLine.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/CommandLine.h @@ -35,6 +35,7 @@ class CommandLine { public: + static void parseCommandLineForClientInstance(); static void parseCommandLineForStartup(); static void parseCommandLineForEngineInit(); }; diff --git a/GeneralsMD/Code/GameEngine/Include/Common/STLTypedefs.h b/GeneralsMD/Code/GameEngine/Include/Common/STLTypedefs.h index 374e7bb51b..b803041195 100644 --- a/GeneralsMD/Code/GameEngine/Include/Common/STLTypedefs.h +++ b/GeneralsMD/Code/GameEngine/Include/Common/STLTypedefs.h @@ -79,6 +79,7 @@ enum DrawableID CPP_11(: Int); #include #include #include +#include "MallocAllocator.h" // List of AsciiStrings to allow list of ThingTemplate names from INI and such typedef std::list< AsciiString > AsciiStringList; @@ -113,6 +114,13 @@ typedef std::vector::iterator BoolVectorIterator; typedef std::map< NameKeyType, Real, std::less > ProductionChangeMap; typedef std::map< NameKeyType, VeterancyLevel, std::less > ProductionVeterancyMap; +namespace stl +{ +typedef std::basic_string, malloc_allocator > malloc_string; +typedef std::basic_string, malloc_allocator > malloc_wstring; + +} // namespace stl + // Some useful, common hash and equal_to functors for use with hash_map namespace rts { diff --git a/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h b/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h index 1f1ada1dac..8a545d06ae 100644 --- a/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h +++ b/GeneralsMD/Code/GameEngine/Include/GameClient/ClientInstance.h @@ -21,6 +21,7 @@ namespace rts { // TheSuperHackers @feature Adds support for launching multiple game clients and keeping track of their instance id. +// This class must not allocate using 'new' because it can be used before the Memory Manager is initialized. class ClientInstance { diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index dc967b179c..26f9ec17eb 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -427,15 +427,27 @@ Int parseReplay(char *args[], int num) exit(1); } TheWritableGlobalData->m_simulateReplays.push_back(filename); - + TheWritableGlobalData->m_playIntro = FALSE; TheWritableGlobalData->m_afterIntro = TRUE; TheWritableGlobalData->m_playSizzle = FALSE; TheWritableGlobalData->m_shellMapOn = FALSE; - // Make replay playback possible while other clients (possible retail) are running - rts::ClientInstance::setMultiInstance(TRUE); - rts::ClientInstance::skipPrimaryInstance(); + return 2; + } + return 1; +} + +Int parseReplayForClientInstance(char *args[], int num) +{ + if (num > 1) + { + if (!rts::ClientInstance::isInitialized()) + { + // Make replay playback possible while other clients (possible retail) are running + rts::ClientInstance::setMultiInstance(TRUE); + rts::ClientInstance::skipPrimaryInstance(); + } return 2; } @@ -1140,6 +1152,11 @@ Int parseClearDebugLevel(char *args[], int num) } #endif +static CommandLineParam paramsForClientInstance[] = +{ + { "-replay", parseReplayForClientInstance }, +}; + // Initial Params are parsed before Windows Creation. // Note that except for TheGlobalData, no other global objects exist yet when these are parsed. static CommandLineParam paramsForStartup[] = @@ -1382,9 +1399,9 @@ char *nextParam(char *newSource, const char *seps) static void parseCommandLine(const CommandLineParam* params, int numParams) { - std::vector argv; + std::vector> argv; - std::string cmdLine = GetCommandLineA(); + stl::malloc_string cmdLine = GetCommandLineA(); char *token = nextParam(&cmdLine[0], "\" "); while (token != NULL) { @@ -1412,7 +1429,7 @@ static void parseCommandLine(const CommandLineParam* params, int numParams) // and functions to handle them. Comparisons can be case-(in)sensitive, and // can check the entire string (for testing the presence of a flag) or check // just the start (for a key=val argument). The handling function can also - // look at the next argument(s), to accomodate multi-arg parameters, e.g. "-p 1234". + // look at the next argument(s), to accommodate multi-arg parameters, e.g. "-p 1234". while (argm_commandLineData.m_hasParsedCommandLineForStartup) return; TheWritableGlobalData->m_commandLineData.m_hasParsedCommandLineForStartup = true; @@ -1458,7 +1485,9 @@ void CommandLine::parseCommandLineForStartup() void CommandLine::parseCommandLineForEngineInit() { - createGlobalData(); + assert(TheDynamicMemoryAllocator != NULL); + + createTheGlobalData(); DEBUG_ASSERTCRASH(TheGlobalData->m_commandLineData.m_hasParsedCommandLineForStartup, ("parseCommandLineForStartup is expected to be called before parseCommandLineForEngineInit\n")); diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/Debug.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/Debug.cpp index 173c496ad4..7e2f4f30de 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/Debug.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/Debug.cpp @@ -367,8 +367,9 @@ void DebugInit(int flags) #ifdef DEBUG_LOGGING // TheSuperHackers @info Debug initialization can happen very early. - // Therefore, parse initial commandline and initialize the client instance now. - CommandLine::parseCommandLineForStartup(); + // Determine the client instance id before creating the log file with an instance specific name. + CommandLine::parseCommandLineForClientInstance(); + if (!rts::ClientInstance::initialize()) return; diff --git a/GeneralsMD/Code/GameEngine/Source/Common/System/GameMemory.cpp b/GeneralsMD/Code/GameEngine/Source/Common/System/GameMemory.cpp index 220cc0b532..05f95411cd 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/System/GameMemory.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/System/GameMemory.cpp @@ -3412,6 +3412,8 @@ void initMemoryManager() { if (TheMemoryPoolFactory == NULL) { + DEBUG_LOG(("*** Initing Memory Manager")); + Int numSubPools; const PoolInitRec *pParms; userMemoryManagerGetDmaParms(&numSubPools, &pParms); diff --git a/GeneralsMD/Code/GameEngine/Source/GameClient/ClientInstance.cpp b/GeneralsMD/Code/GameEngine/Source/GameClient/ClientInstance.cpp index 59edcc3c32..ef559676cc 100644 --- a/GeneralsMD/Code/GameEngine/Source/GameClient/ClientInstance.cpp +++ b/GeneralsMD/Code/GameEngine/Source/GameClient/ClientInstance.cpp @@ -37,14 +37,14 @@ bool ClientInstance::initialize() { return true; } - + // Create a mutex with a unique name to Generals in order to determine if our app is already running. // WARNING: DO NOT use this number for any other application except Generals. while (true) { if (isMultiInstance()) { - std::string guidStr = getFirstInstanceName(); + stl::malloc_string guidStr = getFirstInstanceName(); if (s_instanceIndex > 0u) { char idStr[33]; diff --git a/GeneralsMD/Code/Main/WinMain.cpp b/GeneralsMD/Code/Main/WinMain.cpp index c2754bc541..3c332a7aa0 100644 --- a/GeneralsMD/Code/Main/WinMain.cpp +++ b/GeneralsMD/Code/Main/WinMain.cpp @@ -799,7 +799,6 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, } ::SetCurrentDirectory(buffer); - CommandLine::parseCommandLineForStartup(); #ifdef RTS_DEBUG // Turn on Memory heap tracking @@ -839,6 +838,12 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, gLoadScreenBitmap = (HBITMAP)LoadImage(hInstance, "Install_Final.bmp", IMAGE_BITMAP, 0, 0, LR_SHARED|LR_LOADFROMFILE); #endif + // start the log + DEBUG_INIT(DEBUG_FLAGS_DEFAULT); + initMemoryManager(); + + CommandLine::parseCommandLineForStartup(); + // register windows class and create application window if(!TheGlobalData->m_headless && initializeAppWindows(hInstance, nCmdShow, TheGlobalData->m_windowed) == false) return exitcode; @@ -855,11 +860,7 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // BGC - initialize COM // OleInitialize(NULL); - // start the log - DEBUG_INIT(DEBUG_FLAGS_DEFAULT); - initMemoryManager(); - // Set up version info TheVersion = NEW Version; TheVersion->setVersion(VERSION_MAJOR, VERSION_MINOR, VERSION_BUILDNUM, VERSION_LOCALBUILDNUM, @@ -868,6 +869,8 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, // TheSuperHackers @refactor The instance mutex now lives in its own class. + CommandLine::parseCommandLineForClientInstance(); + if (!rts::ClientInstance::initialize()) { HWND ccwindow = FindWindow(rts::ClientInstance::getFirstInstanceName(), NULL); From 50b67244e4a1ff824b0729437bc98e6f204d871c Mon Sep 17 00:00:00 2001 From: xezon <4720891+xezon@users.noreply.github.com> Date: Sun, 6 Jul 2025 22:28:23 +0200 Subject: [PATCH 2/2] Fix VC6 compile error --- GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp index 26f9ec17eb..34de63536a 100644 --- a/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp +++ b/GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp @@ -1399,7 +1399,7 @@ char *nextParam(char *newSource, const char *seps) static void parseCommandLine(const CommandLineParam* params, int numParams) { - std::vector> argv; + std::vector > argv; stl::malloc_string cmdLine = GetCommandLineA(); char *token = nextParam(&cmdLine[0], "\" ");