// ----------------------------------------------------------------------------- // Checks if the given data is a valid pod archive // ----------------------------------------------------------------------------- bool PodArchive::isPodArchive(MemChunk& mc) { // Check size for header if (mc.size() < 84) return false; // Read no. of files mc.seek(0, 0); uint32_t num_files; mc.read(&num_files, 4); // Read id char id[80]; mc.read(id, 80); // Check size for directory if (mc.size() < 84 + (num_files * 40)) return false; // Read directory and check offsets FileEntry entry; for (unsigned a = 0; a < num_files; a++) { mc.read(&entry, 40); if (entry.offset + entry.size > mc.size()) return false; } return true; }
// ----------------------------------------------------------------------------- // Checks if the given data is a valid Quake wad2 archive // ----------------------------------------------------------------------------- bool Wad2Archive::isWad2Archive(MemChunk& mc) { // Check size if (mc.size() < 12) return false; // Check for IWAD/PWAD header if (mc[0] != 'W' || mc[1] != 'A' || mc[2] != 'D' || (mc[3] != '2' && mc[3] != '3')) return false; // Get number of lumps and directory offset int32_t num_lumps = 0; int32_t dir_offset = 0; mc.seek(4, SEEK_SET); mc.read(&num_lumps, 4); mc.read(&dir_offset, 4); // Reset MemChunk (just in case) mc.seek(0, SEEK_SET); // Byteswap values for big endian if needed num_lumps = wxINT32_SWAP_ON_BE(num_lumps); dir_offset = wxINT32_SWAP_ON_BE(dir_offset); // Check directory offset is decent if ((unsigned)(dir_offset + (num_lumps * 32)) > mc.size() || dir_offset < 12) return false; // If it's passed to here it's probably a wad2 file return true; }
// ----------------------------------------------------------------------------- // Checks if the given data is a valid Dark Forces lfd archive // ----------------------------------------------------------------------------- bool LfdArchive::isLfdArchive(MemChunk& mc) { // Check size if (mc.size() < 12) return false; // Check magic header if (mc[0] != 'R' || mc[1] != 'M' || mc[2] != 'A' || mc[3] != 'P') return false; // Get offset of first entry uint32_t dir_offset = 0; mc.seek(12, SEEK_SET); mc.read(&dir_offset, 4); dir_offset = wxINT32_SWAP_ON_BE(dir_offset) + 16; if (dir_offset % 16) return false; char type1[5]; char type2[5]; char name1[9]; char name2[9]; uint32_t len1; uint32_t len2; mc.read(type1, 4); type1[4] = 0; mc.read(name1, 8); name1[8] = 0; mc.read(&len1, 4); len1 = wxINT32_SWAP_ON_BE(len1); // Check size if ((unsigned)mc.size() < (dir_offset + 16 + len1)) return false; // Compare mc.seek(dir_offset, SEEK_SET); mc.read(type2, 4); type2[4] = 0; mc.read(name2, 8); name2[8] = 0; mc.read(&len2, 4); len2 = wxINT32_SWAP_ON_BE(len2); if (strcmp(type1, type2) != 0 || strcmp(name1, name2) != 0 || len1 != len2) return false; // If it's passed to here it's probably a lfd file return true; }
// ----------------------------------------------------------------------------- // Checks if the given data is a valid Duke Nukem 3D grp archive // ----------------------------------------------------------------------------- bool GrpArchive::isGrpArchive(MemChunk& mc) { // Check size if (mc.size() < 16) return false; // Get number of lumps uint32_t num_lumps = 0; char ken_magic[13] = ""; mc.seek(0, SEEK_SET); mc.read(ken_magic, 12); // "KenSilverman" mc.read(&num_lumps, 4); // No. of lumps in grp // Byteswap values for big endian if needed num_lumps = wxINT32_SWAP_ON_BE(num_lumps); // Null-terminate the magic header ken_magic[12] = 0; // Check the header if (!(S_CMP(wxString::From8BitData(ken_magic), "KenSilverman"))) return false; // Compute total size uint32_t totalsize = (1 + num_lumps) * 16; uint32_t size = 0; for (uint32_t a = 0; a < num_lumps; ++a) { mc.read(ken_magic, 12); mc.read(&size, 4); totalsize += size; } // Check if total size is correct if (totalsize > mc.size()) return false; // If it's passed to here it's probably a grp file return true; }
// ----------------------------------------------------------------------------- // Checks if the given data is a valid BZip2 archive // ----------------------------------------------------------------------------- bool BZip2Archive::isBZip2Archive(MemChunk& mc) { size_t size = mc.size(); if (size < 14) return false; // Read header uint8_t header[4]; mc.read(header, 4); // Check for BZip2 header (reject BZip1 headers) if (header[0] == 'B' && header[1] == 'Z' && header[2] == 'h' && (header[3] >= '1' && header[3] <= '9')) return true; return false; }
// ----------------------------------------------------------------------------- // Reads bzip2 format data from a MemChunk // Returns true if successful, false otherwise // ----------------------------------------------------------------------------- bool BZip2Archive::open(MemChunk& mc) { size_t size = mc.size(); if (size < 14) return false; // Read header uint8_t header[4]; mc.read(header, 4); // Check for BZip2 header (reject BZip1 headers) if (!(header[0] == 'B' && header[1] == 'Z' && header[2] == 'h' && (header[3] >= '1' && header[3] <= '9'))) return false; // Build name from filename string name = filename(false); wxFileName fn(name); if (!fn.GetExt().CmpNoCase("tbz") || !fn.GetExt().CmpNoCase("tb2") || !fn.GetExt().CmpNoCase("tbz2")) fn.SetExt("tar"); else if (!fn.GetExt().CmpNoCase("bz2")) fn.ClearExt(); name = fn.GetFullName(); // Let's create the entry setMuted(true); auto entry = std::make_shared<ArchiveEntry>(name, size); MemChunk xdata; if (Compression::bzip2Decompress(mc, xdata)) { entry->importMemChunk(xdata); } else { setMuted(false); return false; } rootDir()->addEntry(entry); EntryType::detectEntryType(entry.get()); entry->setState(ArchiveEntry::State::Unmodified); setMuted(false); setModified(false); announce("opened"); // Finish return true; }
void Xz::doParse(const MemChunk& chunk, std::shared_ptr<data::Compress>& data) { unsigned int size = chunk.size(); // check initial size if (size < 6) Except::reportError(size, "xz, magic", "unexpected end of data"); if (chunk.uncompare(Xz::mMagic, 6)) Except::reportError(0, "xz, magic", "invalid magic"); mSrcColorizer.addHighlight(0, 6, QColor(255, 128, 0, 64)); mSrcColorizer.addSeparation(6, 2); // check header size if (size < 12) Except::reportError(size, "xz, header", "unexpected end of data"); mSrcColorizer.addHighlight(6, 6, QColor(255, 0, 0, 64)); mSrcColorizer.addSeparation(8, 1); mSrcColorizer.addSeparation(12, 2); if (chunk[6] || chunk[7] & 0xF0) Except::reportError(6, "xz, stream flags", "invalid stream flags"); unsigned char checkMethod = chunk[7] & 0x0F; if (chunk.getUint32LE(8) != Hasher::getCRC32(chunk.subChunk(6, 2))) Except::reportError(8, "xz, stream flags crc", "invalid crc"); // parse blocks unsigned int processed = 12; std::vector<std::pair<uint64_t, uint64_t> > records; for (;;) { if (processed >= size) Except::reportError(size, "xz, block", "unexpected end of data"); if (chunk[processed] == 0) break; uint64_t unpaddedSize, uncompressedSize; this->parseBlock(chunk, processed, size, checkMethod, unpaddedSize, uncompressedSize); records.push_back(std::make_pair(unpaddedSize, uncompressedSize)); } // parse index unsigned int beginIndex = processed; this->parseIndex(chunk, processed, size, records); unsigned int indexSize = processed - beginIndex; // parse footer if (!Util::checkRange(processed, 12, size)) Except::reportError(size, "xz, stream footer", "unexpected end of data"); mSrcColorizer.addHighlight(processed, 12, QColor(255, 128, 0, 64)); mSrcColorizer.addSeparation(processed + 4, 1); mSrcColorizer.addSeparation(processed + 8, 1); mSrcColorizer.addSeparation(processed + 10, 1); mSrcColorizer.addSeparation(processed + 12, 2); if (chunk.getUint32LE(processed) != Hasher::getCRC32(chunk.subChunk(processed + 4, 6))) Except::reportError(processed, "xz, stream footer", "invalid crc"); unsigned int backwardSize = chunk.getUint32LE(processed + 4); if (indexSize != (backwardSize + 1) << 2) Except::reportError(processed + 4, "xz, stream footer", "backward size does not match index size"); if (chunk.subChunk(6, 2) != chunk.subChunk(processed + 8, 2)) Except::reportError(processed + 8, "xz, stream footer", "stream flags don't match header"); if (chunk.uncompare(Xz::mMagicFooter, 2, processed + 10)) Except::reportError(processed + 10, "xz, stream footer", "invalid magic"); data = std::make_shared<data::Compress>(chunk, mDecompChunk, mSrcColorizer, mDecompColorizer); }
void Xz::parseBlock(const MemChunk& chunk, unsigned int& processed, unsigned int size, unsigned char checkMethod, uint64_t& unpaddedSize, uint64_t& uncompressedSize) { std::vector<Filter> filters; uint64_t compressedSize; bool hasCompressedSize; uint64_t decompressedSize; bool hasDecompressedSize; unsigned int blockHeaderStart = processed; this->parseBlockHeader(chunk, processed, size, filters, compressedSize, hasCompressedSize, decompressedSize, hasDecompressedSize); unsigned int blockStart = processed; MemChunk tmpChunk; for (auto& filter : filters) { switch (filter.mId) { case 0x21: { unsigned int compSize = this->decodeLzma2(chunk, tmpChunk, processed, size, filter); if (hasCompressedSize && compressedSize != compSize) Except::reportError(blockStart, "xz, block", "compressed size field does not match actual size"); break; } default: Except::reportError(filter.mPos, "xz, filter", "unsupported filter"); } } if (hasDecompressedSize && decompressedSize != tmpChunk.size()) Except::reportError(blockStart, "xz, block", "decompressed size field does not match actual size"); uncompressedSize = tmpChunk.size(); mSrcColorizer.addHighlight(blockStart, processed - blockStart, QColor(192, 192, 192, 64)); mDecompChunk.append(tmpChunk); mSrcColorizer.addSeparation(processed, 2); unsigned int padding = this->checkPadding(chunk, processed, size, "xz, block padding"); switch (checkMethod) { case 0x00: // none break; case 0x01: // CRC32 if (!Util::checkRange(processed, 4, size)) Except::reportError(size, "xz, block check", "unexpected end of data"); if (chunk.getUint32LE(processed) != Hasher::getCRC32(tmpChunk)) Except::reportError(processed, "xz, block check", "invalid check"); mSrcColorizer.addHighlight(processed, 4, QColor(0, 255, 0, 64)); processed += 4; mSrcColorizer.addSeparation(processed, 2); break; case 0x04: // CRC64 if (!Util::checkRange(processed, 8, size)) Except::reportError(size, "xz, block check", "unexpected end of data"); if (chunk.getUint64LE(processed) != Hasher::getCRC64(tmpChunk)) Except::reportError(processed, "xz, block check", "invalid check"); mSrcColorizer.addHighlight(processed, 8, QColor(0, 255, 0, 64)); processed += 8; mSrcColorizer.addSeparation(processed, 2); break; case 0x0A: // SHA-256 if (!Util::checkRange(processed, 32, size)) Except::reportError(size, "xz, block check", "unexpected end of data"); if (chunk.subChunk(processed, 32) != Hasher::getSha256(tmpChunk).chunk()) Except::reportError(processed, "xz, block check", "invalid check"); mSrcColorizer.addHighlight(processed, 32, QColor(0, 255, 0, 64)); processed += 32; mSrcColorizer.addSeparation(processed, 2); break; default: Except::reportError(processed, "xz, block check", "unknown check method"); } unpaddedSize = processed - blockHeaderStart - padding; }
// ----------------------------------------------------------------------------- // Reads grp format data from a MemChunk // Returns true if successful, false otherwise // ----------------------------------------------------------------------------- bool GrpArchive::open(MemChunk& mc) { // Check data was given if (!mc.hasData()) return false; // Read grp header uint32_t num_lumps = 0; char ken_magic[13] = ""; mc.seek(0, SEEK_SET); mc.read(ken_magic, 12); // "KenSilverman" mc.read(&num_lumps, 4); // No. of lumps in grp // Byteswap values for big endian if needed num_lumps = wxINT32_SWAP_ON_BE(num_lumps); // Null-terminate the magic header ken_magic[12] = 0; // Check the header if (!(S_CMP(wxString::FromAscii(ken_magic), "KenSilverman"))) { Log::error(S_FMT("GrpArchive::openFile: File %s has invalid header", filename_)); Global::error = "Invalid grp header"; return false; } // Stop announcements (don't want to be announcing modification due to entries being added etc) setMuted(true); // The header takes as much space as a directory entry uint32_t entryoffset = 16 * (1 + num_lumps); // Read the directory UI::setSplashProgressMessage("Reading grp archive data"); for (uint32_t d = 0; d < num_lumps; d++) { // Update splash window progress UI::setSplashProgress(((float)d / (float)num_lumps)); // Read lump info char name[13] = ""; uint32_t offset = entryoffset; uint32_t size = 0; mc.read(name, 12); // Name mc.read(&size, 4); // Size name[12] = '\0'; // Byteswap values for big endian if needed size = wxINT32_SWAP_ON_BE(size); // Increase offset of next entry by this entry's size entryoffset += size; // If the lump data goes past the end of the file, // the grpfile is invalid if (offset + size > mc.size()) { Log::error("GrpArchive::open: grp archive is invalid or corrupt"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Create & setup lump auto nlump = std::make_shared<ArchiveEntry>(wxString::FromAscii(name), size); nlump->setLoaded(false); nlump->exProp("Offset") = (int)offset; nlump->setState(ArchiveEntry::State::Unmodified); // Add to entry list rootDir()->addEntry(nlump); } // Detect all entry types MemChunk edata; UI::setSplashProgressMessage("Detecting entry types"); for (size_t a = 0; a < numEntries(); a++) { // Update splash window progress UI::setSplashProgress((((float)a / (float)num_lumps))); // Get entry auto entry = entryAt(a); // Read entry data if it isn't zero-sized if (entry->size() > 0) { // Read the entry data mc.exportMemChunk(edata, getEntryOffset(entry), entry->size()); 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(ArchiveEntry::State::Unmodified); } // Setup variables setMuted(false); setModified(false); announce("opened"); UI::setSplashProgressMessage(""); return true; }
// ----------------------------------------------------------------------------- // Writes the pod archive to a MemChunk // Returns true if successful, false otherwise // ----------------------------------------------------------------------------- bool PodArchive::write(MemChunk& mc, bool update) { // Get all entries vector<ArchiveEntry*> entries; putEntryTreeAsList(entries); // Process entries int ndirs = 0; uint32_t data_size = 0; for (auto& entry : entries) { if (entry->type() == EntryType::folderType()) ndirs++; else data_size += entry->size(); } // Init MemChunk mc.clear(); mc.reSize(4 + 80 + (entries.size() * 40) + data_size, false); LOG_MESSAGE(5, "MC size %d", mc.size()); // Write no. entries uint32_t n_entries = entries.size() - ndirs; LOG_MESSAGE(5, "n_entries %d", n_entries); mc.write(&n_entries, 4); // Write id LOG_MESSAGE(5, "id %s", id_); mc.write(id_, 80); // Write directory FileEntry fe; fe.offset = 4 + 80 + (n_entries * 40); for (auto& entry : entries) { if (entry->type() == EntryType::folderType()) continue; // Name memset(fe.name, 0, 32); string path = entry->path(true); path.Replace("/", "\\"); path = path.AfterFirst('\\'); // LOG_MESSAGE(2, path); memcpy(fe.name, CHR(path), path.Len()); // Size fe.size = entry->size(); // Write directory entry mc.write(fe.name, 32); mc.write(&fe.size, 4); mc.write(&fe.offset, 4); LOG_MESSAGE( 5, "entry %s: old=%d new=%d size=%d", fe.name, entry->exProp("Offset").intValue(), fe.offset, entry->size()); // Next offset fe.offset += fe.size; } // Write entry data for (auto& entry : entries) if (entry->type() != EntryType::folderType()) mc.write(entry->rawData(), entry->size()); return true; }
// ----------------------------------------------------------------------------- // Reads wad format data from a MemChunk // Returns true if successful, false otherwise // ----------------------------------------------------------------------------- bool Wad2Archive::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[0] != 'W' || wad_type[1] != 'A' || wad_type[2] != 'D' || (wad_type[3] != '2' && wad_type[3] != '3')) { Log::error("Wad2Archive::open: Invalid header"); Global::error = "Invalid wad2 header"; return false; } if (wad_type[3] == '3') wad3_ = true; // Stop announcements (don't want to be announcing modification due to entries being added etc) setMuted(true); // Read the directory mc.seek(dir_offset, SEEK_SET); UI::setSplashProgressMessage("Reading wad archive data"); for (uint32_t d = 0; d < num_lumps; d++) { // Update splash window progress UI::setSplashProgress(((float)d / (float)num_lumps)); // Read lump info Wad2Entry info; mc.read(&info, 32); // Byteswap values for big endian if needed info.offset = wxINT32_SWAP_ON_BE(info.offset); info.size = wxINT32_SWAP_ON_BE(info.size); info.dsize = wxINT32_SWAP_ON_BE(info.dsize); // If the lump data goes past the end of the file, // the wadfile is invalid if ((unsigned)(info.offset + info.dsize) > mc.size()) { Log::error("Wad2Archive::open: Wad2 archive is invalid or corrupt"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Create & setup lump auto nlump = std::make_shared<ArchiveEntry>(wxString::FromAscii(info.name, 16), info.dsize); nlump->setLoaded(false); nlump->exProp("Offset") = (int)info.offset; nlump->exProp("W2Type") = info.type; nlump->exProp("W2Size") = (int)info.size; nlump->exProp("W2Comp") = !!(info.cmprs); nlump->setState(ArchiveEntry::State::Unmodified); // Add to entry list rootDir()->addEntry(nlump); } // Detect all entry types MemChunk edata; UI::setSplashProgressMessage("Detecting entry types"); for (size_t a = 0; a < numEntries(); a++) { // Update splash window progress UI::setSplashProgress((((float)a / (float)num_lumps))); // Get entry auto entry = entryAt(a); // Read entry data if it isn't zero-sized if (entry->size() > 0) { // Read the entry data mc.exportMemChunk(edata, (int)entry->exProp("Offset"), entry->size()); 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(ArchiveEntry::State::Unmodified); } // Detect maps (will detect map entry types) UI::setSplashProgressMessage("Detecting maps"); detectMaps(); // Setup variables setMuted(false); setModified(false); announce("opened"); UI::setSplashProgressMessage(""); return true; }
// ----------------------------------------------------------------------------- // Reads lfd format data from a MemChunk // Returns true if successful, false otherwise // ----------------------------------------------------------------------------- bool LfdArchive::open(MemChunk& mc) { // Check data was given if (!mc.hasData()) return false; // Check size if (mc.size() < 16) return false; // Check magic header if (mc[0] != 'R' || mc[1] != 'M' || mc[2] != 'A' || mc[3] != 'P') return false; // Get directory length uint32_t dir_len = 0; mc.seek(12, SEEK_SET); mc.read(&dir_len, 4); dir_len = wxINT32_SWAP_ON_BE(dir_len); // Check size if ((unsigned)mc.size() < (dir_len) || dir_len % 16) return false; // Guess number of lumps uint32_t num_lumps = dir_len / 16; // Stop announcements (don't want to be announcing modification due to entries being added etc) setMuted(true); // Read each entry UI::setSplashProgressMessage("Reading lfd archive data"); size_t offset = dir_len + 16; size_t size = mc.size(); for (uint32_t d = 0; offset < size; d++) { // Update splash window progress UI::setSplashProgress(((float)d / (float)num_lumps)); // Read lump info uint32_t length = 0; char type[5] = ""; char name[9] = ""; mc.read(type, 4); // Type mc.read(name, 8); // Name mc.read(&length, 4); // Size name[8] = '\0'; type[4] = 0; // Move past the header offset += 16; // Byteswap values for big endian if needed length = wxINT32_SWAP_ON_BE(length); // If the lump data goes past the end of the file, // the gobfile is invalid if (offset + length > size) { LOG_MESSAGE(1, "LfdArchive::open: lfd archive is invalid or corrupt"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Create & setup lump wxFileName fn(name); fn.SetExt(type); auto nlump = std::make_shared<ArchiveEntry>(fn.GetFullName(), length); nlump->setLoaded(false); nlump->exProp("Offset") = (int)offset; nlump->setState(ArchiveEntry::State::Unmodified); // Add to entry list rootDir()->addEntry(nlump); // Move to next entry offset += length; mc.seek(offset, SEEK_SET); } if (num_lumps != numEntries()) LOG_MESSAGE(1, "Warning: computed %i lumps, but actually %i entries", num_lumps, numEntries()); // Detect all entry types MemChunk edata; UI::setSplashProgressMessage("Detecting entry types"); for (size_t a = 0; a < numEntries(); a++) { // Update splash window progress UI::setSplashProgress((((float)a / (float)num_lumps))); // Get entry auto entry = entryAt(a); // Read entry data if it isn't zero-sized if (entry->size() > 0) { // Read the entry data mc.exportMemChunk(edata, getEntryOffset(entry), entry->size()); 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(ArchiveEntry::State::Unmodified); } // Setup variables setMuted(false); setModified(false); announce("opened"); UI::setSplashProgressMessage(""); return true; }