/* 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 }
// ----------------------------------------------------------------------------- // Called when an announcement is recieved from one of the archives in the list // ----------------------------------------------------------------------------- void ArchiveManager::onAnnouncement(Announcer* announcer, const string& event_name, MemChunk& event_data) { // Reset event data for reading event_data.seek(0, SEEK_SET); // Check that the announcement came from an archive in the list int32_t index = archiveIndex((Archive*)announcer); if (index >= 0) { // If the archive was saved if (event_name == "saved") { MemChunk mc; mc.write(&index, 4); announce("archive_saved", mc); } // If the archive was modified if (event_name == "modified" || event_name == "entry_modified") { MemChunk mc; mc.write(&index, 4); announce("archive_modified", mc); } } }
/* AnimatedEntryPanel::saveEntry * Saves any changes made to the entry *******************************************************************/ bool AnimatedEntryPanel::saveEntry() { MemChunk mc; mc.seek(0, SEEK_SET); animated_t anim; for (uint32_t a = 0; a < animated.nEntries(); a++) { AnimatedEntry* ent = animated.getEntry(a); for (size_t i = 0; i < 9; ++i) { if (ent->getFirst().length() > i) anim.first[i] = ent->getFirst()[i]; else anim.first[i] = 0; if (ent->getLast().length() > i) anim.last[i] = ent->getLast()[i]; else anim.last[i] = 0; } anim.speed = ent->getSpeed(); anim.type = ent->getType(); if (ent->getDecals()) anim.type |= ANIM_DECALS; mc.write(&anim, 23); } anim.type = 255; mc.write(&anim, 1); bool success = entry->importMemChunk(mc); if (success) { for (uint32_t a = 0; a < animated.nEntries(); a++) list_entries->setItemStatus(a, LV_STATUS_NORMAL); } return success; }
/* SwitchesEntryPanel::saveEntry * Saves any changes made to the entry *******************************************************************/ bool SwitchesEntryPanel::saveEntry() { MemChunk mc; mc.seek(0, SEEK_SET); switches_t swch; for (uint32_t a = 0; a < switches.nEntries(); a++) { SwitchesEntry* ent = switches.getEntry(a); for (size_t i = 0; i < 9; ++i) { if (ent->getOff().length() > i) swch.off[i] = ent->getOff()[i]; else swch.off[i] = 0; if (ent->getOn().length() > i) swch.on[i] = ent->getOn()[i]; else swch.on[i] = 0; } swch.type = ent->getType(); mc.write(&swch, 20); } memset(&swch, 0, 20); mc.write(&swch, 20); bool success = entry->importMemChunk(mc); if (success) { for (uint32_t a = 0; a < switches.nEntries(); a++) list_entries->setItemStatus(a, LV_STATUS_NORMAL); } return success; }
// ----------------------------------------------------------------------------- // 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; }
/* 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 }
// ----------------------------------------------------------------------------- // Opens [dir] as a DirArchive and adds it to the list. // Returns a pointer to the archive or nullptr if an error occurred. // ----------------------------------------------------------------------------- Archive* ArchiveManager::openDirArchive(const string& dir, bool manage, bool silent) { auto new_archive = getArchive(dir); LOG_MESSAGE(1, "Opening directory %s as archive", dir); // If the archive is already open, just return it if (new_archive) { // Announce open if (!silent) { MemChunk mc; uint32_t index = archiveIndex(new_archive); mc.write(&index, 4); announce("archive_opened", mc); } return new_archive; } new_archive = new DirArchive(); // If it opened successfully, add it to the list if needed & return it, // Otherwise, delete it and return nullptr if (new_archive->open(dir)) { if (manage) { // Add the archive addArchive(new_archive); // Announce open if (!silent) { MemChunk mc; uint32_t index = archiveIndex(new_archive); mc.write(&index, 4); announce("archive_opened", mc); } // Add to recent files addRecentFile(dir); } // Return the opened archive return new_archive; } else { LOG_MESSAGE(1, "Error: " + Global::error); delete new_archive; return nullptr; } }
/* Archive::removeEntry * Removes [entry] from the archive. If [delete_entry] is true, the * entry will also be deleted. Returns true if the removal succeeded *******************************************************************/ bool Archive::removeEntry(ArchiveEntry* entry, bool delete_entry) { // Abort if read only if (read_only) return false; // Check entry if (!checkEntry(entry)) return false; // Check if entry is locked if (entry->isLocked()) return false; // Get its directory ArchiveTreeNode* dir = entry->getParentDir(); // Error if entry has no parent directory if (!dir) return false; // Create undo step if (UndoRedo::currentlyRecording()) UndoRedo::currentManager()->recordUndoStep(new EntryCreateDeleteUS(false, entry)); // Get the entry index int index = dir->entryIndex(entry); // Announce (before actually removing in case entry is still needed) MemChunk mc; wxUIntPtr ptr = wxPtrToUInt(entry); mc.write(&index, sizeof(int)); mc.write(&ptr, sizeof(wxUIntPtr)); announce("entry_removing", mc); // Remove it from its directory bool ok = dir->removeEntry(index); // If it was removed ok if (ok) { // Announce removed announce("entry_removed", mc); // Delete if necessary if (delete_entry) delete entry; // Update variables etc setModified(true); } return ok; }
/* 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; }
/* Archive::createDir * Creates a directory at [path], starting from [base]. If * [base] is NULL, the root directory is used. Returns the created * directory. If the directory requested to be created already * exists, it will be returned *******************************************************************/ ArchiveTreeNode* Archive::createDir(string path, ArchiveTreeNode* base) { // Abort if read only if (read_only) return dir_root; // If no base dir specified, set it to root if (!base) base = dir_root; if (path.IsEmpty()) return base; // Create the directory ArchiveTreeNode* dir = (ArchiveTreeNode*)((STreeNode*)base)->addChild(path); // Record undo step if (UndoRedo::currentlyRecording()) UndoRedo::currentManager()->recordUndoStep(new DirCreateDeleteUS(true, dir)); // Set the archive state to modified setModified(true); // Announce MemChunk mc; wxUIntPtr ptr = wxPtrToUInt(dir); mc.write(&ptr, sizeof(wxUIntPtr)); announce("directory_added", mc); return dir; }
// ----------------------------------------------------------------------------- // Renames [dir] to [new_name]. // Returns false if [dir] isn't part of the archive, true otherwise // ----------------------------------------------------------------------------- bool Archive::renameDir(ArchiveTreeNode* dir, string_view new_name) { // Abort if read only if (read_only_) return false; // Check the directory is part of this archive if (!dir || dir->archive() != this) return false; // Rename the directory if needed if (dir->name() == new_name) { if (UndoRedo::currentlyRecording()) UndoRedo::currentManager()->recordUndoStep(std::make_unique<DirRenameUS>(dir, new_name)); dir->setName(new_name); dir->dirEntry()->setState(ArchiveEntry::State::Modified); } else return true; // Announce MemChunk mc; wxUIntPtr ptr = wxPtrToUInt(dir); mc.write(&ptr, sizeof(wxUIntPtr)); announce("directory_modified", mc); // Update variables etc setModified(true); return true; }
/* Archive::renameDir * Renames [dir] to [new_name]. Returns false if [dir] isn't part of * the archive, true otherwise *******************************************************************/ bool Archive::renameDir(ArchiveTreeNode* dir, string new_name) { // Abort if read only if (read_only) return false; // Check the directory is part of this archive if (dir->getArchive() != this) return false; // Rename the directory if needed if (!(S_CMPNOCASE(dir->getName(), new_name))) { if (UndoRedo::currentlyRecording()) UndoRedo::currentManager()->recordUndoStep(new DirRenameUS(dir, new_name)); dir->setName(new_name); dir->getDirEntry()->setState(1); } else return true; // Announce MemChunk mc; wxUIntPtr ptr = wxPtrToUInt(dir); mc.write(&ptr, sizeof(wxUIntPtr)); announce("directory_modified", mc); // Update variables etc setModified(true); return true; }
// ----------------------------------------------------------------------------- // 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; }
// ----------------------------------------------------------------------------- // Renames [entry] with [name]. // Returns false if the entry was invalid, true otherwise // ----------------------------------------------------------------------------- bool Archive::renameEntry(ArchiveEntry* entry, string_view name) { // Abort if read only if (read_only_) return false; // Check entry if (!checkEntry(entry)) return false; // Check if entry is locked if (entry->isLocked()) return false; // Check for directory if (entry->type() == EntryType::folderType()) return renameDir(dir(entry->path(true)), name); // Announce (before actually renaming in case old name is still needed) MemChunk mc; int index = entryIndex(entry); wxUIntPtr ptr = wxPtrToUInt(entry); mc.write(&index, sizeof(int)); mc.write(&ptr, sizeof(wxUIntPtr)); announce("entry_renaming", mc); // Create undo step if (UndoRedo::currentlyRecording()) UndoRedo::currentManager()->recordUndoStep(std::make_unique<EntryRenameUS>(entry, name)); // Rename the entry entry->setName(name); entry->formatName(formatDesc()); entry->setState(ArchiveEntry::State::Modified, true); // Announce modification entryStateChanged(entry); return true; }
// ----------------------------------------------------------------------------- // Adds [entry] to [dir] at [position]. // If [dir] is null it is added to the root dir. // If [position] is out of bounds, it is added to the end of the dir. // If [copy] is true, a copy of [entry] is added (rather than [entry] itself). // Returns the added entry, or null if [entry] is invalid or the archive is // read-only // ----------------------------------------------------------------------------- ArchiveEntry* Archive::addEntry(ArchiveEntry* entry, unsigned position, ArchiveTreeNode* dir, bool copy) { // Abort if read only if (read_only_) return nullptr; // Check valid entry if (!entry) return nullptr; // If no dir given, set it to the root dir if (!dir) dir = &dir_root_; // Make a copy of the entry to add if needed if (copy) entry = new ArchiveEntry(*entry); // Add the entry dir->addEntry(entry, position); entry->formatName(formatDesc()); // Update variables etc setModified(true); entry->state_ = ArchiveEntry::State::New; // Announce MemChunk mc; wxUIntPtr ptr = wxPtrToUInt(entry); mc.write(&position, sizeof(uint32_t)); mc.write(&ptr, sizeof(wxUIntPtr)); announce("entry_added", mc); // Create undo step if (UndoRedo::currentlyRecording()) UndoRedo::currentManager()->recordUndoStep(std::make_unique<EntryCreateDeleteUS>(true, entry)); return entry; }
/* MemChunk::readMC * Reads [size] bytes of data into [mc]. Returns false if attempting * to read outside the chunk, true otherwise *******************************************************************/ bool MemChunk::readMC(MemChunk& mc, uint32_t size) { if (cur_ptr + size >= this->size) return false; if (mc.write(data + cur_ptr, size)) { cur_ptr += size; return true; } else return false; }
/* ColourConfiguration::writeConfiguration * Writes the current colour configuration to text data [mc] *******************************************************************/ bool ColourConfiguration::writeConfiguration(MemChunk& mc) { string cfgstring = "colours\n{\n"; ColourHashMap::iterator i = cc_colours.begin(); // Go through all properties while (i != cc_colours.end()) { // Skip if it doesn't 'exist' cc_col_t cc = i->second; if (!cc.exists) { i++; continue; } // Colour definition name cfgstring += S_FMT("\t%s\n\t{\n", i->first); // Full name cfgstring += S_FMT("\t\tname = \"%s\";\n", cc.name); // Group cfgstring += S_FMT("\t\tgroup = \"%s\";\n", cc.group); // Colour values cfgstring += S_FMT("\t\trgb = %d, %d, %d;\n", cc.colour.r, cc.colour.g, cc.colour.b); // Alpha if (cc.colour.a < 255) cfgstring += S_FMT("\t\talpha = %d;\n", cc.colour.a); // Additive if (cc.colour.blend == 1) cfgstring += "\t\tadditive = true;\n"; cfgstring += "\t}\n\n"; // Next colour i++; } cfgstring += "}\n"; mc.write(cfgstring.ToAscii(), cfgstring.size()); 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; }
/* PaletteEntryPanel::saveEntry * Writes all loaded palettes in the palette entry *******************************************************************/ bool PaletteEntryPanel::saveEntry() { MemChunk full; MemChunk mc; // Write each palette as raw data for (size_t a = 0; a < palettes.size(); a++) { palettes[a]->saveMem(mc, Palette8bit::FORMAT_RAW); full.write(mc.getData(), 768); } entry->importMemChunk(full); setModified(false); return true; }
bool writeImage(SImage& image, MemChunk& data, Palette* pal, int index) { // Can't write if RGBA if (image.getType() == RGBA) return false; // Check size if (!validSize(image.getWidth(), image.getHeight())) return false; // Just dump image data to memchunk data.clear(); data.write(imageData(image), image.getWidth() * image.getHeight()); return true; }
// ----------------------------------------------------------------------------- // Writes the current colour configuration to text data [mc] // ----------------------------------------------------------------------------- bool ColourConfiguration::writeConfiguration(MemChunk& mc) { std::string cfgstring = "colours\n{\n"; // Go through all properties for (const auto& i : cc_colours) { // Skip if it doesn't 'exist' const auto& cc = i.second; if (!cc.exists) continue; // Colour definition name cfgstring += fmt::format("\t{}\n\t{{\n", i.first); // Full name cfgstring += fmt::format("\t\tname = \"{}\";\n", cc.name); // Group cfgstring += fmt::format("\t\tgroup = \"{}\";\n", cc.group); // Colour values cfgstring += fmt::format("\t\trgb = {}, {}, {};\n", cc.colour.r, cc.colour.g, cc.colour.b); // Alpha if (cc.colour.a < 255) cfgstring += fmt::format("\t\talpha = {};\n", cc.colour.a); // Additive if (cc.blend_additive) cfgstring += "\t\tadditive = true;\n"; cfgstring += "\t}\n\n"; } cfgstring += "}\n\ntheme\n{\n"; cfgstring += fmt::format("\tline_hilight_width = {:1.3f};\n", line_hilight_width); cfgstring += fmt::format("\tline_selection_width = {:1.3f};\n", line_selection_width); cfgstring += fmt::format("\tflat_alpha = {:1.3f};\n", flat_alpha); cfgstring += "}\n"; mc.write(cfgstring.data(), cfgstring.size()); 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; }
/* 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; }
/* 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; }
/* 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; }
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::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; }
/* 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; }
/* 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; }