Skip to content

[GEN][ZH] Fix Memory Manager initialization issues #1236

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ set(WWLIB_SRC
#lzostraw.cpp
#lzostraw.h
#lzo_conf.h
MallocAllocator.h
#md5.cpp
#md5.h
mempool.h
Expand Down
129 changes: 129 additions & 0 deletions Core/Libraries/Source/WWVegas/WWLib/MallocAllocator.h
Original file line number Diff line number Diff line change
@@ -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 <http://www.gnu.org/licenses/>.
*/

#pragma once

#include <cstdlib> // malloc, free
#include <cstddef> // std::size_t, std::ptrdiff_t
#include <new> // std::bad_alloc


namespace stl
{

// STL allocator that uses malloc and free. Useful if allocations are meant to bypass new and delete.

template <typename T>
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 <typename U>
struct rebind
{
typedef malloc_allocator<U> other;
};

malloc_allocator() throw() {}

#if !(defined(_MSC_VER) && _MSC_VER < 1300)
malloc_allocator(const malloc_allocator&) throw() {}
#endif

template <typename U>
malloc_allocator(const malloc_allocator<U>&) 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<pointer>(p);
}

void deallocate(pointer p, size_type)
{
::free(p);
}

void construct(pointer p, const T& val)
{
new (static_cast<void*>(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 <typename T1, typename T2>
bool operator==(const malloc_allocator<T1>&, const malloc_allocator<T2>&) throw() {
return true;
}

template <typename T1, typename T2>
bool operator!=(const malloc_allocator<T1>&, const malloc_allocator<T2>&) throw() {
return false;
}

} // namespace stl


#if defined(USING_STLPORT)

// This tells STLport how to rebind malloc_allocator
namespace std
{
template <class _Tp1, class _Tp2>
struct __stl_alloc_rebind_helper;

template <class Tp1, class Tp2>
inline stl::malloc_allocator<Tp2>& __stl_alloc_rebind(stl::malloc_allocator<Tp1>& a, const Tp2*) {
return *reinterpret_cast<stl::malloc_allocator<Tp2>*>(&a);
}

template <class Tp1, class Tp2>
inline const stl::malloc_allocator<Tp2>& __stl_alloc_rebind(const stl::malloc_allocator<Tp1>& a, const Tp2*) {
return *reinterpret_cast<const stl::malloc_allocator<Tp2>*>(&a);
}
}

#endif
1 change: 1 addition & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/CommandLine.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class CommandLine
{
public:

static void parseCommandLineForClientInstance();
static void parseCommandLineForStartup();
static void parseCommandLineForEngineInit();
};
Expand Down
8 changes: 8 additions & 0 deletions GeneralsMD/Code/GameEngine/Include/Common/STLTypedefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ enum DrawableID CPP_11(: Int);
#include <stack>
#include <string>
#include <vector>
#include "MallocAllocator.h"

// List of AsciiStrings to allow list of ThingTemplate names from INI and such
typedef std::list< AsciiString > AsciiStringList;
Expand Down Expand Up @@ -113,6 +114,13 @@ typedef std::vector<Bool>::iterator BoolVectorIterator;
typedef std::map< NameKeyType, Real, std::less<NameKeyType> > ProductionChangeMap;
typedef std::map< NameKeyType, VeterancyLevel, std::less<NameKeyType> > ProductionVeterancyMap;

namespace stl
{
typedef std::basic_string<char, std::char_traits<char>, malloc_allocator<char> > malloc_string;
typedef std::basic_string<wchar_t, std::char_traits<wchar_t>, malloc_allocator<wchar_t> > malloc_wstring;

} // namespace stl

// Some useful, common hash and equal_to functors for use with hash_map
namespace rts
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down
53 changes: 41 additions & 12 deletions GeneralsMD/Code/GameEngine/Source/Common/CommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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[] =
Expand Down Expand Up @@ -1382,9 +1399,9 @@ char *nextParam(char *newSource, const char *seps)

static void parseCommandLine(const CommandLineParam* params, int numParams)
{
std::vector<char*> argv;
std::vector<char*, stl::malloc_allocator<char*> > argv;

std::string cmdLine = GetCommandLineA();
stl::malloc_string cmdLine = GetCommandLineA();
char *token = nextParam(&cmdLine[0], "\" ");
while (token != NULL)
{
Expand Down Expand Up @@ -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 (arg<argc)
{
// Look at arg #i
Expand All @@ -1437,18 +1454,28 @@ static void parseCommandLine(const CommandLineParam* params, int numParams)
}
}

void createGlobalData()
void createTheGlobalData()
{
if (TheGlobalData == NULL)
TheWritableGlobalData = NEW GlobalData;
}

void CommandLine::parseCommandLineForClientInstance()
{
// TheSuperHackers @info This function must not allocate using 'new' because it can be called before the Memory Manager is initialized.
// This function is potentially called multiple times.

parseCommandLine(paramsForClientInstance, ARRAY_SIZE(paramsForClientInstance));
}

void CommandLine::parseCommandLineForStartup()
{
// We need the GlobalData initialized before parsing the command line.
// Note that this function is potentially called multiple times and only initializes the first time.
createGlobalData();
assert(TheDynamicMemoryAllocator != NULL);

// TheSuperHackers @info TheGlobalData needs to be initialized before parsing the command line.
createTheGlobalData();

// This function is potentially called multiple times and only initializes the first time.
if (TheGlobalData->m_commandLineData.m_hasParsedCommandLineForStartup)
return;
TheWritableGlobalData->m_commandLineData.m_hasParsedCommandLineForStartup = true;
Expand All @@ -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"));
Expand Down
5 changes: 3 additions & 2 deletions GeneralsMD/Code/GameEngine/Source/Common/System/Debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3412,6 +3412,8 @@ void initMemoryManager()
{
if (TheMemoryPoolFactory == NULL)
{
DEBUG_LOG(("*** Initing Memory Manager"));

Int numSubPools;
const PoolInitRec *pParms;
userMemoryManagerGetDmaParms(&numSubPools, &pParms);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
13 changes: 8 additions & 5 deletions GeneralsMD/Code/Main/WinMain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -799,7 +799,6 @@ Int APIENTRY WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,
}
::SetCurrentDirectory(buffer);

CommandLine::parseCommandLineForStartup();

#ifdef RTS_DEBUG
// Turn on Memory heap tracking
Expand Down Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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);
Expand Down
Loading