]> Shamusworld >> Repos - guemap/blob - src/file.cpp
Fix room adding to work properly, misc. small changes.
[guemap] / src / file.cpp
1 //
2 // GUEmap
3 //
4 // (C) 1997-2007 Christopher J. Madsen
5 // (C) 2019 James Hammons
6 //
7 // GUEmap is licensed under either version 2 of the GPL, or (at your option)
8 // any later version.  See LICENSE file for details.
9 //
10 // Implementation of the FileReader class
11 //
12 // N.B.: These functions are now endian-safe (before v3, they were whatever
13 //       Microsoft's serializer made them); the native format is Little Endian
14 //       and can be properly read and written regardless of the underlying
15 //       architecture.
16 //
17
18 #include "file.h"
19
20 #include "globals.h"
21 #include "mapdoc.h"
22
23 static const uint8_t fileHeader[8] = { 0xC7, 0x55, 0xC5, 0x6D, 0xE1, 0x70, 0x83, 0x0D };
24
25 //
26 // Class FileReader:
27 //--------------------------------------------------------------------
28 // Member Variables:
29 //   ar:  The CArchive we're reading from
30 //   rcCache:
31 //     A cached record code (from peekNextRecord)
32 //      -1 if cache is empty
33 //       0 if at end of file
34 //
35 //--------------------------------------------------------------------
36 // Constructor:
37 //
38 // Input:
39 //   archive:  The CArchive to read from
40 //
41 FileReader::FileReader(FILE * archive): ar(archive), rcCache(-1)
42 {
43 }
44
45 uint8_t FileReader::byte()
46 {
47         return fgetc(ar);
48 }
49
50 uint16_t FileReader::word()
51 {
52         uint16_t b1 = fgetc(ar);
53         uint16_t b2 = fgetc(ar);
54
55         return (b2 << 8) | b1;
56 }
57
58 uint32_t FileReader::dword()
59 {
60         uint32_t dword = 0;
61
62         for(int i=0; i<4; i++)
63                 dword = (dword << 8) | fgetc(ar);
64
65         return dword;
66 }
67
68 void FileReader::str(string & s)
69 {
70         StrIdx len;
71         uint8_t lenB = byte();
72
73         if (lenB != 0xFF)
74                 len = lenB;
75         else
76         {
77                 uint16_t lenW = word();
78
79                 if (lenW != 0xFFFF)
80                         len = lenW;
81                 else
82                 {
83                         uint32_t lenDW = dword();
84                         len = lenDW;
85                 }
86         }
87
88         if (len)
89         {
90                 char buf[len + 1];
91                 fread(buf, 1, len, ar);
92                 buf[len] = 0;
93                 s = buf;
94         }
95         else
96                 s.erase();
97 }
98
99 bool FileReader::boolean()
100 {
101         return bool(byte());
102 }
103
104 //
105 // Skip to the beginning of a record:
106 //
107 // Throws a badIndex (corrupt file) exception if the stream is not at
108 // the beginning of a record.  If the record is not of the specified
109 // type, it is skipped and the process repeats until the specified
110 // type of record is found.  If EOF is encountered while searching, it
111 // throws endOfFile.
112 //
113 // If no errors occur, the record header is removed from the stream.
114 //
115 // Input:
116 //   type:  The record type to search for
117 //
118 void FileReader::findRecord(uint8_t type)
119 {
120         uint8_t rc, c;
121
122         // Repeat until error or success
123         for(;;)
124         {
125                 // Make sure we are at the beginning of a record:
126                 if (rcCache > 0)
127                 {
128                         rc = rcCache;
129                         rcCache = -1;
130                 }
131                 else
132                 {
133                         if ((rcCache == 0) || (byte() != fcStartRecord))
134                         {
135                                 corrupt:
136 //                              AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
137                                 return;
138                         }
139
140                         rc = byte();
141                 }
142
143                 // See if we found the right kind of record:
144                 if (rc == type)
145                         return;
146
147                 // Wrong record type, so skip fields until we find the end of the record
148                 while ((c = byte()) != fcEndRecord)
149                 {
150                         skipField(c);
151                 }
152
153                 if (byte() != rc)
154                         // The ending record type does not match the beginning type
155                         goto corrupt;
156         }
157 }
158
159 //
160 // Find out what type the next record is:
161 //
162 // The stream must be at the beginning of a record or at end of file.
163 //
164 // Returns:
165 //   The type of the record about to be read
166 //   0 if stream is at end of file (WITHOUT throwing endOfFile)
167 //
168 uint8_t FileReader::peekNextRecord()
169 {
170         if (rcCache >= 0)
171                 return rcCache;
172
173         uint8_t b;
174
175 #if 0
176         try
177         {
178                 ar >> b;
179         }
180         catch (CArchiveException * e)
181         {
182                 if (e->m_cause == CArchiveException::endOfFile)
183                 {
184                         e->Delete();
185                         return (rcCache = 0);     // Signal end-of-file
186                 }
187
188                 throw;                      // Re-throw any other exception
189         }
190 #else
191         int c = fgetc(ar);
192
193         if (c == EOF)
194         {
195                 return (rcCache = 0);
196         }
197
198         b = (uint8_t)c;
199 #endif
200
201         if (b != fcStartRecord)
202 //              AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
203                 return 0xFF;//???
204
205         b = fgetc(ar);
206
207         rcCache = b;
208         return b;
209 }
210
211 //
212 // Skip an unrecognized field:
213 //
214 // Skips over an unknown or unwanted field.  If this is an extended field code,
215 // you MUST read the extended type info before calling skipField().
216 //
217 // Input:
218 //   fieldCode:  The field code for this field.  For an extended field, this is
219 //               the fcExtended code (with size information), not the extended
220 //               ID byte.
221 //
222 void FileReader::skipField(uint8_t fieldCode)
223 {
224         // Get the field size (lower 3 bits of code)
225         uint32_t size = fieldCode & fcSizeMask;
226
227         switch (size)
228         {
229         case 0: // String
230                 size = byte();
231
232                 if (size == 0xFF)
233                 {
234                         size = word();
235
236                         if (size == 0xFFFF)
237                                 size = dword();
238                 }
239                 break;
240         case 1:                      // 1-4 byte fields
241         case 2:
242         case 3:
243         case 4:
244                 break;
245         case 5:
246                 // 8 byte field
247                 size = 8;
248                 break;
249         case 6:
250                 // Variable length field of size byte
251                 size = byte();
252                 break;
253         case 7:
254                 // Variable length field of size word
255                 size = word();
256                 break;
257         }
258
259         while (size-- > 0)
260                 fgetc(ar);
261 }
262
263 void WriteByte(FILE * fp, uint8_t b)
264 {
265         fputc(b, fp);
266 }
267
268 void WriteWord(FILE * fp, uint16_t w)
269 {
270         fputc(w & 0xFF, fp);
271         fputc(w >> 8, fp);
272 }
273
274 void WriteDWord(FILE * fp, uint32_t dw)
275 {
276         fputc((dw >> 0) & 0xFF, fp);
277         fputc((dw >> 8) & 0xFF, fp);
278         fputc((dw >> 16) & 0xFF, fp);
279         fputc((dw >> 24) & 0xFF, fp);
280 }
281
282 void WriteBool(FILE * fp, bool b)
283 {
284         fputc((b ? 1 : 0), fp);
285 }
286
287 void WriteString(FILE * fp, std::string s)
288 {
289         const int len = s.length();
290
291         if (len < 0xFF)
292                 WriteByte(fp, len);
293         else
294         {
295                 WriteByte(fp, 0xFF);
296
297                 if (len < 0xFFFF)
298                         WriteWord(fp, len);
299                 else
300                 {
301                         WriteWord(fp, 0xFFFF);
302                         WriteDWord(fp, len);
303                 }
304         }
305
306         if (len)
307                 fwrite(s.c_str(), 1, len, fp);
308 }
309
310 //
311 // Current GUEmap file version is 5; can read 2 thru 4 too
312 //
313 bool ReadFile(MapDoc * doc, const char * name)
314 {
315         FILE * ar = fopen(name, "r");
316
317         if (!ar)
318                 return false;
319
320         uint8_t buf[sizeof(fileHeader)];
321
322         if ((fread(buf, 1, sizeof(buf), ar) != sizeof(buf))
323                 || memcmp(fileHeader, buf, sizeof(buf)))
324         {
325 //                      AfxThrowArchiveException(CArchiveException::badClass, ar.GetFile()->GetFilePath());
326                 printf("ReadFile: Bad header\n");
327                 return false;
328         }
329
330         FileReader fr(ar);
331
332         uint16_t version = fr.word();
333 printf("ReadFile: Version # is %i\n", version);
334
335         if ((version < 2) || (version > 5))
336         {
337 //                      AfxThrowArchiveException(CArchiveException::badSchema, ar.GetFile()->GetFilePath());
338                 printf("ReadFile: Bad version number (%u)\n", version);
339                 return false;
340         }
341
342         FileReader read(ar);
343         uint16_t edgeCount, roomCount;
344         uint8_t pageCount = 0;
345         uint8_t b;
346
347         // Get version number (??? Again ???)
348         /*uint16_t version2 =*/ read.word();
349
350         // Read map information
351         read.findRecord(recMap);
352
353         while ((b = read.byte()) != fcEndRecord)
354         {
355                 switch (b)
356                 {
357                 case fcMapName:
358                         read.str(doc->name);
359                         break;
360                 case fcMapNote:
361                         read.str(doc->note);
362                         break;
363                 case fcMapNumEdges:
364                         edgeCount = read.word();
365                         break;
366                 case fcMapNumPages:
367                         pageCount = read.byte();
368                         break;
369                 case fcMapNumRooms:
370                         roomCount = read.word();
371                         break;
372                 default:
373                         read.skipField(b);
374                         break;
375                 }
376         }
377 //printf("Number of edges/rooms/pages: %u/%u/%u\n", edgeCount, roomCount, pageCount);
378
379         if (read.byte() != recMap)
380         {
381                 corruptFile:
382 //                      AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
383                 printf("ReadFile: Corrupted file\n");
384                 return false;
385         }
386
387         // Read rooms
388         doc->room.resize(roomCount);
389
390         for(RoomItr r=doc->room.getVector().begin(); r!=doc->room.getVector().end(); r++)
391         {
392                 read.findRecord(recRoom);
393                 *r = new MapRoom();
394                 (**r).flags = rfBorder;
395
396                 while ((b = read.byte()) != fcEndRecord)
397                 {
398                         switch (b)
399                         {
400                         case fcRoomFlags: (**r).flags = read.byte(); break;
401                         case fcRoomName:  read.str((**r).name);      break;
402                         case fcRoomNote:  read.str((**r).note);      break;
403                         case fcRoomPosition:
404                         (**r).pos.rx() = read.word() * gridX;
405                         (**r).pos.ry() = read.word() * gridY;
406                         break;
407                         default: read.skipField(b); break;
408                         }
409                 }
410
411                 // Adjust for wonky coordinates for old map versions (2-4)
412                 if (version < 5)
413                         (**r).pos.ry() -= gridY * ((**r).flags & rfCorner ? 1 : 3);
414
415                 QRect rect = (**r).getRect();
416                 uint8_t tmp = read.byte();
417
418                 if ((tmp != recRoom) || (rect.right() > doc->docSize.width()) || (rect.top() > doc->docSize.height()))
419                 {
420                         printf("\ntmp=%u (should be 2), rect.right=%i, docSize.width=%i, rect.bottom=%i, rect.top=%i, docSize.height=%i\n", tmp, rect.right(), doc->docSize.width(), rect.bottom(), rect.top(), doc->docSize.height());
421                         goto corruptFile;
422                 }
423         }
424
425         // Read edges
426         doc->edge.resize(edgeCount);
427
428         for(EdgeItr e=doc->edge.begin(); e!=doc->edge.end(); e++)
429         {
430                 read.findRecord(recEdge);
431                 memset(&(*e), 0, sizeof(*e));
432
433                 while ((b = read.byte()) != fcEndRecord)
434                 {
435                         switch (b)
436                         {
437                         case fcEdgeEnd1:
438                                 e->end1  = RoomCorner(read.byte());
439                                 e->room1 = read.byte();
440                                 break;
441                         case fcEdgeEnd1L:
442                                 e->end1  = RoomCorner(read.byte());
443                                 e->room1 = read.word();
444                                 break;
445                         case fcEdgeEnd2:
446                                 e->end2  = RoomCorner(read.byte());
447                                 e->room2 = read.byte();
448                                 break;
449                         case fcEdgeEnd2L:
450                                 e->end2  = RoomCorner(read.byte());
451                                 e->room2 = read.word();
452                                 break;
453                         case fcEdgeType:
454                                 e->type1 = read.byte();
455                                 e->type2 = read.byte();
456                                 break;
457                         default: read.skipField(b);
458                                 break;
459                         }
460                 }
461
462                 if ((read.byte() != recEdge) || (e->room1 >= roomCount)
463                         || (e->end1 < 0) || (e->end1 >= rcNumCorners)
464                         || ((e->type1 & etDirection) > etOut) || (!(e->type1 & etNoRoom2)
465                         && ((e->room2 >= roomCount) || (e->room2 == e->room1)
466                         || (e->end2 < 0) || (e->end2 >= rcNumCorners)
467                         || ((e->type2 & etDirection) > etOut))))
468                         goto corruptFile;
469         }
470
471         // Read pages
472         if (pageCount)
473         {
474                 doc->page.resize(pageCount);
475
476                 for(PageItr p=doc->page.begin(); p!=doc->page.end(); p++)
477                 {
478                         read.findRecord(recPage);
479
480                         while ((b = read.byte()) != fcEndRecord)
481                         {
482                                 switch (b)
483                                 {
484                                 case fcPagePosition:
485                                         p->pos.rx() = read.word() * gridX;
486                                         p->pos.ry() = read.word() * gridY;
487                                         break;
488                                 default:
489                                         read.skipField(b);
490                                         break;
491                                 }
492                         }
493
494                         QRect rect = doc->getPageRect(*p);
495
496                         if ((read.byte() != recPage)
497                                 || (rect.right() > doc->docSize.width())
498                                 || (rect.top() > doc->docSize.height()))
499                                 goto corruptFile;
500                 }
501         }
502
503         doc->needBackup = true;
504         fclose(ar);
505
506         return true;
507 }
508
509 //
510 // Write a GUEmap v5 file
511 //
512 bool WriteFile(MapDoc * doc, const char * name)
513 {
514         FILE * file = fopen(name, "w");
515
516         if (!file)
517                 return false;
518
519         fwrite(fileHeader, 1, sizeof(fileHeader), file);
520
521         QSize gridSize;
522         doc->getGridSize(gridSize);
523
524         // We only write version 5 now, no turning back  :-P
525         WriteWord(file, 5);
526
527         // Write the map information record
528         WriteWord(file, 4);                             // Version 4 (??? OF WHAT ???)
529         WriteByte(file, fcStartRecord);
530         WriteByte(file, recMap);
531
532         if (!doc->name.empty())
533         {
534                 WriteByte(file, fcMapName);
535                 WriteString(file, doc->name);
536         }
537
538         if (!doc->note.empty())
539         {
540                 WriteByte(file, fcMapNote);
541                 WriteString(file, doc->note);
542         }
543
544         WriteByte(file, fcMapNumRooms);
545         WriteWord(file, doc->room.size());
546         WriteByte(file, fcMapNumEdges);
547         WriteWord(file, doc->edge.size());
548         WriteByte(file, fcMapNumPages);
549         WriteByte(file, doc->page.size());
550         WriteByte(file, fcMapSize);
551         WriteWord(file, gridSize.width());
552         WriteWord(file, gridSize.height());
553         WriteByte(file, fcEndRecord);
554         WriteByte(file, recMap);
555
556         // Write the rooms
557         for(RoomConstItr r=doc->room.getVector().begin(); r!=doc->room.getVector().end(); r++)
558         {
559                 WriteByte(file, fcStartRecord);
560                 WriteByte(file, recRoom);
561                 WriteByte(file, fcRoomPosition);
562                 WriteWord(file, (*r)->pos.x() / gridX);
563                 WriteWord(file, (*r)->pos.y() / gridY);
564
565                 if (!(*r)->name.empty())
566                 {
567                         WriteByte(file, fcRoomName);
568                         WriteString(file, (*r)->name);
569                 }
570
571                 if (!(*r)->note.empty())
572                 {
573                         WriteByte(file, fcRoomNote);
574                         WriteString(file, (*r)->note);
575                 }
576
577                 if ((*r)->flags != rfBorder)
578                 {
579                         WriteByte(file, fcRoomFlags);
580                         WriteByte(file, (*r)->flags);
581                 }
582
583                 WriteByte(file, fcEndRecord);
584                 WriteByte(file, recRoom);
585         }
586
587         // Write the edges
588         for(EdgeConstItr e=doc->edge.begin(); e!=doc->edge.end(); e++)
589         {
590                 WriteByte(file, fcStartRecord);
591                 WriteByte(file, recEdge);
592
593                 if (e->room1 > 0xFF)
594                 {
595                         WriteByte(file, fcEdgeEnd1L);
596                         WriteByte(file, e->end1);
597                         WriteWord(file, e->room1);
598                 }
599                 else
600                 {
601                         WriteByte(file, fcEdgeEnd1);
602                         WriteByte(file, e->end1);
603                         WriteByte(file, e->room1);
604                 }
605
606                 if (e->type1 || e->type2)
607                 {
608                         WriteByte(file, fcEdgeType);
609                         WriteByte(file, e->type1);
610                         WriteByte(file, e->type2);
611                 }
612
613                 if (!(e->type1 & etNoRoom2))
614                 {
615                         if (e->room2 > 0xFF)
616                         {
617                                 WriteByte(file, fcEdgeEnd2L);
618                                 WriteByte(file, e->end2);
619                                 WriteWord(file, e->room2);
620                         }
621                         else
622                         {
623                                 WriteByte(file, fcEdgeEnd2);
624                                 WriteByte(file, e->end2);
625                                 WriteByte(file, e->room2);
626                         }
627                 }
628
629                 WriteByte(file, fcEndRecord);
630                 WriteByte(file, recEdge);
631         }
632
633         // Write the pages
634         for(PageConstItr p=doc->page.begin(); p!=doc->page.end(); p++)
635         {
636                 WriteByte(file, fcStartRecord);
637                 WriteByte(file, recPage);
638                 WriteByte(file, fcPagePosition);
639                 WriteWord(file, p->pos.x() / gridX);
640                 WriteWord(file, p->pos.y() / gridY);
641                 WriteByte(file, fcEndRecord);
642                 WriteByte(file, recPage);
643         }
644
645         fclose(file);
646
647         return true;
648 }