/* ResArchive::readDirectory * Reads a res directory from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool ResArchive::readDirectory(MemChunk& mc, size_t dir_offset, size_t num_lumps, ArchiveTreeNode* parent) { if (!parent) { LOG_MESSAGE(1, "ReadDir: No parent node"); Global::error = "Archive is invalid and/or corrupt"; return false; } mc.seek(dir_offset, SEEK_SET); for (uint32_t d = 0; d < num_lumps; d++) { // Update splash window progress UI::setSplashProgress(((float)d / (float)num_lumps)); // Read lump info char magic[4] = ""; char name[15] = ""; uint32_t dumzero1, dumzero2; uint16_t dumff, dumze; uint8_t flags = 0; uint32_t offset = 0; uint32_t size = 0; mc.read(magic, 4); // ReS\0 mc.read(name, 14); // Name mc.read(&offset, 4); // Offset mc.read(&size, 4); // Size // Check the identifier if (magic[0] != 'R' || magic[1] != 'e' || magic[2] != 'S' || magic[3] != 0) { LOG_MESSAGE(1, "ResArchive::readDir: Entry %s (%i@0x%x) has invalid directory entry", name, size, offset); Global::error = "Archive is invalid and/or corrupt"; return false; } // Byteswap values for big endian if needed offset = wxINT32_SWAP_ON_BE(offset); size = wxINT32_SWAP_ON_BE(size); name[14] = '\0'; mc.read(&dumze, 2); if (dumze) LOG_MESSAGE(1, "Flag guard not null for entry %s", name); mc.read(&flags, 1); if (flags != 1 && flags != 17) LOG_MESSAGE(1, "Unknown flag value for entry %s", name); mc.read(&dumzero1, 4); if (dumzero1) LOG_MESSAGE(1, "Near-end values not set to zero for entry %s", name); mc.read(&dumff, 2); if (dumff != 0xFFFF) LOG_MESSAGE(1, "Dummy set to a non-FF value for entry %s", name); mc.read(&dumzero2, 4); if (dumzero2) LOG_MESSAGE(1, "Trailing values not set to zero for entry %s", name); // If the lump data goes past the end of the file, // the resfile is invalid if (offset + size > mc.getSize()) { LOG_MESSAGE(1, "ResArchive::readDirectory: Res archive is invalid or corrupt, offset overflow"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Create & setup lump ArchiveEntry* nlump = new ArchiveEntry(wxString::FromAscii(name), size); nlump->setLoaded(false); nlump->exProp("Offset") = (int)offset; nlump->setState(0); // Read entry data if it isn't zero-sized if (nlump->getSize() > 0) { // Read the entry data MemChunk edata; mc.exportMemChunk(edata, offset, size); nlump->importMemChunk(edata); } // What if the entry is a directory? size_t d_o, n_l; if (isResArchive(nlump->getMCData(), d_o, n_l)) { ArchiveTreeNode* ndir = createDir(name, parent); if (ndir) { UI::setSplashProgressMessage(S_FMT("Reading res archive data: %s directory", name)); // Save offset to restore it once the recursion is done size_t myoffset = mc.currentPos(); readDirectory(mc, d_o, n_l, ndir); ndir->getDirEntry()->setState(0); // Restore offset and clean out the entry mc.seek(myoffset, SEEK_SET); delete nlump; } else { delete nlump; return false; } // Not a directory, then add to entry list } else { parent->addEntry(nlump); // Detect entry type EntryType::detectEntryType(nlump); // Unload entry data if needed if (!archive_load_data) nlump->unloadData(); // Set entry to unchanged nlump->setState(0); } } return true; }
/* ADatArchive::write * Writes the dat archive to a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool ADatArchive::write(MemChunk& mc, bool update) { // Clear current data mc.clear(); MemChunk directory; MemChunk compressed; // Get archive tree as a list vector<ArchiveEntry*> entries; getEntryTreeAsList(entries); // Write header long dir_offset = wxINT32_SWAP_ON_BE(16); long dir_size = wxINT32_SWAP_ON_BE(0); char pack[4] = { 'A', 'D', 'A', 'T' }; uint32_t version = wxINT32_SWAP_ON_BE(9); mc.seek(0, SEEK_SET); mc.write(pack, 4); mc.write(&dir_offset, 4); mc.write(&dir_size, 4); mc.write(&version, 4); // Write entry data for (unsigned a = 0; a < entries.size(); a++) { // Skip folders if (entries[a]->getType() == EntryType::folderType()) continue; // Create compressed version of the lump MemChunk * entry = NULL; if (Compression::ZlibDeflate(entries[a]->getMCData(), compressed, 9)) { entry = &compressed; } else { entry = &(entries[a]->getMCData()); wxLogMessage("Entry %s couldn't be deflated", CHR(entries[a]->getName())); } // Update entry int offset = mc.currentPos(); if (update) { entries[a]->setState(0); entries[a]->exProp("Offset") = (int)offset; } /////////////////////////////////// // Step 1: Write directory entry // /////////////////////////////////// // Check entry name string name = entries[a]->getPath(true); name.Remove(0, 1); // Remove leading / if (name.Len() > 128) { wxLogMessage("Warning: Entry %s path is too long (> 128 characters), putting it in the root directory", CHR(name)); wxFileName fn(name); name = fn.GetFullName(); if (name.Len() > 128) name.Truncate(128); } // Write entry name char name_data[128]; memset(name_data, 0, 128); memcpy(name_data, CHR(name), name.Length()); directory.write(name_data, 128); // Write entry offset long myoffset = wxINT32_SWAP_ON_BE(offset); directory.write(&myoffset, 4); // Write full entry size long decsize = wxINT32_SWAP_ON_BE(entries[a]->getSize()); directory.write(&decsize, 4); // Write compressed entry size long compsize = wxINT32_SWAP_ON_BE(entry->getSize()); directory.write(&compsize, 4); // Write whatever it is that should be there // TODO: Reverse engineer what exactly it is // and implement something valid for the game. long whatever = 0; directory.write(&whatever, 4); ////////////////////////////// // Step 2: Write entry data // ////////////////////////////// mc.write(entry->getData(), entry->getSize()); } // Write directory dir_offset = wxINT32_SWAP_ON_BE(mc.currentPos()); dir_size = wxINT32_SWAP_ON_BE(directory.getSize()); mc.write(directory.getData(), directory.getSize()); // Update directory offset and size in header mc.seek(4, SEEK_SET); mc.write(&dir_offset, 4); mc.write(&dir_size, 4); // Yay! Finished! return true; }
/* GZipArchive::open * Reads gzip format data from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool GZipArchive::open(MemChunk& mc) { // Minimal metadata size is 18: 10 for header, 8 for footer size_t mds = 18; size_t size = mc.getSize(); if (mds > size) return false; // Read header uint8_t header[4]; mc.read(header, 4); // Check for GZip header; we'll only accept deflated gzip files // and reject any field using unknown flags if ((!(header[0] == GZIP_ID1 && header[1] == GZIP_ID2 && header[2] == GZIP_DEFLATE)) || (header[3] & GZIP_FLG_FUNKN)) return false; bool ftext, fhcrc, fxtra, fname, fcmnt; ftext = (header[3] & GZIP_FLG_FTEXT) ? true : false; fhcrc = (header[3] & GZIP_FLG_FHCRC) ? true : false; fxtra = (header[3] & GZIP_FLG_FXTRA) ? true : false; fname = (header[3] & GZIP_FLG_FNAME) ? true : false; fcmnt = (header[3] & GZIP_FLG_FCMNT) ? true : false; flags = header[3]; mc.read(&mtime, 4); mtime = wxUINT32_SWAP_ON_BE(mtime); mc.read(&xfl, 1); mc.read(&os, 1); // Skip extra fields which may be there if (fxtra) { uint16_t xlen; mc.read(&xlen, 2); xlen = wxUINT16_SWAP_ON_BE(xlen); mds += xlen + 2; if (mds > size) return false; mc.exportMemChunk(xtra, mc.currentPos(), xlen); mc.seek(xlen, SEEK_CUR); } // Skip past name, if any string name; if (fname) { char c; do { mc.read(&c, 1); if (c) name += c; ++mds; } while (c != 0 && size > mds); } else { // Build name from filename name = getFilename(false); wxFileName fn(name); if (!fn.GetExt().CmpNoCase("tgz")) fn.SetExt("tar"); else if (!fn.GetExt().CmpNoCase("gz")) fn.ClearExt(); name = fn.GetFullName(); } // Skip past comment if (fcmnt) { char c; do { mc.read(&c, 1); if (c) comment += c; ++mds; } while (c != 0 && size > mds); wxLogMessage("Archive %s says:\n %s", CHR(getFilename(true)), CHR(comment)); } // Skip past CRC 16 check if (fhcrc) { uint8_t* crcbuffer = new uint8_t[mc.currentPos()]; memcpy(crcbuffer, mc.getData(), mc.currentPos()); uint32_t fullcrc = Misc::crc(crcbuffer, mc.currentPos()); delete[] crcbuffer; uint16_t hcrc; mc.read(&hcrc, 2); hcrc = wxUINT16_SWAP_ON_BE(hcrc); mds += 2; if (hcrc != (fullcrc & 0x0000FFFF)) { wxLogMessage("CRC-16 mismatch for GZip header"); } } // Header is over if (mds > size || mc.currentPos() + 8 > size) return false; // Let's create the entry setMuted(true); ArchiveEntry* entry = new ArchiveEntry(name, size - mds); MemChunk xdata; if (Compression::GZipInflate(mc, xdata)) { entry->importMemChunk(xdata); } else { delete entry; setMuted(false); return false; } getRoot()->addEntry(entry); EntryType::detectEntryType(entry); entry->setState(0); setMuted(false); setModified(false); announce("opened"); // Finish return true; }
/* WadArchive::open * Reads wad format data from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool WadArchive::open(MemChunk& mc) { // Check data was given if (!mc.hasData()) return false; // Read wad header uint32_t num_lumps = 0; uint32_t dir_offset = 0; char wad_type[4] = ""; mc.seek(0, SEEK_SET); mc.read(&wad_type, 4); // Wad type mc.read(&num_lumps, 4); // No. of lumps in wad mc.read(&dir_offset, 4); // Offset to directory // Byteswap values for big endian if needed num_lumps = wxINT32_SWAP_ON_BE(num_lumps); dir_offset = wxINT32_SWAP_ON_BE(dir_offset); // Check the header if (wad_type[1] != 'W' || wad_type[2] != 'A' || wad_type[3] != 'D') { wxLogMessage("WadArchive::openFile: File %s has invalid header", filename); Global::error = "Invalid wad header"; return false; } // Check for iwad if (wad_type[0] == 'I') iwad = true; // Stop announcements (don't want to be announcing modification due to entries being added etc) setMuted(true); vector<uint32_t> offsets; // Read the directory mc.seek(dir_offset, SEEK_SET); theSplashWindow->setProgressMessage("Reading wad archive data"); for (uint32_t d = 0; d < num_lumps; d++) { // Update splash window progress theSplashWindow->setProgress(((float)d / (float)num_lumps)); // Read lump info char name[9] = ""; uint32_t offset = 0; uint32_t size = 0; mc.read(&offset, 4); // Offset mc.read(&size, 4); // Size mc.read(name, 8); // Name name[8] = '\0'; // Byteswap values for big endian if needed offset = wxINT32_SWAP_ON_BE(offset); size = wxINT32_SWAP_ON_BE(size); // Check to catch stupid shit if (size > 0) { if (offset == 0) { LOG_MESSAGE(2, "No."); continue; } if (VECTOR_EXISTS(offsets, offset)) { LOG_MESSAGE(1, "Ignoring entry %d: %s, is a clone of a previous entry", d, name); continue; } offsets.push_back(offset); } // Hack to open Operation: Rheingold WAD files if (size == 0 && offset > mc.getSize()) offset = 0; // Is there a compression/encryption thing going on? bool jaguarencrypt = !!(name[0] & 0x80); // look at high bit name[0] = name[0] & 0x7F; // then strip it away // Look for encryption shenanigans size_t actualsize = size; if (jaguarencrypt) { if (d < num_lumps - 1) { size_t pos = mc.currentPos(); uint32_t nextoffset = 0; for (int i = 0; i + d < num_lumps; ++i) { mc.read(&nextoffset, 4); if (nextoffset != 0) break; mc.seek(12, SEEK_CUR); } nextoffset = wxINT32_SWAP_ON_BE(nextoffset); if (nextoffset == 0) nextoffset = dir_offset; mc.seek(pos, SEEK_SET); actualsize = nextoffset - offset; } else { if (offset > dir_offset) { actualsize = mc.getSize() - offset; } else { actualsize = dir_offset - offset; } } } // If the lump data goes past the end of the file, // the wadfile is invalid if (offset + actualsize > mc.getSize()) { wxLogMessage("WadArchive::open: Wad archive is invalid or corrupt"); Global::error = S_FMT("Archive is invalid and/or corrupt (lump %d: %s data goes past end of file)", d, name); setMuted(false); return false; } // Create & setup lump ArchiveEntry* nlump = new ArchiveEntry(wxString::FromAscii(name), size); nlump->setLoaded(false); nlump->exProp("Offset") = (int)offset; nlump->setState(0); if (jaguarencrypt) { nlump->setEncryption(ENC_JAGUAR); nlump->exProp("FullSize") = (int)size; } // Add to entry list getRoot()->addEntry(nlump); } // Detect namespaces (needs to be done before type detection as some types // rely on being within certain namespaces) updateNamespaces(); // Detect all entry types MemChunk edata; theSplashWindow->setProgressMessage("Detecting entry types"); for (size_t a = 0; a < numEntries(); a++) { // Update splash window progress theSplashWindow->setProgress((((float)a / (float)numEntries()))); // Get entry ArchiveEntry* entry = getEntry(a); // Read entry data if it isn't zero-sized if (entry->getSize() > 0) { // Read the entry data mc.exportMemChunk(edata, getEntryOffset(entry), entry->getSize()); if (entry->isEncrypted()) { if (entry->exProps().propertyExists("FullSize") && (unsigned)(int)(entry->exProp("FullSize")) > entry->getSize()) edata.reSize((int)(entry->exProp("FullSize")), true); if (!JaguarDecode(edata)) wxLogMessage("%i: %s (following %s), did not decode properly", a, entry->getName(), a>0?getEntry(a-1)->getName():"nothing"); } entry->importMemChunk(edata); } // Detect entry type EntryType::detectEntryType(entry); // Unload entry data if needed if (!archive_load_data) entry->unloadData(); // Set entry to unchanged entry->setState(0); } // Identify #included lumps (DECORATE, GLDEFS, etc.) detectIncludes(); // Detect maps (will detect map entry types) theSplashWindow->setProgressMessage("Detecting maps"); detectMaps(); // Setup variables setMuted(false); setModified(false); //if (iwad && iwad_lock) read_only = true; announce("opened"); theSplashWindow->setProgressMessage(""); return true; }
/* GZipArchive::isGZipArchive * Checks if the given data is a valid GZip archive *******************************************************************/ bool GZipArchive::isGZipArchive(MemChunk& mc) { // Minimal metadata size is 18: 10 for header, 8 for footer size_t mds = 18; size_t size = mc.getSize(); if (size < mds) return false; // Read header uint8_t header[4]; mc.read(header, 4); // Check for GZip header; we'll only accept deflated gzip files // and reject any field using unknown flags if (!(header[0] == GZIP_ID1 && header[1] == GZIP_ID2 && header[2] == GZIP_DEFLATE) || (header[3] & GZIP_FLG_FUNKN)) return false; bool ftext, fhcrc, fxtra, fname, fcmnt; ftext = (header[3] & GZIP_FLG_FTEXT) ? true : false; fhcrc = (header[3] & GZIP_FLG_FHCRC) ? true : false; fxtra = (header[3] & GZIP_FLG_FXTRA) ? true : false; fname = (header[3] & GZIP_FLG_FNAME) ? true : false; fcmnt = (header[3] & GZIP_FLG_FCMNT) ? true : false; uint32_t mtime; mc.read(&mtime, 4); uint8_t xfl; mc.read(&xfl, 1); uint8_t os; mc.read(&os, 1); // Skip extra fields which may be there if (fxtra) { uint16_t xlen; mc.read(&xlen, 2); xlen = wxUINT16_SWAP_ON_BE(xlen); mds += xlen + 2; if (mds > size) return false; mc.seek(xlen, SEEK_CUR); } // Skip past name, if any if (fname) { string name; char c; do { mc.read(&c, 1); if (c) name += c; ++mds; } while (c != 0 && size > mds); } // Skip past comment if (fcmnt) { string comment; char c; do { mc.read(&c, 1); if (c) comment += c; ++mds; } while (c != 0 && size > mds); } // Skip past CRC 16 check if (fhcrc) { uint16_t hcrc; mc.read(&hcrc, 2); mds += 2; } // Header is over if (mds > size || mc.currentPos() + 8 > size) return false; // If it's passed to here it's probably a gzip file return true; }