/* TextureXPanel::exportTexture * Create standalone image entries of any selected textures *******************************************************************/ void TextureXPanel::exportTexture() { // Get selected textures vector<long> selec_num = list_textures->getSelection(); vector<CTexture*> selection; if (!tx_entry) return; //saveTEXTUREX(); Archive* archive = tx_entry->getParent(); bool force_rgba = texture_editor->getBlendRGBA(); // Go through selection for (unsigned a = 0; a < selec_num.size(); ++a) { selection.push_back(texturex.getTexture(selec_num[a])); } // Create gfx conversion dialog GfxConvDialog gcd(this); // Send selection to the gcd gcd.openTextures(selection, texture_editor->getPalette(), archive, force_rgba); // Run the gcd gcd.ShowModal(); // Show splash window theSplashWindow->show("Writing converted image data...", true); // Write any changes for (unsigned a = 0; a < selection.size(); a++) { // Update splash window theSplashWindow->setProgressMessage(selection[a]->getName()); theSplashWindow->setProgress((float)a / (float)selection.size()); // Skip if the image wasn't converted if (!gcd.itemModified(a)) continue; // Get image and conversion info SImage* image = gcd.getItemImage(a); SIFormat* format = gcd.getItemFormat(a); // Write converted image back to entry MemChunk mc; format->saveImage(*image, mc, force_rgba ? NULL : gcd.getItemPalette(a)); ArchiveEntry* lump = new ArchiveEntry; lump->importMemChunk(mc); lump->rename(selection[a]->getName()); archive->addEntry(lump, "textures"); EntryType::detectEntryType(lump); lump->setExtensionByType(); } // Hide splash window theSplashWindow->hide(); }
/* DatArchive::open * Reads wad format data from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool DatArchive::open(MemChunk& mc) { // Check data was given if (!mc.hasData()) return false; const uint8_t* mcdata = mc.getData(); // Read dat header mc.seek(0, SEEK_SET); uint16_t num_lumps; uint32_t dir_offset, unknown; mc.read(&num_lumps, 2); // Size mc.read(&dir_offset, 4); // Directory offset mc.read(&unknown, 4); // Unknown value num_lumps = wxINT16_SWAP_ON_BE(num_lumps); dir_offset = wxINT32_SWAP_ON_BE(dir_offset); unknown = wxINT32_SWAP_ON_BE(unknown); string lastname(wxString::FromAscii("-noname-")); size_t namecount = 0; // 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); theSplashWindow->setProgressMessage("Reading dat 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 uint32_t offset = 0; uint32_t size = 0; uint16_t nameofs = 0; uint16_t flags = 0; mc.read(&offset, 4); // Offset mc.read(&size, 4); // Size mc.read(&nameofs, 2); // Name offset mc.read(&flags, 2); // Flags (only one: RLE encoded) // Byteswap values for big endian if needed offset = wxINT32_SWAP_ON_BE(offset); size = wxINT32_SWAP_ON_BE(size); nameofs = wxINT16_SWAP_ON_BE(nameofs); flags = wxINT16_SWAP_ON_BE(flags); // If the lump data goes past the directory, // the data file is invalid if (offset + size > mc.getSize()) { wxLogMessage("DatArchive::open: Dat archive is invalid or corrupt at entry %i", d); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } string myname; if (nameofs != 0) { size_t len = 1; size_t start = nameofs+dir_offset; for (size_t i = start; mcdata[i] != 0; ++i) { ++len; } lastname = myname = wxString::FromAscii(mcdata+start, len); namecount = 0; } else { myname = S_FMT("%s+%d", lastname, ++namecount); } // Create & setup lump ArchiveEntry* nlump = new ArchiveEntry(myname, size); nlump->setLoaded(false); nlump->exProp("Offset") = (int)offset; nlump->setState(0); if (flags & 1) nlump->setEncryption(ENC_SCRLE0); // Check for markers if (!nlump->getName().Cmp("startflats")) flats[0] = d; if (!nlump->getName().Cmp("endflats")) flats[1] = d; if (!nlump->getName().Cmp("startsprites")) sprites[0] = d; if (!nlump->getName().Cmp("endmonsters")) sprites[1] = d; if (!nlump->getName().Cmp("startwalls")) walls[0] = d; if (!nlump->getName().Cmp("endwalls")) walls[1] = d; // Add to entry list getRoot()->addEntry(nlump); } // 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)num_lumps))); // 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()); entry->importMemChunk(edata); } // Detect entry type EntryType::detectEntryType(entry); // Set entry to unchanged entry->setState(0); } // Detect maps (will detect map entry types) //theSplashWindow->setProgressMessage("Detecting maps"); //detectMaps(); // Setup variables setMuted(false); setModified(false); announce("opened"); theSplashWindow->setProgressMessage(""); return true; }
/* ResArchive::readDirectory * Reads a res directory from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool ResArchive::readDirectory(MemChunk& mc, size_t dir_offset, size_t num_lumps, ArchiveTreeNode* parent) { if (!parent) { LOG_MESSAGE(1, "ReadDir: No parent node"); Global::error = "Archive is invalid and/or corrupt"; return false; } mc.seek(dir_offset, SEEK_SET); for (uint32_t d = 0; d < num_lumps; d++) { // Update splash window progress UI::setSplashProgress(((float)d / (float)num_lumps)); // Read lump info char magic[4] = ""; char name[15] = ""; uint32_t dumzero1, dumzero2; uint16_t dumff, dumze; uint8_t flags = 0; uint32_t offset = 0; uint32_t size = 0; mc.read(magic, 4); // ReS\0 mc.read(name, 14); // Name mc.read(&offset, 4); // Offset mc.read(&size, 4); // Size // Check the identifier if (magic[0] != 'R' || magic[1] != 'e' || magic[2] != 'S' || magic[3] != 0) { LOG_MESSAGE(1, "ResArchive::readDir: Entry %s (%i@0x%x) has invalid directory entry", name, size, offset); Global::error = "Archive is invalid and/or corrupt"; return false; } // Byteswap values for big endian if needed offset = wxINT32_SWAP_ON_BE(offset); size = wxINT32_SWAP_ON_BE(size); name[14] = '\0'; mc.read(&dumze, 2); if (dumze) LOG_MESSAGE(1, "Flag guard not null for entry %s", name); mc.read(&flags, 1); if (flags != 1 && flags != 17) LOG_MESSAGE(1, "Unknown flag value for entry %s", name); mc.read(&dumzero1, 4); if (dumzero1) LOG_MESSAGE(1, "Near-end values not set to zero for entry %s", name); mc.read(&dumff, 2); if (dumff != 0xFFFF) LOG_MESSAGE(1, "Dummy set to a non-FF value for entry %s", name); mc.read(&dumzero2, 4); if (dumzero2) LOG_MESSAGE(1, "Trailing values not set to zero for entry %s", name); // If the lump data goes past the end of the file, // the resfile is invalid if (offset + size > mc.getSize()) { LOG_MESSAGE(1, "ResArchive::readDirectory: Res archive is invalid or corrupt, offset overflow"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Create & setup lump ArchiveEntry* nlump = new ArchiveEntry(wxString::FromAscii(name), size); nlump->setLoaded(false); nlump->exProp("Offset") = (int)offset; nlump->setState(0); // Read entry data if it isn't zero-sized if (nlump->getSize() > 0) { // Read the entry data MemChunk edata; mc.exportMemChunk(edata, offset, size); nlump->importMemChunk(edata); } // What if the entry is a directory? size_t d_o, n_l; if (isResArchive(nlump->getMCData(), d_o, n_l)) { ArchiveTreeNode* ndir = createDir(name, parent); if (ndir) { UI::setSplashProgressMessage(S_FMT("Reading res archive data: %s directory", name)); // Save offset to restore it once the recursion is done size_t myoffset = mc.currentPos(); readDirectory(mc, d_o, n_l, ndir); ndir->getDirEntry()->setState(0); // Restore offset and clean out the entry mc.seek(myoffset, SEEK_SET); delete nlump; } else { delete nlump; return false; } // Not a directory, then add to entry list } else { parent->addEntry(nlump); // Detect entry type EntryType::detectEntryType(nlump); // Unload entry data if needed if (!archive_load_data) nlump->unloadData(); // Set entry to unchanged nlump->setState(0); } } return true; }
/* ADatArchive::open * Reads dat format data from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool ADatArchive::open(MemChunk& mc) { // Check given data is valid if (mc.getSize() < 16) return false; // Read dat header char magic[4]; long dir_offset; long dir_size; mc.seek(0, SEEK_SET); mc.read(magic, 4); mc.read(&dir_offset, 4); mc.read(&dir_size, 4); // Check it if (magic[0] != 'A' || magic[1] != 'D' || magic[2] != 'A' || magic[3] != 'T') { wxLogMessage("ADatArchive::open: Opening failed, invalid header"); Global::error = "Invalid dat header"; return false; } // Stop announcements (don't want to be announcing modification due to entries being added etc) setMuted(true); // Read the directory size_t num_entries = dir_size / DIRENTRY; mc.seek(dir_offset, SEEK_SET); theSplashWindow->setProgressMessage("Reading dat archive data"); for (uint32_t d = 0; d < num_entries; d++) { // Update splash window progress theSplashWindow->setProgress(((float)d / (float)num_entries)); // Read entry info char name[128]; long offset; long decsize; long compsize; long whatever; // No idea what this could be mc.read(name, 128); mc.read(&offset, 4); mc.read(&decsize, 4); mc.read(&compsize, 4); mc.read(&whatever, 4); // Byteswap if needed offset = wxINT32_SWAP_ON_BE(offset); decsize = wxINT32_SWAP_ON_BE(decsize); compsize = wxINT32_SWAP_ON_BE(compsize); // Check offset+size if ((unsigned)(offset + compsize) > mc.getSize()) { wxLogMessage("ADatArchive::open: dat archive is invalid or corrupt (entry goes past end of file)"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Parse name wxFileName fn(wxString::FromAscii(name, 128)); // Create directory if needed ArchiveTreeNode* dir = createDir(fn.GetPath(true, wxPATH_UNIX)); // Create entry ArchiveEntry* entry = new ArchiveEntry(fn.GetFullName(), compsize); entry->exProp("Offset") = (int)offset; entry->exProp("FullSize") = (int)decsize; entry->setLoaded(false); entry->setState(0); // Add to directory dir->addEntry(entry); } // Detect all entry types MemChunk edata; vector<ArchiveEntry*> all_entries; getEntryTreeAsList(all_entries); theSplashWindow->setProgressMessage("Detecting entry types"); for (size_t a = 0; a < all_entries.size(); a++) { // Update splash window progress theSplashWindow->setProgress((((float)a / (float)num_entries))); // Get entry ArchiveEntry* entry = all_entries[a]; // Read entry data if it isn't zero-sized if (entry->getSize() > 0) { // Read the entry data mc.exportMemChunk(edata, (int)entry->exProp("Offset"), entry->getSize()); MemChunk xdata; if (Compression::ZlibInflate(edata, xdata, (int)entry->exProp("FullSize"))) entry->importMemChunk(xdata); else { wxLogMessage("Entry %s couldn't be inflated", CHR(entry->getName())); 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); } // Setup variables setMuted(false); setModified(false); announce("opened"); theSplashWindow->setProgressMessage(""); return true; }
/* RffArchive::open * Reads grp format data from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool RffArchive::open(MemChunk& mc) { // Check data was given if (!mc.hasData()) return false; // Read grp header uint8_t magic[4]; uint32_t version, dir_offset, num_lumps; mc.seek(0, SEEK_SET); mc.read(magic, 4); // Should be "RFF\x18" mc.read(&version, 4); // 0x01 0x03 \x00 \x00 mc.read(&dir_offset, 4); // Offset to directory mc.read(&num_lumps, 4); // No. of lumps in rff // Byteswap values for big endian if needed dir_offset = wxINT32_SWAP_ON_BE(dir_offset); num_lumps = wxINT32_SWAP_ON_BE(num_lumps); version = wxINT32_SWAP_ON_BE(version); // Check the header if (magic[0] != 'R' || magic[1] != 'F' || magic[2] != 'F' || magic[3] != 0x1A || version != 0x301) { wxLogMessage("RffArchive::openFile: File %s has invalid header", filename); Global::error = "Invalid rff header"; return false; } // Stop announcements (don't want to be announcing modification due to entries being added etc) setMuted(true); // Read the directory RFFLump* lumps = new RFFLump[num_lumps]; mc.seek(dir_offset, SEEK_SET); theSplashWindow->setProgressMessage("Reading rff archive data"); mc.read (lumps, num_lumps * sizeof(RFFLump)); BloodCrypt (lumps, dir_offset, num_lumps * sizeof(RFFLump)); 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[13] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; uint32_t offset = wxINT32_SWAP_ON_BE(lumps[d].FilePos); uint32_t size = wxINT32_SWAP_ON_BE(lumps[d].Size); // Reconstruct name int i, j = 0; for (i = 0; i < 8; ++i) { if (lumps[d].Name[i] == 0) break; name[i] = lumps[d].Name[i]; } for (name[i++] = '.'; j < 3; ++j) name[i+j] = lumps[d].Extension[j]; // If the lump data goes past the end of the file, // the rfffile is invalid if (offset + size > mc.getSize()) { wxLogMessage("RffArchive::open: rff archive is invalid or corrupt"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Create & setup lump ArchiveEntry* nlump = new ArchiveEntry(wxString::FromAscii(name), size); nlump->setLoaded(false); nlump->exProp("Offset") = (int)offset; nlump->setState(0); // Is the entry encrypted? if (lumps[d].Flags & 0x10) nlump->setEncryption(ENC_BLOOD); // Add to entry list getRoot()->addEntry(nlump); } delete[] lumps; // 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)num_lumps))); // 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 the entry is encrypted, decrypt it if (entry->isEncrypted()) { uint8_t* cdata = new uint8_t[entry->getSize()]; memcpy(cdata, edata.getData(), entry->getSize()); int cryptlen = entry->getSize() < 256 ? entry->getSize() : 256; BloodCrypt(cdata, 0, cryptlen); edata.importMem(cdata, entry->getSize()); delete[] cdata; } // Import data 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); } // Detect maps (will detect map entry types) //theSplashWindow->setProgressMessage("Detecting maps"); //detectMaps(); // Setup variables setMuted(false); setModified(false); announce("opened"); theSplashWindow->setProgressMessage(""); 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; }
/* GobArchive::open * Reads gob format data from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool GobArchive::open(MemChunk& mc) { // Check data was given if (!mc.hasData()) return false; // Check size if (mc.getSize() < 12) return false; // Check magic header if (mc[0] != 'G' || mc[1] != 'O' || mc[2] != 'B' || mc[3] != 0xA) return false; // Get directory offset uint32_t dir_offset = 0; mc.seek(4, SEEK_SET); mc.read(&dir_offset, 4); dir_offset = wxINT32_SWAP_ON_BE(dir_offset); // Check size if ((unsigned)mc.getSize() < (dir_offset + 4)) return false; // Get number of lumps uint32_t num_lumps = 0; mc.seek(dir_offset, SEEK_SET); mc.read(&num_lumps, 4); num_lumps = wxINT32_SWAP_ON_BE(num_lumps); // Compute directory size uint32_t dir_size = (num_lumps * 21) + 4; if ((unsigned)mc.getSize() < (dir_offset + dir_size)) return false; // Stop announcements (don't want to be announcing modification due to entries being added etc) setMuted(true); // Read the directory theSplashWindow->setProgressMessage("Reading gob 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 uint32_t offset = 0; uint32_t size = 0; char name[13] = ""; mc.read(&offset, 4); // Offset mc.read(&size, 4); // Size mc.read(name, 13); // Name name[12] = '\0'; // Byteswap values for big endian if needed size = wxINT32_SWAP_ON_BE(size); // If the lump data goes past the end of the file, // the gobfile is invalid if (offset + size > mc.getSize()) { wxLogMessage("GobArchive::open: gob archive is invalid or corrupt"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Create & setup lump ArchiveEntry* nlump = new ArchiveEntry(wxString::FromAscii(name), size); nlump->setLoaded(false); nlump->exProp("Offset") = (int)offset; nlump->setState(0); // Add to entry list getRoot()->addEntry(nlump); } // 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)num_lumps))); // 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()); 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); } // Setup variables setMuted(false); setModified(false); announce("opened"); theSplashWindow->setProgressMessage(""); return true; }
// ----------------------------------------------------------------------------- // Reads disk format data from a MemChunk. // Returns true if successful, false otherwise // ----------------------------------------------------------------------------- bool DiskArchive::open(MemChunk& mc) { size_t mcsize = mc.getSize(); // Check given data is valid if (mcsize < 80) return false; // Read disk header uint32_t num_entries; mc.seek(0, SEEK_SET); mc.read(&num_entries, 4); num_entries = wxUINT32_SWAP_ON_LE(num_entries); size_t start_offset = (72 * num_entries) + 8; if (mcsize < start_offset) return false; // Stop announcements (don't want to be announcing modification due to entries being added etc) setMuted(true); // Read the directory UI::setSplashProgressMessage("Reading disk archive data"); for (uint32_t d = 0; d < num_entries; d++) { // Update splash window progress UI::setSplashProgress(((float)d / (float)num_entries)); // Read entry info DiskEntry dent; mc.read(&dent, 72); // Byteswap if needed dent.length = wxUINT32_SWAP_ON_LE(dent.length); dent.offset = wxUINT32_SWAP_ON_LE(dent.offset); // Increase offset to make it relative to start of archive dent.offset += start_offset; // Check offset+size if (dent.offset + dent.length > mcsize) { LOG_MESSAGE(1, "DiskArchive::open: Disk archive is invalid or corrupt (entry goes past end of file)"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Parse name string name = wxString::FromAscii(dent.name, 64); name.Replace("\\", "/"); name.Replace("GAME:/", ""); wxFileName fn(name); // Create directory if needed ArchiveTreeNode* dir = createDir(fn.GetPath(true, wxPATH_UNIX)); // Create entry ArchiveEntry* entry = new ArchiveEntry(fn.GetFullName(), dent.length); entry->exProp("Offset") = (int)dent.offset; entry->setLoaded(false); entry->setState(0); // Add to directory dir->addEntry(entry); } // Detect all entry types MemChunk edata; vector<ArchiveEntry*> all_entries; getEntryTreeAsList(all_entries); UI::setSplashProgressMessage("Detecting entry types"); for (size_t a = 0; a < all_entries.size(); a++) { // Update splash window progress UI::setSplashProgress((((float)a / (float)num_entries))); // Get entry ArchiveEntry* entry = all_entries[a]; // Read entry data if it isn't zero-sized if (entry->getSize() > 0) { // Read the entry data mc.exportMemChunk(edata, (int)entry->exProp("Offset"), entry->getSize()); 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); } // Setup variables setMuted(false); setModified(false); announce("opened"); UI::setSplashProgressMessage(""); 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; }
/* HogArchive::open * Reads hog format data from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool HogArchive::open(MemChunk& mc) { // Check data was given if (!mc.hasData()) return false; // Check size size_t archive_size = mc.getSize(); if (archive_size < 3) return false; // Check magic header (DHF for "Descent Hog File") if (mc[0] != 'D' || mc[1] != 'H' || mc[2] != 'F') return false; // Stop announcements (don't want to be announcing modification due to entries being added etc) setMuted(true); // Iterate through files to see if the size seems okay theSplashWindow->setProgressMessage("Reading hog archive data"); size_t iter_offset = 3; uint32_t num_lumps = 0; while (iter_offset < archive_size) { // Update splash window progress theSplashWindow->setProgress(((float)iter_offset / (float)archive_size)); // If the lump data goes past the end of the file, // the hogfile is invalid if (iter_offset + 17 > archive_size) { wxLogMessage("HogArchive::open: hog archive is invalid or corrupt"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Setup variables num_lumps++; size_t offset = iter_offset + 17; size_t size = READ_L32(mc, iter_offset + 13); char name[14] = ""; mc.seek(iter_offset, SEEK_SET); mc.read(name, 13); name[13] = 0; // Create & setup lump ArchiveEntry* nlump = new ArchiveEntry(wxString::FromAscii(name), size); nlump->setLoaded(false); nlump->exProp("Offset") = (int)offset; nlump->setState(0); // Add to entry list getRoot()->addEntry(nlump); // Update entry size to compute next offset iter_offset = offset + size; } // 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)num_lumps))); // 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()); 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); } // Setup variables setMuted(false); setModified(false); announce("opened"); theSplashWindow->setProgressMessage(""); return true; }
/* GZipArchive::open * Reads gzip format data from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool GZipArchive::open(MemChunk& mc) { // Minimal metadata size is 18: 10 for header, 8 for footer size_t mds = 18; size_t size = mc.getSize(); if (mds > size) return false; // Read header uint8_t header[4]; mc.read(header, 4); // Check for GZip header; we'll only accept deflated gzip files // and reject any field using unknown flags if ((!(header[0] == GZIP_ID1 && header[1] == GZIP_ID2 && header[2] == GZIP_DEFLATE)) || (header[3] & GZIP_FLG_FUNKN)) return false; bool ftext, fhcrc, fxtra, fname, fcmnt; ftext = (header[3] & GZIP_FLG_FTEXT) ? true : false; fhcrc = (header[3] & GZIP_FLG_FHCRC) ? true : false; fxtra = (header[3] & GZIP_FLG_FXTRA) ? true : false; fname = (header[3] & GZIP_FLG_FNAME) ? true : false; fcmnt = (header[3] & GZIP_FLG_FCMNT) ? true : false; flags = header[3]; mc.read(&mtime, 4); mtime = wxUINT32_SWAP_ON_BE(mtime); mc.read(&xfl, 1); mc.read(&os, 1); // Skip extra fields which may be there if (fxtra) { uint16_t xlen; mc.read(&xlen, 2); xlen = wxUINT16_SWAP_ON_BE(xlen); mds += xlen + 2; if (mds > size) return false; mc.exportMemChunk(xtra, mc.currentPos(), xlen); mc.seek(xlen, SEEK_CUR); } // Skip past name, if any string name; if (fname) { char c; do { mc.read(&c, 1); if (c) name += c; ++mds; } while (c != 0 && size > mds); } else { // Build name from filename name = getFilename(false); wxFileName fn(name); if (!fn.GetExt().CmpNoCase("tgz")) fn.SetExt("tar"); else if (!fn.GetExt().CmpNoCase("gz")) fn.ClearExt(); name = fn.GetFullName(); } // Skip past comment if (fcmnt) { char c; do { mc.read(&c, 1); if (c) comment += c; ++mds; } while (c != 0 && size > mds); wxLogMessage("Archive %s says:\n %s", CHR(getFilename(true)), CHR(comment)); } // Skip past CRC 16 check if (fhcrc) { uint8_t* crcbuffer = new uint8_t[mc.currentPos()]; memcpy(crcbuffer, mc.getData(), mc.currentPos()); uint32_t fullcrc = Misc::crc(crcbuffer, mc.currentPos()); delete[] crcbuffer; uint16_t hcrc; mc.read(&hcrc, 2); hcrc = wxUINT16_SWAP_ON_BE(hcrc); mds += 2; if (hcrc != (fullcrc & 0x0000FFFF)) { wxLogMessage("CRC-16 mismatch for GZip header"); } } // Header is over if (mds > size || mc.currentPos() + 8 > size) return false; // Let's create the entry setMuted(true); ArchiveEntry* entry = new ArchiveEntry(name, size - mds); MemChunk xdata; if (Compression::GZipInflate(mc, xdata)) { entry->importMemChunk(xdata); } else { delete entry; setMuted(false); return false; } getRoot()->addEntry(entry); EntryType::detectEntryType(entry); entry->setState(0); setMuted(false); setModified(false); announce("opened"); // Finish return true; }
/* MapEditorConfigDialog::onBtnNewMap * Called when the 'New Map' button is clicked *******************************************************************/ void MapEditorConfigDialog::onBtnNewMap(wxCommandEvent& e) { // Get selected game/port index int sel_port = choice_port_config->GetSelection() - 1; if (sel_port >= 0) sel_port = ports_list[sel_port]; int sel_game = choice_game_config->GetSelection(); // Create new map dialog NewMapDialog dlg(this, sel_game, sel_port, maps); // Show it dlg.SetInitialSize(wxSize(250, -1)); dlg.CenterOnParent(); if (dlg.ShowModal() == wxID_OK) { string mapname = dlg.getMapName(); if (mapname.IsEmpty()) return; // Check the map name isn't already taken for (unsigned a = 0; a < maps.size(); a++) { if (S_CMPNOCASE(maps[a].name, mapname)) { wxMessageBox("Map " + mapname + " already exists", "Error"); return; } } // Get selected map format int map_format = MAP_DOOM; if (dlg.getMapFormat() == "Hexen") map_format = MAP_HEXEN; else if (dlg.getMapFormat() == "UDMF") map_format = MAP_UDMF; else if (dlg.getMapFormat() == "Doom64") map_format = MAP_DOOM64; // Check archive type if (archive->getType() == ARCHIVE_WAD) { // Create new (empty) map at the end of the wad ArchiveEntry* head = archive->addNewEntry(mapname); ArchiveEntry* end = NULL; if (map_format == MAP_UDMF) { // UDMF archive->addNewEntry("TEXTMAP"); end = archive->addNewEntry("ENDMAP"); } else { // Doom(64) / Hexen archive->addNewEntry("THINGS"); archive->addNewEntry("LINEDEFS"); archive->addNewEntry("SIDEDEFS"); archive->addNewEntry("VERTEXES"); end = archive->addNewEntry("SECTORS"); // Hexen if (map_format == MAP_HEXEN) end = archive->addNewEntry("BEHAVIOR"); } // Refresh map list populateMapList(); list_maps->selectItem(list_maps->GetItemCount()-1); } else if (archive->getType() == ARCHIVE_ZIP || archive->getType() == ARCHIVE_FOLDER) { // Create new wad archive for the map Archive* wad = new WadArchive(); // Create new (empty) map at the end of the wad ArchiveEntry* head = wad->addNewEntry(mapname); ArchiveEntry* end = NULL; if (map_format == MAP_UDMF) { // UDMF wad->addNewEntry("TEXTMAP"); end = wad->addNewEntry("ENDMAP"); } else { // Doom(64) / Hexen wad->addNewEntry("THINGS"); wad->addNewEntry("LINEDEFS"); wad->addNewEntry("SIDEDEFS"); wad->addNewEntry("VERTEXES"); end = wad->addNewEntry("SECTORS"); // Hexen if (map_format == MAP_HEXEN) end = wad->addNewEntry("BEHAVIOR"); // Doom 64 else if (map_format == MAP_DOOM64) { wad->addNewEntry("LIGHTS"); end = wad->addNewEntry("MACROS"); } } // Add new map entry to the maps dir //ArchiveEntry* mapentry = archive->addNewEntry(mapname+".wad", 0xFFFFFFFF, archive->createDir("maps")); ArchiveEntry* mapentry = archive->addNewEntry(mapname + ".wad", "maps"); MemChunk mc; wad->write(mc); mapentry->importMemChunk(mc); // Clean up delete wad; // Refresh map list populateMapList(); list_maps->selectItem(list_maps->GetItemCount()-1); } } }
/* MapPreviewCanvas::createImage * Draws the map in an image * TODO: Factorize code with normal draw() and showMap() functions. * TODO: Find a way to generate an arbitrary-sized image through * tiled rendering. *******************************************************************/ void MapPreviewCanvas::createImage(ArchiveEntry& ae, int width, int height) { // Find extents of map mep_vertex_t m_min(999999.0, 999999.0); mep_vertex_t m_max(-999999.0, -999999.0); for (unsigned a = 0; a < verts.size(); a++) { if (verts[a].x < m_min.x) m_min.x = verts[a].x; if (verts[a].x > m_max.x) m_max.x = verts[a].x; if (verts[a].y < m_min.y) m_min.y = verts[a].y; if (verts[a].y > m_max.y) m_max.y = verts[a].y; } double mapwidth = m_max.x - m_min.x; double mapheight = m_max.y - m_min.y; if (width == 0) width = -5; if (height == 0) height = -5; if (width < 0) width = mapwidth / abs(width); if (height < 0) height = mapheight / abs(height); // Setup colours rgba_t col_save_background = ColourConfiguration::getColour("map_image_background"); rgba_t col_save_line_1s = ColourConfiguration::getColour("map_image_line_1s"); rgba_t col_save_line_2s = ColourConfiguration::getColour("map_image_line_2s"); rgba_t col_save_line_special = ColourConfiguration::getColour("map_image_line_special"); rgba_t col_save_line_macro = ColourConfiguration::getColour("map_image_line_macro"); // Setup OpenGL rigmarole GLuint texID, fboID; if (GLEW_ARB_framebuffer_object) { glGenTextures(1, &texID); glBindTexture(GL_TEXTURE_2D, texID); // We don't use mipmaps, but OpenGL will refuse to attach // the texture to the framebuffer if they are not present glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); glBindTexture(GL_TEXTURE_2D, 0); glGenFramebuffersEXT(1, &fboID); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboID); glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texID, 0); GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT); } glViewport(0, 0, width, height); // Setup the screen projection glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, width, 0, height, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); // Clear glClearColor(((double)col_save_background.r)/255.f, ((double)col_save_background.g)/255.f, ((double)col_save_background.b)/255.f, ((double)col_save_background.a)/255.f); glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT); // Translate to inside of pixel (otherwise inaccuracies can occur on certain gl implementations) if (OpenGL::accuracyTweak()) glTranslatef(0.375f, 0.375f, 0); // Zoom/offset to show full map // Offset to center of map offset_x = m_min.x + (mapwidth * 0.5); offset_y = m_min.y + (mapheight * 0.5); // Zoom to fit whole map double x_scale = ((double)width) / mapwidth; double y_scale = ((double)height) / mapheight; zoom = MIN(x_scale, y_scale); zoom *= 0.95; // Translate to middle of canvas glTranslated(width>>1, height>>1, 0); // Zoom glScaled(zoom, zoom, 1); // Translate to offset glTranslated(-offset_x, -offset_y, 0); // Setup drawing glDisable(GL_TEXTURE_2D); glColor4f(1.0f, 1.0f, 1.0f, 1.0f); glLineWidth(map_image_thickness); glEnable(GL_LINE_SMOOTH); // Draw lines for (unsigned a = 0; a < lines.size(); a++) { mep_line_t line = lines[a]; // Check ends if (line.v1 >= verts.size() || line.v2 >= verts.size()) continue; // Get vertices mep_vertex_t v1 = verts[lines[a].v1]; mep_vertex_t v2 = verts[lines[a].v2]; // Set colour if (line.special) OpenGL::setColour(col_save_line_special); else if (line.macro) OpenGL::setColour(col_save_line_macro); else if (line.twosided) OpenGL::setColour(col_save_line_2s); else OpenGL::setColour(col_save_line_1s); // Draw line glBegin(GL_LINES); glVertex2d(v1.x, v1.y); glVertex2d(v2.x, v2.y); glEnd(); } glLineWidth(1.0f); glDisable(GL_LINE_SMOOTH); uint8_t* ImageBuffer = new uint8_t[width * height * 4]; glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, ImageBuffer); if (GLEW_ARB_framebuffer_object) { glBindFramebuffer(GL_FRAMEBUFFER, 0); glDeleteTextures( 1, &texID ); glDeleteFramebuffersEXT( 1, &fboID ); } SImage img; img.setImageData(ImageBuffer, width, height, RGBA); img.mirror(true); MemChunk mc; SIFormat::getFormat("png")->saveImage(img, mc); ae.importMemChunk(mc); }
/* LfdArchive::open * 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.getSize() < 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.getSize() < (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 theSplashWindow->setProgressMessage("Reading lfd archive data"); size_t offset = dir_len + 16; size_t size = mc.getSize(); for (uint32_t d = 0; offset < size; d++) { // Update splash window progress theSplashWindow->setProgress(((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) { wxLogMessage("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); ArchiveEntry* nlump = new ArchiveEntry(fn.GetFullName(), length); nlump->setLoaded(false); nlump->exProp("Offset") = (int)offset; nlump->setState(0); // Add to entry list getRoot()->addEntry(nlump); // Move to next entry offset += length; mc.seek(offset, SEEK_SET); } if (num_lumps != numEntries()) wxLogMessage("Warning: computed %i lumps, but actually %i entries", num_lumps, numEntries()); // 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)num_lumps))); // 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()); 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); } // Setup variables setMuted(false); setModified(false); announce("opened"); theSplashWindow->setProgressMessage(""); return true; }
/* PakArchive::open * Reads pak format data from a MemChunk * Returns true if successful, false otherwise *******************************************************************/ bool PakArchive::open(MemChunk& mc) { // Check given data is valid if (mc.getSize() < 12) return false; // Read pak header char pack[4]; long dir_offset; long dir_size; mc.seek(0, SEEK_SET); mc.read(pack, 4); mc.read(&dir_offset, 4); mc.read(&dir_size, 4); // Check it if (pack[0] != 'P' || pack[1] != 'A' || pack[2] != 'C' || pack[3] != 'K') { wxLogMessage("PakArchive::open: Opening failed, invalid header"); Global::error = "Invalid pak header"; return false; } // Stop announcements (don't want to be announcing modification due to entries being added etc) setMuted(true); // Read the directory size_t num_entries = dir_size / 64; mc.seek(dir_offset, SEEK_SET); theSplashWindow->setProgressMessage("Reading pak archive data"); for (uint32_t d = 0; d < num_entries; d++) { // Update splash window progress theSplashWindow->setProgress(((float)d / (float)num_entries)); // Read entry info char name[56]; long offset; long size; mc.read(name, 56); mc.read(&offset, 4); mc.read(&size, 4); // Byteswap if needed offset = wxINT32_SWAP_ON_BE(offset); size = wxINT32_SWAP_ON_BE(size); // Check offset+size if ((unsigned)(offset + size) > mc.getSize()) { wxLogMessage("PakArchive::open: Pak archive is invalid or corrupt (entry goes past end of file)"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Parse name wxFileName fn(wxString::FromAscii(name, 56)); // Create directory if needed ArchiveTreeNode* dir = createDir(fn.GetPath(true, wxPATH_UNIX)); // Create entry ArchiveEntry* entry = new ArchiveEntry(fn.GetFullName(), size); entry->exProp("Offset") = (int)offset; entry->setLoaded(false); entry->setState(0); // Add to directory dir->addEntry(entry); } // Detect all entry types MemChunk edata; vector<ArchiveEntry*> all_entries; getEntryTreeAsList(all_entries); theSplashWindow->setProgressMessage("Detecting entry types"); for (size_t a = 0; a < all_entries.size(); a++) { // Update splash window progress theSplashWindow->setProgress((((float)a / (float)num_entries))); // Get entry ArchiveEntry* entry = all_entries[a]; // Read entry data if it isn't zero-sized if (entry->getSize() > 0) { // Read the entry data mc.exportMemChunk(edata, (int)entry->exProp("Offset"), entry->getSize()); 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); } // Setup variables setMuted(false); setModified(false); announce("opened"); theSplashWindow->setProgressMessage(""); return true; }
/* GfxEntryPanel::handleAction * Handles the action [id]. Returns true if the action was handled, * false otherwise *******************************************************************/ bool GfxEntryPanel::handleAction(string id) { // Don't handle actions if hidden if (!isActivePanel()) return false; // We're only interested in "pgfx_" actions if (!id.StartsWith("pgfx_")) return false; // Mirror if (id == "pgfx_mirror") { // Mirror X getImage()->mirror(false); // Update UI gfx_canvas->updateImageTexture(); gfx_canvas->Refresh(); // Update variables image_data_modified = true; setModified(); } // Flip else if (id == "pgfx_flip") { // Mirror Y getImage()->mirror(true); // Update UI gfx_canvas->updateImageTexture(); gfx_canvas->Refresh(); // Update variables image_data_modified = true; setModified(); } // Rotate else if (id == "pgfx_rotate") { // Prompt for rotation angle string angles[] = { "90", "180", "270" }; int choice = wxGetSingleChoiceIndex("Select rotation angle", "Rotate", 3, angles, 0); // Rotate image switch (choice) { case 0: getImage()->rotate(90); break; case 1: getImage()->rotate(180); break; case 2: getImage()->rotate(270); break; default: break; } // Update UI gfx_canvas->updateImageTexture(); gfx_canvas->Refresh(); // Update variables image_data_modified = true; setModified(); } // Translate else if (id == "pgfx_translate") { // Create translation editor dialog Palette8bit* pal = theMainWindow->getPaletteChooser()->getSelectedPalette(); TranslationEditorDialog ted(theMainWindow, pal, " Colour Remap", gfx_canvas->getImage()); // Create translation to edit ted.openTranslation(prev_translation); // Show the dialog if (ted.ShowModal() == wxID_OK) { // Apply translation to image getImage()->applyTranslation(&ted.getTranslation(), pal); // Update UI gfx_canvas->updateImageTexture(); gfx_canvas->Refresh(); // Update variables image_data_modified = true; gfx_canvas->updateImageTexture(); setModified(); prev_translation.copy(ted.getTranslation()); } } // Colourise else if (id == "pgfx_colourise") { Palette8bit* pal = theMainWindow->getPaletteChooser()->getSelectedPalette(); GfxColouriseDialog gcd(theMainWindow, entry, pal); gcd.setColour(last_colour); // Show colourise dialog if (gcd.ShowModal() == wxID_OK) { // Colourise image getImage()->colourise(gcd.getColour(), pal); // Update UI gfx_canvas->updateImageTexture(); gfx_canvas->Refresh(); // Update variables image_data_modified = true; Refresh(); setModified(); } rgba_t gcdcol = gcd.getColour(); last_colour = S_FMT("RGB(%d, %d, %d)", gcdcol.r, gcdcol.g, gcdcol.b); } // Tint else if (id == "pgfx_tint") { Palette8bit* pal = theMainWindow->getPaletteChooser()->getSelectedPalette(); GfxTintDialog gtd(theMainWindow, entry, pal); gtd.setValues(last_tint_colour, last_tint_amount); // Show tint dialog if (gtd.ShowModal() == wxID_OK) { // Tint image getImage()->tint(gtd.getColour(), gtd.getAmount(), pal); // Update UI gfx_canvas->updateImageTexture(); gfx_canvas->Refresh(); // Update variables image_data_modified = true; Refresh(); setModified(); } rgba_t gtdcol = gtd.getColour(); last_tint_colour = S_FMT("RGB(%d, %d, %d)", gtdcol.r, gtdcol.g, gtdcol.b); last_tint_amount = (int)(gtd.getAmount() * 100.0); } // Crop else if (id == "pgfx_crop") { Palette8bit* pal = theMainWindow->getPaletteChooser()->getSelectedPalette(); GfxCropDialog gcd(theMainWindow, entry, pal); // Show crop dialog if (gcd.ShowModal() == wxID_OK) { // stuff } } // alPh/tRNS else if (id == "pgfx_alph" || id == "pgfx_trns") { setModified(); Refresh(); } // Optimize PNG else if (id == "pgfx_pngopt") { // This is a special case. If we set the entry as modified, SLADE will prompt // to save it, rewriting the entry and cancelling the optimization done... if (EntryOperations::optimizePNG(entry)) setModified(false); else wxMessageBox("Warning: Couldn't optimize this image, check console log for info", "Warning", wxOK|wxCENTRE|wxICON_WARNING); Refresh(); } // Extract all else if (id == "pgfx_extract") { extractAll(); } // Convert else if (id == "pgfx_convert") { GfxConvDialog gcd(theMainWindow); gcd.CenterOnParent(); gcd.openEntry(entry); gcd.ShowModal(); if (gcd.itemModified(0)) { // Get image and conversion info SImage* image = gcd.getItemImage(0); SIFormat* format = gcd.getItemFormat(0); // Write converted image back to entry format->saveImage(*image, entry_data, gcd.getItemPalette(0)); // This makes the "save" button (and the setModified stuff) redundant and confusing! // The alternative is to save to entry effectively (uncomment the importMemChunk line) // but remove the setModified and image_data_modified lines, and add a call to refresh // to get the PNG tRNS status back in sync. //entry->importMemChunk(entry_data); image_data_modified = true; setModified(); // Fix tRNS status if we converted to paletted PNG int MENU_GFXEP_PNGOPT = theApp->getAction("pgfx_pngopt")->getWxId(); int MENU_GFXEP_ALPH = theApp->getAction("pgfx_alph")->getWxId(); int MENU_GFXEP_TRNS = theApp->getAction("pgfx_trns")->getWxId(); int MENU_ARCHGFX_EXPORTPNG = theApp->getAction("arch_gfx_exportpng")->getWxId(); if (format->getName() == "PNG") { ArchiveEntry temp; temp.importMemChunk(entry_data); temp.setType(EntryType::getType("png")); menu_custom->Enable(MENU_GFXEP_ALPH, true); menu_custom->Enable(MENU_GFXEP_TRNS, true); menu_custom->Check(MENU_GFXEP_TRNS, EntryOperations::gettRNSChunk(&temp)); menu_custom->Enable(MENU_ARCHGFX_EXPORTPNG, false); menu_custom->Enable(MENU_GFXEP_PNGOPT, true); toolbar->enableGroup("PNG", true); } else { menu_custom->Enable(MENU_GFXEP_ALPH, false); menu_custom->Enable(MENU_GFXEP_TRNS, false); menu_custom->Enable(MENU_ARCHGFX_EXPORTPNG, true); menu_custom->Enable(MENU_GFXEP_PNGOPT, false); toolbar->enableGroup("PNG", false); } // Refresh getImage()->open(entry_data, 0, format->getId()); gfx_canvas->Refresh(); } } // Unknown action else return false; // Action handled return true; }
/* Wad2Archive::open * 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')) { wxLogMessage("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); 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 wad2entry_t 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.getSize()) { wxLogMessage("Wad2Archive::open: Wad2 archive is invalid or corrupt"); Global::error = "Archive is invalid and/or corrupt"; setMuted(false); return false; } // Create & setup lump ArchiveEntry* nlump = new 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(0); // Add to entry list getRoot()->addEntry(nlump); } // 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)num_lumps))); // 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, (int)entry->exProp("Offset"), entry->getSize()); 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); } // Detect maps (will detect map entry types) theSplashWindow->setProgressMessage("Detecting maps"); detectMaps(); // Setup variables setMuted(false); setModified(false); announce("opened"); theSplashWindow->setProgressMessage(""); return true; }
// ---------------------------------------------------------------------------- // Handles the action [id]. // Returns true if the action was handled, false otherwise // ---------------------------------------------------------------------------- bool GfxEntryPanel::handleEntryPanelAction(std::string_view id) { // We're only interested in "pgfx_" actions if (!StrUtil::startsWith(id, "pgfx_")) return false; // For pgfx_brush actions, the string after pgfx is a brush name if (StrUtil::startsWith(id, "pgfx_brush")) { gfx_canvas_->setBrush(SBrush::get(std::string{ id })); button_brush_->setIcon(StrUtil::afterFirst(id, '_')); } // Editing - drag mode else if (id == "pgfx_drag") { editing_ = false; gfx_canvas_->setEditingMode(GfxCanvas::EditMode::None); } // Editing - draw mode else if (id == "pgfx_draw") { editing_ = true; gfx_canvas_->setEditingMode(GfxCanvas::EditMode::Paint); gfx_canvas_->setPaintColour(cb_colour_->colour()); } // Editing - erase mode else if (id == "pgfx_erase") { editing_ = true; gfx_canvas_->setEditingMode(GfxCanvas::EditMode::Erase); } // Editing - translate mode else if (id == "pgfx_magic") { editing_ = true; gfx_canvas_->setEditingMode(GfxCanvas::EditMode::Translate); } // Editing - set translation else if (id == "pgfx_settrans") { // Create translation editor dialog TranslationEditorDialog ted( theMainWindow, *theMainWindow->paletteChooser()->selectedPalette(), " Colour Remap", image()); // Create translation to edit ted.openTranslation(edit_translation_); // Show the dialog if (ted.ShowModal() == wxID_OK) { // Set the translation edit_translation_.copy(ted.getTranslation()); gfx_canvas_->setTranslation(&edit_translation_); } } // Editing - set brush else if (id == "pgfx_setbrush") { auto p = button_brush_->GetScreenPosition() -= GetScreenPosition(); p.y += button_brush_->GetMaxHeight(); PopupMenu(menu_brushes_, p); } // Mirror else if (id == "pgfx_mirror") { // Mirror X image()->mirror(false); // Update UI gfx_canvas_->updateImageTexture(); gfx_canvas_->Refresh(); // Update variables image_data_modified_ = true; setModified(); } // Flip else if (id == "pgfx_flip") { // Mirror Y image()->mirror(true); // Update UI gfx_canvas_->updateImageTexture(); gfx_canvas_->Refresh(); // Update variables image_data_modified_ = true; setModified(); } // Rotate else if (id == "pgfx_rotate") { // Prompt for rotation angle wxString angles[] = { "90", "180", "270" }; int choice = wxGetSingleChoiceIndex("Select rotation angle", "Rotate", 3, angles, 0); // Rotate image switch (choice) { case 0: image()->rotate(90); break; case 1: image()->rotate(180); break; case 2: image()->rotate(270); break; default: break; } // Update UI gfx_canvas_->updateImageTexture(); gfx_canvas_->Refresh(); // Update variables image_data_modified_ = true; setModified(); } // Translate else if (id == "pgfx_remap") { // Create translation editor dialog auto pal = MainEditor::currentPalette(); TranslationEditorDialog ted(theMainWindow, *pal, " Colour Remap", &gfx_canvas_->image()); // Create translation to edit ted.openTranslation(prev_translation_); // Show the dialog if (ted.ShowModal() == wxID_OK) { // Apply translation to image image()->applyTranslation(&ted.getTranslation(), pal); // Update UI gfx_canvas_->updateImageTexture(); gfx_canvas_->Refresh(); // Update variables image_data_modified_ = true; gfx_canvas_->updateImageTexture(); setModified(); prev_translation_.copy(ted.getTranslation()); } } // Colourise else if (id == "pgfx_colourise") { auto pal = MainEditor::currentPalette(); GfxColouriseDialog gcd(theMainWindow, entry_, *pal); gcd.setColour(last_colour); // Show colourise dialog if (gcd.ShowModal() == wxID_OK) { // Colourise image image()->colourise(gcd.colour(), pal); // Update UI gfx_canvas_->updateImageTexture(); gfx_canvas_->Refresh(); // Update variables image_data_modified_ = true; Refresh(); setModified(); } last_colour = gcd.colour().toString(ColRGBA::StringFormat::RGB); } // Tint else if (id == "pgfx_tint") { auto pal = MainEditor::currentPalette(); GfxTintDialog gtd(theMainWindow, entry_, *pal); gtd.setValues(last_tint_colour, last_tint_amount); // Show tint dialog if (gtd.ShowModal() == wxID_OK) { // Tint image image()->tint(gtd.colour(), gtd.amount(), pal); // Update UI gfx_canvas_->updateImageTexture(); gfx_canvas_->Refresh(); // Update variables image_data_modified_ = true; Refresh(); setModified(); } last_tint_colour = gtd.colour().toString(ColRGBA::StringFormat::RGB); last_tint_amount = (int)(gtd.amount() * 100.0); } // Crop else if (id == "pgfx_crop") { auto image = this->image(); auto pal = MainEditor::currentPalette(); GfxCropDialog gcd(theMainWindow, image, pal); // Show crop dialog if (gcd.ShowModal() == wxID_OK) { // Prompt to adjust offsets auto crop = gcd.cropRect(); if (crop.tl.x > 0 || crop.tl.y > 0) { if (wxMessageBox( "Do you want to adjust the offsets? This will keep the graphic in the same relative " "position it was before cropping.", "Adjust Offsets?", wxYES_NO) == wxYES) { image->setXOffset(image->offset().x - crop.tl.x); image->setYOffset(image->offset().y - crop.tl.y); } } // Crop image image->crop(crop.x1(), crop.y1(), crop.x2(), crop.y2()); // Update UI gfx_canvas_->updateImageTexture(); gfx_canvas_->Refresh(); // Update variables image_data_modified_ = true; Refresh(); setModified(); } } // alPh/tRNS else if (id == "pgfx_alph" || id == "pgfx_trns") { setModified(); Refresh(); } // Optimize PNG else if (id == "pgfx_pngopt") { // This is a special case. If we set the entry as modified, SLADE will prompt // to save it, rewriting the entry and cancelling the optimization done... if (EntryOperations::optimizePNG(entry_)) setModified(false); else wxMessageBox( "Warning: Couldn't optimize this image, check console log for info", "Warning", wxOK | wxCENTRE | wxICON_WARNING); Refresh(); } // Extract all else if (id == "pgfx_extract") { extractAll(); } // Convert else if (id == "pgfx_convert") { GfxConvDialog gcd(theMainWindow); gcd.CenterOnParent(); gcd.openEntry(entry_); gcd.ShowModal(); if (gcd.itemModified(0)) { // Get image and conversion info auto image = gcd.itemImage(0); auto format = gcd.itemFormat(0); // Write converted image back to entry format->saveImage(*image, entry_data_, gcd.itemPalette(0)); // This makes the "save" button (and the setModified stuff) redundant and confusing! // The alternative is to save to entry effectively (uncomment the importMemChunk line) // but remove the setModified and image_data_modified lines, and add a call to refresh // to get the PNG tRNS status back in sync. // entry->importMemChunk(entry_data); image_data_modified_ = true; setModified(); // Fix tRNS status if we converted to paletted PNG int MENU_GFXEP_PNGOPT = SAction::fromId("pgfx_pngopt")->wxId(); int MENU_GFXEP_ALPH = SAction::fromId("pgfx_alph")->wxId(); int MENU_GFXEP_TRNS = SAction::fromId("pgfx_trns")->wxId(); int MENU_ARCHGFX_EXPORTPNG = SAction::fromId("arch_gfx_exportpng")->wxId(); if (format->name() == "PNG") { ArchiveEntry temp; temp.importMemChunk(entry_data_); temp.setType(EntryType::fromId("png")); menu_custom_->Enable(MENU_GFXEP_ALPH, true); menu_custom_->Enable(MENU_GFXEP_TRNS, true); menu_custom_->Check(MENU_GFXEP_TRNS, EntryOperations::gettRNSChunk(&temp)); menu_custom_->Enable(MENU_ARCHGFX_EXPORTPNG, false); menu_custom_->Enable(MENU_GFXEP_PNGOPT, true); toolbar_->enableGroup("PNG", true); } else { menu_custom_->Enable(MENU_GFXEP_ALPH, false); menu_custom_->Enable(MENU_GFXEP_TRNS, false); menu_custom_->Enable(MENU_ARCHGFX_EXPORTPNG, true); menu_custom_->Enable(MENU_GFXEP_PNGOPT, false); toolbar_->enableGroup("PNG", false); } // Refresh this->image()->open(entry_data_, 0, format->id()); gfx_canvas_->Refresh(); } } // Unknown action else return false; // Action handled return true; }