4 // (C) 1997-2007 Christopher J. Madsen
5 // (C) 2019 James Hammons
7 // GUEmap is licensed under either version 2 of the GPL, or (at your option)
8 // any later version. See LICENSE file for details.
10 // Implementation of the FileReader class
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
24 static const uint8_t fileHeader[8] = { 0xC7, 0x55, 0xC5, 0x6D, 0xE1, 0x70, 0x83, 0x0D };
28 //--------------------------------------------------------------------
30 // ar: The CArchive we're reading from
32 // A cached record code (from peekNextRecord)
33 // -1 if cache is empty
34 // 0 if at end of file
36 //--------------------------------------------------------------------
40 // archive: The CArchive to read from
42 FileReader::FileReader(FILE * archive): ar(archive), rcCache(-1)
47 uint8_t FileReader::byte()
53 uint16_t FileReader::word()
55 uint16_t b1 = fgetc(ar);
56 uint16_t b2 = fgetc(ar);
58 return (b2 << 8) | b1;
62 uint32_t FileReader::dword()
66 for(int i=0; i<4; i++)
67 dword = (dword << 8) | fgetc(ar);
73 void FileReader::str(string & s)
76 uint8_t lenB = byte();
82 uint16_t lenW = word();
88 uint32_t lenDW = dword();
96 fread(buf, 1, len, ar);
105 bool FileReader::boolean()
112 // Skip to the beginning of a record:
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
120 // If no errors occur, the record header is removed from the stream.
123 // type: The record type to search for
125 void FileReader::findRecord(uint8_t type)
129 // Repeat until error or success
132 // Make sure we are at the beginning of a record:
140 if ((rcCache == 0) || (byte() != fcStartRecord))
143 // AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
150 // See if we found the right kind of record:
154 // Wrong record type, so skip fields until we find the end of the record
155 while ((c = byte()) != fcEndRecord)
161 // The ending record type does not match the beginning type
168 // Find out what type the next record is:
170 // The stream must be at the beginning of a record or at end of file.
173 // The type of the record about to be read
174 // 0 if stream is at end of file (WITHOUT throwing endOfFile)
176 uint8_t FileReader::peekNextRecord()
188 catch (CArchiveException * e)
190 if (e->m_cause == CArchiveException::endOfFile)
193 return (rcCache = 0); // Signal end-of-file
196 throw; // Re-throw any other exception
203 return (rcCache = 0);
209 if (b != fcStartRecord)
210 // AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
221 // Skip an unrecognized field:
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().
227 // fieldCode: The field code for this field. For an extended field, this is
228 // the fcExtended code (with size information), not the extended
231 void FileReader::skipField(uint8_t fieldCode)
233 // Get the field size (lower 3 bits of code)
234 uint32_t size = fieldCode & fcSizeMask;
249 case 1: // 1-4 byte fields
259 // Variable length field of size byte
263 // Variable length field of size word
273 void WriteByte(FILE * fp, uint8_t b)
279 void WriteWord(FILE * fp, uint16_t w)
286 void WriteDWord(FILE * fp, uint32_t dw)
288 fputc((dw >> 0) & 0xFF, fp);
289 fputc((dw >> 8) & 0xFF, fp);
290 fputc((dw >> 16) & 0xFF, fp);
291 fputc((dw >> 24) & 0xFF, fp);
295 void WriteBool(FILE * fp, bool b)
297 fputc((b ? 1 : 0), fp);
301 void WriteString(FILE * fp, std::string s)
303 const int len = s.length();
315 WriteWord(fp, 0xFFFF);
321 fwrite(s.c_str(), 1, len, fp);
326 // Current GUEmap file version is 5; can read 2 thru 4 too
328 bool ReadFile(MapDoc * doc, const char * name)
330 FILE * ar = fopen(name, "r");
335 uint8_t buf[sizeof(fileHeader)];
337 if ((fread(buf, 1, sizeof(buf), ar) != sizeof(buf))
338 || memcmp(fileHeader, buf, sizeof(buf)))
340 // AfxThrowArchiveException(CArchiveException::badClass, ar.GetFile()->GetFilePath());
341 printf("ReadFile: Bad header\n");
347 uint16_t version = fr.word();
348 printf("ReadFile: Version # is %i\n", version);
350 if ((version < 2) || (version > 5))
352 // AfxThrowArchiveException(CArchiveException::badSchema, ar.GetFile()->GetFilePath());
353 printf("ReadFile: Bad version number (%u)\n", version);
358 uint16_t edgeCount, roomCount;
359 uint8_t pageCount = 0;
362 // Get version number (??? Again ???)
363 /*uint16_t version2 =*/ read.word();
365 // Read map information
366 read.findRecord(recMap);
368 while ((b = read.byte()) != fcEndRecord)
379 edgeCount = read.word();
382 pageCount = read.byte();
385 roomCount = read.word();
392 //printf("Number of edges/rooms/pages: %u/%u/%u\n", edgeCount, roomCount, pageCount);
394 if (read.byte() != recMap)
397 // AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
398 printf("ReadFile: Corrupted file\n");
403 doc->room.resize(roomCount);
405 for(RoomItr r=doc->room.getVector().begin(); r!=doc->room.getVector().end(); r++)
407 read.findRecord(recRoom);
409 (**r).flags = rfBorder;
411 while ((b = read.byte()) != fcEndRecord)
415 case fcRoomFlags: (**r).flags = read.byte(); break;
416 case fcRoomName: read.str((**r).name); break;
417 case fcRoomNote: read.str((**r).note); break;
419 (**r).pos.rx() = read.word() * gridX;
420 (**r).pos.ry() = read.word() * gridY;
422 default: read.skipField(b); break;
426 // Adjust for wonky coordinates for old map versions (2-4)
428 (**r).pos.ry() -= gridY * ((**r).flags & rfCorner ? 1 : 3);
430 QRect rect = (**r).getRect();
431 uint8_t tmp = read.byte();
433 if ((tmp != recRoom) || (rect.right() > doc->docSize.width()) || (rect.top() > doc->docSize.height()))
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());
441 doc->edge.resize(edgeCount);
443 for(EdgeItr e=doc->edge.begin(); e!=doc->edge.end(); e++)
445 read.findRecord(recEdge);
446 memset(&(*e), 0, sizeof(*e));
448 while ((b = read.byte()) != fcEndRecord)
453 e->end1 = RoomCorner(read.byte());
454 e->room1 = read.byte();
457 e->end1 = RoomCorner(read.byte());
458 e->room1 = read.word();
461 e->end2 = RoomCorner(read.byte());
462 e->room2 = read.byte();
465 e->end2 = RoomCorner(read.byte());
466 e->room2 = read.word();
469 e->type1 = read.byte();
470 e->type2 = read.byte();
472 default: read.skipField(b);
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))))
489 doc->page.resize(pageCount);
491 for(PageItr p=doc->page.begin(); p!=doc->page.end(); p++)
493 read.findRecord(recPage);
495 while ((b = read.byte()) != fcEndRecord)
500 p->pos.rx() = read.word() * gridX;
501 p->pos.ry() = read.word() * gridY;
509 QRect rect = doc->getPageRect(*p);
511 if ((read.byte() != recPage)
512 || (rect.right() > doc->docSize.width())
513 || (rect.top() > doc->docSize.height()))
518 doc->needBackup = true;
526 // Write a GUEmap v5 file
528 bool WriteFile(MapDoc * doc, const char * name)
530 FILE * file = fopen(name, "w");
535 fwrite(fileHeader, 1, sizeof(fileHeader), file);
538 doc->getGridSize(gridSize);
540 // We only write version 5 now, no turning back :-P
543 // Write the map information record
544 WriteWord(file, 4); // Version 4 (??? OF WHAT ???)
545 WriteByte(file, fcStartRecord);
546 WriteByte(file, recMap);
548 if (!doc->name.empty())
550 WriteByte(file, fcMapName);
551 WriteString(file, doc->name);
554 if (!doc->note.empty())
556 WriteByte(file, fcMapNote);
557 WriteString(file, doc->note);
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);
573 for(RoomConstItr r=doc->room.getVector().begin(); r!=doc->room.getVector().end(); r++)
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);
581 if (!(*r)->name.empty())
583 WriteByte(file, fcRoomName);
584 WriteString(file, (*r)->name);
587 if (!(*r)->note.empty())
589 WriteByte(file, fcRoomNote);
590 WriteString(file, (*r)->note);
593 if ((*r)->flags != rfBorder)
595 WriteByte(file, fcRoomFlags);
596 WriteByte(file, (*r)->flags);
599 WriteByte(file, fcEndRecord);
600 WriteByte(file, recRoom);
604 for(EdgeConstItr e=doc->edge.begin(); e!=doc->edge.end(); e++)
606 WriteByte(file, fcStartRecord);
607 WriteByte(file, recEdge);
611 WriteByte(file, fcEdgeEnd1L);
612 WriteByte(file, e->end1);
613 WriteWord(file, e->room1);
617 WriteByte(file, fcEdgeEnd1);
618 WriteByte(file, e->end1);
619 WriteByte(file, e->room1);
622 if (e->type1 || e->type2)
624 WriteByte(file, fcEdgeType);
625 WriteByte(file, e->type1);
626 WriteByte(file, e->type2);
629 if (!(e->type1 & etNoRoom2))
633 WriteByte(file, fcEdgeEnd2L);
634 WriteByte(file, e->end2);
635 WriteWord(file, e->room2);
639 WriteByte(file, fcEdgeEnd2);
640 WriteByte(file, e->end2);
641 WriteByte(file, e->room2);
645 WriteByte(file, fcEndRecord);
646 WriteByte(file, recEdge);
650 for(PageConstItr p=doc->page.begin(); p!=doc->page.end(); p++)
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);