Improve map image validation

- Add check for map image data being loaded from map to avoid null pointer access.
- Add check for size of map image data to avoid out-of-bounds reads.
- Add check for map layer item not being found to avoid null pointer access.
- Fix inconsistent handling of layers with invalid images. In some cases only `-1` was considered non-textured whereas all invalid values should be.
- Ensure correct texture is always used for game layers instead of considering the image property of game layers which cannot be set via the map editor.
- Skip loading images which are not used by any quads or tiles layers.
- Replace member variable `m_aTextureUsedByTileOrQuadLayerFlag` with local variable.
- Mark pointers as `const` when possible.
- Avoid C-style casts.
This commit is contained in:
Robert Müller 2024-10-03 22:09:20 +02:00
parent fb7d8ab912
commit 8f37729f5b
3 changed files with 57 additions and 41 deletions

View file

@ -37,8 +37,6 @@ CMapImages::CMapImages(int TextureSize)
mem_zero(m_aEntitiesIsLoaded, sizeof(m_aEntitiesIsLoaded)); mem_zero(m_aEntitiesIsLoaded, sizeof(m_aEntitiesIsLoaded));
m_SpeedupArrowIsLoaded = false; m_SpeedupArrowIsLoaded = false;
mem_zero(m_aTextureUsedByTileOrQuadLayerFlag, sizeof(m_aTextureUsedByTileOrQuadLayerFlag));
str_copy(m_aEntitiesPath, "editor/entities_clear"); str_copy(m_aEntitiesPath, "editor/entities_clear");
static_assert(std::size(gs_apModEntitiesNames) == MAP_IMAGE_MOD_TYPE_COUNT, "Mod name string count is not equal to mod type count"); static_assert(std::size(gs_apModEntitiesNames) == MAP_IMAGE_MOD_TYPE_COUNT, "Mod name string count is not equal to mod type count");
@ -61,41 +59,44 @@ void CMapImages::OnMapLoadImpl(class CLayers *pLayers, IMap *pMap)
// unload all textures // unload all textures
for(int i = 0; i < m_Count; i++) for(int i = 0; i < m_Count; i++)
{ {
Graphics()->UnloadTexture(&(m_aTextures[i])); Graphics()->UnloadTexture(&m_aTextures[i]);
m_aTextureUsedByTileOrQuadLayerFlag[i] = 0;
} }
m_Count = 0;
int Start; int Start;
pMap->GetType(MAPITEMTYPE_IMAGE, &Start, &m_Count); pMap->GetType(MAPITEMTYPE_IMAGE, &Start, &m_Count);
m_Count = clamp<int>(m_Count, 0, MAX_MAPIMAGES); m_Count = clamp<int>(m_Count, 0, MAX_MAPIMAGES);
for(int g = 0; g < pLayers->NumGroups(); g++) unsigned char aTextureUsedByTileOrQuadLayerFlag[MAX_MAPIMAGES] = {0}; // 0: nothing, 1(as flag): tile layer, 2(as flag): quad layer
for(int GroupIndex = 0; GroupIndex < pLayers->NumGroups(); GroupIndex++)
{ {
CMapItemGroup *pGroup = pLayers->GetGroup(g); const CMapItemGroup *pGroup = pLayers->GetGroup(GroupIndex);
if(!pGroup) if(!pGroup)
{ {
continue; continue;
} }
for(int l = 0; l < pGroup->m_NumLayers; l++) for(int LayerIndex = 0; LayerIndex < pGroup->m_NumLayers; LayerIndex++)
{ {
CMapItemLayer *pLayer = pLayers->GetLayer(pGroup->m_StartLayer + l); const CMapItemLayer *pLayer = pLayers->GetLayer(pGroup->m_StartLayer + LayerIndex);
if(!pLayer)
{
continue;
}
if(pLayer->m_Type == LAYERTYPE_TILES) if(pLayer->m_Type == LAYERTYPE_TILES)
{ {
CMapItemLayerTilemap *pTLayer = (CMapItemLayerTilemap *)pLayer; const CMapItemLayerTilemap *pLayerTilemap = reinterpret_cast<const CMapItemLayerTilemap *>(pLayer);
if(pTLayer->m_Image >= 0 && pTLayer->m_Image < m_Count) if(pLayerTilemap->m_Image >= 0 && pLayerTilemap->m_Image < m_Count)
{ {
m_aTextureUsedByTileOrQuadLayerFlag[pTLayer->m_Image] |= 1; aTextureUsedByTileOrQuadLayerFlag[pLayerTilemap->m_Image] |= 1;
} }
} }
else if(pLayer->m_Type == LAYERTYPE_QUADS) else if(pLayer->m_Type == LAYERTYPE_QUADS)
{ {
CMapItemLayerQuads *pQLayer = (CMapItemLayerQuads *)pLayer; const CMapItemLayerQuads *pLayerQuads = reinterpret_cast<const CMapItemLayerQuads *>(pLayer);
if(pQLayer->m_Image >= 0 && pQLayer->m_Image < m_Count) if(pLayerQuads->m_Image >= 0 && pLayerQuads->m_Image < m_Count)
{ {
m_aTextureUsedByTileOrQuadLayerFlag[pQLayer->m_Image] |= 2; aTextureUsedByTileOrQuadLayerFlag[pLayerQuads->m_Image] |= 2;
} }
} }
} }
@ -107,8 +108,14 @@ void CMapImages::OnMapLoadImpl(class CLayers *pLayers, IMap *pMap)
bool ShowWarning = false; bool ShowWarning = false;
for(int i = 0; i < m_Count; i++) for(int i = 0; i < m_Count; i++)
{ {
const int LoadFlag = (((m_aTextureUsedByTileOrQuadLayerFlag[i] & 1) != 0) ? TextureLoadFlag : 0) | (((m_aTextureUsedByTileOrQuadLayerFlag[i] & 2) != 0) ? 0 : (Graphics()->HasTextureArraysSupport() ? IGraphics::TEXLOAD_NO_2D_TEXTURE : 0)); if(aTextureUsedByTileOrQuadLayerFlag[i] == 0)
const CMapItemImage_v2 *pImg = (CMapItemImage_v2 *)pMap->GetItem(Start + i); {
// skip loading unused images
continue;
}
const int LoadFlag = (((aTextureUsedByTileOrQuadLayerFlag[i] & 1) != 0) ? TextureLoadFlag : 0) | (((aTextureUsedByTileOrQuadLayerFlag[i] & 2) != 0) ? 0 : (Graphics()->HasTextureArraysSupport() ? IGraphics::TEXLOAD_NO_2D_TEXTURE : 0));
const CMapItemImage_v2 *pImg = static_cast<const CMapItemImage_v2 *>(pMap->GetItem(Start + i));
const char *pName = pMap->GetDataString(pImg->m_ImageName); const char *pName = pMap->GetDataString(pImg->m_ImageName);
if(pName == nullptr || pName[0] == '\0') if(pName == nullptr || pName[0] == '\0')
@ -151,10 +158,20 @@ void CMapImages::OnMapLoadImpl(class CLayers *pLayers, IMap *pMap)
ImageInfo.m_Height = pImg->m_Height; ImageInfo.m_Height = pImg->m_Height;
ImageInfo.m_Format = CImageInfo::FORMAT_RGBA; ImageInfo.m_Format = CImageInfo::FORMAT_RGBA;
ImageInfo.m_pData = static_cast<uint8_t *>(pMap->GetData(pImg->m_ImageData)); ImageInfo.m_pData = static_cast<uint8_t *>(pMap->GetData(pImg->m_ImageData));
char aTexName[IO_MAX_PATH_LENGTH]; if(ImageInfo.m_pData && (size_t)pMap->GetDataSize(pImg->m_ImageData) >= ImageInfo.DataSize())
str_format(aTexName, sizeof(aTexName), "embedded: %s", pName); {
m_aTextures[i] = Graphics()->LoadTextureRaw(ImageInfo, LoadFlag, aTexName); char aTexName[IO_MAX_PATH_LENGTH];
pMap->UnloadData(pImg->m_ImageData); str_format(aTexName, sizeof(aTexName), "embedded: %s", pName);
m_aTextures[i] = Graphics()->LoadTextureRaw(ImageInfo, LoadFlag, aTexName);
pMap->UnloadData(pImg->m_ImageData);
}
else
{
pMap->UnloadData(pImg->m_ImageData);
log_error("mapimages", "Failed to load map image %d: failed to load data.", i);
ShowWarning = true;
continue;
}
} }
pMap->UnloadData(pImg->m_ImageName); pMap->UnloadData(pImg->m_ImageName);
ShowWarning = ShowWarning || m_aTextures[i].IsNullTexture(); ShowWarning = ShowWarning || m_aTextures[i].IsNullTexture();

View file

@ -37,7 +37,6 @@ class CMapImages : public CComponent
friend class CMenuBackground; friend class CMenuBackground;
IGraphics::CTextureHandle m_aTextures[MAX_MAPIMAGES]; IGraphics::CTextureHandle m_aTextures[MAX_MAPIMAGES];
int m_aTextureUsedByTileOrQuadLayerFlag[MAX_MAPIMAGES]; // 0: nothing, 1(as flag): tile layer, 2(as flag): quad layer
int m_Count; int m_Count;
char m_aEntitiesPath[IO_MAX_PATH_LENGTH]; char m_aEntitiesPath[IO_MAX_PATH_LENGTH];

View file

@ -386,15 +386,8 @@ void CMapLayers::OnMapLoad()
if(pLayer->m_Type == LAYERTYPE_TILES && Graphics()->IsTileBufferingEnabled()) if(pLayer->m_Type == LAYERTYPE_TILES && Graphics()->IsTileBufferingEnabled())
{ {
bool DoTextureCoords = false;
CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer; CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer;
if(pTMap->m_Image == -1) const bool DoTextureCoords = IsEntityLayer || (pTMap->m_Image >= 0 && pTMap->m_Image < m_pImages->Num());
{
if(IsEntityLayer)
DoTextureCoords = true;
}
else
DoTextureCoords = true;
int DataIndex = 0; int DataIndex = 0;
unsigned int TileSize = 0; unsigned int TileSize = 0;
@ -740,7 +733,7 @@ void CMapLayers::OnMapLoad()
m_vpQuadLayerVisuals.push_back(new SQuadLayerVisuals()); m_vpQuadLayerVisuals.push_back(new SQuadLayerVisuals());
SQuadLayerVisuals *pQLayerVisuals = m_vpQuadLayerVisuals.back(); SQuadLayerVisuals *pQLayerVisuals = m_vpQuadLayerVisuals.back();
bool Textured = (pQLayer->m_Image != -1); const bool Textured = pQLayer->m_Image >= 0 && pQLayer->m_Image < m_pImages->Num();
vtmpQuads.clear(); vtmpQuads.clear();
vtmpQuadsTextured.clear(); vtmpQuadsTextured.clear();
@ -1543,15 +1536,18 @@ void CMapLayers::OnRender()
if(pLayer->m_Type == LAYERTYPE_TILES) if(pLayer->m_Type == LAYERTYPE_TILES)
{ {
CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer; CMapItemLayerTilemap *pTMap = (CMapItemLayerTilemap *)pLayer;
if(pTMap->m_Image < 0 || pTMap->m_Image >= m_pImages->Num()) if(IsGameLayer)
{ {
if(!IsGameLayer) Graphics()->TextureSet(m_pImages->GetEntities(MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH));
Graphics()->TextureClear(); }
else else if(pTMap->m_Image >= 0 && pTMap->m_Image < m_pImages->Num())
Graphics()->TextureSet(m_pImages->GetEntities(MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH)); {
Graphics()->TextureSet(m_pImages->Get(pTMap->m_Image));
} }
else else
Graphics()->TextureSet(m_pImages->Get(pTMap->m_Image)); {
Graphics()->TextureClear();
}
CTile *pTiles = (CTile *)m_pLayers->Map()->GetData(pTMap->m_Data); CTile *pTiles = (CTile *)m_pLayers->Map()->GetData(pTMap->m_Data);
unsigned int Size = m_pLayers->Map()->GetDataSize(pTMap->m_Data); unsigned int Size = m_pLayers->Map()->GetDataSize(pTMap->m_Data);
@ -1608,10 +1604,14 @@ void CMapLayers::OnRender()
else if(pLayer->m_Type == LAYERTYPE_QUADS) else if(pLayer->m_Type == LAYERTYPE_QUADS)
{ {
CMapItemLayerQuads *pQLayer = (CMapItemLayerQuads *)pLayer; CMapItemLayerQuads *pQLayer = (CMapItemLayerQuads *)pLayer;
if(pQLayer->m_Image < 0 || pQLayer->m_Image >= m_pImages->Num()) if(pQLayer->m_Image >= 0 && pQLayer->m_Image < m_pImages->Num())
Graphics()->TextureClear(); {
else
Graphics()->TextureSet(m_pImages->Get(pQLayer->m_Image)); Graphics()->TextureSet(m_pImages->Get(pQLayer->m_Image));
}
else
{
Graphics()->TextureClear();
}
CQuad *pQuads = (CQuad *)m_pLayers->Map()->GetDataSwapped(pQLayer->m_Data); CQuad *pQuads = (CQuad *)m_pLayers->Map()->GetDataSwapped(pQLayer->m_Data);
if(m_Type == TYPE_BACKGROUND_FORCE || m_Type == TYPE_FULL_DESIGN) if(m_Type == TYPE_BACKGROUND_FORCE || m_Type == TYPE_FULL_DESIGN)