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