1 //------------------------------------------------------------------------
2 //  WAD Reading / Writing
3 //------------------------------------------------------------------------
4 //
5 //  Eureka DOOM Editor
6 //
7 //  Copyright (C) 2001-2019 Andrew Apted
8 //  Copyright (C) 1997-2003 André Majorel et al
9 //
10 //  This program is free software; you can redistribute it and/or
11 //  modify it under the terms of the GNU General Public License
12 //  as published by the Free Software Foundation; either version 2
13 //  of the License, or (at your option) any later version.
14 //
15 //  This program is distributed in the hope that it will be useful,
16 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
17 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 //  GNU General Public License for more details.
19 //
20 //------------------------------------------------------------------------
21 //
22 //  Based on Yadex which incorporated code from DEU 5.21 that was put
23 //  in the public domain in 1994 by Raphaël Quinet and Brendon Wyber.
24 //
25 //------------------------------------------------------------------------
26 
27 #include "main.h"
28 
29 #include <algorithm>
30 
31 #include "lib_adler.h"
32 #include "w_rawdef.h"
33 #include "w_wad.h"
34 
35 
36 Wad_file * game_wad;
37 Wad_file * edit_wad;
38 
39 std::vector<Wad_file *> master_dir;
40 
41 // UDMF support is unfinished and hence disabled by default.
42 bool udmf_testing = false;
43 
44 
45 #define MAX_LUMPS_IN_A_LEVEL	21
46 
47 
48 //------------------------------------------------------------------------
49 //  LUMP Handling
50 //------------------------------------------------------------------------
51 
Lump_c(Wad_file * _par,const char * _nam,int _start,int _len)52 Lump_c::Lump_c(Wad_file *_par, const char *_nam, int _start, int _len) :
53 	parent(_par), l_start(_start), l_length(_len)
54 {
55 	name = StringDup(_nam);
56 
57 	SYS_ASSERT(name);
58 
59 	// ensure lump name is uppercase
60 	y_strupr((char *)name);
61 }
62 
63 
Lump_c(Wad_file * _par,const struct raw_wad_entry_s * entry)64 Lump_c::Lump_c(Wad_file *_par, const struct raw_wad_entry_s *entry) :
65 	parent(_par)
66 {
67 	// handle the entry name, which can lack a terminating NUL
68 	char buffer[10];
69 	strncpy(buffer, entry->name, 8);
70 	buffer[8] = 0;
71 
72 	name = StringDup(buffer);
73 
74 	l_start  = LE_U32(entry->pos);
75 	l_length = LE_U32(entry->size);
76 
77 //	DebugPrintf("new lump '%s' @ %d len:%d\n", name, l_start, l_length);
78 
79 	if (l_length == 0)
80 		l_start = 0;
81 }
82 
83 
~Lump_c()84 Lump_c::~Lump_c()
85 {
86 	StringFree(name);
87 }
88 
89 
MakeEntry(struct raw_wad_entry_s * entry)90 void Lump_c::MakeEntry(struct raw_wad_entry_s *entry)
91 {
92 	W_StoreString(entry->name, name, sizeof(entry->name));
93 
94 	entry->pos  = LE_U32(l_start);
95 	entry->size = LE_U32(l_length);
96 }
97 
98 
Rename(const char * new_name)99 void Lump_c::Rename(const char *new_name)
100 {
101 	StringFree(name);
102 
103 	name = StringDup(new_name);
104 	SYS_ASSERT(name);
105 
106 	// ensure lump name is uppercase
107 	y_strupr((char *)name);
108 }
109 
110 
Seek(int offset)111 bool Lump_c::Seek(int offset)
112 {
113 	SYS_ASSERT(offset >= 0);
114 
115 	return (fseek(parent->fp, l_start + offset, SEEK_SET) == 0);
116 }
117 
118 
Read(void * data,int len)119 bool Lump_c::Read(void *data, int len)
120 {
121 	SYS_ASSERT(data && len > 0);
122 
123 	return (fread(data, len, 1, parent->fp) == 1);
124 }
125 
126 
GetLine(char * buffer,size_t buf_size)127 bool Lump_c::GetLine(char *buffer, size_t buf_size)
128 {
129 	int cur_pos = (int)ftell(parent->fp);
130 
131 	if (cur_pos < 0)
132 		return false;
133 
134 	cur_pos -= l_start;
135 
136 	if (cur_pos >= l_length)
137 		return false;  // EOF
138 
139 	char *dest = buffer;
140 	char *dest_end = buffer + buf_size - 1;
141 
142 	for (; cur_pos < l_length && dest < dest_end ; cur_pos++)
143 	{
144 		*dest++ = fgetc(parent->fp);
145 
146 		if (dest[-1] == '\n')
147 			break;
148 
149 		if (ferror(parent->fp))
150 			return false;
151 
152 		if (feof(parent->fp))
153 			break;
154 	}
155 
156 	*dest = 0;
157 
158 	return true;  // OK
159 }
160 
161 
Write(const void * data,int len)162 bool Lump_c::Write(const void *data, int len)
163 {
164 	SYS_ASSERT(data && len > 0);
165 
166 	l_length += len;
167 
168 	return (fwrite(data, len, 1, parent->fp) == 1);
169 }
170 
171 
Printf(const char * msg,...)172 void Lump_c::Printf(const char *msg, ...)
173 {
174 	static char buffer[FL_PATH_MAX];
175 
176 	va_list args;
177 
178 	va_start(args, msg);
179 	vsnprintf(buffer, sizeof(buffer), msg, args);
180 	va_end(args);
181 
182 	buffer[sizeof(buffer) - 1] = 0;
183 
184 	Write(buffer, (int)strlen(buffer));
185 }
186 
187 
Finish()188 bool Lump_c::Finish()
189 {
190 	if (l_length == 0)
191 		l_start = 0;
192 
193 	return parent->FinishLump(l_length);
194 }
195 
196 
197 //------------------------------------------------------------------------
198 //  WAD Reading Interface
199 //------------------------------------------------------------------------
200 
Wad_file(const char * _name,char _mode,FILE * _fp)201 Wad_file::Wad_file(const char *_name, char _mode, FILE * _fp) :
202 	mode(_mode), fp(_fp), kind('P'),
203 	total_size(0), directory(),
204 	dir_start(0), dir_count(0), dir_crc(0),
205 	levels(), patches(), sprites(), flats(), tx_tex(),
206 	begun_write(false), insert_point(-1)
207 {
208 	filename = StringDup(_name);
209 }
210 
~Wad_file()211 Wad_file::~Wad_file()
212 {
213 	LogPrintf("Closing WAD file: %s\n", filename);
214 
215 	fclose(fp);
216 
217 	// free the directory
218 	for (short k = 0 ; k < NumLumps() ; k++)
219 		delete directory[k];
220 
221 	directory.clear();
222 
223 	StringFree(filename);
224 }
225 
226 
Open(const char * filename,char mode)227 Wad_file * Wad_file::Open(const char *filename, char mode)
228 {
229 	SYS_ASSERT(mode == 'r' || mode == 'w' || mode == 'a');
230 
231 	if (mode == 'w')
232 		return Create(filename, mode);
233 
234 	LogPrintf("Opening WAD file: %s\n", filename);
235 
236 	FILE *fp = NULL;
237 
238 retry:
239 	fp = fopen(filename, (mode == 'r' ? "rb" : "r+b"));
240 
241 	if (! fp)
242 	{
243 		// mimic the fopen() semantics
244 		if (mode == 'a' && errno == ENOENT)
245 			return Create(filename, mode);
246 
247 		// if file is read-only, open in 'r' mode instead
248 		if (mode == 'a' && (errno == EACCES || errno == EROFS))
249 		{
250 			LogPrintf("Open r/w failed, trying again in read mode...\n");
251 			mode = 'r';
252 			goto retry;
253 		}
254 
255 		int what = errno;
256 		LogPrintf("Open failed: %s\n", strerror(what));
257 		return NULL;
258 	}
259 
260 	Wad_file *w = new Wad_file(filename, mode, fp);
261 
262 	// determine total size (seek to end)
263 	if (fseek(fp, 0, SEEK_END) != 0)
264 		FatalError("Error determining WAD size.\n");
265 
266 	w->total_size = (int)ftell(fp);
267 
268 	DebugPrintf("total_size = %d\n", w->total_size);
269 
270 	if (w->total_size < 0)
271 		FatalError("Error determining WAD size.\n");
272 
273 	if (! w->ReadDirectory())
274 	{
275 		delete w;
276 		LogPrintf("Open wad failed (reading directory)\n");
277 		return NULL;
278 	}
279 
280 	w->DetectLevels();
281 	w->ProcessNamespaces();
282 
283 	return w;
284 }
285 
286 
Create(const char * filename,char mode)287 Wad_file * Wad_file::Create(const char *filename, char mode)
288 {
289 	LogPrintf("Creating new WAD file: %s\n", filename);
290 
291 	FILE *fp = fopen(filename, "w+b");
292 	if (! fp)
293 		return NULL;
294 
295 	Wad_file *w = new Wad_file(filename, mode, fp);
296 
297 	// write out base header
298 	raw_wad_header_t header;
299 
300 	memset(&header, 0, sizeof(header));
301 	memcpy(header.ident, "PWAD", 4);
302 
303 	fwrite(&header, sizeof(header), 1, fp);
304 	fflush(fp);
305 
306 	w->total_size = (int)sizeof(header);
307 
308 	return w;
309 }
310 
311 
Validate(const char * filename)312 bool Wad_file::Validate(const char *filename)
313 {
314 	FILE *fp = fopen(filename, "rb");
315 
316 	if (! fp)
317 		return false;
318 
319 	raw_wad_header_t header;
320 
321 	if (fread(&header, sizeof(header), 1, fp) != 1)
322 	{
323 		fclose(fp);
324 		return false;
325 	}
326 
327 	if (! ( header.ident[1] == 'W' &&
328 			header.ident[2] == 'A' &&
329 			header.ident[3] == 'D'))
330 	{
331 		fclose(fp);
332 		return false;
333 	}
334 
335 	fclose(fp);
336 
337 	return true;  // OK
338 }
339 
340 
WhatLevelPart(const char * name)341 static int WhatLevelPart(const char *name)
342 {
343 	if (y_stricmp(name, "THINGS")   == 0) return 1;
344 	if (y_stricmp(name, "LINEDEFS") == 0) return 2;
345 	if (y_stricmp(name, "SIDEDEFS") == 0) return 3;
346 	if (y_stricmp(name, "VERTEXES") == 0) return 4;
347 	if (y_stricmp(name, "SECTORS")  == 0) return 5;
348 
349 	return 0;
350 }
351 
IsLevelLump(const char * name)352 static bool IsLevelLump(const char *name)
353 {
354 	if (y_stricmp(name, "SEGS")     == 0) return true;
355 	if (y_stricmp(name, "SSECTORS") == 0) return true;
356 	if (y_stricmp(name, "NODES")    == 0) return true;
357 	if (y_stricmp(name, "REJECT")   == 0) return true;
358 	if (y_stricmp(name, "BLOCKMAP") == 0) return true;
359 	if (y_stricmp(name, "BEHAVIOR") == 0) return true;
360 	if (y_stricmp(name, "SCRIPTS")  == 0) return true;
361 
362 	return WhatLevelPart(name) != 0;
363 }
364 
IsGLNodeLump(const char * name)365 static bool IsGLNodeLump(const char *name)
366 {
367 	if (y_strnicmp(name, "GL_", 3) == 0 ) return true;
368 
369 	return false;
370 }
371 
372 
GetLump(short index)373 Lump_c * Wad_file::GetLump(short index)
374 {
375 	SYS_ASSERT(0 <= index && index < NumLumps());
376 	SYS_ASSERT(directory[index]);
377 
378 	return directory[index];
379 }
380 
381 
FindLump(const char * name)382 Lump_c * Wad_file::FindLump(const char *name)
383 {
384 	for (short k = 0 ; k < NumLumps() ; k++)
385 		if (y_stricmp(directory[k]->name, name) == 0)
386 			return directory[k];
387 
388 	return NULL;  // not found
389 }
390 
FindLumpNum(const char * name)391 short Wad_file::FindLumpNum(const char *name)
392 {
393 	for (short k = 0 ; k < NumLumps() ; k++)
394 		if (y_stricmp(directory[k]->name, name) == 0)
395 			return k;
396 
397 	return -1;  // not found
398 }
399 
400 
LevelLookupLump(short lev_num,const char * name)401 short Wad_file::LevelLookupLump(short lev_num, const char *name)
402 {
403 	short start = LevelHeader(lev_num);
404 
405 	// determine how far past the level marker (MAP01 etc) to search
406 	short finish = LevelLastLump(lev_num);
407 
408 	for (short k = start+1 ; k <= finish ; k++)
409 	{
410 		SYS_ASSERT(0 <= k && k < NumLumps());
411 
412 		if (y_stricmp(directory[k]->name, name) == 0)
413 			return k;
414 	}
415 
416 	return -1;  // not found
417 }
418 
419 
LevelFind(const char * name)420 short Wad_file::LevelFind(const char *name)
421 {
422 	for (short k = 0 ; k < (int)levels.size() ; k++)
423 	{
424 		short index = levels[k];
425 
426 		SYS_ASSERT(0 <= index && index < NumLumps());
427 		SYS_ASSERT(directory[index]);
428 
429 		if (y_stricmp(directory[index]->name, name) == 0)
430 			return k;
431 	}
432 
433 	return -1;  // not found
434 }
435 
436 
LevelLastLump(short lev_num)437 short Wad_file::LevelLastLump(short lev_num)
438 {
439 	short start = LevelHeader(lev_num);
440 
441 	int count = 1;
442 
443 	// UDMF level?
444 	if (y_stricmp(directory[start+1]->name, "TEXTMAP") == 0)
445 	{
446 		while (count < MAX_LUMPS_IN_A_LEVEL && start+count < NumLumps())
447 		{
448 			if (y_stricmp(directory[start+count]->name, "ENDMAP") == 0)
449 			{
450 				count++;
451 				break;
452 			}
453 
454 			count++;
455 		}
456 
457 		return start + count - 1;
458 	}
459 
460 	// standard DOOM or HEXEN format
461 	while (count < MAX_LUMPS_IN_A_LEVEL &&
462 		   start+count < NumLumps() &&
463 		   (IsLevelLump(directory[start+count]->name) ||
464 		    IsGLNodeLump(directory[start+count]->name)) )
465 	{
466 		count++;
467 	}
468 
469 	return start + count - 1;
470 }
471 
472 
LevelFindByNumber(int number)473 short Wad_file::LevelFindByNumber(int number)
474 {
475 	// sanity check
476 	if (number <= 0 || number > 99)
477 		return -1;
478 
479 	char buffer[16];
480 	short index;
481 
482 	 // try MAP## first
483 	snprintf(buffer, sizeof(buffer), "MAP%02d", number);
484 
485 	index = LevelFind(buffer);
486 	if (index >= 0)
487 		return index;
488 
489 	// otherwise try E#M#
490 	snprintf(buffer, sizeof(buffer), "E%dM%d", MAX(1, number / 10), number % 10);
491 
492 	index = LevelFind(buffer);
493 	if (index >= 0)
494 		return index;
495 
496 	return -1;  // not found
497 }
498 
499 
LevelFindFirst()500 short Wad_file::LevelFindFirst()
501 {
502 	if (levels.size() > 0)
503 		return 0;
504 	else
505 		return -1;  // none
506 }
507 
508 
LevelHeader(short lev_num)509 short Wad_file::LevelHeader(short lev_num)
510 {
511 	SYS_ASSERT(0 <= lev_num && lev_num < LevelCount());
512 
513 	return levels[lev_num];
514 }
515 
516 
LevelFormat(short lev_num)517 map_format_e Wad_file::LevelFormat(short lev_num)
518 {
519 	int start = LevelHeader(lev_num);
520 
521 	if (y_stricmp(directory[start+1]->name, "TEXTMAP") == 0)
522 		return MAPF_UDMF;
523 
524 	if (start + LL_BEHAVIOR < (int)NumLumps())
525 	{
526 		const char *name = GetLump(start + LL_BEHAVIOR)->Name();
527 
528 		if (y_stricmp(name, "BEHAVIOR") == 0)
529 			return MAPF_Hexen;
530 	}
531 
532 	return MAPF_Doom;
533 }
534 
535 
FindLumpInNamespace(const char * name,char group)536 Lump_c * Wad_file::FindLumpInNamespace(const char *name, char group)
537 {
538 	short k;
539 
540 	switch (group)
541 	{
542 		case 'P':
543 			for (k = 0 ; k < (short)patches.size() ; k++)
544 				if (y_stricmp(directory[patches[k]]->name, name) == 0)
545 					return directory[patches[k]];
546 			break;
547 
548 		case 'S':
549 			for (k = 0 ; k < (short)sprites.size() ; k++)
550 				if (y_stricmp(directory[sprites[k]]->name, name) == 0)
551 					return directory[sprites[k]];
552 			break;
553 
554 		case 'F':
555 			for (k = 0 ; k < (short)flats.size() ; k++)
556 				if (y_stricmp(directory[flats[k]]->name, name) == 0)
557 					return directory[flats[k]];
558 			break;
559 
560 		default:
561 			BugError("FindLumpInNamespace: bad group '%c'\n", group);
562 	}
563 
564 	return NULL; // not found!
565 }
566 
567 
ReadDirectory()568 bool Wad_file::ReadDirectory()
569 {
570 	rewind(fp);
571 
572 	raw_wad_header_t header;
573 
574 	if (fread(&header, sizeof(header), 1, fp) != 1)
575 	{
576 		LogPrintf("Error reading WAD header.\n");
577 		return false;
578 	}
579 
580 	kind = header.ident[0];
581 
582 	dir_start = LE_S32(header.dir_start);
583 	dir_count = LE_S32(header.num_entries);
584 
585 	if (dir_count < 0 || dir_count > 32000)
586 	{
587 		LogPrintf("Bad WAD header, too many entries (%d)\n", dir_count);
588 		return false;
589 	}
590 
591 	crc32_c checksum;
592 
593 	if (fseek(fp, dir_start, SEEK_SET) != 0)
594 	{
595 		LogPrintf("Error seeking to WAD directory.\n");
596 		return false;
597 	}
598 
599 	for (short i = 0 ; i < dir_count ; i++)
600 	{
601 		raw_wad_entry_t entry;
602 
603 		if (fread(&entry, sizeof(entry), 1, fp) != 1)
604 		{
605 			LogPrintf("Error reading entry in WAD directory.\n");
606 			return false;
607 		}
608 
609 		// update the checksum with each _RAW_ entry
610 		checksum.AddBlock((u8_t *) &entry, sizeof(entry));
611 
612 		Lump_c *lump = new Lump_c(this, &entry);
613 
614 		// check if entry is valid
615 		// [ the total_size value was computed in parent function ]
616 		if (lump->l_length != 0)
617 		{
618 			const int max_size = 99999999;
619 
620 			if (lump->l_length < 0 || lump->l_start < 0 ||
621 				lump->l_length >= max_size ||
622 				lump->l_start > total_size ||
623 				lump->l_start + lump->l_length > total_size)
624 			{
625 				LogPrintf("WARNING: clearing lump '%s' with invalid position (%d+%d > %d)\n",
626 						lump->name, lump->l_start, lump->l_length, total_size);
627 
628 				lump->l_start = 0;
629 				lump->l_length = 0;
630 			}
631 		}
632 
633 		directory.push_back(lump);
634 	}
635 
636 	dir_crc = checksum.raw;
637 
638 	DebugPrintf("Loaded directory. crc = %08x\n", dir_crc);
639 	return true;
640 }
641 
642 
DetectLevels()643 void Wad_file::DetectLevels()
644 {
645 	// Determine what lumps in the wad are level markers, based on
646 	// the lumps which follow it.  Store the result in the 'levels'
647 	// vector.  The test here is rather lax, as I'm told certain
648 	// wads exist with a non-standard ordering of level lumps.
649 
650 	for (short k = 0 ; k+1 < NumLumps() ; k++)
651 	{
652 		int part_mask  = 0;
653 		int part_count = 0;
654 
655 		// check for UDMF levels
656 		if (udmf_testing && y_stricmp(directory[k+1]->name, "TEXTMAP") == 0)
657 		{
658 			levels.push_back(k);
659 			DebugPrintf("Detected level : %s (UDMF)\n", directory[k]->name);
660 			continue;
661 		}
662 
663 		// check whether the next four lumps are level lumps
664 		for (short i = 1 ; i <= 4 ; i++)
665 		{
666 			if (k+i >= NumLumps())
667 				break;
668 
669 			int part = WhatLevelPart(directory[k+i]->name);
670 
671 			if (part == 0)
672 				break;
673 
674 			// do not allow duplicates
675 			if (part_mask & (1 << part))
676 				break;
677 
678 			part_mask |= (1 << part);
679 			part_count++;
680 		}
681 
682 		if (part_count == 4)
683 		{
684 			levels.push_back(k);
685 
686 			DebugPrintf("Detected level : %s\n", directory[k]->name);
687 		}
688 	}
689 
690 	// sort levels into alphabetical order
691 	// (mainly for the 'N' next map and 'P' prev map commands)
692 
693 	SortLevels();
694 }
695 
696 
SortLevels()697 void Wad_file::SortLevels()
698 {
699 	std::sort(levels.begin(), levels.end(), level_name_CMP_pred(this));
700 }
701 
702 
IsDummyMarker(const char * name)703 static bool IsDummyMarker(const char *name)
704 {
705 	// matches P1_START, F3_END etc...
706 
707 	if (strlen(name) < 3)
708 		return false;
709 
710 	if (! strchr("PSF", toupper(name[0])))
711 		return false;
712 
713 	if (! isdigit(name[1]))
714 		return false;
715 
716 	if (y_stricmp(name+2, "_START") == 0 ||
717 	    y_stricmp(name+2, "_END") == 0)
718 		return true;
719 
720 	return false;
721 }
722 
723 
ProcessNamespaces()724 void Wad_file::ProcessNamespaces()
725 {
726 	char active = 0;
727 
728 	for (short k = 0 ; k < NumLumps() ; k++)
729 	{
730 		const char *name = directory[k]->name;
731 
732 		// skip the sub-namespace markers
733 		if (IsDummyMarker(name))
734 			continue;
735 
736 		if (y_stricmp(name, "P_START") == 0 || y_stricmp(name, "PP_START") == 0)
737 		{
738 			if (active && active != 'P')
739 				LogPrintf("WARNING: missing %c_END marker.\n", active);
740 
741 			active = 'P';
742 			continue;
743 		}
744 		else if (y_stricmp(name, "P_END") == 0 || y_stricmp(name, "PP_END") == 0)
745 		{
746 			if (active != 'P')
747 				LogPrintf("WARNING: stray P_END marker found.\n");
748 
749 			active = 0;
750 			continue;
751 		}
752 
753 		if (y_stricmp(name, "S_START") == 0 || y_stricmp(name, "SS_START") == 0)
754 		{
755 			if (active && active != 'S')
756 				LogPrintf("WARNING: missing %c_END marker.\n", active);
757 
758 			active = 'S';
759 			continue;
760 		}
761 		else if (y_stricmp(name, "S_END") == 0 || y_stricmp(name, "SS_END") == 0)
762 		{
763 			if (active != 'S')
764 				LogPrintf("WARNING: stray S_END marker found.\n");
765 
766 			active = 0;
767 			continue;
768 		}
769 
770 		if (y_stricmp(name, "F_START") == 0 || y_stricmp(name, "FF_START") == 0)
771 		{
772 			if (active && active != 'F')
773 				LogPrintf("WARNING: missing %c_END marker.\n", active);
774 
775 			active = 'F';
776 			continue;
777 		}
778 		else if (y_stricmp(name, "F_END") == 0 || y_stricmp(name, "FF_END") == 0)
779 		{
780 			if (active != 'F')
781 				LogPrintf("WARNING: stray F_END marker found.\n");
782 
783 			active = 0;
784 			continue;
785 		}
786 
787 		if (y_stricmp(name, "TX_START") == 0)
788 		{
789 			if (active && active != 'T')
790 				LogPrintf("WARNING: missing %c_END marker.\n", active);
791 
792 			active = 'T';
793 			continue;
794 		}
795 		else if (y_stricmp(name, "TX_END") == 0)
796 		{
797 			if (active != 'T')
798 				LogPrintf("WARNING: stray TX_END marker found.\n");
799 
800 			active = 0;
801 			continue;
802 		}
803 
804 		if (active)
805 		{
806 			if (directory[k]->Length() == 0)
807 			{
808 				LogPrintf("WARNING: skipping empty lump %s in %c_START\n",
809 						  name, active);
810 				continue;
811 			}
812 
813 //			DebugPrintf("Namespace %c lump : %s\n", active, name);
814 
815 			switch (active)
816 			{
817 				case 'P': patches.push_back(k); break;
818 				case 'S': sprites.push_back(k); break;
819 				case 'F': flats.  push_back(k); break;
820 				case 'T': tx_tex. push_back(k); break;
821 
822 				default:
823 					BugError("ProcessNamespaces: active = 0x%02x\n", (int)active);
824 			}
825 		}
826 	}
827 
828 	if (active)
829 		LogPrintf("WARNING: Missing %c_END marker (at EOF)\n", active);
830 }
831 
832 
WasExternallyModified()833 bool Wad_file::WasExternallyModified()
834 {
835 	if (fseek(fp, 0, SEEK_END) != 0)
836 		FatalError("Error determining WAD size.\n");
837 
838 	if (total_size != (int)ftell(fp))
839 		return true;
840 
841 	rewind(fp);
842 
843 	raw_wad_header_t header;
844 
845 	if (fread(&header, sizeof(header), 1, fp) != 1)
846 		FatalError("Error reading WAD header.\n");
847 
848 	if (dir_start != LE_S32(header.dir_start) ||
849 		dir_count != LE_S32(header.num_entries))
850 		return true;
851 
852 	fseek(fp, dir_start, SEEK_SET);
853 
854 	crc32_c checksum;
855 
856 	for (short i = 0 ; i < dir_count ; i++)
857 	{
858 		raw_wad_entry_t entry;
859 
860 		if (fread(&entry, sizeof(entry), 1, fp) != 1)
861 			FatalError("Error reading WAD directory.\n");
862 
863 		checksum.AddBlock((u8_t *) &entry, sizeof(entry));
864 
865 	}
866 
867 	DebugPrintf("New CRC : %08x\n", checksum.raw);
868 
869 	return (dir_crc != checksum.raw);
870 }
871 
872 
873 //------------------------------------------------------------------------
874 //  WAD Writing Interface
875 //------------------------------------------------------------------------
876 
BeginWrite()877 void Wad_file::BeginWrite()
878 {
879 	if (mode == 'r')
880 		BugError("Wad_file::BeginWrite() called on read-only file\n");
881 
882 	if (begun_write)
883 		BugError("Wad_file::BeginWrite() called again without EndWrite()\n");
884 
885 	// put the size into a quantum state
886 	total_size = 0;
887 
888 	begun_write = true;
889 }
890 
891 
EndWrite()892 void Wad_file::EndWrite()
893 {
894 	if (! begun_write)
895 		BugError("Wad_file::EndWrite() called without BeginWrite()\n");
896 
897 	begun_write = false;
898 
899 	WriteDirectory();
900 
901 	// reset the insertion point
902 	insert_point = -1;
903 }
904 
905 
RenameLump(short index,const char * new_name)906 void Wad_file::RenameLump(short index, const char *new_name)
907 {
908 	SYS_ASSERT(begun_write);
909 	SYS_ASSERT(0 <= index && index < NumLumps());
910 
911 	Lump_c *lump = directory[index];
912 	SYS_ASSERT(lump);
913 
914 	lump->Rename(new_name);
915 }
916 
917 
RemoveLumps(short index,short count)918 void Wad_file::RemoveLumps(short index, short count)
919 {
920 	SYS_ASSERT(begun_write);
921 	SYS_ASSERT(0 <= index && index < NumLumps());
922 	SYS_ASSERT(directory[index]);
923 
924 	short i;
925 
926 	for (i = 0 ; i < count ; i++)
927 	{
928 		delete directory[index + i];
929 	}
930 
931 	for (i = index ; i+count < NumLumps() ; i++)
932 		directory[i] = directory[i+count];
933 
934 	directory.resize(directory.size() - (size_t)count);
935 
936 	// fix various arrays containing lump indices
937 	FixGroup(levels,  index, 0, count);
938 	FixGroup(patches, index, 0, count);
939 	FixGroup(sprites, index, 0, count);
940 	FixGroup(flats,   index, 0, count);
941 	FixGroup(tx_tex,  index, 0, count);
942 
943 	// reset the insertion point
944 	insert_point = -1;
945 }
946 
947 
RemoveLevel(short lev_num)948 void Wad_file::RemoveLevel(short lev_num)
949 {
950 	SYS_ASSERT(begun_write);
951 	SYS_ASSERT(0 <= lev_num && lev_num < LevelCount());
952 
953 	short start  = LevelHeader(lev_num);
954 	short finish = LevelLastLump(lev_num);
955 
956 	// NOTE: FixGroup() will remove the entry in levels[]
957 
958 	RemoveLumps(start, finish - start + 1);
959 }
960 
961 
RemoveGLNodes(short lev_num)962 void Wad_file::RemoveGLNodes(short lev_num)
963 {
964 	SYS_ASSERT(begun_write);
965 	SYS_ASSERT(0 <= lev_num && lev_num < LevelCount());
966 
967 	short start  = LevelHeader(lev_num);
968 	short finish = LevelLastLump(lev_num);
969 
970 	start++;
971 
972 	while (start <= finish &&
973 		   IsLevelLump(directory[start]->name))
974 	{
975 		start++;
976 	}
977 
978 	short count = 0;
979 
980 	while (start+count <= finish &&
981 		   IsGLNodeLump(directory[start+count]->name))
982 	{
983 		count++;
984 	}
985 
986 	if (count > 0)
987 		RemoveLumps(start, count);
988 }
989 
990 
RemoveZNodes(short lev_num)991 void Wad_file::RemoveZNodes(short lev_num)
992 {
993 	SYS_ASSERT(begun_write);
994 	SYS_ASSERT(0 <= lev_num && lev_num < LevelCount());
995 
996 	short start  = LevelHeader(lev_num);
997 	short finish = LevelLastLump(lev_num);
998 
999 	for ( ; start <= finish ; start++)
1000 	{
1001 		if (y_stricmp(directory[start]->name, "ZNODES") == 0)
1002 		{
1003 			RemoveLumps(start, 1);
1004 			break;
1005 		}
1006 	}
1007 }
1008 
1009 
FixGroup(std::vector<short> & group,short index,short num_added,short num_removed)1010 void Wad_file::FixGroup(std::vector<short>& group, short index,
1011                         short num_added, short num_removed)
1012 {
1013 	bool did_remove = false;
1014 
1015 	for (short k = 0 ; k < (short)group.size() ; k++)
1016 	{
1017 		if (group[k] < index)
1018 			continue;
1019 
1020 		if (group[k] < index + num_removed)
1021 		{
1022 			group[k] = -1;
1023 			did_remove = true;
1024 			continue;
1025 		}
1026 
1027 		group[k] += num_added;
1028 		group[k] -= num_removed;
1029 	}
1030 
1031 	if (did_remove)
1032 	{
1033 		std::vector<short>::iterator ENDP;
1034 		ENDP = std::remove(group.begin(), group.end(), (short)-1);
1035 		group.erase(ENDP, group.end());
1036 	}
1037 }
1038 
1039 
AddLump(const char * name,int max_size)1040 Lump_c * Wad_file::AddLump(const char *name, int max_size)
1041 {
1042 	SYS_ASSERT(begun_write);
1043 
1044 	begun_max_size = max_size;
1045 
1046 	int start = PositionForWrite(max_size);
1047 
1048 	Lump_c *lump = new Lump_c(this, name, start, 0);
1049 
1050 	// check if the insert_point is still valid
1051 	if (insert_point >= NumLumps())
1052 		insert_point = -1;
1053 
1054 	if (insert_point >= 0)
1055 	{
1056 		// fix various arrays containing lump indices
1057 		FixGroup(levels,  insert_point, 1, 0);
1058 		FixGroup(patches, insert_point, 1, 0);
1059 		FixGroup(sprites, insert_point, 1, 0);
1060 		FixGroup(flats,   insert_point, 1, 0);
1061 		FixGroup(tx_tex,  insert_point, 1, 0);
1062 
1063 		directory.insert(directory.begin() + insert_point, lump);
1064 
1065 		insert_point++;
1066 	}
1067 	else  // add to end
1068 	{
1069 		directory.push_back(lump);
1070 	}
1071 
1072 	return lump;
1073 }
1074 
1075 
RecreateLump(Lump_c * lump,int max_size)1076 void Wad_file::RecreateLump(Lump_c *lump, int max_size)
1077 {
1078 	SYS_ASSERT(begun_write);
1079 
1080 	begun_max_size = max_size;
1081 
1082 	int start = PositionForWrite(max_size);
1083 
1084 	lump->l_start  = start;
1085 	lump->l_length = 0;
1086 }
1087 
1088 
AddLevel(const char * name,int max_size,short * lev_num)1089 Lump_c * Wad_file::AddLevel(const char *name, int max_size, short *lev_num)
1090 {
1091 	int actual_point = insert_point;
1092 
1093 	if (actual_point < 0 || actual_point > NumLumps())
1094 		actual_point = NumLumps();
1095 
1096 	Lump_c * lump = AddLump(name, max_size);
1097 
1098 	if (lev_num)
1099 	{
1100 		*lev_num = (short)levels.size();
1101 	}
1102 
1103 	levels.push_back(actual_point);
1104 
1105 	return lump;
1106 }
1107 
1108 
InsertPoint(short index)1109 void Wad_file::InsertPoint(short index)
1110 {
1111 	// this is validated on usage
1112 	insert_point = index;
1113 }
1114 
1115 
HighWaterMark()1116 int Wad_file::HighWaterMark()
1117 {
1118 	int offset = (int)sizeof(raw_wad_header_t);
1119 
1120 	for (short k = 0 ; k < NumLumps() ; k++)
1121 	{
1122 		Lump_c *lump = directory[k];
1123 
1124 		// ignore zero-length lumps (their offset could be anything)
1125 		if (lump->Length() <= 0)
1126 			continue;
1127 
1128 		int l_end = lump->l_start + lump->l_length;
1129 
1130 		l_end = ((l_end + 3) / 4) * 4;
1131 
1132 		if (offset < l_end)
1133 			offset = l_end;
1134 	}
1135 
1136 	return offset;
1137 }
1138 
1139 
FindFreeSpace(int length)1140 int Wad_file::FindFreeSpace(int length)
1141 {
1142 	length = ((length + 3) / 4) * 4;
1143 
1144 	// collect non-zero length lumps and sort by their offset
1145 	std::vector<Lump_c *> sorted_dir;
1146 
1147 	for (short k = 0 ; k < NumLumps() ; k++)
1148 	{
1149 		Lump_c *lump = directory[k];
1150 
1151 		if (lump->Length() > 0)
1152 			sorted_dir.push_back(lump);
1153 	}
1154 
1155 	std::sort(sorted_dir.begin(), sorted_dir.end(), Lump_c::offset_CMP_pred());
1156 
1157 
1158 	int offset = (int)sizeof(raw_wad_header_t);
1159 
1160 	for (unsigned int k = 0 ; k < sorted_dir.size() ; k++)
1161 	{
1162 		Lump_c *lump = sorted_dir[k];
1163 
1164 		int l_start = lump->l_start;
1165 		int l_end   = lump->l_start + lump->l_length;
1166 
1167 		l_end = ((l_end + 3) / 4) * 4;
1168 
1169 		if (l_end <= offset)
1170 			continue;
1171 
1172 		if (l_start >= offset + length)
1173 			continue;
1174 
1175 		// the lump overlapped the current gap, so bump offset
1176 
1177 		offset = l_end;
1178 	}
1179 
1180 	return offset;
1181 }
1182 
1183 
PositionForWrite(int max_size)1184 int Wad_file::PositionForWrite(int max_size)
1185 {
1186 	int want_pos;
1187 
1188 	if (max_size <= 0)
1189 		want_pos = HighWaterMark();
1190 	else
1191 		want_pos = FindFreeSpace(max_size);
1192 
1193 	// determine if position is past end of file
1194 	// (difference should only be a few bytes)
1195 	//
1196 	// Note: doing this for every new lump may be a little expensive,
1197 	//       but trying to optimise it away will just make the code
1198 	//       needlessly complex and hard to follow.
1199 
1200 	if (fseek(fp, 0, SEEK_END) < 0)
1201 		FatalError("Error seeking to new write position.\n");
1202 
1203 	total_size = (int)ftell(fp);
1204 
1205 	if (total_size < 0)
1206 		FatalError("Error seeking to new write position.\n");
1207 
1208 	if (want_pos > total_size)
1209 	{
1210 		if (want_pos >= total_size + 8)
1211 			FatalError("Internal Error: lump positions are beyond end of file\n(%d > %d)\n",
1212 				want_pos, total_size);
1213 
1214 		WritePadding(want_pos - total_size);
1215 	}
1216 	else if (want_pos == total_size)
1217 	{
1218 		/* ready to write */
1219 	}
1220 	else
1221 	{
1222 		if (fseek(fp, want_pos, SEEK_SET) < 0)
1223 			FatalError("Error seeking to new write position.\n");
1224 	}
1225 
1226 	DebugPrintf("POSITION FOR WRITE: %d  (total_size %d)\n", want_pos, total_size);
1227 
1228 	return want_pos;
1229 }
1230 
1231 
FinishLump(int final_size)1232 bool Wad_file::FinishLump(int final_size)
1233 {
1234 	fflush(fp);
1235 
1236 	// sanity check
1237 	if (begun_max_size >= 0)
1238 		if (final_size > begun_max_size)
1239 			BugError("Internal Error: wrote too much in lump (%d > %d)\n",
1240 					 final_size, begun_max_size);
1241 
1242 	int pos = (int)ftell(fp);
1243 
1244 	if (pos & 3)
1245 	{
1246 		WritePadding(4 - (pos & 3));
1247 	}
1248 
1249 	fflush(fp);
1250 	return true;
1251 }
1252 
1253 
WritePadding(int count)1254 int Wad_file::WritePadding(int count)
1255 {
1256 	static byte zeros[8] = { 0,0,0,0,0,0,0,0 };
1257 
1258 	SYS_ASSERT(1 <= count && count <= 8);
1259 
1260 	fwrite(zeros, count, 1, fp);
1261 
1262 	return count;
1263 }
1264 
1265 
1266 //
1267 // IDEA : Truncate file to "total_size" after writing the directory.
1268 //
1269 //        On Linux / MacOSX, this can be done as follows:
1270 //                 - fflush(fp)   -- ensure STDIO has empty buffers
1271 //                 - ftruncate(fileno(fp), total_size);
1272 //                 - freopen(fp)
1273 //
1274 //        On Windows:
1275 //                 - instead of ftruncate, use _chsize() or _chsize_s()
1276 //                   [ investigate what the difference is.... ]
1277 //
1278 
1279 
WriteDirectory()1280 void Wad_file::WriteDirectory()
1281 {
1282 	dir_start = PositionForWrite();
1283 	dir_count = NumLumps();
1284 
1285 	DebugPrintf("WriteDirectory...\n");
1286 	DebugPrintf("dir_start:%d  dir_count:%d\n", dir_start, dir_count);
1287 
1288 	crc32_c checksum;
1289 
1290 	for (short k = 0 ; k < dir_count ; k++)
1291 	{
1292 		Lump_c *lump = directory[k];
1293 		SYS_ASSERT(lump);
1294 
1295 		raw_wad_entry_t entry;
1296 
1297 		lump->MakeEntry(&entry);
1298 
1299 		// update the CRC
1300 		checksum.AddBlock((u8_t *) &entry, sizeof(entry));
1301 
1302 		if (fwrite(&entry, sizeof(entry), 1, fp) != 1)
1303 			FatalError("Error writing WAD directory.\n");
1304 	}
1305 
1306 	dir_crc = checksum.raw;
1307 	DebugPrintf("dir_crc: %08x\n", dir_crc);
1308 
1309 	fflush(fp);
1310 
1311 	total_size = (int)ftell(fp);
1312 	DebugPrintf("total_size: %d\n", total_size);
1313 
1314 	if (total_size < 0)
1315 		FatalError("Error determining WAD size.\n");
1316 
1317 	// update header at start of file
1318 
1319 	rewind(fp);
1320 
1321 	raw_wad_header_t header;
1322 
1323 	memcpy(header.ident, (kind == 'I') ? "IWAD" : "PWAD", 4);
1324 
1325 	header.dir_start   = LE_U32(dir_start);
1326 	header.num_entries = LE_U32(dir_count);
1327 
1328 	if (fwrite(&header, sizeof(header), 1, fp) != 1)
1329 		FatalError("Error writing WAD header.\n");
1330 
1331 	fflush(fp);
1332 }
1333 
1334 
Backup(const char * new_filename)1335 bool Wad_file::Backup(const char *new_filename)
1336 {
1337 	fflush(fp);
1338 
1339 	return FileCopy(PathName(), new_filename);
1340 }
1341 
1342 
1343 //------------------------------------------------------------------------
1344 //  GLOBAL API
1345 //------------------------------------------------------------------------
1346 
1347 
W_FindLump(const char * name)1348 Lump_c * W_FindLump(const char *name)
1349 {
1350 	for (short i = (int)master_dir.size()-1 ; i >= 0 ; i--)
1351 	{
1352 		Lump_c *L = master_dir[i]->FindLump(name);
1353 		if (L)
1354 			return L;
1355 	}
1356 
1357 	return NULL;  // not found
1358 }
1359 
1360 
W_FindSpriteLump(const char * name)1361 Lump_c * W_FindSpriteLump(const char *name)
1362 {
1363 	for (short i = (int)master_dir.size()-1 ; i >= 0 ; i--)
1364 	{
1365 		Lump_c *L = master_dir[i]->FindLumpInNamespace(name, 'S');
1366 		if (L)
1367 			return L;
1368 	}
1369 
1370 	return NULL;  // not found
1371 }
1372 
1373 
W_FindPatchLump(const char * name)1374 Lump_c * W_FindPatchLump(const char *name)
1375 {
1376 	for (short i = (int)master_dir.size()-1 ; i >= 0 ; i--)
1377 	{
1378 		Lump_c *L = master_dir[i]->FindLumpInNamespace(name, 'P');
1379 		if (L)
1380 			return L;
1381 	}
1382 
1383 	// Fallback: try free-standing lumps
1384 	Lump_c *L = W_FindLump(name);
1385 
1386 	// TODO: verify lump is OK (size etc)
1387 	if (L)
1388 		return L;
1389 
1390 	return NULL;  // not found
1391 }
1392 
1393 
W_LoadLumpData(Lump_c * lump,byte ** buf_ptr)1394 int W_LoadLumpData(Lump_c *lump, byte ** buf_ptr)
1395 {
1396 	// include an extra byte, used to NUL-terminate a text buffer
1397 	*buf_ptr = new byte[lump->Length() + 1];
1398 
1399 	if (lump->Length() > 0)
1400 	{
1401 		if (! lump->Seek() ||
1402 			! lump->Read(*buf_ptr, lump->Length()))
1403 			FatalError("W_LoadLumpData: read error loading lump.\n");
1404 	}
1405 
1406 	(*buf_ptr)[lump->Length()] = 0;
1407 
1408 	return lump->Length();
1409 }
1410 
1411 
W_FreeLumpData(byte ** buf_ptr)1412 void W_FreeLumpData(byte ** buf_ptr)
1413 {
1414 	if (*buf_ptr)
1415 	{
1416 		delete[] *buf_ptr;
1417 		*buf_ptr = NULL;
1418 	}
1419 }
1420 
1421 
1422 //------------------------------------------------------------------------
1423 
MasterDir_Add(Wad_file * wad)1424 void MasterDir_Add(Wad_file *wad)
1425 {
1426 	DebugPrintf("MasterDir: adding '%s'\n", wad->PathName());
1427 
1428 	master_dir.push_back(wad);
1429 }
1430 
1431 
MasterDir_Remove(Wad_file * wad)1432 void MasterDir_Remove(Wad_file *wad)
1433 {
1434 	DebugPrintf("MasterDir: removing '%s'\n", wad->PathName());
1435 
1436 	std::vector<Wad_file *>::iterator ENDP;
1437 
1438 	ENDP = std::remove(master_dir.begin(), master_dir.end(), wad);
1439 
1440 	master_dir.erase(ENDP, master_dir.end());
1441 }
1442 
1443 
MasterDir_CloseAll()1444 void MasterDir_CloseAll()
1445 {
1446 	while (master_dir.size() > 0)
1447 	{
1448 		Wad_file *wad = master_dir.back();
1449 
1450 		master_dir.pop_back();
1451 
1452 		delete wad;
1453 	}
1454 }
1455 
1456 
W_FilenameAbsCompare(const char * A,const char * B)1457 int W_FilenameAbsCompare(const char *A, const char *B)
1458 {
1459 	static char A_buffer[FL_PATH_MAX];
1460 	static char B_buffer[FL_PATH_MAX];
1461 
1462 	fl_filename_absolute(A_buffer, sizeof(A_buffer), A);
1463 	fl_filename_absolute(B_buffer, sizeof(B_buffer), B);
1464 
1465 	return y_stricmp(A_buffer, B_buffer);
1466 }
1467 
1468 
W_StoreString(char * buf,const char * str,size_t buflen)1469 void W_StoreString(char *buf, const char *str, size_t buflen)
1470 {
1471 	memset(buf, 0, buflen);
1472 
1473 	for (size_t i = 0 ; i < buflen && str[i] ; i++)
1474 		buf[i] = str[i];
1475 }
1476 
1477 
MasterDir_HaveFilename(const char * chk_path)1478 bool MasterDir_HaveFilename(const char *chk_path)
1479 {
1480 	for (unsigned int k = 0 ; k < master_dir.size() ; k++)
1481 	{
1482 		const char *wad_path = master_dir[k]->PathName();
1483 
1484 		if (W_FilenameAbsCompare(wad_path, chk_path) == 0)
1485 			return true;
1486 	}
1487 
1488 	return false;
1489 }
1490 
1491 
1492 //--- editor settings ---
1493 // vi:ts=4:sw=4:noexpandtab
1494