/* SImage::getIndexedData * Loads the image as index data into <mc>. Returns false if image is * invalid, true otherwise *******************************************************************/ bool SImage::getIndexedData(MemChunk& mc) { // Check the image is valid if (!isValid()) return false; // Init rgb data mc.reSize(width * height, false); // Cannot do this for trucolor graphics. if (type == RGBA) return false; else if (type == PALMASK) { mc.write(data, width * height); return true; } else if (type == ALPHAMAP) { mc.write(data, width * height); return true; } return false; // Invalid image type }
/* SImage::getRGBAData * Loads the image as RGBA data into <mc>. Returns false if image is * invalid, true otherwise *******************************************************************/ bool SImage::getRGBAData(MemChunk& mc, Palette8bit* pal) { // Check the image is valid if (!isValid()) return false; // Init rgba data mc.reSize(width * height * 4, false); // If data is already in RGBA format just return a copy if (type == RGBA) { mc.importMem(data, width * height * 4); return true; } // Convert paletted else if (type == PALMASK) { // Get palette to use if (has_palette || !pal) pal = &palette; uint8_t rgba[4]; for (int a = 0; a < width * height; a++) { // Get colour rgba_t col = pal->colour(data[a]); // Set alpha if (mask) col.a = mask[a]; else col.a = 255; col.write(rgba); // Write colour to array mc.write(rgba, 4); // Write array to MemChunk } return true; } // Convert if alpha map else if (type == ALPHAMAP) { uint8_t rgba[4]; rgba_t col; for (int a = 0; a < width * height; a++) { // Get pixel as colour (greyscale) col.set(data[a], data[a], data[a], data[a]); col.write(rgba); // Write colour to array mc.write(rgba, 4); // Write array to MemChunk } } return false; // Invalid image type }
// ----------------------------------------------------------------------------- // Writes the wad archive to a MemChunk // Returns true if successful, false otherwise // ----------------------------------------------------------------------------- bool Wad2Archive::write(MemChunk& mc, bool update) { // Determine directory offset & individual lump offsets uint32_t dir_offset = 12; ArchiveEntry* entry = nullptr; for (uint32_t l = 0; l < numEntries(); l++) { entry = entryAt(l); entry->exProp("Offset") = (int)dir_offset; dir_offset += entry->size(); } // Clear/init MemChunk mc.clear(); mc.seek(0, SEEK_SET); mc.reSize(dir_offset + numEntries() * 32); // Setup wad type char wad_type[4] = { 'W', 'A', 'D', '2' }; if (wad3_) wad_type[3] = '3'; // Write the header uint32_t num_lumps = numEntries(); mc.write(wad_type, 4); mc.write(&num_lumps, 4); mc.write(&dir_offset, 4); // Write the lumps for (uint32_t l = 0; l < num_lumps; l++) { entry = entryAt(l); mc.write(entry->rawData(), entry->size()); } // Write the directory for (uint32_t l = 0; l < num_lumps; l++) { entry = entryAt(l); // Setup directory entry Wad2Entry info; memset(info.name, 0, 16); memcpy(info.name, CHR(entry->name()), entry->name().Len()); info.cmprs = (bool)entry->exProp("W2Comp"); info.dsize = entry->size(); info.size = entry->size(); info.offset = (int)entry->exProp("Offset"); info.type = (int)entry->exProp("W2Type"); // Write it mc.write(&info, 32); if (update) entry->setState(ArchiveEntry::State::Unmodified); } return true; }
/* GobArchive::write * Writes the gob archive to a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool GobArchive::write(MemChunk& mc, bool update) { // Determine directory offset & individual lump offsets uint32_t dir_offset = 8; ArchiveEntry* entry = NULL; for (uint32_t l = 0; l < numEntries(); l++) { entry = getEntry(l); setEntryOffset(entry, dir_offset); dir_offset += entry->getSize(); } // Clear/init MemChunk mc.clear(); mc.seek(0, SEEK_SET); mc.reSize(dir_offset + 4 + numEntries() * 21); // Write the header uint32_t num_lumps = wxINT32_SWAP_ON_BE(numEntries()); dir_offset = wxINT32_SWAP_ON_BE(dir_offset); char header[4] = { 'G', 'O', 'B', 0xA }; mc.write(header, 4); mc.write(&dir_offset, 4); // Write the lumps for (uint32_t l = 0; l < numEntries(); l++) { entry = getEntry(l); mc.write(entry->getData(), entry->getSize()); } // Write the directory mc.write(&num_lumps, 4); for (uint32_t l = 0; l < numEntries(); l++) { entry = getEntry(l); char name[13] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; long offset = wxINT32_SWAP_ON_BE(getEntryOffset(entry)); long size = wxINT32_SWAP_ON_BE(entry->getSize()); for (size_t c = 0; c < entry->getName().length() && c < 13; c++) name[c] = entry->getName()[c]; mc.write(&offset, 4); mc.write(&size, 4); mc.write(name, 13); if (update) { entry->setState(0); entry->exProp("Offset") = (int)offset; } } return true; }
/* SImage::getRGBData * Loads the image as RGB data into <mc>. Returns false if image is * invalid, true otherwise *******************************************************************/ bool SImage::getRGBData(MemChunk& mc, Palette8bit* pal) { // Check the image is valid if (!isValid()) return false; // Init rgb data mc.reSize(width * height * 3, false); if (type == RGBA) { // RGBA format, remove alpha information for (int a = 0; a < width * height * 4; a += 4) mc.write(&data[a], 3); return true; } else if (type == PALMASK) { // Paletted, convert to RGB // Get palette to use if (has_palette || !pal) pal = &palette; // Build RGB data uint8_t rgba[4]; for (int a = 0; a < width * height; a ++) { pal->colour(data[a]).write(rgba); mc.write(rgba, 3); } return true; } else if (type == ALPHAMAP) { // Alpha map, convert to RGB uint8_t rgba[4]; rgba_t col; for (int a = 0; a < width * height; a++) { // Get pixel as colour (greyscale) col.set(data[a], data[a], data[a], data[a]); col.write(rgba); // Write colour to array mc.write(rgba, 3); // Write array to MemChunk } } return false; // Invalid image type }
/* HogArchive::write * Writes the hog archive to a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool HogArchive::write(MemChunk& mc, bool update) { // Determine individual lump offsets uint32_t offset = 3; ArchiveEntry* entry = NULL; for (uint32_t l = 0; l < numEntries(); l++) { offset += 17; entry = getEntry(l); setEntryOffset(entry, offset); if (update) { entry->setState(0); entry->exProp("Offset") = (int)offset; } offset += entry->getSize(); } // Clear/init MemChunk mc.clear(); mc.seek(0, SEEK_SET); mc.reSize(offset); // Write the header char header[3] = { 'D', 'H', 'F' }; mc.write(header, 3); // Write the lumps for (uint32_t l = 0; l < numEntries(); l++) { entry = getEntry(l); mc.write(entry->getData(), entry->getSize()); } // Write the directory for (uint32_t l = 0; l < numEntries(); l++) { entry = getEntry(l); char name[13] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; long size = wxINT32_SWAP_ON_BE(entry->getSize()); for (size_t c = 0; c < entry->getName().length() && c < 13; c++) name[c] = entry->getName()[c]; mc.write(name, 13); mc.write(&size, 4); mc.write(entry->getData(), entry->getSize()); } return true; }
/* MemChunk::exportMemChunk * Writes the MemChunk data to another MemChunk, starting from * [start] to [start+size]. If [size] is 0, writes from [start] to * the end of the data *******************************************************************/ bool MemChunk::exportMemChunk(MemChunk& mc, uint32_t start, uint32_t size) { // Check data exists if (!hasData()) return false; // Check parameters if (start >= this->size || start + size > this->size) return false; // Check size if (size == 0) size = this->size - start; // Write data to MemChunk mc.reSize(size, false); return mc.importMem(data+start, size); }
// ----------------------------------------------------------------------------- // Writes the grp archive to a MemChunk // Returns true if successful, false otherwise // ----------------------------------------------------------------------------- bool GrpArchive::write(MemChunk& mc, bool update) { // Clear/init MemChunk mc.clear(); mc.seek(0, SEEK_SET); mc.reSize((1 + numEntries()) * 16); ArchiveEntry* entry; // Write the header uint32_t num_lumps = numEntries(); mc.write("KenSilverman", 12); mc.write(&num_lumps, 4); // Write the directory for (uint32_t l = 0; l < num_lumps; l++) { entry = entryAt(l); char name[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; long size = entry->size(); for (size_t c = 0; c < entry->name().length() && c < 12; c++) name[c] = entry->name()[c]; mc.write(name, 12); mc.write(&size, 4); if (update) { long offset = getEntryOffset(entry); entry->setState(ArchiveEntry::State::Unmodified); entry->exProp("Offset") = (int)offset; } } // Write the lumps for (uint32_t l = 0; l < num_lumps; l++) { entry = entryAt(l); mc.write(entry->rawData(), entry->size()); } return true; }
/* DatArchive::write * Writes the dat archive to a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool DatArchive::write(MemChunk& mc, bool update) { // Only two bytes are used for storing entry amount, // so abort for excessively large files: if (numEntries() > 65535) return false; // Determine directory offset, name offsets & individual lump offsets uint32_t dir_offset = 10; uint16_t name_offset = numEntries() * 12; uint32_t name_size = 0; string previousname = ""; uint16_t* nameoffsets = new uint16_t[numEntries()]; ArchiveEntry* entry = NULL; for (uint16_t l = 0; l < numEntries(); l++) { entry = getEntry(l); setEntryOffset(entry, dir_offset); dir_offset += entry->getSize(); // Does the entry has a name? string name = entry->getName(); if (l > 0 && previousname.length() > 0 && name.length() > previousname.length() && !previousname.compare(0, previousname.length(), name, 0, previousname.length()) && name.at(previousname.length()) == '+') { // This is a fake name name = ""; nameoffsets[l] = 0; } else { // This is a true name previousname = name; nameoffsets[l] = uint16_t(name_offset + name_size); name_size += name.length() + 1; } } // Clear/init MemChunk mc.clear(); mc.seek(0, SEEK_SET); mc.reSize(dir_offset + name_size + numEntries() * 12); // Write the header uint16_t num_lumps = wxINT16_SWAP_ON_BE(numEntries()); dir_offset = wxINT32_SWAP_ON_BE(dir_offset); uint32_t unknown = 0; mc.write(&num_lumps, 2); mc.write(&dir_offset, 4); mc.write(&unknown, 4); // Write the lumps for (uint16_t l = 0; l < numEntries(); l++) { entry = getEntry(l); mc.write(entry->getData(), entry->getSize()); } // Write the directory for (uint16_t l = 0; l < num_lumps; l++) { entry = getEntry(l); uint32_t offset = wxINT32_SWAP_ON_BE(getEntryOffset(entry)); uint32_t size = wxINT32_SWAP_ON_BE(entry->getSize()); uint16_t nameofs = wxINT16_SWAP_ON_BE(nameoffsets[l]); uint16_t flags = wxINT16_SWAP_ON_BE((entry->isEncrypted() == ENC_SCRLE0) ? 1 : 0); mc.write(&offset, 4); // Offset mc.write(&size, 4); // Size mc.write(&nameofs, 2); // Name offset mc.write(&flags, 2); // Flags if (update) { entry->setState(0); entry->exProp("Offset") = (int)wxINT32_SWAP_ON_BE(offset); } } // Write the names for (uint16_t l = 0; l < num_lumps; l++) { uint8_t zero = 0; entry = getEntry(l); if (nameoffsets[l]) { mc.write(CHR(entry->getName()), entry->getName().length()); mc.write(&zero, 1); } } // Clean-up delete[] nameoffsets; // Finished! return true; }
// ----------------------------------------------------------------------------- // Writes Chasm bin archive to a MemChunk // Returns true if successful, false otherwise // ----------------------------------------------------------------------------- bool ChasmBinArchive::write(MemChunk& mc, bool update) { // Clear current data mc.clear(); // Get archive tree as a list vector<ArchiveEntry*> entries; getEntryTreeAsList(entries); // Check limit of entries count const uint16_t num_entries = static_cast<uint16_t>(entries.size()); if (num_entries > MAX_ENTRY_COUNT) { LOG_MESSAGE(1, "ChasmBinArchive::write: Bin archive can contain no more than %u entries", MAX_ENTRY_COUNT); Global::error = "Maximum number of entries exceeded for Chasm: The Rift bin archive"; return false; } // Init data size static const uint32_t HEADER_TOC_SIZE = HEADER_SIZE + ENTRY_SIZE * MAX_ENTRY_COUNT; mc.reSize(HEADER_TOC_SIZE, false); mc.fillData(0); // Write header const char magic[4] = { 'C', 'S', 'i', 'd' }; mc.seek(0, SEEK_SET); mc.write(magic, 4); mc.write(&num_entries, sizeof num_entries); // Write directory uint32_t offset = HEADER_TOC_SIZE; for (uint16_t i = 0; i < num_entries; ++i) { ArchiveEntry* const entry = entries[i]; // Update entry if (update) { entry->setState(0); entry->exProp("Offset") = static_cast<int>(offset); } // Check entry name string name = entry->getName(); uint8_t name_length = static_cast<uint8_t>(name.Length()); if (name_length > NAME_SIZE - 1) { LOG_MESSAGE(1, "Warning: Entry %s name is too long, it will be truncated", name); name.Truncate(NAME_SIZE - 1); name_length = static_cast<uint8_t>(NAME_SIZE - 1); } // Write entry name char name_data[NAME_SIZE] = {}; memcpy(name_data, &name_length, 1); memcpy(name_data + 1, CHR(name), name_length); mc.write(name_data, NAME_SIZE); // Write entry size const uint32_t size = entry->getSize(); mc.write(&size, sizeof size); // Write entry offset mc.write(&offset, sizeof offset); // Increment/update offset offset += size; } // Write entry data mc.reSize(offset); mc.seek(HEADER_TOC_SIZE, SEEK_SET); for (uint16_t i = 0; i < num_entries; ++i) { ArchiveEntry* const entry = entries[i]; mc.write(entry->getData(), entry->getSize()); } return true; }
/* EntryOperations::modifytRNSChunk * Add or remove the tRNS chunk from a PNG entry * Returns true if the entry was altered *******************************************************************/ bool EntryOperations::modifytRNSChunk(ArchiveEntry* entry, bool value) { // Avoid NULL pointers, they're annoying. if (!entry || !entry->getType()) return false; // Don't bother if the entry is locked. if (entry->isLocked()) return false; // Check entry type if (!(entry->getType()->getFormat() == "img_png")) { wxLogMessage("Entry \"%s\" is of type \"%s\" rather than PNG", entry->getName(), entry->getTypeString()); return false; } // Read width and height from IHDR chunk const uint8_t* data = entry->getData(true); const ihdr_t* ihdr = (ihdr_t*)(data + 12); // tRNS chunks are only valid for paletted PNGs, and must be before the first IDAT. // Specs say they must be after PLTE chunk as well, so to play it safe, we'll insert // them just before the first IDAT. uint32_t trns_start = 0; uint32_t trns_size = 0; uint32_t idat_start = 0; for (uint32_t a = 0; a < entry->getSize(); a++) { // Check for 'tRNS' header if (data[a] == 't' && data[a + 1] == 'R' && data[a + 2] == 'N' && data[a + 3] == 'S') { trns_start = a - 4; trans_chunk_t* trns = (trans_chunk_t*)(data + a); trns_size = 12 + READ_B32(data, a-4); } // Stop when we get to the 'IDAT' chunk if (data[a] == 'I' && data[a + 1] == 'D' && data[a + 2] == 'A' && data[a + 3] == 'T') { idat_start = a - 4; break; } } // The IDAT chunk starts before the header is finished, this doesn't make sense, abort. if (idat_start < 33) return false; // We want to set tRNS, and it is already there: nothing to do. if (value && trns_start > 0) return false; // We want to unset tRNS, and it is already not there: nothing to do either. else if (!value && trns_start == 0) return false; // We want to set tRNS, which is missing: create it. We're just going to set index 0 to 0, // and leave the rest of the palette indices alone. else if (value && trns_start == 0) { // Build new PNG from the original w/ the new tRNS chunk MemChunk npng; // Init new png data size npng.reSize(entry->getSize() + 13); // Write PNG header stuff up to the first IDAT chunk npng.write(data, idat_start); // Create new tRNS chunk uint32_t csize = wxUINT32_SWAP_ON_LE(1); trans_chunk_t gc = { 't', 'R', 'N', 'S', '\0' }; uint32_t dcrc = wxUINT32_SWAP_ON_LE(Misc::crc((uint8_t*)&gc, 5)); // Write tRNS chunk npng.write(&csize, 4); npng.write(&gc, 5); npng.write(&dcrc, 4); // Write the rest of the PNG data uint32_t to_write = entry->getSize() - idat_start; npng.write(data + idat_start, to_write); // Load new png data to the entry entry->importMemChunk(npng); } // We want to unset tRNS, which is present: delete it. else if (!value && trns_start > 0) { // Build new PNG from the original without the tRNS chunk MemChunk npng; uint32_t rest_start = trns_start + trns_size; // Init new png data size npng.reSize(entry->getSize() - trns_size); // Write PNG header and stuff up to tRNS start npng.write(data, trns_start); // Write the rest of the PNG data uint32_t to_write = entry->getSize() - rest_start; npng.write(data + rest_start, to_write); // Load new png data to the entry entry->importMemChunk(npng); } // We don't know what we want, but it can't be good, so we do nothing. else return false; return true; }
bool PaletteEntryPanel::generateColormaps() { if (!entry || !entry->getParent() || ! palettes[0]) return false; MemChunk mc; SImage img; MemChunk imc; mc.reSize(34*256); mc.seek(0, SEEK_SET); imc.reSize(34*256*4); imc.seek(0, SEEK_SET); uint8_t rgba[4]; rgba[3] = 255; rgba_t rgb; float grey; // Generate 34 maps: the first 32 for diminishing light levels, // the 33th for the inverted grey map used by invulnerability. // The 34th colormap remains empty and black. for (size_t l = 0; l < 34; ++l) { for (size_t c = 0; c < 256; ++c) { rgb = palettes[0]->colour(c); if (l < 32) { // Generate light maps DIMINISH(rgb.r, l); DIMINISH(rgb.g, l); DIMINISH(rgb.b, l); #if (0) } else if (l == GREENMAP) { // Point of mostly useless trivia: the green "light amp" colormap in the Press Release beta // have colors that, on average, correspond to a bit less than (R*75/256, G*225/256, B*115/256) #endif } else if (l == GRAYMAP) { // Generate inverse map grey = ((float)rgb.r/256.0 * col_greyscale_r) + ((float)rgb.g/256.0 * col_greyscale_g) + ((float)rgb.b/256.0 * col_greyscale_b); grey = 1.0 - grey; // Clamp value: with Id Software's values, the sum is greater than 1.0 (0.299+0.587+0.144=1.030) // This means the negation above can give a negative value (for example, with RGB values of 247 or more), // which will not be converted correctly to unsigned 8-bit int in the rgba_t struct. if (grey < 0.0) grey = 0; rgb.r = rgb.g = rgb.b = grey*255; } else { // Fill with 0 rgb = palettes[0]->colour(0); } rgba[0] = rgb.r; rgba[1] = rgb.g; rgba[2] = rgb.b; imc.write(&rgba, 4); mc[(256*l)+c] = palettes[0]->nearestColour(rgb); } } #if 0 // Create truecolor image uint8_t* imd = new uint8_t[256*34*4]; memcpy(imd, imc.getData(), 256*34*4); img.setImageData(imd, 256, 34, RGBA); // imd will be freed by img's destructor ArchiveEntry* tcolormap; string name = entry->getName(true) + "-tcm.png"; tcolormap = new ArchiveEntry(name); if (tcolormap) { entry->getParent()->addEntry(tcolormap); SIFormat::getFormat("png")->saveImage(img, tcolormap->getMCData()); EntryType::detectEntryType(tcolormap); } #endif // Now override or create new entry ArchiveEntry* colormap; colormap = entry->getParent()->getEntry("COLORMAP", true); bool preexisting = colormap != NULL; if (!colormap) { // We need to create this entry colormap = new ArchiveEntry("COLORMAP.lmp", 34*256); } if (!colormap) return false; colormap->importMemChunk(mc); if (!preexisting) { entry->getParent()->addEntry(colormap); } return true; }
/* EntryOperations::setGfxOffsets * Changes the offsets of the given gfx entry. Returns false if the * entry is invalid or not an offset-supported format, true otherwise *******************************************************************/ bool EntryOperations::setGfxOffsets(ArchiveEntry* entry, int x, int y) { if (entry == NULL || entry->getType() == NULL) return false; // Check entry type EntryType* type = entry->getType(); string entryformat = type->getFormat(); if (!(entryformat == "img_doom" || entryformat == "img_doom_arah" || entryformat == "img_doom_alpha" || entryformat == "img_doom_beta" || entryformat == "img_png")) { wxLogMessage("Entry \"%s\" is of type \"%s\" which does not support offsets", entry->getName(), entry->getType()->getName()); return false; } // Doom gfx format, normal and beta version. // Also arah format from alpha 0.2 because it uses the same header format. if (entryformat == "img_doom" || entryformat == "img_doom_beta" || entryformat == "image_doom_arah") { // Get patch header patch_header_t header; entry->seek(0, SEEK_SET); entry->read(&header, 8); // Apply new offsets header.left = wxINT16_SWAP_ON_BE((int16_t)x); header.top = wxINT16_SWAP_ON_BE((int16_t)y); // Write new header to entry entry->seek(0, SEEK_SET); entry->write(&header, 8); } // Doom alpha gfx format else if (entryformat == "img_doom_alpha") { // Get patch header entry->seek(0, SEEK_SET); oldpatch_header_t header; entry->read(&header, 4); // Apply new offsets header.left = (int8_t)x; header.top = (int8_t)y; // Write new header to entry entry->seek(0, SEEK_SET); entry->write(&header, 4); } // PNG format else if (entryformat == "img_png") { // Find existing grAb chunk const uint8_t* data = entry->getData(true); uint32_t grab_start = 0; for (uint32_t a = 0; a < entry->getSize(); a++) { // Check for 'grAb' header if (data[a] == 'g' && data[a + 1] == 'r' && data[a + 2] == 'A' && data[a + 3] == 'b') { grab_start = a - 4; break; } // Stop when we get to the 'IDAT' chunk if (data[a] == 'I' && data[a + 1] == 'D' && data[a + 2] == 'A' && data[a + 3] == 'T') break; } // Create new grAb chunk uint32_t csize = wxUINT32_SWAP_ON_LE(8); grab_chunk_t gc ={ { 'g', 'r', 'A', 'b' }, wxINT32_SWAP_ON_LE(x), wxINT32_SWAP_ON_LE(y) }; uint32_t dcrc = wxUINT32_SWAP_ON_LE(Misc::crc((uint8_t*)&gc, 12)); // Build new PNG from the original w/ the new grAb chunk MemChunk npng; uint32_t rest_start = 33; // Init new png data size if (grab_start == 0) npng.reSize(entry->getSize() + 20); else npng.reSize(entry->getSize()); // Write PNG header and IHDR chunk npng.write(data, 33); // If no existing grAb chunk was found, write new one here if (grab_start == 0) { npng.write(&csize, 4); npng.write(&gc, 12); npng.write(&dcrc, 4); } else { // Otherwise write any other data before the existing grAb chunk uint32_t to_write = grab_start - 33; npng.write(data + 33, to_write); rest_start = grab_start + 20; // And now write the new grAb chunk npng.write(&csize, 4); npng.write(&gc, 12); npng.write(&dcrc, 4); } // Write the rest of the PNG data uint32_t to_write = entry->getSize() - rest_start; npng.write(data + rest_start, to_write); // Load new png data to the entry entry->importMemChunk(npng); // Set its type back to png entry->setType(type); } else return false; return true; }
/* EntryOperations::modifyalPhChunk * Add or remove the alPh chunk from a PNG entry *******************************************************************/ bool EntryOperations::modifyalPhChunk(ArchiveEntry* entry, bool value) { if (!entry || !entry->getType()) return false; // Don't bother if the entry is locked. if (entry->isLocked()) return false; // Check entry type if (!(entry->getType()->getFormat() == "img_png")) { wxLogMessage("Entry \"%s\" is of type \"%s\" rather than PNG", entry->getName(), entry->getType()->getName()); return false; } // Read width and height from IHDR chunk const uint8_t* data = entry->getData(true); const ihdr_t* ihdr = (ihdr_t*)(data + 12); // Find existing alPh chunk uint32_t alph_start = 0; for (uint32_t a = 0; a < entry->getSize(); a++) { // Check for 'alPh' header if (data[a] == 'a' && data[a + 1] == 'l' && data[a + 2] == 'P' && data[a + 3] == 'h') { alph_start = a - 4; break; } // Stop when we get to the 'IDAT' chunk if (data[a] == 'I' && data[a + 1] == 'D' && data[a + 2] == 'A' && data[a + 3] == 'T') break; } // We want to set alPh, and it is already there: nothing to do. if (value && alph_start > 0) return false; // We want to unset alPh, and it is already not there: nothing to do either. else if (!value && alph_start == 0) return false; // We want to set alPh, which is missing: create it. else if (value && alph_start == 0) { // Build new PNG from the original w/ the new alPh chunk MemChunk npng; // Init new png data size npng.reSize(entry->getSize() + 12); // Write PNG header and IHDR chunk npng.write(data, 33); // Create new alPh chunk uint32_t csize = wxUINT32_SWAP_ON_LE(0); alph_chunk_t gc = { 'a', 'l', 'P', 'h' }; uint32_t dcrc = wxUINT32_SWAP_ON_LE(Misc::crc((uint8_t*)&gc, 4)); // Create alPh chunk npng.write(&csize, 4); npng.write(&gc, 4); npng.write(&dcrc, 4); // Write the rest of the PNG data uint32_t to_write = entry->getSize() - 33; npng.write(data + 33, to_write); // Load new png data to the entry entry->importMemChunk(npng); } // We want to unset alPh, which is present: delete it. else if (!value && alph_start > 0) { // Build new PNG from the original without the alPh chunk MemChunk npng; uint32_t rest_start = alph_start + 12; // Init new png data size npng.reSize(entry->getSize() - 12); // Write PNG info before alPh chunk npng.write(data, alph_start); // Write the rest of the PNG data uint32_t to_write = entry->getSize() - rest_start; npng.write(data + rest_start, to_write); // Load new png data to the entry entry->importMemChunk(npng); } // We don't know what we want, but it can't be good, so we do nothing. else return false; return true; }
/* WadArchive::write * Writes the wad archive to a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool WadArchive::write(MemChunk& mc, bool update) { // Don't write if iwad if (iwad && iwad_lock) { Global::error = "IWAD saving disabled"; return false; } // Determine directory offset & individual lump offsets uint32_t dir_offset = 12; ArchiveEntry* entry = NULL; for (uint32_t l = 0; l < numEntries(); l++) { entry = getEntry(l); setEntryOffset(entry, dir_offset); dir_offset += entry->getSize(); } // Clear/init MemChunk mc.clear(); mc.seek(0, SEEK_SET); mc.reSize(dir_offset + numEntries() * 16); // Setup wad type char wad_type[4] = { 'P', 'W', 'A', 'D' }; if (iwad) wad_type[0] = 'I'; // Write the header uint32_t num_lumps = numEntries(); mc.write(wad_type, 4); mc.write(&num_lumps, 4); mc.write(&dir_offset, 4); // Write the lumps for (uint32_t l = 0; l < num_lumps; l++) { entry = getEntry(l); mc.write(entry->getData(), entry->getSize()); } // Write the directory for (uint32_t l = 0; l < num_lumps; l++) { entry = getEntry(l); char name[8] = { 0, 0, 0, 0, 0, 0, 0, 0 }; long offset = getEntryOffset(entry); long size = entry->getSize(); for (size_t c = 0; c < entry->getName().length() && c < 8; c++) name[c] = entry->getName()[c]; mc.write(&offset, 4); mc.write(&size, 4); mc.write(name, 8); if (update) { entry->setState(0); entry->exProp("Offset") = (int)offset; } } return true; }
// ----------------------------------------------------------------------------- // Writes the disk archive to a MemChunk. // Returns true if successful, false otherwise // ----------------------------------------------------------------------------- bool DiskArchive::write(MemChunk& mc, bool update) { // Clear current data mc.clear(); // Get archive tree as a list vector<ArchiveEntry*> entries; getEntryTreeAsList(entries); // Process entry list uint32_t num_entries = 0; uint32_t size_entries = 0; for (unsigned a = 0; a < entries.size(); a++) { // Ignore folder entries if (entries[a]->getType() == EntryType::folderType()) continue; // Increment directory offset and size size_entries += entries[a]->getSize(); ++num_entries; } // Init data size uint32_t start_offset = 8 + (num_entries * 72); uint32_t offset = start_offset; mc.reSize(size_entries + start_offset, false); // Write header num_entries = wxUINT32_SWAP_ON_LE(num_entries); mc.seek(0, SEEK_SET); mc.write(&num_entries, 4); // Write directory for (unsigned a = 0; a < entries.size(); a++) { // Skip folders if (entries[a]->getType() == EntryType::folderType()) continue; // Update entry if (update) { entries[a]->setState(0); entries[a]->exProp("Offset") = (int)offset; } // Check entry name string name = entries[a]->getPath(true); name.Replace("/", "\\"); // The leading "GAME:\" part of the name means there is only 58 usable characters for path if (name.Len() > 58) { LOG_MESSAGE( 1, "Warning: Entry %s path is too long (> 58 characters), putting it in the root directory", name); wxFileName fn(name); name = fn.GetFullName(); if (name.Len() > 57) name.Truncate(57); // Add leading "\" name = "\\" + name; } name = "GAME:" + name; DiskEntry dent; // Write entry name // The names field are padded with FD for doom.disk, FE for doom2.disk. No idea whether // a non-null padding is actually required, though. It probably should work with anything. memset(dent.name, 0xFE, 64); memcpy(dent.name, CHR(name), name.Length()); dent.name[name.Length()] = 0; // Write entry offset dent.offset = wxUINT32_SWAP_ON_LE(offset - start_offset); // Write entry size dent.length = wxUINT32_SWAP_ON_LE(entries[a]->getSize()); // Actually write stuff mc.write(&dent, 72); // Increment/update offset offset += wxUINT32_SWAP_ON_LE(dent.length); } // Finish writing header size_entries = wxUINT32_SWAP_ON_LE(size_entries); mc.write(&size_entries, 4); // Write entry data for (unsigned a = 0; a < entries.size(); a++) { // Skip folders if (entries[a]->getType() == EntryType::folderType()) continue; // Write data mc.write(entries[a]->getData(), entries[a]->getSize()); } 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; }
// ----------------------------------------------------------------------------- // 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; }
/* PakArchive::write * Writes the pak archive to a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool PakArchive::write(MemChunk& mc, bool update) { // Clear current data mc.clear(); // Get archive tree as a list vector<ArchiveEntry*> entries; getEntryTreeAsList(entries); // Process entry list long dir_offset = 12; long dir_size = 0; for (unsigned a = 0; a < entries.size(); a++) { // Ignore folder entries if (entries[a]->getType() == EntryType::folderType()) continue; // Increment directory offset and size dir_offset += entries[a]->getSize(); dir_size += 64; } // Init data size mc.reSize(dir_offset + dir_size, false); // Write header char pack[4] = { 'P', 'A', 'C', 'K' }; mc.seek(0, SEEK_SET); mc.write(pack, 4); mc.write(&dir_offset, 4); mc.write(&dir_size, 4); // Write directory mc.seek(dir_offset, SEEK_SET); long offset = 12; for (unsigned a = 0; a < entries.size(); a++) { // Skip folders if (entries[a]->getType() == EntryType::folderType()) continue; // Update entry if (update) { entries[a]->setState(0); entries[a]->exProp("Offset") = (int)offset; } // Check entry name string name = entries[a]->getPath(true); name.Remove(0, 1); // Remove leading / if (name.Len() > 56) { wxLogMessage("Warning: Entry %s path is too long (> 56 characters), putting it in the root directory", name); wxFileName fn(name); name = fn.GetFullName(); if (name.Len() > 56) name.Truncate(56); } // Write entry name char name_data[56]; memset(name_data, 0, 56); memcpy(name_data, CHR(name), name.Length()); mc.write(name_data, 56); // Write entry offset mc.write(&offset, 4); // Write entry size long size = entries[a]->getSize(); mc.write(&size, 4); // Increment/update offset offset += size; } // Write entry data mc.seek(12, SEEK_SET); for (unsigned a = 0; a < entries.size(); a++) { // Skip folders if (entries[a]->getType() == EntryType::folderType()) continue; // Write data mc.write(entries[a]->getData(), entries[a]->getSize()); } return true; }
/* LfdArchive::write * Writes the lfd archive to a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool LfdArchive::write(MemChunk& mc, bool update) { // Determine total size uint32_t dir_size = (numEntries() + 1)<<4; uint32_t total_size = dir_size; ArchiveEntry* entry = NULL; for (uint32_t l = 0; l < numEntries(); l++) { entry = getEntry(l); total_size += 16; setEntryOffset(entry, total_size); if (update) { entry->setState(0); entry->exProp("Offset") = (int)total_size; } total_size += entry->getSize(); } // Clear/init MemChunk mc.clear(); mc.seek(0, SEEK_SET); mc.reSize(total_size); // Variables char type[5] = "RMAP"; char name[9] = "resource"; size_t size = wxINT32_SWAP_ON_BE(numEntries()<<4); // Write the resource map first mc.write(type, 4); mc.write(name, 8); mc.write(&size,4); for (uint32_t l = 0; l < numEntries(); l++) { entry = getEntry(l); for (int t = 0; t < 5; ++t) type[t] = 0; for (int n = 0; n < 9; ++n) name[n] = 0; size = wxINT32_SWAP_ON_BE(entry->getSize()); wxFileName fn(entry->getName()); for (size_t c = 0; c < fn.GetName().length() && c < 9; c++) name[c] = fn.GetName()[c]; for (size_t c = 0; c < fn.GetExt().length() && c < 5; c++) type[c] = fn.GetExt()[c]; mc.write(type, 4); mc.write(name, 8); mc.write(&size,4); } // Write the lumps for (uint32_t l = 0; l < numEntries(); l++) { entry = getEntry(l); for (int t = 0; t < 5; ++t) type[t] = 0; for (int n = 0; n < 9; ++n) name[n] = 0; size = wxINT32_SWAP_ON_BE(entry->getSize()); wxFileName fn(entry->getName()); for (size_t c = 0; c < fn.GetName().length() && c < 9; c++) name[c] = fn.GetName()[c]; for (size_t c = 0; c < fn.GetExt().length() && c < 5; c++) type[c] = fn.GetExt()[c]; mc.write(type, 4); mc.write(name, 8); mc.write(&size,4); mc.write(entry->getData(), entry->getSize()); } return true; }
/* GLTexture::loadImagePortion * Loads a portion of a SImage to the texture. Only used internally, * the portion must be 128x128 in size *******************************************************************/ bool GLTexture::loadImagePortion(SImage* image, rect_t rect, Palette8bit* pal, bool add) { // Check image was given if (!image) return false; // Check image is valid if (!image->isValid()) return false; // Check portion rect is valid if (rect.width() <= 0 || rect.height() <= 0) return false; // Get RGBA image data MemChunk rgba; image->getRGBAData(rgba, pal); // Init texture data MemChunk portion; portion.reSize(rect.width() * rect.height() * 4, false); portion.fillData(0); // Read portion of image if rect isn't completely outside the image if (!(rect.left() >= image->getWidth() || rect.right() < 0 || rect.top() >= image->getHeight() || rect.bottom() < 0)) { // Determine start of each row to read uint32_t row_start = 0; if (rect.left() > 0) row_start = rect.left(); // Determine width of each row to read uint32_t row_width = rect.right() - row_start; if (rect.right() >= image->getWidth()) row_width = image->getWidth() - row_start; // Determine difference between the left of the portion and the left of the image uint32_t skip = 0; if (rect.left() < 0) skip = (0 - rect.left()) * 4; // Create temp row buffer uint8_t* buf = new uint8_t[rect.width() * 4]; // Go through each row for (int32_t row = rect.top(); row < rect.bottom(); row++) { // Clear row buffer memset(buf, 0, rect.width() * 4); // Check that the current row is within the image if (row >= 0 && row < image->getHeight()) { // Seek to current row in image data rgba.seek((row * image->getWidth() + row_start) * 4, SEEK_SET); // Copy the row data rgba.read(buf + skip, row_width * 4); } // Write the row portion.write(buf, rect.width() * 4); } // Free buffer delete[] buf; } scale_x = scale_y = 1.0; // Generate texture from rgba data return loadData(portion.getData(), rect.width(), rect.height(), add); }