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