1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3#include "map.h"
4
5#include <base/dbg.h>
6#include <base/fs.h>
7#include <base/log.h>
8#include <base/mem.h>
9#include <base/str.h>
10
11#include <engine/storage.h>
12
13#include <game/gamecore.h>
14#include <game/mapitems.h>
15
16CMap::CMap() = default;
17
18CMap::~CMap()
19{
20 Unload();
21}
22
23int CMap::GetDataSize(int Index) const
24{
25 return m_DataFile.GetDataSize(Index);
26}
27
28void *CMap::GetData(int Index)
29{
30 return m_DataFile.GetData(Index);
31}
32
33void *CMap::GetDataSwapped(int Index)
34{
35 return m_DataFile.GetDataSwapped(Index);
36}
37
38const char *CMap::GetDataString(int Index)
39{
40 return m_DataFile.GetDataString(Index);
41}
42
43void CMap::UnloadData(int Index)
44{
45 m_DataFile.UnloadData(Index);
46}
47
48int CMap::NumData() const
49{
50 return m_DataFile.NumData();
51}
52
53int CMap::GetItemSize(int Index)
54{
55 return m_DataFile.GetItemSize(Index);
56}
57
58void *CMap::GetItem(int Index, int *pType, int *pId, CUuid *pUuid)
59{
60 return m_DataFile.GetItem(Index, pType, pId, pUuid);
61}
62
63void CMap::GetType(int Type, int *pStart, int *pNum)
64{
65 m_DataFile.GetType(Type, pStart, pNum);
66}
67
68int CMap::FindItemIndex(int Type, int Id)
69{
70 return m_DataFile.FindItemIndex(Type, Id);
71}
72
73void *CMap::FindItem(int Type, int Id)
74{
75 return m_DataFile.FindItem(Type, Id);
76}
77
78int CMap::NumItems() const
79{
80 return m_DataFile.NumItems();
81}
82
83static bool AtMostOneBitSet(int Flags)
84{
85 // https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2
86 return (Flags & (Flags - 1)) == 0;
87}
88
89bool CMap::Load(const char *pFullName, IStorage *pStorage, const char *pPath, int StorageType)
90{
91 // Ensure current datafile is not left in an inconsistent state if loading fails,
92 // by loading the new datafile separately first.
93 CDataFileReader NewDataFile;
94 if(!NewDataFile.Open(pFullName, pStorage, pPath, StorageType))
95 return false;
96
97 if(!ValidateMapVersion(NewDataFile))
98 {
99 NewDataFile.Close();
100 return false;
101 }
102
103 int GroupsStart, GroupsNum, LayersStart, LayersNum;
104 NewDataFile.GetType(Type: MAPITEMTYPE_GROUP, pStart: &GroupsStart, pNum: &GroupsNum);
105 NewDataFile.GetType(Type: MAPITEMTYPE_LAYER, pStart: &LayersStart, pNum: &LayersNum);
106
107 // Replace map items for old versions with items compatible with latest version to avoid version checks when using the map items.
108 // Ensure that we have a game layer and game group.
109 const CMapItemLayerTilemap *pGameLayer = nullptr;
110 std::set<int> UsedLayerItemIndices;
111 for(int GroupIndex = 0; GroupIndex < GroupsNum; GroupIndex++)
112 {
113 const size_t GroupItemSize = NewDataFile.GetItemSize(Index: GroupsStart + GroupIndex);
114 if(GroupItemSize < sizeof(CMapItemGroup_v1))
115 {
116 log_error("map/load", "Group %d is truncated (size %" PRIzu ").", GroupIndex, GroupItemSize);
117 return false;
118 }
119 const CMapItemGroup *pGroup = static_cast<CMapItemGroup *>(NewDataFile.GetItem(Index: GroupsStart + GroupIndex));
120 if(pGroup->m_StartLayer < 0 || pGroup->m_NumLayers < 0 ||
121 (int64_t)pGroup->m_StartLayer + pGroup->m_NumLayers > LayersNum)
122 {
123 log_error("map/load", "Group %d uses invalid layers %d to %d (the map contains %d layers).",
124 GroupIndex, pGroup->m_StartLayer, pGroup->m_StartLayer + pGroup->m_NumLayers - 1, LayersNum);
125 return false;
126 }
127 for(int LayerIndex = 0; LayerIndex < pGroup->m_NumLayers; LayerIndex++)
128 {
129 const int LayerItemIndex = LayersStart + pGroup->m_StartLayer + LayerIndex;
130 const auto &[_, LayerUnique] = UsedLayerItemIndices.emplace(args: LayerItemIndex);
131 if(!LayerUnique)
132 {
133 log_error("map/load", "Layer %d in group %d is also being used by another group.", LayerIndex, GroupIndex);
134 return false;
135 }
136 CMapItemLayer *pLayer = static_cast<CMapItemLayer *>(NewDataFile.GetItem(Index: LayerItemIndex));
137 const size_t LayerItemSize = NewDataFile.GetItemSize(Index: LayerItemIndex);
138 if(LayerItemSize < sizeof(CMapItemLayer))
139 {
140 log_error("map/load", "Layer %d in group %d is truncated (size %" PRIzu ").", LayerIndex, GroupIndex, LayerItemSize);
141 return false;
142 }
143
144 if(pLayer->m_Version != 0)
145 {
146 log_debug("map/load", "Layer %d in group %d has unused version set to %d. Resetting to 0.", LayerIndex, GroupIndex, pLayer->m_Version);
147 pLayer->m_Version = 0;
148 }
149
150 if(pLayer->m_Type == LAYERTYPE_TILES)
151 {
152 if(!UpgradeAndValidateTilesLayerItem(NewDataFile, GroupIndex, LayerIndex, pLayerTilemapBase: reinterpret_cast<CMapItemLayerTilemap *>(pLayer), LayerItemIndex, LayerItemSize))
153 {
154 return false;
155 }
156 // The item may have been replaced, so the pointer must be determined again.
157 const CMapItemLayerTilemap *pLayerTilemap = static_cast<CMapItemLayerTilemap *>(NewDataFile.GetItem(Index: LayerItemIndex));
158 if(pLayerTilemap->m_Flags & TILESLAYERFLAG_GAME)
159 {
160 pGameLayer = pLayerTilemap;
161 }
162 }
163 }
164 }
165 if(pGameLayer == nullptr)
166 {
167 log_error("map/load", "Game layer is missing.");
168 return false;
169 }
170
171 // Lazily validate data and replace compressed tile layers with uncompressed ones.
172 std::set<int> UsedDataIndices;
173 for(int GroupIndex = 0; GroupIndex < GroupsNum; GroupIndex++)
174 {
175 const CMapItemGroup *pGroup = static_cast<CMapItemGroup *>(NewDataFile.GetItem(Index: GroupsStart + GroupIndex));
176 for(int LayerIndex = 0; LayerIndex < pGroup->m_NumLayers; LayerIndex++)
177 {
178 CMapItemLayer *pLayer = static_cast<CMapItemLayer *>(NewDataFile.GetItem(Index: LayersStart + pGroup->m_StartLayer + LayerIndex));
179 if(pLayer->m_Type == LAYERTYPE_TILES)
180 {
181 if(!ValidateAndUnpackTilesLayerData(NewDataFile, GroupIndex, LayerIndex, pLayerTilemap: reinterpret_cast<const CMapItemLayerTilemap *>(pLayer), GameLayer: *pGameLayer, UsedDataIndices))
182 {
183 return false;
184 }
185 }
186 }
187 }
188
189 // Load and implicitly validate game layer tile data immediately because we need it.
190 // Do not preload other data to avoid excessive memory usage.
191 if(NewDataFile.GetData(Index: pGameLayer->m_Data) == nullptr)
192 {
193 log_error("map/load", "Game layer data is invalid.");
194 return false;
195 }
196
197 // Replace existing datafile with new datafile
198 m_DataFile.Close();
199 m_DataFile = std::move(NewDataFile);
200 return true;
201}
202
203bool CMap::Load(IStorage *pStorage, const char *pPath, int StorageType)
204{
205 char aFilename[IO_MAX_PATH_LENGTH];
206 fs_split_file_extension(filename: fs_filename(path: pPath), name: aFilename, name_size: sizeof(aFilename));
207 return Load(pFullName: aFilename, pStorage, pPath, StorageType);
208}
209
210void CMap::Unload()
211{
212 m_DataFile.Close();
213}
214
215bool CMap::IsLoaded() const
216{
217 return m_DataFile.IsOpen();
218}
219
220IOHANDLE CMap::File() const
221{
222 return m_DataFile.File();
223}
224
225const char *CMap::FullName() const
226{
227 return m_DataFile.FullName();
228}
229
230const char *CMap::BaseName() const
231{
232 return m_DataFile.BaseName();
233}
234
235const char *CMap::Path() const
236{
237 return m_DataFile.Path();
238}
239
240SHA256_DIGEST CMap::Sha256() const
241{
242 return m_DataFile.Sha256();
243}
244
245unsigned CMap::Crc() const
246{
247 return m_DataFile.Crc();
248}
249
250int CMap::Size() const
251{
252 return m_DataFile.Size();
253}
254
255bool CMap::ValidateMapVersion(CDataFileReader &NewDataFile)
256{
257 const int VersionItemIndex = NewDataFile.FindItemIndex(Type: MAPITEMTYPE_VERSION, Id: 0);
258 if(VersionItemIndex < 0)
259 {
260 log_error("map/load", "Map version item is missing.");
261 return false;
262 }
263 const size_t VersionItemSize = NewDataFile.GetItemSize(Index: VersionItemIndex);
264 if(VersionItemSize < sizeof(CMapItemVersion))
265 {
266 log_error("map/load", "Map version item is truncated (size %" PRIzu ").", VersionItemSize);
267 return false;
268 }
269 const CMapItemVersion *pVersionItem = static_cast<CMapItemVersion *>(NewDataFile.GetItem(Index: VersionItemIndex));
270 if(pVersionItem->m_Version != 1)
271 {
272 log_error("map/load", "Map version %d is not supported.", pVersionItem->m_Version);
273 return false;
274 }
275 return true;
276}
277
278bool CMap::ExtractTiles(CTile *pDest, size_t DestSize, const CTile *pSrc, size_t SrcSize)
279{
280 size_t DestIndex = 0;
281 size_t SrcIndex = 0;
282 while(DestIndex < DestSize && SrcIndex < SrcSize)
283 {
284 if(pSrc[SrcIndex].m_MustBe0 != 0)
285 {
286 log_error("map/load", "Tile layer data contains non-zero padding value %d at index %" PRIzu ".",
287 pSrc[SrcIndex].m_MustBe0, SrcIndex);
288 return false;
289 }
290 for(unsigned Counter = 0; Counter <= pSrc[SrcIndex].m_Skip && DestIndex < DestSize; Counter++)
291 {
292 pDest[DestIndex].m_Index = pSrc[SrcIndex].m_Index;
293 pDest[DestIndex].m_Flags = pSrc[SrcIndex].m_Flags;
294 pDest[DestIndex].m_Skip = 0;
295 pDest[DestIndex].m_MustBe0 = 0;
296 DestIndex++;
297 }
298 SrcIndex++;
299 }
300 if(DestIndex != DestSize)
301 {
302 log_error("map/load", "Tile layer data is truncated (got %" PRIzu ", wanted %" PRIzu ").",
303 DestIndex, DestSize);
304 return false;
305 }
306 if(SrcIndex != SrcSize)
307 {
308 log_error("map/load", "Too much tile layer data (read %" PRIzu ", total %" PRIzu ").",
309 SrcIndex, SrcSize);
310 return false;
311 }
312 return true;
313}
314
315static bool EnsureTileLayerProperties(int GroupIndex, int LayerIndex, CMapItemLayerTilemap &LayerTilemap)
316{
317 if(LayerTilemap.m_Width < 2)
318 {
319 log_error("map/load", "Tile layer %d in group %d has invalid width %d.",
320 LayerIndex, GroupIndex, LayerTilemap.m_Width);
321 return false;
322 }
323
324 if(LayerTilemap.m_Height < 2)
325 {
326 log_error("map/load", "Tile layer %d in group %d has invalid height %d.",
327 LayerIndex, GroupIndex, LayerTilemap.m_Height);
328 return false;
329 }
330
331 const auto &&EnsureValidName = [&](const char *pExpectedName) {
332 char aCurrentName[sizeof(LayerTilemap.m_aName)];
333 if(!IntsToStr(pInts: LayerTilemap.m_aName, NumInts: std::size(LayerTilemap.m_aName), pStr: aCurrentName, StrSize: std::size(aCurrentName)))
334 {
335 log_error("map/load", "Tile layer %d in group %d has invalid name.",
336 LayerIndex, GroupIndex);
337 return false;
338 }
339 else if(pExpectedName != nullptr && str_comp(a: aCurrentName, b: pExpectedName) != 0)
340 {
341 log_debug("map/load", "Physics tile layer %d in group %d has unexpected name '%s'. Resetting to '%s'.",
342 LayerIndex, GroupIndex, aCurrentName, pExpectedName);
343 StrToInts(pInts: LayerTilemap.m_aName, NumInts: std::size(LayerTilemap.m_aName), pStr: pExpectedName);
344 }
345 return true;
346 };
347
348 const auto &&EnsureDefaultColor = [&]() {
349 const CColor DefaultColor = CColor{255, 255, 255, 255};
350 if(LayerTilemap.m_Color != DefaultColor)
351 {
352 log_debug("map/load", "Physics tile layer %d in group %d has unexpected color (%d, %d, %d, %d). Resetting to default.",
353 LayerIndex, GroupIndex, LayerTilemap.m_Color.r, LayerTilemap.m_Color.g, LayerTilemap.m_Color.b, LayerTilemap.m_Color.a);
354 LayerTilemap.m_Color = DefaultColor;
355 }
356 };
357
358 const auto &&EnsureNoDetailFlag = [&]() {
359 if(LayerTilemap.m_Layer.m_Flags & LAYERFLAG_DETAIL)
360 {
361 log_debug("map/load", "Physics tile layer %d in group %d has detail flag set. Resetting to non-detail.",
362 LayerIndex, GroupIndex);
363 LayerTilemap.m_Layer.m_Flags &= ~LAYERFLAG_DETAIL;
364 }
365 };
366
367 if(LayerTilemap.m_Flags & TILESLAYERFLAG_GAME)
368 {
369 if(!EnsureValidName("Game"))
370 {
371 return false;
372 }
373 EnsureDefaultColor();
374 EnsureNoDetailFlag();
375 }
376 else if(LayerTilemap.m_Flags & TILESLAYERFLAG_TELE)
377 {
378 if(!EnsureValidName("Tele"))
379 {
380 return false;
381 }
382 EnsureDefaultColor();
383 EnsureNoDetailFlag();
384 }
385 else if(LayerTilemap.m_Flags & TILESLAYERFLAG_SPEEDUP)
386 {
387 if(!EnsureValidName("Speedup"))
388 {
389 return false;
390 }
391 EnsureDefaultColor();
392 EnsureNoDetailFlag();
393 }
394 else if(LayerTilemap.m_Flags & TILESLAYERFLAG_FRONT)
395 {
396 if(!EnsureValidName("Front"))
397 {
398 return false;
399 }
400 EnsureDefaultColor();
401 EnsureNoDetailFlag();
402 }
403 else if(LayerTilemap.m_Flags & TILESLAYERFLAG_SWITCH)
404 {
405 if(!EnsureValidName("Switch"))
406 {
407 return false;
408 }
409 EnsureDefaultColor();
410 EnsureNoDetailFlag();
411 }
412 else if(LayerTilemap.m_Flags & TILESLAYERFLAG_TUNE)
413 {
414 if(!EnsureValidName("Tune"))
415 {
416 return false;
417 }
418 EnsureDefaultColor();
419 EnsureNoDetailFlag();
420 }
421 else
422 {
423 if(!EnsureValidName(nullptr))
424 {
425 return false;
426 }
427
428 if(!in_range(a: LayerTilemap.m_Color.r, lower: 0, upper: 255) ||
429 !in_range(a: LayerTilemap.m_Color.g, lower: 0, upper: 255) ||
430 !in_range(a: LayerTilemap.m_Color.b, lower: 0, upper: 255) ||
431 !in_range(a: LayerTilemap.m_Color.a, lower: 0, upper: 255))
432 {
433 log_error("map/load", "Tile layer %d in group %d has invalid color (%d, %d, %d, %d).",
434 LayerIndex, GroupIndex, LayerTilemap.m_Color.r, LayerTilemap.m_Color.g, LayerTilemap.m_Color.b, LayerTilemap.m_Color.a);
435 return false;
436 }
437 }
438
439 const auto &&EnsureUnsetPhysicsData = [&](int TilesLayerFlag, int *pDataIndex, const char *pName) {
440 if((LayerTilemap.m_Flags & TilesLayerFlag) == 0 && *pDataIndex != -1)
441 {
442 log_debug("map/load", "Tile layer %d in group %d has unused %s data index %d. Resetting to -1.",
443 LayerIndex, GroupIndex, pName, *pDataIndex);
444 *pDataIndex = -1;
445 }
446 };
447 EnsureUnsetPhysicsData(TILESLAYERFLAG_TELE, &LayerTilemap.m_Tele, "tele");
448 EnsureUnsetPhysicsData(TILESLAYERFLAG_SPEEDUP, &LayerTilemap.m_Speedup, "speedup");
449 EnsureUnsetPhysicsData(TILESLAYERFLAG_FRONT, &LayerTilemap.m_Front, "front");
450 EnsureUnsetPhysicsData(TILESLAYERFLAG_SWITCH, &LayerTilemap.m_Switch, "switch");
451 EnsureUnsetPhysicsData(TILESLAYERFLAG_TUNE, &LayerTilemap.m_Tune, "tune");
452
453 return true;
454}
455
456bool CMap::UpgradeAndValidateTilesLayerItem(
457 CDataFileReader &NewDataFile, int GroupIndex, int LayerIndex,
458 CMapItemLayerTilemap_v2 *pLayerTilemapBase, int LayerItemIndex, size_t LayerItemSize)
459{
460 if(LayerItemSize < sizeof(CMapItemLayerTilemap_v2))
461 {
462 log_error("map/load", "Tile layer %d in group %d is truncated (size %" PRIzu ").",
463 LayerIndex, GroupIndex, LayerItemSize);
464 return false;
465 }
466
467 if(!in_range(a: pLayerTilemapBase->m_Version, lower: 2, upper: 4))
468 {
469 log_error("map/load", "Tile layer %d in group %d has unsupported version %d.",
470 LayerIndex, GroupIndex, pLayerTilemapBase->m_Version);
471 return false;
472 }
473
474 if(!AtMostOneBitSet(Flags: pLayerTilemapBase->m_Flags & (TILESLAYERFLAG_GAME | TILESLAYERFLAG_TELE | TILESLAYERFLAG_SPEEDUP | TILESLAYERFLAG_FRONT | TILESLAYERFLAG_SWITCH | TILESLAYERFLAG_TUNE)))
475 {
476 log_error("map/load", "Tile layer %d in group %d has invalid combination of flags %d. At most one physics tile layer flag can be set.",
477 LayerIndex, GroupIndex, pLayerTilemapBase->m_Flags);
478 return false;
479 }
480
481 const auto &&UnpackPhysicsLayerDataIndex = [&](int TilesLayerFlag, int *pTargetDataIndex, const int *pSourceDataIndex, const CMapItemLayerTilemap_v2 *pSourceTileLayer, const char *pName) {
482 // We have to check the size individually for each tile data index because old maps were created
483 // containing only some prefix of the physics tile data indices without incrementing the version.
484 if(LayerItemSize < reinterpret_cast<const uint8_t *>(pSourceDataIndex) - reinterpret_cast<const uint8_t *>(pSourceTileLayer) + sizeof(*pSourceDataIndex))
485 {
486 if(pSourceTileLayer->m_Flags & TilesLayerFlag)
487 {
488 log_error("map/load", "%s layer %d in group %d is truncated (version %d, size %" PRIzu ").",
489 pName, LayerIndex, GroupIndex, pSourceTileLayer->m_Version, LayerItemSize);
490 return false;
491 }
492 *pTargetDataIndex = -1;
493 }
494 else
495 {
496 *pTargetDataIndex = *pSourceDataIndex;
497 }
498 return true;
499 };
500
501 if(pLayerTilemapBase->m_Version == 2)
502 {
503 const CMapItemLayerTilemap_v2Legacy *pLayerTilemapLegacy = static_cast<const CMapItemLayerTilemap_v2Legacy *>(pLayerTilemapBase);
504 CMapItemLayerTilemap OverriddenLayerTilemap;
505 mem_copy(dest: &OverriddenLayerTilemap, source: pLayerTilemapLegacy, size: sizeof(CMapItemLayerTilemap_v2));
506
507 // Version 2 items have no name. Default to empty string. We fix the name of physics layers later.
508 StrToInts(pInts: OverriddenLayerTilemap.m_aName, NumInts: std::size(OverriddenLayerTilemap.m_aName), pStr: "");
509
510 if(!UnpackPhysicsLayerDataIndex(TILESLAYERFLAG_TELE, &OverriddenLayerTilemap.m_Tele, &pLayerTilemapLegacy->m_Tele, pLayerTilemapBase, "Tele") ||
511 !UnpackPhysicsLayerDataIndex(TILESLAYERFLAG_SPEEDUP, &OverriddenLayerTilemap.m_Speedup, &pLayerTilemapLegacy->m_Speedup, pLayerTilemapBase, "Speedup") ||
512 !UnpackPhysicsLayerDataIndex(TILESLAYERFLAG_FRONT, &OverriddenLayerTilemap.m_Front, &pLayerTilemapLegacy->m_Front, pLayerTilemapBase, "Front") ||
513 !UnpackPhysicsLayerDataIndex(TILESLAYERFLAG_SWITCH, &OverriddenLayerTilemap.m_Switch, &pLayerTilemapLegacy->m_Switch, pLayerTilemapBase, "Switch") ||
514 !UnpackPhysicsLayerDataIndex(TILESLAYERFLAG_TUNE, &OverriddenLayerTilemap.m_Tune, &pLayerTilemapLegacy->m_Tune, pLayerTilemapBase, "Tune"))
515 {
516 return false;
517 }
518 if(!EnsureTileLayerProperties(GroupIndex, LayerIndex, LayerTilemap&: OverriddenLayerTilemap))
519 {
520 return false;
521 }
522 if(!NewDataFile.OverrideItemData(Index: LayerItemIndex, pData: &OverriddenLayerTilemap, Size: sizeof(OverriddenLayerTilemap)))
523 {
524 return false;
525 }
526 }
527 else if(LayerItemSize < sizeof(CMapItemLayerTilemap_v3Teeworlds))
528 {
529 // Only the physics layer data indices added by DDRace may be truncated in
530 // version 3 and 4 items, the layer name must always be complete.
531 log_error("map/load", "Tile layer %d in group %d is truncated (version %d, size %" PRIzu ").",
532 LayerIndex, GroupIndex, pLayerTilemapBase->m_Version, LayerItemSize);
533 return false;
534 }
535 else if(LayerItemSize < sizeof(CMapItemLayerTilemap))
536 {
537 const CMapItemLayerTilemap *pLayerTilemapLegacy = static_cast<const CMapItemLayerTilemap *>(pLayerTilemapBase);
538 CMapItemLayerTilemap OverriddenLayerTilemap;
539 mem_copy(dest: &OverriddenLayerTilemap, source: pLayerTilemapLegacy, size: sizeof(CMapItemLayerTilemap_v3Teeworlds));
540
541 if(!UnpackPhysicsLayerDataIndex(TILESLAYERFLAG_TELE, &OverriddenLayerTilemap.m_Tele, &pLayerTilemapLegacy->m_Tele, pLayerTilemapBase, "Tele") ||
542 !UnpackPhysicsLayerDataIndex(TILESLAYERFLAG_SPEEDUP, &OverriddenLayerTilemap.m_Speedup, &pLayerTilemapLegacy->m_Speedup, pLayerTilemapBase, "Speedup") ||
543 !UnpackPhysicsLayerDataIndex(TILESLAYERFLAG_FRONT, &OverriddenLayerTilemap.m_Front, &pLayerTilemapLegacy->m_Front, pLayerTilemapBase, "Front") ||
544 !UnpackPhysicsLayerDataIndex(TILESLAYERFLAG_SWITCH, &OverriddenLayerTilemap.m_Switch, &pLayerTilemapLegacy->m_Switch, pLayerTilemapBase, "Switch") ||
545 !UnpackPhysicsLayerDataIndex(TILESLAYERFLAG_TUNE, &OverriddenLayerTilemap.m_Tune, &pLayerTilemapLegacy->m_Tune, pLayerTilemapBase, "Tune"))
546 {
547 return false;
548 }
549 if(!EnsureTileLayerProperties(GroupIndex, LayerIndex, LayerTilemap&: OverriddenLayerTilemap))
550 {
551 return false;
552 }
553 if(!NewDataFile.OverrideItemData(Index: LayerItemIndex, pData: &OverriddenLayerTilemap, Size: sizeof(OverriddenLayerTilemap)))
554 {
555 return false;
556 }
557 }
558 else // latest version, whole CMapItemLayerTilemap available
559 {
560 if(!EnsureTileLayerProperties(GroupIndex, LayerIndex, LayerTilemap&: *static_cast<CMapItemLayerTilemap *>(pLayerTilemapBase)))
561 {
562 return false;
563 }
564 }
565
566 return true;
567}
568
569bool CMap::ValidateAndUnpackTilesLayerData(CDataFileReader &NewDataFile, int GroupIndex, int LayerIndex, const CMapItemLayerTilemap *pLayerTilemap, const CMapItemLayerTilemap &GameLayer, std::set<int> &UsedDataIndices)
570{
571 size_t TileSize;
572 int DataIndex;
573 int LayerType;
574 if(pLayerTilemap->m_Flags & TILESLAYERFLAG_GAME)
575 {
576 TileSize = sizeof(CTile);
577 DataIndex = pLayerTilemap->m_Data;
578 LayerType = LAYERTYPE_GAME;
579 }
580 else if(pLayerTilemap->m_Flags & TILESLAYERFLAG_TELE)
581 {
582 TileSize = sizeof(CTeleTile);
583 DataIndex = pLayerTilemap->m_Tele;
584 LayerType = LAYERTYPE_TELE;
585 }
586 else if(pLayerTilemap->m_Flags & TILESLAYERFLAG_SPEEDUP)
587 {
588 TileSize = sizeof(CSpeedupTile);
589 DataIndex = pLayerTilemap->m_Speedup;
590 LayerType = LAYERTYPE_SPEEDUP;
591 }
592 else if(pLayerTilemap->m_Flags & TILESLAYERFLAG_FRONT)
593 {
594 TileSize = sizeof(CTile);
595 DataIndex = pLayerTilemap->m_Front;
596 LayerType = LAYERTYPE_FRONT;
597 }
598 else if(pLayerTilemap->m_Flags & TILESLAYERFLAG_SWITCH)
599 {
600 TileSize = sizeof(CSwitchTile);
601 DataIndex = pLayerTilemap->m_Switch;
602 LayerType = LAYERTYPE_SWITCH;
603 }
604 else if(pLayerTilemap->m_Flags & TILESLAYERFLAG_TUNE)
605 {
606 TileSize = sizeof(CTuneTile);
607 DataIndex = pLayerTilemap->m_Tune;
608 LayerType = LAYERTYPE_TUNE;
609 }
610 else
611 {
612 TileSize = sizeof(CTile);
613 DataIndex = pLayerTilemap->m_Data;
614 LayerType = LAYERTYPE_TILES;
615 }
616
617 if(DataIndex < 0 || DataIndex >= NewDataFile.NumData())
618 {
619 log_error("map/load", "Tile data index %d of layer %d in group %d is invalid.", DataIndex, LayerIndex, GroupIndex);
620 return false;
621 }
622
623 const auto &[_, DataUnique] = UsedDataIndices.emplace(args&: DataIndex);
624 if(!DataUnique)
625 {
626 log_error("map/load", "Tile data index %d of layer %d in group %d is not unique.", DataIndex, LayerIndex, GroupIndex);
627 return false;
628 }
629
630 const size_t TilemapCount = (size_t)pLayerTilemap->m_Width * pLayerTilemap->m_Height;
631 const size_t TilemapSize = TilemapCount * TileSize;
632
633 if(((int)TilemapCount / pLayerTilemap->m_Width != pLayerTilemap->m_Height) || (TilemapSize / TileSize != TilemapCount))
634 {
635 log_error("map/load", "Tile layer %d in group %d is too big (%d * %d * %" PRIzu " causes an integer overflow).",
636 LayerIndex, GroupIndex, pLayerTilemap->m_Width, pLayerTilemap->m_Height, TileSize);
637 return false;
638 }
639
640 // The collision uses the size of the game layer for the data of all physics layers,
641 // so physics layers must contain at least as many tiles as the game layer.
642 if(LayerType != LAYERTYPE_TILES && LayerType != LAYERTYPE_GAME &&
643 TilemapCount < (size_t)GameLayer.m_Width * GameLayer.m_Height)
644 {
645 log_error("map/load", "Physics layer %d in group %d is smaller than the game layer (%d * %d < %d * %d).",
646 LayerIndex, GroupIndex, pLayerTilemap->m_Width, pLayerTilemap->m_Height, GameLayer.m_Width, GameLayer.m_Height);
647 return false;
648 }
649
650 NewDataFile.AddDataProcessor(Index: DataIndex, DataProcessor: [pLayerTilemap, TileSize, LayerType, GroupIndex, LayerIndex, TilemapCount, TilemapSize](void *pData, size_t Size) -> std::pair<void *, size_t> {
651 const size_t SavedTilesSize = Size / TileSize;
652 if(pLayerTilemap->m_Version >= 4)
653 {
654 // CMapItemLayerTilemap with this version are only written to maps in upstream Teeworlds.
655 // The tile data of tilemaps using this version must be unpacked by repeating tiles
656 // according to the CTile::m_Skip values of the packed tile data.
657 if(LayerType != LAYERTYPE_TILES && LayerType != LAYERTYPE_GAME)
658 {
659 log_error("map/load", "Layer %d in group %d uses tileskip but this is only supported for tiles and game layers.",
660 LayerIndex, GroupIndex);
661 free(ptr: pData);
662 return std::make_pair(x: nullptr, y: 0);
663 }
664 CTile *pTiles = static_cast<CTile *>(malloc(size: TilemapSize));
665 if(pTiles == nullptr)
666 {
667 log_error("map/load", "Failed to allocate memory for layer %d in group %d (size %d * %d).",
668 LayerIndex, GroupIndex, pLayerTilemap->m_Width, pLayerTilemap->m_Height);
669 free(ptr: pData);
670 return std::make_pair(x: nullptr, y: 0);
671 }
672 else if(!ExtractTiles(pDest: pTiles, DestSize: (size_t)pLayerTilemap->m_Width * pLayerTilemap->m_Height, pSrc: static_cast<const CTile *>(pData), SrcSize: SavedTilesSize))
673 {
674 log_error("map/load", "Failed to extract tiles of layer %d in group %d.",
675 LayerIndex, GroupIndex);
676 free(ptr: pTiles);
677 free(ptr: pData);
678 return std::make_pair(x: nullptr, y: 0);
679 }
680 free(ptr: pData);
681 return std::make_pair(x&: pTiles, y: TilemapSize);
682 }
683 else if(SavedTilesSize < TilemapCount)
684 {
685 log_error("map/load", "Tile data of layer %d in group %d is truncated (got %" PRIzu ", wanted %" PRIzu ").",
686 LayerIndex, GroupIndex, SavedTilesSize, TilemapCount);
687 free(ptr: pData);
688 return std::make_pair(x: nullptr, y: 0);
689 }
690 else if(LayerType == LAYERTYPE_TILES || LayerType == LAYERTYPE_GAME || LayerType == LAYERTYPE_FRONT)
691 {
692 const CTile *pTileData = static_cast<const CTile *>(pData);
693 for(size_t TileIndex = 0; TileIndex < TilemapCount; ++TileIndex)
694 {
695 if(pTileData[TileIndex].m_Skip != 0)
696 {
697 log_error("map/load", "Tile data of layer %d in group %d contains non-zero skip value %d at index %" PRIzu " but version %d does not use tileskip.",
698 LayerIndex, GroupIndex, pTileData[TileIndex].m_Skip, TileIndex, pLayerTilemap->m_Version);
699 free(ptr: pData);
700 return std::make_pair(x: nullptr, y: 0);
701 }
702 if(pTileData[TileIndex].m_MustBe0 != 0)
703 {
704 log_error("map/load", "Tile data of layer %d in group %d contains non-zero padding value %d at index %" PRIzu ".",
705 LayerIndex, GroupIndex, pTileData[TileIndex].m_MustBe0, TileIndex);
706 free(ptr: pData);
707 return std::make_pair(x: nullptr, y: 0);
708 }
709 }
710 }
711 else if(LayerType == LAYERTYPE_SPEEDUP)
712 {
713 const CSpeedupTile *pSpeedupData = static_cast<const CSpeedupTile *>(pData);
714 for(size_t TileIndex = 0; TileIndex < TilemapCount; ++TileIndex)
715 {
716 if(pSpeedupData[TileIndex].m_MustBe0 != 0)
717 {
718 log_error("map/load", "Speedup tile data of layer %d in group %d contains non-zero padding value %d at index %" PRIzu ".",
719 LayerIndex, GroupIndex, pSpeedupData[TileIndex].m_MustBe0, TileIndex);
720 free(ptr: pData);
721 return std::make_pair(x: nullptr, y: 0);
722 }
723 }
724 }
725 return std::make_pair(x&: pData, y&: Size);
726 });
727
728 return true;
729}
730
731extern std::unique_ptr<IMap> CreateMap()
732{
733 return std::make_unique<CMap>();
734}
735