Compare commits

...

3 commits

Author SHA1 Message Date
Dennis Felsing 70ed41b37f Estonian fixes (by Cammo) 2024-03-11 00:16:37 +01:00
Robert Müller 7208b9a67b Add validation for StrToInts and IntsToStr
Add strict validation for `StrToInts` function. Because this function should only be used with trusted internal strings, assertions are added to ensure that the string is not truncated. Some buffer sizes are adjusted accordingly, so truncation cannot happen.

Add less strict validation for `IntsToStr` function. An additional argument specifying the size of the output buffer is added to assert that the size of the output buffer is sufficient. However, because this function is used to decode data sent by the server and read from maps and ghosts, invalid input data should never result in crashes or invalid UTF-8 strings. The function will now unpack an empty string and return `false`, if the string contains invalid UTF-8.

The inline definition of the functions is not wanted, because it requires adding a `system.h` include in `gamecore.h`. Therefore the tools now have to depend on game-shared, which previously only worked because the functions were inline.

Tests are added to ensure the function still behaves the same as before for valid inputs and correctly handles invalid inputs.
2024-03-11 00:04:31 +01:00
Dennis Felsing 389efa0c27 Revert "Add validation for StrToInts and IntsToStr, move and rename"
This reverts commit 653c5d07d4.
2024-03-11 00:03:12 +01:00
14 changed files with 134 additions and 167 deletions

View file

@ -2707,6 +2707,7 @@ if(TOOLS)
src/tools/${TOOL}.cpp
${EXTRA_TOOL_SRC}
$<TARGET_OBJECTS:engine-shared>
$<TARGET_OBJECTS:game-shared>
)
target_include_directories(${TOOL} SYSTEM PRIVATE ${TOOL_INCLUDE_DIRS})
target_link_libraries(${TOOL} ${TOOL_LIBS})

View file

@ -3,6 +3,7 @@
# Cammodude
#modified by:
# Cammodude 2024-09-03 22:19:06
# Cammodude 2024-10-03 18:04:16
##### /authors #####
##### translated strings #####
@ -83,7 +84,7 @@ Console
== Konsool
Controls
== Kontrollit
== Kontrollid
Count players only
== Loe ainult mängijaid
@ -317,7 +318,7 @@ Reset filter
== Lähtesta filter
Score
== Seis
== Skoor
Score limit
== Punktilimiit
@ -365,7 +366,7 @@ Spectate next
== Jälgi järgmist
Spectate previous
== Järgi eelmist
== Jälgi eelmist
Spectator mode
== Pealtvaataja režiim
@ -521,7 +522,7 @@ Replay feature is disabled!
== Korduse funktsioon on keelatud!
The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs.
== Tekstuuri %s laius ei jagu %d -ga või kõrgus ei jagu %d -ga, mis võib põhjustada visuaalseid vigu.
== Tekstuuri %s laius ei jagu %d-ga või kõrgus ei jagu %d-ga, mis võib põhjustada visuaalseid vigu.
Warning
== Hoiatus
@ -1707,10 +1708,10 @@ Entities
== Üksused
Door Laser Outline Color
== Ukse laseri Kontuuri Värv
== Ukse Laseri Kontuuri Värv
Door Laser Inner Color
== Ukse laseri Sisene Värv
== Ukse Laseri Sisene Värv
Freeze Laser Outline Color
== Külmutava Laseri Kontuuri Värv

View file

