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
23 static const uint8_t fileHeader[8] = { 0xC7, 0x55, 0xC5, 0x6D, 0xE1, 0x70, 0x83, 0x0D };
27 //--------------------------------------------------------------------
29 // ar: The CArchive we're reading from
31 // A cached record code (from peekNextRecord)
32 // -1 if cache is empty
33 // 0 if at end of file
35 //--------------------------------------------------------------------
39 // archive: The CArchive to read from
41 FileReader::FileReader(FILE * archive): ar(archive), rcCache(-1)
45 uint8_t FileReader::byte()
50 uint16_t FileReader::word()
52 uint16_t b1 = fgetc(ar);
53 uint16_t b2 = fgetc(ar);
55 return (b2 << 8) | b1;
58 uint32_t FileReader::dword()
62 for(int i=0; i<4; i++)
63 dword = (dword << 8) | fgetc(ar);
68 void FileReader::str(string & s)
71 uint8_t lenB = byte();
77 uint16_t lenW = word();
83 uint32_t lenDW = dword();
91 fread(buf, 1, len, ar);
99 bool FileReader::boolean()
105 // Skip to the beginning of a record:
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
113 // If no errors occur, the record header is removed from the stream.
116 // type: The record type to search for
118 void FileReader::findRecord(uint8_t type)
122 // Repeat until error or success
125 // Make sure we are at the beginning of a record:
133 if ((rcCache == 0) || (byte() != fcStartRecord))
136 // AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
143 // See if we found the right kind of record:
147 // Wrong record type, so skip fields until we find the end of the record
148 while ((c = byte()) != fcEndRecord)
154 // The ending record type does not match the beginning type
160 // Find out what type the next record is:
162 // The stream must be at the beginning of a record or at end of file.
165 // The type of the record about to be read
166 // 0 if stream is at end of file (WITHOUT throwing endOfFile)
168 uint8_t FileReader::peekNextRecord()
180 catch (CArchiveException * e)
182 if (e->m_cause == CArchiveException::endOfFile)
185 return (rcCache = 0); // Signal end-of-file
188 throw; // Re-throw any other exception
195 return (rcCache = 0);
201 if (b != fcStartRecord)
202 // AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
212 // Skip an unrecognized field:
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().
218 // fieldCode: The field code for this field. For an extended field, this is
219 // the fcExtended code (with size information), not the extended
222 void FileReader::skipField(uint8_t fieldCode)
224 // Get the field size (lower 3 bits of code)
225 uint32_t size = fieldCode & fcSizeMask;
240 case 1: // 1-4 byte fields
250 // Variable length field of size byte
254 // Variable length field of size word
263 void WriteByte(FILE * fp, uint8_t b)
268 void WriteWord(FILE * fp, uint16_t w)
274 void WriteDWord(FILE * fp, uint32_t dw)
276 fputc((dw >> 0) & 0xFF, fp);
277 fputc((dw >> 8) & 0xFF, fp);
278 fputc((dw >> 16) & 0xFF, fp);
279 fputc((dw >> 24) & 0xFF, fp);
282 void WriteBool(FILE * fp, bool b)
284 fputc((b ? 1 : 0), fp);
287 void WriteString(FILE * fp, std::string s)
289 const int len = s.length();
301 WriteWord(fp, 0xFFFF);
307 fwrite(s.c_str(), 1, len, fp);
311 // Current GUEmap file version is 5; can read 2 thru 4 too
313 bool ReadFile(MapDoc * doc, const char * name)
315 FILE * ar = fopen(name, "r");
320 uint8_t buf[sizeof(fileHeader)];
322 if ((fread(buf, 1, sizeof(buf), ar) != sizeof(buf))
323 || memcmp(fileHeader, buf, sizeof(buf)))
325 // AfxThrowArchiveException(CArchiveException::badClass, ar.GetFile()->GetFilePath());
326 printf("ReadFile: Bad header\n");
332 uint16_t version = fr.word();
333 printf("ReadFile: Version # is %i\n", version);
335 if ((version < 2) || (version > 5))
337 // AfxThrowArchiveException(CArchiveException::badSchema, ar.GetFile()->GetFilePath());
338 printf("ReadFile: Bad version number (%u)\n", version);
343 uint16_t edgeCount, roomCount;
344 uint8_t pageCount = 0;
347 // Get version number (??? Again ???)
348 /*uint16_t version2 =*/ read.word();
350 // Read map information
351 read.findRecord(recMap);
353 while ((b = read.byte()) != fcEndRecord)
364 edgeCount = read.word();
367 pageCount = read.byte();
370 roomCount = read.word();
377 //printf("Number of edges/rooms/pages: %u/%u/%u\n", edgeCount, roomCount, pageCount);
379 if (read.byte() != recMap)
382 // AfxThrowArchiveException(CArchiveException::badIndex, ar.GetFile()->GetFilePath());
383 printf("ReadFile: Corrupted file\n");
388 doc->room.resize(roomCount);
390 for(RoomItr r=doc->room.getVector().begin(); r!=doc->room.getVector().end(); r++)
392 read.findRecord(recRoom);
394 (**r).flags = rfBorder;
396 while ((b = read.byte()) != fcEndRecord)
400 case fcRoomFlags: (**r).flags = read.byte(); break;
401 case fcRoomName: read.str((**r).name); break;
402 case fcRoomNote: read.str((**r).note); break;
404 (**r).pos.rx() = read.word() * gridX;
405 (**r).pos.ry() = read.word() * gridY;
407 default: read.skipField(b); break;
411 // Adjust for wonky coordinates for old map versions (2-4)
413 (**r).pos.ry() -= gridY * ((**r).flags & rfCorner ? 1 : 3);
415 QRect rect = (**r).getRect();
416 uint8_t tmp = read.byte();
418 if ((tmp != recRoom) || (rect.right() > doc->docSize.width()) || (rect.top() > doc->docSize.height()))
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());
426 doc->edge.resize(edgeCount);
428 for(EdgeItr e=doc->edge.begin(); e!=doc->edge.end(); e++)
430 read.findRecord(recEdge);
431 memset(&(*e), 0, sizeof(*e));
433 while ((b = read.byte()) != fcEndRecord)
438 e->end1 = RoomCorner(read.byte());
439 e->room1 = read.byte();
442 e->end1 = RoomCorner(read.byte());
443 e->room1 = read.word();
446 e->end2 = RoomCorner(read.byte());
447 e->room2 = read.byte();
450 e->end2 = RoomCorner(read.byte());
451 e->room2 = read.word();
454 e->type1 = read.byte();
455 e->type2 = read.byte();
457 default: read.skipField(b);
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))))
474 doc->page.resize(pageCount);
476 for(PageItr p=doc->page.begin(); p!=doc->page.end(); p++)
478 read.findRecord(recPage);
480 while ((b = read.byte()) != fcEndRecord)
485 p->pos.rx() = read.word() * gridX;
486 p->pos.ry() = read.word() * gridY;
494 QRect rect = doc->getPageRect(*p);
496 if ((read.byte() != recPage)
497 || (rect.right() > doc->docSize.width())
498 || (rect.top() > doc->docSize.height()))
503 doc->needBackup = true;
510 // Write a GUEmap v5 file
512 bool WriteFile(MapDoc * doc, const char * name)
514 FILE * file = fopen(name, "w");
519 fwrite(fileHeader, 1, sizeof(fileHeader), file);
522 doc->getGridSize(gridSize);
524 // We only write version 5 now, no turning back :-P
527 // Write the map information record
528 WriteWord(file, 4); // Version 4 (??? OF WHAT ???)
529 WriteByte(file, fcStartRecord);
530 WriteByte(file, recMap);
532 if (!doc->name.empty())
534 WriteByte(file, fcMapName);
535 WriteString(file, doc->name);
538 if (!doc->note.empty())
540 WriteByte(file, fcMapNote);
541 WriteString(file, doc->note);
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);
557 for(RoomConstItr r=doc->room.getVector().begin(); r!=doc->room.getVector().end(); r++)
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);
565 if (!(*r)->name.empty())
567 WriteByte(file, fcRoomName);
568 WriteString(file, (*r)->name);
571 if (!(*r)->note.empty())
573 WriteByte(file, fcRoomNote);
574 WriteString(file, (*r)->note);
577 if ((*r)->flags != rfBorder)
579 WriteByte(file, fcRoomFlags);
580 WriteByte(file, (*r)->flags);
583 WriteByte(file, fcEndRecord);
584 WriteByte(file, recRoom);
588 for(EdgeConstItr e=doc->edge.begin(); e!=doc->edge.end(); e++)
590 WriteByte(file, fcStartRecord);
591 WriteByte(file, recEdge);
595 WriteByte(file, fcEdgeEnd1L);
596 WriteByte(file, e->end1);
597 WriteWord(file, e->room1);
601 WriteByte(file, fcEdgeEnd1);
602 WriteByte(file, e->end1);
603 WriteByte(file, e->room1);
606 if (e->type1 || e->type2)
608 WriteByte(file, fcEdgeType);
609 WriteByte(file, e->type1);
610 WriteByte(file, e->type2);
613 if (!(e->type1 & etNoRoom2))
617 WriteByte(file, fcEdgeEnd2L);
618 WriteByte(file, e->end2);
619 WriteWord(file, e->room2);
623 WriteByte(file, fcEdgeEnd2);
624 WriteByte(file, e->end2);
625 WriteByte(file, e->room2);
629 WriteByte(file, fcEndRecord);
630 WriteByte(file, recEdge);
634 for(PageConstItr p=doc->page.begin(); p!=doc->page.end(); p++)
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);