Skip to content
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/GameEngine/Include/GameNetwork/LANAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,7 @@ struct LANMessage
{
char options[m_lanMaxOptionsLength+1];
} GameOptions;
static_assert(ARRAY_SIZE(GameOptions.options) > m_lanMaxOptionsLength, "GameOptions.options buffer must be larger than m_lanMaxOptionsLength");

};
};
Expand Down
20 changes: 17 additions & 3 deletions Core/GameEngine/Source/GameNetwork/GameInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
#include "GameNetwork/LANAPI.h" // for testing packet size
#include "GameNetwork/LANAPICallbacks.h" // for testing packet size
#include "WWLib/strtok_r.h"
#include "WWLib/utf8.h"



Expand Down Expand Up @@ -891,6 +892,20 @@ Bool GameInfo::isSandbox()

static const char slotListID = 'S';

// TheSuperHackers @bugfix Truncates the name to at most maxByteCount bytes without splitting
// a multibyte UTF-8 character. A non-positive budget empties the name; retail spun forever
// there, because removing the last character of an already empty string is a no-op.
static void truncatePlayerName(AsciiString& name, Int maxByteCount)
{
if (maxByteCount <= 0)
{
name.clear();
return;
}

name.truncateTo(static_cast<Int>(Utf8_Truncate_Len(name.str(), name.getLength(), maxByteCount)));
}

AsciiString GameInfoToAsciiString( const GameInfo *game )
{
if (!game)
Expand Down Expand Up @@ -953,8 +968,7 @@ AsciiString GameInfoToAsciiString( const GameInfo *game )
int lenRem = m_lanMaxOptionsLength - lenCur; //length remaining before overflowing
int lenMax = lenRem / (MAX_SLOTS-i); //share lenRem with all remaining slots
AsciiString name = WideCharStringToMultiByte(slot->getName().str()).c_str();
while( name.getLength() > lenMax )
name.removeLastChar(); //what a horrible way to truncate. I hate AsciiString.
truncatePlayerName( name, lenMax );

str.format( "H%s%s", name.str(), tmp.str() );
}
Expand Down Expand Up @@ -988,7 +1002,7 @@ AsciiString GameInfoToAsciiString( const GameInfo *game )
}
optionsString.concat(';');

DEBUG_ASSERTCRASH(!TheLAN || (optionsString.getLength() < m_lanMaxOptionsLength),
DEBUG_ASSERTCRASH(!TheLAN || (optionsString.getLength() <= m_lanMaxOptionsLength),
("WARNING: options string is longer than expected! Length is %d, but max is %d!",
optionsString.getLength(), m_lanMaxOptionsLength));

Expand Down
2 changes: 1 addition & 1 deletion Core/GameEngine/Source/GameNetwork/LANAPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ void LANAPI::RequestGameStartTimer( Int seconds )

void LANAPI::RequestGameOptions( AsciiString gameOptions, Bool isPublic, UnsignedInt ip /* = 0 */ )
{
DEBUG_ASSERTCRASH(gameOptions.getLength() < m_lanMaxOptionsLength, ("Game options string is too long!"));
DEBUG_ASSERTCRASH(gameOptions.getLength() <= m_lanMaxOptionsLength, ("Game options string is too long!"));

if (!m_currentGame)
return;
Expand Down
2 changes: 2 additions & 0 deletions Core/Libraries/Source/WWVegas/WWLib/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ set(WWLIB_SRC
trim.cpp
trim.h
uarray.h
utf8.cpp
utf8.h
vector.cpp
Vector.h
visualc.h
Expand Down
41 changes: 41 additions & 0 deletions Core/Libraries/Source/WWVegas/WWLib/utf8.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2026 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/>.
*/

#include "always.h"
#include "utf8.h"

// A UTF-8 continuation byte matches 10xxxxxx, so it can never start a sequence.
static bool Utf8_Is_Continuation_Byte(char c)
{
return ((unsigned char)c & 0xC0) == 0x80;
}

size_t Utf8_Truncate_Len(const char* src, size_t srcLen, size_t maxLen)
{
if (srcLen <= maxLen)
{
return srcLen;
}

size_t len = maxLen;
while (len > 0 && Utf8_Is_Continuation_Byte(src[len]))
{
--len;
}
return len;
}
27 changes: 27 additions & 0 deletions Core/Libraries/Source/WWVegas/WWLib/utf8.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2026 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 <stddef.h>

// Returns the largest length not greater than maxLen at which the srcLen bytes of the UTF-8 string
// src can be cut without splitting a multibyte sequence, by backing off the continuation bytes at
// the cut point. Returns srcLen when the string already fits in maxLen. Returns 0 when no whole
// sequence fits, which is also what malformed UTF-8 yields once it has no lead byte to back off to.
size_t Utf8_Truncate_Len(const char* src, size_t srcLen, size_t maxLen);
Loading