@ -4037,100 +4037,6 @@ const char *str_next_token(const char *str, const char *delim, char *buffer, int
return tok + len;
}
void str_to_int32(int *ints, size_t num_ints, const char *str)
{
dbg_assert(num_ints > 0, "str_to_int32: num_ints invalid");
// Clear all integers, which also ensures null-termination,
// as the last byte is never written to.
for(size_t i = 0; i < num_ints; i++)
{
ints[i] = 0;
}
size_t byte_index = 0;
const size_t total_bytes = num_ints * sizeof(int) - 1; // -1 for null-termination
auto &&write_byte = [ints, &byte_index](int b) mutable {
ints[byte_index / sizeof(int)] |= b << ((sizeof(int) - byte_index % sizeof(int) - 1) * 8);
byte_index++;
};
// Write each UTF-8 codepoint individually
while(true)
{
const int codepoint = str_utf8_decode(&str);
dbg_assert(codepoint != -1, "str_to_int32: invalid UTF-8 in string");
if(codepoint == 0)
{
break;
}
char encoded[4];
const size_t encoded_count = str_utf8_encode(encoded, codepoint);
dbg_assert(encoded_count <= total_bytes - byte_index, "str_to_int32: string truncated");
for(size_t i = 0; i < encoded_count; i++)
{
write_byte(encoded[i] + 128);
}
}
// Write padding
while(byte_index < total_bytes)
{
write_byte(128);
}
}
void int32_to_str(const int *ints, size_t num_ints, char *str, size_t str_size)
{
dbg_assert(num_ints > 0, "int32_to_str: num_ints invalid");
dbg_assert(str_size >= num_ints * sizeof(int), "int32_to_str: str_size invalid");
// Unpack string without validation
size_t str_index = 0;
for(size_t int_index = 0; int_index < num_ints; int_index++)
{
const int current_int = ints[int_index];
str[str_index] = ((current_int >> 24) & 0xff) - 128;
str_index++;
str[str_index] = ((current_int >> 16) & 0xff) - 128;
str_index++;
str[str_index] = ((current_int >> 8) & 0xff) - 128;
str_index++;
str[str_index] = (current_int & 0xff) - 128;
str_index++;
}
// Ensure null-termination
str[str_index - 1] = '\0';
// Validate
const char *check_str = str;
int str_check_index = 0;
while(true)
{
const char *prev_check_str = check_str;
const int codepoint = str_utf8_decode(&check_str);
if(codepoint == 0)
{
// Check for (early) null-termination.
str[str_check_index] = '\0';
break;
}
const size_t codepoint_size = check_str - prev_check_str;
if(codepoint == -1)
{
// Replace invalid codepoints with question mark characters instead of the Unicode
// replacement character, because the replacement character is 3 bytes long, so the
// string with added replacement characters may not fit into the buffer.
for(size_t i = 0; i < codepoint_size; i++)
{
str[str_check_index + i] = '?';
}
}
str_check_index += codepoint_size;
}
}
static_assert(sizeof(unsigned) == 4, "unsigned must be 4 bytes in size");
static_assert(sizeof(unsigned) == sizeof(int), "unsigned and int must have the same size");

View file

