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..34de63536a 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);