1 // tool "framework"
2 #include "maplib.h"
3 
4 // Self
5 #include "mapload.h"
6 
7 // Global
8 uint8_t terrainTypes[MAX_TILE_TEXTURES];
9 
mapFree(GAMEMAP * map)10 void mapFree(GAMEMAP *map)
11 {
12 	if (map)
13 	{
14 		unsigned int i;
15 
16 		free(map->mGateways);
17 		free(map->mMapTiles);
18 
19 		for (i = 0; i < ARRAY_SIZE(map->mLndObjects); ++i)
20 		{
21 			free(map->mLndObjects[i]);
22 		}
23 	}
24 	free(map);
25 }
26 
27 /* Initialise the map structure */
mapLoad(char * filename)28 GAMEMAP *mapLoad(char *filename)
29 {
30 	char		path[PATH_MAX];
31 	GAMEMAP		*map = (GAMEMAP *)malloc(sizeof(*map));
32 	uint32_t	i, j, gwVersion;
33 	char		aFileType[4];
34 	bool		littleEndian = true;
35 	PHYSFS_file	*fp = NULL;
36 	bool		counted[MAX_PLAYERS];
37 	uint16_t	pType;
38 
39 	// this cries out for a class based design
40 	#define readU8(v) ( littleEndian ? PHYSFS_readULE8(fp, v) : PHYSFS_readUBE8(fp, v) )
41 	#define readU16(v) ( littleEndian ? PHYSFS_readULE16(fp, v) : PHYSFS_readUBE16(fp, v) )
42 	#define readU32(v) ( littleEndian ? PHYSFS_readULE32(fp, v) : PHYSFS_readUBE32(fp, v) )
43 	#define readS8(v) ( littleEndian ? PHYSFS_readSLE8(fp, v) : PHYSFS_readSBE8(fp, v) )
44 	#define readS16(v) ( littleEndian ? PHYSFS_readSLE16(fp, v) : PHYSFS_readSBE16(fp, v) )
45 	#define readS32(v) ( littleEndian ? PHYSFS_readSLE32(fp, v) : PHYSFS_readSBE32(fp, v) )
46 
47 	/* === Load map data === */
48 
49 	strcpy(path, filename);
50 	strcat(path, "/game.map");
51 	fp = PHYSFS_openRead(path);
52 	map->mGateways = NULL;
53 	map->mMapTiles = NULL;
54 
55 	if (!fp)
56 	{
57 		debug(LOG_ERROR, "Could not open %s", path);
58 		map->mapVersion = 0;
59 		map->width = UINT32_MAX;
60 		map->height = UINT32_MAX;
61 		map->mMapTiles = NULL;
62 		goto mapfailure;
63 	}
64 	else if (WZ_PHYSFS_readBytes(fp, aFileType, 4) != 4
65 		|| !readU32(&map->mapVersion)
66 		|| !readU32(&map->width)
67 		|| !readU32(&map->height)
68 		|| aFileType[0] != 'm'
69 		|| aFileType[1] != 'a'
70 		|| aFileType[2] != 'p')
71 	{
72 		debug(LOG_ERROR, "Bad header in %s", path);
73 		goto failure;
74 	}
75 	else if (map->mapVersion <= 9)
76 	{
77 		debug(LOG_ERROR, "%s: Unsupported save format version %u", path, map->mapVersion);
78 		goto failure;
79 	}
80 	else if (map->mapVersion > 36)
81 	{
82 		debug(LOG_ERROR, "%s: Undefined save format version %u", path, map->mapVersion);
83 		goto failure;
84 	}
85 	else if (map->width * map->height > MAP_MAXAREA)
86 	{
87 		debug(LOG_ERROR, "Map %s too large : %d %d", path, map->width, map->height);
88 		goto failure;
89 	}
90 
91 	/* Allocate the memory for the map */
92 	map->mMapTiles = (MAPTILE *)calloc(map->width * map->height, sizeof(*map->mMapTiles));
93 	if (!map->mMapTiles)
94 	{
95 		debug(LOG_ERROR, "Out of memory");
96 		goto failure;
97 	}
98 
99 	/* Load in the map data */
100 	for (i = 0; i < map->width * map->height; i++)
101 	{
102 		uint16_t	texture;
103 		uint8_t		height;
104 
105 		if (!readU16(&texture) || !readU8(&height))
106 		{
107 			debug(LOG_ERROR, "%s: Error during savegame load", path);
108 			goto failure;
109 		}
110 
111 		map->mMapTiles[i].texture = static_cast<TerrainType>(texture);
112 		map->mMapTiles[i].height = height;
113 		for (j = 0; j < MAX_PLAYERS; j++)
114 		{
115 			map->mMapTiles[i].tileVisBits = (uint8_t)(map->mMapTiles[i].tileVisBits &~ (uint8_t)(1 << j));
116 		}
117 	}
118 
119 	if (!readU32(&gwVersion) || !readU32(&map->numGateways) || gwVersion != 1)
120 	{
121 		debug(LOG_ERROR, "Bad gateway in %s", path);
122 		goto failure;
123 	}
124 
125 	map->mGateways = (GATEWAY *)calloc(map->numGateways, sizeof(*map->mGateways));
126 	for (i = 0; i < map->numGateways; i++)
127 	{
128 		if (!readU8(&map->mGateways[i].x1) || !readU8(&map->mGateways[i].y1)
129 			|| !readU8(&map->mGateways[i].x2) || !readU8(&map->mGateways[i].y2))
130 		{
131 			debug(LOG_ERROR, "%s: Failed to read gateway info", path);
132 			goto failure;
133 		}
134 	}
135 	PHYSFS_close(fp);
136 mapfailure:
137 
138 	/* === Load game data === */
139 
140 	strcpy(path, filename);
141 	strcat(path, ".gam");
142 	fp = PHYSFS_openRead(path);
143 	if (!fp)
144 	{
145 		debug(LOG_ERROR, "Game file %s not found", path);
146 		goto failure;
147 	}
148 	else if (WZ_PHYSFS_readBytes(fp, aFileType, 4) != 4
149 		|| aFileType[0] != 'g'
150 		|| aFileType[1] != 'a'
151 		|| aFileType[2] != 'm'
152 		|| aFileType[3] != 'e'
153 		|| !readU32(&map->gameVersion))
154 	{
155 		debug(LOG_ERROR, "Bad header in %s", path);
156 		goto failure;
157 	}
158 	if (map->gameVersion > 35)	// big-endian
159 	{
160 		littleEndian = false;
161 	}
162 	if (!readU32(&map->gameTime)
163 		|| !readU32(&map->gameType)
164 		|| !readS32(&map->scrollMinX)
165 		|| !readS32(&map->scrollMinY)
166 		|| !readU32(&map->scrollMaxX)
167 		|| !readU32(&map->scrollMaxY)
168 		|| WZ_PHYSFS_readBytes(fp, map->levelName, 20) != 20)
169 	{
170 		debug(LOG_ERROR, "Bad data in %s", filename);
171 		goto failure;
172 	}
173 	for (i = 0; i < 8; i++)
174 	{
175 		if (map->gameVersion >= 10)
176 		{
177 			uint32_t dummy;	// extracted power, not used
178 
179 			if (!readU32(&map->power[i]) || !readU32(&dummy))
180 			{
181 				debug(LOG_ERROR, "Bad power data in %s", filename);
182 				goto failure;
183 			}
184 		}
185 		else
186 		{
187 			map->power[i] = 0;	// TODO... is there a default?
188 		}
189 	}
190 	PHYSFS_close(fp);
191 
192 
193 	/* === Load feature data === */
194 
195 	littleEndian = true;
196 	strcpy(path, filename);
197 	strcat(path, "/feat.bjo");
198 	fp = PHYSFS_openRead(path);
199 	if (!fp)
200 	{
201 		debug(LOG_ERROR, "Feature file %s not found", path);
202 		map->featVersion = 0;
203 		map->numFeatures = 0;
204 		map->mLndObjects[IMD_FEATURE] = NULL;
205 		goto featfailure;
206 	}
207 	else if (WZ_PHYSFS_readBytes(fp, aFileType, 4) != 4
208 		|| aFileType[0] != 'f'
209 		|| aFileType[1] != 'e'
210 		|| aFileType[2] != 'a'
211 		|| aFileType[3] != 't'
212 		|| !readU32(&map->featVersion)
213 		|| !readU32(&map->numFeatures))
214 	{
215 		debug(LOG_ERROR, "Bad features header in %s", path);
216 		goto failure;
217 	}
218 	map->mLndObjects[IMD_FEATURE] = (LND_OBJECT *)malloc(sizeof(*map->mLndObjects[IMD_FEATURE]) * map->numFeatures);
219 	for(i = 0; i < map->numFeatures; i++)
220 	{
221 		LND_OBJECT *psObj = &map->mLndObjects[IMD_FEATURE][i];
222 		int nameLength = 60;
223 		uint32_t dummy;
224 		uint8_t visibility[8];
225 
226 		if (map->featVersion <= 19)
227 		{
228 			nameLength = 40;
229 		}
230 		if (WZ_PHYSFS_readBytes(fp, psObj->name, nameLength) != nameLength
231 			|| !readU32(&psObj->id)
232 			|| !readU32(&psObj->x) || !readU32(&psObj->y) || !readU32(&psObj->z)
233 			|| !readU32(&psObj->direction)
234 			|| !readU32(&psObj->player)
235 			|| !readU32(&dummy) // BOOL inFire
236 			|| !readU32(&dummy) // burnStart
237 			|| !readU32(&dummy)) // burnDamage
238 		{
239 			debug(LOG_ERROR, "Failed to read feature from %s", path);
240 			goto failure;
241 		}
242 		psObj->player = 0;	// work around invalid feature owner
243 		if (map->featVersion >= 14 && WZ_PHYSFS_readBytes(fp, &visibility, 8) != 8)
244 		{
245 			debug(LOG_ERROR, "Failed to read feature visibility from %s", path);
246 			goto failure;
247 		}
248 		psObj->type = 0;	// IMD LND type for feature
249 		// Sanity check data
250 		if (psObj->x >= map->width * TILE_WIDTH || psObj->y >= map->height * TILE_HEIGHT)
251 		{
252 			debug(LOG_ERROR, "Bad feature coordinate %u(%u, %u)", psObj->id, psObj->x, psObj->y);
253 			goto failure;
254 		}
255 	}
256 	PHYSFS_close(fp);
257 featfailure:
258 
259 
260 	/* === Load terrain data === */
261 
262 	littleEndian = true;
263 	strcpy(path, filename);
264 	strcat(path, "/ttypes.ttp");
265 	fp = PHYSFS_openRead(path);
266 	if (!fp)
267 	{
268 		map->terrainVersion = 0;
269 		goto terrainfailure;
270 	}
271 	else if (WZ_PHYSFS_readBytes(fp, aFileType, 4) != 4
272 		|| aFileType[0] != 't'
273 		|| aFileType[1] != 't'
274 		|| aFileType[2] != 'y'
275 		|| aFileType[3] != 'p'
276 		|| !readU32(&map->terrainVersion)
277 		|| !readU32(&map->numTerrainTypes))
278 	{
279 		debug(LOG_ERROR, "Bad features header in %s", path);
280 		goto failure;
281 	}
282 
283 	if (map->numTerrainTypes >= MAX_TILE_TEXTURES)
284 	{
285 		// Workaround for fugly map editor bug, since we can't fix the map editor
286 		map->numTerrainTypes = MAX_TILE_TEXTURES - 1;
287 	}
288 
289 	// reset the terrain table
290 	memset(terrainTypes, 0, sizeof(terrainTypes));
291 
292 	for (i = 0; i < map->numTerrainTypes; i++)
293 	{
294 		readU16(&pType);
295 
296 		if (pType > TER_MAX)
297 		{
298 			debug(LOG_ERROR, "loadTerrainTypeMap: terrain type out of range");
299 			goto terrainfailure;
300 		}
301 
302 		terrainTypes[i] = (uint8_t)pType;
303 	}
304 
305 	if (terrainTypes[0] == 1 && terrainTypes[1] == 0 && terrainTypes[2] == 2)
306 	{
307 		map->tileset = TILESET_ARIZONA;
308 	}
309 	else if (terrainTypes[0] == 2 && terrainTypes[1] == 2 && terrainTypes[2] == 2)
310 	{
311 		map->tileset = TILESET_URBAN;
312 	}
313 	else if (terrainTypes[0] == 0 && terrainTypes[1] == 0 && terrainTypes[2] == 2)
314 	{
315 		map->tileset = TILESET_ROCKIES;
316 	}
317 	else
318 	{
319 		debug(LOG_ERROR, "Unknown terrain signature in %s: %u %u %u", path,
320 		      terrainTypes[0], terrainTypes[1], terrainTypes[2]);
321 		map->tileset = TILESET_ARIZONA;  // Set something random. Why just have 3 tilesets, anyway?
322 	}
323 
324 	PHYSFS_close(fp);
325 terrainfailure:
326 
327 	/* === Load structure data === */
328 
329 	map->mLndObjects[IMD_STRUCTURE] = NULL;
330 	map->numStructures = 0;
331 	littleEndian = true;
332 	strcpy(path, filename);
333 	strcat(path, "/struct.bjo");
334 	map->mLndObjects[IMD_STRUCTURE] = NULL;
335 	fp = PHYSFS_openRead(path);
336 	if (fp)
337 	{
338 		if (WZ_PHYSFS_readBytes(fp, aFileType, 4) != 4
339 			|| aFileType[0] != 's'
340 			|| aFileType[1] != 't'
341 			|| aFileType[2] != 'r'
342 			|| aFileType[3] != 'u'
343 			|| !readU32(&map->structVersion)
344 			|| !readU32(&map->numStructures))
345 		{
346 			debug(LOG_ERROR, "Bad structure header in %s", path);
347 			goto failure;
348 		}
349 		map->mLndObjects[IMD_STRUCTURE] = (LND_OBJECT *)malloc(sizeof(*map->mLndObjects[IMD_STRUCTURE]) * map->numStructures);
350 		for (i = 0; i < map->numStructures; i++)
351 		{
352 			LND_OBJECT *psObj = &map->mLndObjects[IMD_STRUCTURE][i];
353 			int nameLength = 60;
354 			uint32_t dummy;
355 			uint8_t visibility[8], dummy8;
356 			int16_t dummyS16;
357 			int32_t dummyS32;
358 			char researchName[60];
359 
360 			if (map->structVersion <= 19)
361 			{
362 				nameLength = 40;
363 			}
364 			if (WZ_PHYSFS_readBytes(fp, psObj->name, nameLength) != nameLength
365 				|| !readU32(&psObj->id)
366 				|| !readU32(&psObj->x) || !readU32(&psObj->y) || !readU32(&psObj->z)
367 				|| !readU32(&psObj->direction)
368 				|| !readU32(&psObj->player)
369 				|| !readU32(&dummy) // BOOL inFire
370 				|| !readU32(&dummy) // burnStart
371 				|| !readU32(&dummy) // burnDamage
372 				|| !readU8(&dummy8)	// status - causes structure padding
373 				|| !readU8(&dummy8)	// structure padding
374 				|| !readU8(&dummy8)	// structure padding
375 				|| !readU8(&dummy8) // structure padding
376 				|| !readS32(&dummyS32) // currentBuildPts - aligned on 4 byte boundary
377 				|| !readU32(&dummy) // body
378 				|| !readU32(&dummy) // armour
379 				|| !readU32(&dummy) // resistance
380 				|| !readU32(&dummy) // dummy1
381 				|| !readU32(&dummy) // subjectInc
382 				|| !readU32(&dummy) // timeStarted
383 				|| !readU32(&dummy) // output
384 				|| !readU32(&dummy) // capacity
385 				|| !readU32(&dummy)) // quantity
386 			{
387 				debug(LOG_ERROR, "Failed to read structure from %s", path);
388 				goto failure;
389 			}
390 			if (map->structVersion >= 12
391 				&& (!readU32(&dummy)	// factoryInc
392 					|| !readU8(&dummy8) // loopsPerformed - causes structure padding
393 					|| !readU8(&dummy8) // structure padding
394 					|| !readU8(&dummy8) // structure padding
395 					|| !readU8(&dummy8) // structure padding
396 					|| !readU32(&dummy) // powerAccrued - aligned on 4 byte boundary
397 					|| !readU32(&dummy) // dummy2
398 					|| !readU32(&dummy) // droidTimeStarted
399 					|| !readU32(&dummy) // timeToBuild
400 					|| !readU32(&dummy))) // timeStartHold
401 			{
402 				debug(LOG_ERROR, "Failed to read structure v12 part from %s", path);
403 				goto failure;
404 			}
405 			if (map->structVersion >= 14 && WZ_PHYSFS_readBytes(fp, &visibility, 8) != 8)
406 			{
407 				debug(LOG_ERROR, "Failed to read structure visibility from %s", path);
408 				goto failure;
409 			}
410 			if (map->structVersion >= 15 && WZ_PHYSFS_readBytes(fp, researchName, nameLength) != nameLength)
411 			{
412 				// If version < 20, then this causes no padding, but the short below
413 				// will still cause two bytes padding; however, if version >= 20, we
414 				// will cause 4 bytes padding, but the short below will eat 2 of them,
415 				// leaving us again with only two bytes padding before the next word.
416 				debug(LOG_ERROR, "Failed to read structure v15 part from %s", path);
417 				goto failure;
418 			}
419 			if (map->structVersion >= 17 && !readS16(&dummyS16))
420 			{
421 				debug(LOG_ERROR, "Failed to read structure v17 part from %s", path);
422 				goto failure;
423 			}
424 			if (map->structVersion >= 15 && !readS16(&dummyS16))	// structure padding
425 			{
426 				debug(LOG_ERROR, "Failed to read 16 bits of structure padding from %s", path);
427 				goto failure;
428 			}
429 			if (map->structVersion >= 21 && !readU32(&dummy))
430 			{
431 				debug(LOG_ERROR, "Failed to read structure v21 part from %s", path);
432 				goto failure;
433 			}
434 			psObj->type = IMD_STRUCTURE;
435 			// Sanity check data
436 			if (psObj->player > MAX_PLAYERS)
437 			{
438 				debug(LOG_ERROR, "Bad structure owner %u for structure %d id=%u", psObj->player, i, psObj->id);
439 				goto failure;
440 			}
441 			if (psObj->x >= map->width * TILE_WIDTH || psObj->y >= map->height * TILE_HEIGHT)
442 			{
443 				debug(LOG_ERROR, "Bad structure %d coordinate %u(%u, %u)", i, psObj->id, psObj->x, psObj->y);
444 				goto failure;
445 			}
446 		}
447 		PHYSFS_close(fp);
448 	}
449 
450 
451 	/* === Load droid data === */
452 
453 	map->mLndObjects[IMD_DROID] = NULL;
454 	map->numDroids = 0;
455 	littleEndian = true;
456 	strcpy(path, filename);
457 	strcat(path, "/dinit.bjo");
458 	map->mLndObjects[IMD_DROID] = NULL;
459 	fp = PHYSFS_openRead(path);
460 	if (fp)
461 	{
462 		if (WZ_PHYSFS_readBytes(fp, aFileType, 4) != 4
463 			|| aFileType[0] != 'd'
464 			|| aFileType[1] != 'i'
465 			|| aFileType[2] != 'n'
466 			|| aFileType[3] != 't'
467 			|| !readU32(&map->droidVersion)
468 			|| !readU32(&map->numDroids))
469 		{
470 			debug(LOG_ERROR, "Bad droid header in %s", path);
471 			goto failure;
472 		}
473 		map->mLndObjects[IMD_DROID] = (LND_OBJECT *)malloc(sizeof(*map->mLndObjects[IMD_DROID]) * map->numDroids);
474 		for (i = 0; i < map->numDroids; i++)
475 		{
476 			LND_OBJECT *psObj = &map->mLndObjects[IMD_DROID][i];
477 			int nameLength = 60;
478 			uint32_t dummy;
479 
480 			if (map->droidVersion <= 19)
481 			{
482 				nameLength = 40;
483 			}
484 			if (WZ_PHYSFS_readBytes(fp, psObj->name, nameLength) != nameLength
485 				|| !readU32(&psObj->id)
486 				|| !readU32(&psObj->x) || !readU32(&psObj->y) || !readU32(&psObj->z)
487 				|| !readU32(&psObj->direction)
488 				|| !readU32(&psObj->player)
489 				|| !readU32(&dummy) // BOOL inFire
490 				|| !readU32(&dummy) // burnStart
491 				|| !readU32(&dummy)) // burnDamage
492 			{
493 				debug(LOG_ERROR, "Failed to read droid from %s", path);
494 				goto failure;
495 			}
496 			psObj->type = IMD_DROID;
497 			// Sanity check data
498 			if (psObj->x >= map->width * TILE_WIDTH || psObj->y >= map->height * TILE_HEIGHT)
499 			{
500 				debug(LOG_ERROR, "Bad droid coordinate %u(%u, %u)", psObj->id, psObj->x, psObj->y);
501 				goto failure;
502 			}
503 		}
504 		PHYSFS_close(fp);
505 	}
506 
507 	// Count players by looking for the obligatory construction droids
508 	map->numPlayers = 0;
509 	memset(counted, 0, sizeof(counted));
510 	for(i = 0; i < map->numDroids; i++)
511 	{
512 		LND_OBJECT *psObj = &map->mLndObjects[IMD_DROID][i];
513 
514 		if (counted[psObj->player] == false && (strcmp(psObj->name, "ConstructorDroid") == 0 || strcmp(psObj->name, "ConstructionDroid") == 0))
515 		{
516 			counted[psObj->player] = true;
517 			map->numPlayers++;
518 		}
519 	}
520 
521 	return map;
522 
523 failure:
524 	mapFree(map);
525 	if (fp)
526 	{
527 		PHYSFS_close(fp);
528 	}
529 	return NULL;
530 }
531