@ -2418,9 +2418,6 @@ const char *str_next_token(const char *str, const char *delim, char *buffer, int
*/
int str_in_list(const char *list, const char *delim, const char *needle);
void str_to_int32(int *ints, size_t num_ints, const char *str);
void int32_to_str(const int *ints, size_t num_ints, char *str, size_t str_size);
/**
* Packs 4 big endian bytes into an unsigned.
*

View file

@ -20,7 +20,7 @@ CGhost::CGhost() :
void CGhost::GetGhostSkin(CGhostSkin *pSkin, const char *pSkinName, int UseCustomColor, int ColorBody, int ColorFeet)
{
str_to_int32(&pSkin->m_Skin0, 6, pSkinName);
StrToInts(&pSkin->m_Skin0, 6, pSkinName);
pSkin->m_UseCustomColor = UseCustomColor;
pSkin->m_ColorBody = ColorBody;
pSkin->m_ColorFeet = ColorFeet;
@ -386,7 +386,7 @@ void CGhost::OnRender()
void CGhost::InitRenderInfos(CGhostItem *pGhost)
{
char aSkinName[24];
int32_to_str(&pGhost->m_Skin.m_Skin0, 6, aSkinName, std::size(aSkinName));
IntsToStr(&pGhost->m_Skin.m_Skin0, 6, aSkinName, std::size(aSkinName));
CTeeRenderInfo *pRenderInfo = &pGhost->m_RenderInfo;
const CSkin *pSkin = m_pClient->m_Skins.Find(aSkinName);
@ -690,7 +690,7 @@ void CGhost::OnRefreshSkins()
if(Ghost.Empty())
return;
char aSkinName[24];
int32_to_str(&Ghost.m_Skin.m_Skin0, 6, aSkinName, std::size(aSkinName));
IntsToStr(&Ghost.m_Skin.m_Skin0, 6, aSkinName, std::size(aSkinName));
CTeeRenderInfo *pRenderInfo = &Ghost.m_RenderInfo;
if(aSkinName[0] != '\0')
{

View file

@ -1383,10 +1383,13 @@ void CGameClient::OnNewSnapshot()
{
CClientData *pClient = &m_aClients[ClientID];
int32_to_str(&pInfo->m_Name0, 4, pClient->m_aName, std::size(pClient->m_aName));
int32_to_str(&pInfo->m_Clan0, 3, pClient->m_aClan, std::size(pClient->m_aClan));
if(!IntsToStr(&pInfo->m_Name0, 4, pClient->m_aName, std::size(pClient->m_aName)))
{
str_copy(pClient->m_aName, "nameless tee");
}
IntsToStr(&pInfo->m_Clan0, 3, pClient->m_aClan, std::size(pClient->m_aClan));
pClient->m_Country = pInfo->m_Country;
int32_to_str(&pInfo->m_Skin0, 6, pClient->m_aSkinName, std::size(pClient->m_aSkinName));
IntsToStr(&pInfo->m_Skin0, 6, pClient->m_aSkinName, std::size(pClient->m_aSkinName));
pClient->m_UseCustomColor = pInfo->m_UseCustomColor;
pClient->m_ColorBody = pInfo->m_ColorBody;

View file

@ -8,6 +8,7 @@
#include <engine/sound.h>
#include <engine/storage.h>
#include <game/gamecore.h>
#include <game/mapitems_ex.h>
#include "image.h"
@ -173,7 +174,7 @@ bool CEditorMap::Save(const char *pFileName)
GItem.m_NumLayers = 0;
// save group name
str_to_int32(GItem.m_aName, std::size(GItem.m_aName), pGroup->m_aName);
StrToInts(GItem.m_aName, std::size(GItem.m_aName), pGroup->m_aName);
for(const std::shared_ptr<CLayer> &pLayer : pGroup->m_vpLayers)
{
@ -242,7 +243,7 @@ bool CEditorMap::Save(const char *pFileName)
Item.m_Data = Writer.AddData((size_t)pLayerTiles->m_Width * pLayerTiles->m_Height * sizeof(CTile), pLayerTiles->m_pTiles);
// save layer name
str_to_int32(Item.m_aName, std::size(Item.m_aName), pLayerTiles->m_aName);
StrToInts(Item.m_aName, std::size(Item.m_aName), pLayerTiles->m_aName);
Writer.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item);
@ -284,7 +285,7 @@ bool CEditorMap::Save(const char *pFileName)
Item.m_Data = Writer.AddDataSwapped(pLayerQuads->m_vQuads.size() * sizeof(CQuad), pLayerQuads->m_vQuads.data());
// save layer name
str_to_int32(Item.m_aName, std::size(Item.m_aName), pLayerQuads->m_aName);
StrToInts(Item.m_aName, std::size(Item.m_aName), pLayerQuads->m_aName);
Writer.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item);
@ -310,7 +311,7 @@ bool CEditorMap::Save(const char *pFileName)
Item.m_Data = Writer.AddDataSwapped(pLayerSounds->m_vSources.size() * sizeof(CSoundSource), pLayerSounds->m_vSources.data());
// save layer name
str_to_int32(Item.m_aName, std::size(Item.m_aName), pLayerSounds->m_aName);
StrToInts(Item.m_aName, std::size(Item.m_aName), pLayerSounds->m_aName);
Writer.AddItem(MAPITEMTYPE_LAYER, LayerCount, sizeof(Item), &Item);
GItem.m_NumLayers++;
@ -334,7 +335,7 @@ bool CEditorMap::Save(const char *pFileName)
Item.m_StartPoint = PointCount;
Item.m_NumPoints = m_vpEnvelopes[e]->m_vPoints.size();
Item.m_Synchronized = m_vpEnvelopes[e]->m_Synchronized;
str_to_int32(Item.m_aName, std::size(Item.m_aName), m_vpEnvelopes[e]->m_aName);
StrToInts(Item.m_aName, std::size(Item.m_aName), m_vpEnvelopes[e]->m_aName);
Writer.AddItem(MAPITEMTYPE_ENVELOPE, e, sizeof(Item), &Item);
PointCount += Item.m_NumPoints;
@ -623,7 +624,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
// load group name
if(pGItem->m_Version >= 3)
int32_to_str(pGItem->m_aName, std::size(pGItem->m_aName), pGroup->m_aName, std::size(pGroup->m_aName));
IntsToStr(pGItem->m_aName, std::size(pGItem->m_aName), pGroup->m_aName, std::size(pGroup->m_aName));
for(int l = 0; l < pGItem->m_NumLayers; l++)
{
@ -699,7 +700,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
// load layer name
if(pTilemapItem->m_Version >= 3)
int32_to_str(pTilemapItem->m_aName, std::size(pTilemapItem->m_aName), pTiles->m_aName, std::size(pTiles->m_aName));
IntsToStr(pTilemapItem->m_aName, std::size(pTilemapItem->m_aName), pTiles->m_aName, std::size(pTiles->m_aName));
if(pTiles->m_Tele)
{
@ -825,7 +826,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
// load layer name
if(pQuadsItem->m_Version >= 2)
int32_to_str(pQuadsItem->m_aName, std::size(pQuadsItem->m_aName), pQuads->m_aName, std::size(pQuads->m_aName));
IntsToStr(pQuadsItem->m_aName, std::size(pQuadsItem->m_aName), pQuads->m_aName, std::size(pQuads->m_aName));
void *pData = DataFile.GetDataSwapped(pQuadsItem->m_Data);
pGroup->AddLayer(pQuads);
@ -848,7 +849,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
pSounds->m_Sound = -1;
// load layer name
int32_to_str(pSoundsItem->m_aName, std::size(pSoundsItem->m_aName), pSounds->m_aName, std::size(pSounds->m_aName));
IntsToStr(pSoundsItem->m_aName, std::size(pSoundsItem->m_aName), pSounds->m_aName, std::size(pSounds->m_aName));
// load data
void *pData = DataFile.GetDataSwapped(pSoundsItem->m_Data);
@ -873,7 +874,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
pSounds->m_Sound = -1;
// load layer name
int32_to_str(pSoundsItem->m_aName, std::size(pSoundsItem->m_aName), pSounds->m_aName, std::size(pSounds->m_aName));
IntsToStr(pSoundsItem->m_aName, std::size(pSoundsItem->m_aName), pSounds->m_aName, std::size(pSounds->m_aName));
// load data
CSoundSource_DEPRECATED *pData = (CSoundSource_DEPRECATED *)DataFile.GetDataSwapped(pSoundsItem->m_Data);
@ -940,7 +941,7 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
mem_copy(&pEnv->m_vPoints[p].m_Bezier, pPointBezier, sizeof(CEnvPointBezier));
}
if(pItem->m_aName[0] != -1) // compatibility with old maps
int32_to_str(pItem->m_aName, std::size(pItem->m_aName), pEnv->m_aName, std::size(pEnv->m_aName));
IntsToStr(pItem->m_aName, std::size(pItem->m_aName), pEnv->m_aName, std::size(pEnv->m_aName));
m_vpEnvelopes.push_back(pEnv);
if(pItem->m_Version >= CMapItemEnvelope_v2::CURRENT_VERSION)
pEnv->m_Synchronized = pItem->m_Synchronized;

View file

@ -63,6 +63,57 @@ float CTuningParams::GetWeaponFireDelay(int Weapon) const
}
}
void StrToInts(int *pInts, size_t NumInts, const char *pStr)
{
dbg_assert(NumInts > 0, "StrToInts: NumInts invalid");
const size_t StrSize = str_length(pStr) + 1;
dbg_assert(StrSize <= NumInts * sizeof(int), "StrToInts: string truncated");
for(size_t i = 0; i < NumInts; i++)
{
// Copy to temporary buffer to ensure we don't read past the end of the input string
char aBuf[sizeof(int)] = {0, 0, 0, 0};
for(size_t c = 0; c < sizeof(int) && i * sizeof(int) + c < StrSize; c++)
{
aBuf[c] = pStr[i * sizeof(int) + c];
}
pInts[i] = ((aBuf[0] + 128) << 24) | ((aBuf[1] + 128) << 16) | ((aBuf[2] + 128) << 8) | (aBuf[3] + 128);
}
// Last byte is always zero and unused in this format
pInts[NumInts - 1] &= 0xFFFFFF00;
}
bool IntsToStr(const int *pInts, size_t NumInts, char *pStr, size_t StrSize)
{
dbg_assert(NumInts > 0, "IntsToStr: NumInts invalid");
dbg_assert(StrSize >= NumInts * sizeof(int), "IntsToStr: StrSize invalid");
// Unpack string without validation
size_t StrIndex = 0;
for(size_t IntIndex = 0; IntIndex < NumInts; IntIndex++)
{
const int CurrentInt = pInts[IntIndex];
pStr[StrIndex] = ((CurrentInt >> 24) & 0xff) - 128;
StrIndex++;
pStr[StrIndex] = ((CurrentInt >> 16) & 0xff) - 128;
StrIndex++;
pStr[StrIndex] = ((CurrentInt >> 8) & 0xff) - 128;
StrIndex++;
pStr[StrIndex] = (CurrentInt & 0xff) - 128;
StrIndex++;
}
// Ensure null-termination
pStr[StrIndex - 1] = '\0';
// Ensure valid UTF-8
if(str_utf8_check(pStr))
{
return true;
}
pStr[0] = '\0';
return false;
}
float VelocityRamp(float Value, float Start, float Range, float Curvature)
{
if(Value < Start)

View file

@ -65,6 +65,10 @@ public:
float GetWeaponFireDelay(int Weapon) const;
};
// Do not use these function unless for legacy code!
void StrToInts(int *pInts, size_t NumInts, const char *pStr);
bool IntsToStr(const int *pInts, size_t NumInts, char *pStr, size_t StrSize);
inline vec2 CalcPos(vec2 Pos, vec2 Velocity, float Curvature, float Speed, float Time)
{
vec2 n;

View file

@ -315,10 +315,10 @@ void CPlayer::Snap(int SnappingClient)
if(!pClientInfo)
return;
str_to_int32(&pClientInfo->m_Name0, 4, Server()->ClientName(m_ClientID));
str_to_int32(&pClientInfo->m_Clan0, 3, Server()->ClientClan(m_ClientID));
StrToInts(&pClientInfo->m_Name0, 4, Server()->ClientName(m_ClientID));
StrToInts(&pClientInfo->m_Clan0, 3, Server()->ClientClan(m_ClientID));
pClientInfo->m_Country = Server()->ClientCountry(m_ClientID);
str_to_int32(&pClientInfo->m_Skin0, 6, m_TeeInfos.m_aSkinName);
StrToInts(&pClientInfo->m_Skin0, 6, m_TeeInfos.m_aSkinName);
pClientInfo->m_UseCustomColor = m_TeeInfos.m_UseCustomColor;
pClientInfo->m_ColorBody = m_TeeInfos.m_ColorBody;
pClientInfo->m_ColorFeet = m_TeeInfos.m_ColorFeet;
@ -465,9 +465,9 @@ void CPlayer::FakeSnap()
if(!pClientInfo)
return;
str_to_int32(&pClientInfo->m_Name0, 4, " ");
str_to_int32(&pClientInfo->m_Clan0, 3, "");
str_to_int32(&pClientInfo->m_Skin0, 6, "default");
StrToInts(&pClientInfo->m_Name0, 4, " ");
StrToInts(&pClientInfo->m_Clan0, 3, "");
StrToInts(&pClientInfo->m_Skin0, 6, "default");
if(m_Paused != PAUSE_PAUSED)
return;

View file

@ -2,6 +2,8 @@
#include <base/system.h>
#include <game/gamecore.h>
TEST(Str, Dist)
{
EXPECT_EQ(str_utf8_dist("aaa", "aaa"), 0);
@ -1083,98 +1085,98 @@ TEST(Str, CountChar)
EXPECT_EQ(str_countchr(pStr, 'y'), 0);
}
TEST(Str, StrToInt32)
TEST(Str, StrToInts)
{
int aInts[8];
str_to_int32(aInts, 1, "a");
StrToInts(aInts, 1, "a");
EXPECT_EQ(aInts[0], 0xE1808000);
str_to_int32(aInts, 1, "ab");
StrToInts(aInts, 1, "ab");
EXPECT_EQ(aInts[0], 0xE1E28000);
str_to_int32(aInts, 1, "abc");
StrToInts(aInts, 1, "abc");
EXPECT_EQ(aInts[0], 0xE1E2E300);
str_to_int32(aInts, 2, "abcd");
StrToInts(aInts, 2, "abcd");
EXPECT_EQ(aInts[0], 0xE1E2E3E4);
EXPECT_EQ(aInts[1], 0x80808000);
str_to_int32(aInts, 2, "abcde");
StrToInts(aInts, 2, "abcde");
EXPECT_EQ(aInts[0], 0xE1E2E3E4);
EXPECT_EQ(aInts[1], 0xE5808000);
str_to_int32(aInts, 2, "abcdef");
StrToInts(aInts, 2, "abcdef");
EXPECT_EQ(aInts[0], 0xE1E2E3E4);
EXPECT_EQ(aInts[1], 0xE5E68000);
str_to_int32(aInts, 2, "abcdefg");
StrToInts(aInts, 2, "abcdefg");
EXPECT_EQ(aInts[0], 0xE1E2E3E4);
EXPECT_EQ(aInts[1], 0xE5E6E700);
str_to_int32(aInts, 2, "öüä");
StrToInts(aInts, 2, "öüä");
EXPECT_EQ(aInts[0], 0x4336433C);
EXPECT_EQ(aInts[1], 0x43248000);
str_to_int32(aInts, 3, "aβい🐘");
StrToInts(aInts, 3, "aβい🐘");
EXPECT_EQ(aInts[0], 0xE14E3263);
EXPECT_EQ(aInts[1], 0x0104701F);
EXPECT_EQ(aInts[2], 0x10188000);
// long padding
str_to_int32(aInts, 4, "abc");
StrToInts(aInts, 4, "abc");
EXPECT_EQ(aInts[0], 0xE1E2E380);
EXPECT_EQ(aInts[1], 0x80808080);
EXPECT_EQ(aInts[2], 0x80808080);
EXPECT_EQ(aInts[3], 0x80808000);
}
TEST(Str, Int32ToStr)
TEST(Str, IntsToStr)
{
int aInts[8];
char aStr[sizeof(aInts)];
aInts[0] = 0xE1808000;
int32_to_str(aInts, 1, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "a");
aInts[0] = 0xE1E28000;
int32_to_str(aInts, 1, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "ab");
aInts[0] = 0xE1E2E300;
int32_to_str(aInts, 1, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "abc");
aInts[0] = 0xE1E2E3E4;
aInts[1] = 0x80808000;
int32_to_str(aInts, 2, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 2, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "abcd");
aInts[0] = 0xE1E2E3E4;
aInts[1] = 0xE5808000;
int32_to_str(aInts, 2, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 2, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "abcde");
aInts[0] = 0xE1E2E3E4;
aInts[1] = 0xE5E68000;
int32_to_str(aInts, 2, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 2, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "abcdef");
aInts[0] = 0xE1E2E3E4;
aInts[1] = 0xE5E6E700;
int32_to_str(aInts, 2, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 2, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "abcdefg");
aInts[0] = 0x4336433C;
aInts[1] = 0x43248000;
int32_to_str(aInts, 2, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 2, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "öüä");
aInts[0] = 0xE14E3263;
aInts[1] = 0x0104701F;
aInts[2] = 0x10188000;
int32_to_str(aInts, 3, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 3, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "aβい🐘");
// long padding
@ -1182,7 +1184,7 @@ TEST(Str, Int32ToStr)
aInts[1] = 0x80808080;
aInts[2] = 0x80808080;
aInts[3] = 0x80808000;
int32_to_str(aInts, 4, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 4, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "abc");
// early null character (0x80)
@ -1190,34 +1192,34 @@ TEST(Str, Int32ToStr)
aInts[1] = 0xE1E2E3E4;
aInts[2] = 0xE1E2E3E4;
aInts[3] = 0xE1E2E300;
int32_to_str(aInts, 4, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 4, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "abc");
// invalid UTF-8
aInts[0] = 0xE17FE200;
int32_to_str(aInts, 1, aStr, std::size(aStr));
EXPECT_STREQ(aStr, "a?b");
EXPECT_FALSE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "");
// invalid UTF-8 (0x00 in string data, which is translated to '\x80')
aInts[0] = 0xE100E200;
int32_to_str(aInts, 1, aStr, std::size(aStr));
EXPECT_STREQ(aStr, "a?b");
EXPECT_FALSE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "");
// invalid UTF-8
aInts[0] = 0xE1E2E36F;
aInts[1] = 0x3F40E4E5;
aInts[2] = 0xE67FE700;
int32_to_str(aInts, 3, aStr, std::size(aStr));
EXPECT_STREQ(aStr, "abc???def?g");
EXPECT_FALSE(IntsToStr(aInts, 3, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "");
// invalid UTF-8 and missing null-terminator
aInts[0] = 0x7F7F7F7F;
int32_to_str(aInts, 1, aStr, std::size(aStr));
EXPECT_STREQ(aStr, "???");
EXPECT_FALSE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "");
// missing null-terminator at the end is ignored
aInts[0] = 0xE1E2E3E4;
int32_to_str(aInts, 1, aStr, std::size(aStr));
EXPECT_TRUE(IntsToStr(aInts, 1, aStr, std::size(aStr)));
EXPECT_STREQ(aStr, "abc");
// basic fuzzing: no input integer should result in invalid UTF-8 in the string
@ -1227,14 +1229,11 @@ TEST(Str, Int32ToStr)
aInts[0] = 0xE1 << 24 | i;
aInts[1] = i << 8 | 0xE2;
aInts[2] = 0xE3 << 24 | i;
int32_to_str(aInts, 3, aStr, std::size(aStr));
// the valid codespoints should always appear at the expected positions
ASSERT_EQ(aStr[0], 'a');
ASSERT_EQ(aStr[7], 'b');
ASSERT_EQ(aStr[8], 'c');
const bool ConversionResult = IntsToStr(aInts, 3, aStr, std::size(aStr));
// ensure null-termination before calling str_utf8_check
ASSERT_EQ(aStr[11], '\0');
ASSERT_TRUE(mem_has_null(aStr, std::size(aStr)));
ASSERT_TRUE(str_utf8_check(aStr));
ASSERT_TRUE(ConversionResult || aStr[0] == '\0');
}
}

View file

@ -7,6 +7,8 @@
#include <engine/shared/snapshot.h>
#include <engine/storage.h>
#include <game/gamecore.h>
static const char *TOOL_NAME = "demo_extract_chat";
class CClientSnapshotHandler
@ -102,7 +104,7 @@ public:
if(ClientID < MAX_CLIENTS)
{
CClientData *pClient = &m_aClients[ClientID];
int32_to_str(&pInfo->m_Name0, 4, pClient->m_aName, sizeof(pClient->m_aName));
IntsToStr(&pInfo->m_Name0, 4, pClient->m_aName, sizeof(pClient->m_aName));
}
}
}

View file

@ -7,6 +7,7 @@
#include <engine/graphics.h>
#include <engine/shared/datafile.h>
#include <engine/storage.h>
#include <game/gamecore.h>
#include <game/mapitems.h>
/*
Usage: map_convert_07 <source map filepath> <dest map filepath>
@ -95,7 +96,7 @@ bool CheckImageDimensions(void *pLayerItem, int LayerType, const char *pFilename
return true;
char aTileLayerName[12];
int32_to_str(pTMap->m_aName, std::size(pTMap->m_aName), aTileLayerName, std::size(aTileLayerName));
IntsToStr(pTMap->m_aName, std::size(pTMap->m_aName), aTileLayerName, std::size(aTileLayerName));
const char *pName = g_DataReader.GetDataString(pImgItem->m_ImageName);
dbg_msg("map_convert_07", "%s: Tile layer \"%s\" uses image \"%s\" with width %d, height %d, which is not divisible by 16. This is not supported in Teeworlds 0.7. Please scale the image and replace it manually.", pFilename, aTileLayerName, pName == nullptr ? "(error)" : pName, pImgItem->m_Width, pImgItem->m_Height);

View file

@ -2,6 +2,7 @@
#include <base/system.h>
#include <engine/shared/datafile.h>
#include <engine/storage.h>
#include <game/gamecore.h>
#include <game/mapitems.h>
bool Process(IStorage *pStorage, const char **pMapNames)
@ -64,7 +65,7 @@ bool Process(IStorage *pStorage, const char **pMapNames)
for(int i = 0; i < 2; ++i)
{
apTilemap[i] = (CMapItemLayerTilemap *)apItem[i];
int32_to_str(apTilemap[i]->m_aName, std::size(apTilemap[i]->m_aName), aaName[i], std::size(aaName[i]));
IntsToStr(apTilemap[i]->m_aName, std::size(apTilemap[i]->m_aName), aaName[i], std::size(aaName[i]));
}
if(str_comp(aaName[0], aaName[1]) != 0 || apTilemap[0]->m_Width != apTilemap[1]->m_Width || apTilemap[0]->m_Height != apTilemap[1]->m_Height)