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