// ----------------------------------------------------------------------------- // Removes any patches and associated entries from [archive] that are not used // in any texture definitions // ----------------------------------------------------------------------------- bool ArchiveOperations::removeUnusedPatches(Archive* archive) { if (!archive) return false; // Find PNAMES entry Archive::SearchOptions opt; opt.match_type = EntryType::fromId("pnames"); ArchiveEntry* pnames = archive->findLast(opt); // Find TEXTUREx entries opt.match_type = EntryType::fromId("texturex"); vector<ArchiveEntry*> tx_entries = archive->findAll(opt); // Can't do anything without PNAMES/TEXTUREx if (!pnames || tx_entries.size() == 0) return false; // Open patch table PatchTable ptable; ptable.loadPNAMES(pnames, archive); // Open texturex entries to update patch usage vector<TextureXList*> tx_lists; for (unsigned a = 0; a < tx_entries.size(); a++) { TextureXList* texturex = new TextureXList(); texturex->readTEXTUREXData(tx_entries[a], ptable); for (unsigned t = 0; t < texturex->nTextures(); t++) ptable.updatePatchUsage(texturex->getTexture(t)); tx_lists.push_back(texturex); } // Go through patch table unsigned removed = 0; vector<ArchiveEntry*> to_remove; for (unsigned a = 0; a < ptable.nPatches(); a++) { auto& p = ptable.patch(a); // Check if used in any texture if (p.used_in.size() == 0) { // Unused // If its entry is in the archive, flag it to be removed ArchiveEntry* entry = theResourceManager->getPatchEntry(p.name, "patches", archive); if (entry && entry->getParent() == archive) to_remove.push_back(entry); // Update texturex list patch indices for (unsigned t = 0; t < tx_lists.size(); t++) tx_lists[t]->removePatch(p.name); // Remove the patch from the patch table LOG_MESSAGE(1, "Removed patch %s", p.name); removed++; ptable.removePatch(a--); } } // Remove unused patch entries for (unsigned a = 0; a < to_remove.size(); a++) { LOG_MESSAGE(1, "Removed entry %s", to_remove[a]->getName()); archive->removeEntry(to_remove[a]); } // Write PNAMES changes ptable.writePNAMES(pnames); // Write TEXTUREx changes for (unsigned a = 0; a < tx_lists.size(); a++) tx_lists[a]->writeTEXTUREXData(tx_entries[a], ptable); // Cleanup for (unsigned a = 0; a < tx_lists.size(); a++) delete tx_lists[a]; // Notify user wxMessageBox( S_FMT("Removed %d patches and %lu entries. See console log for details.", removed, to_remove.size()), "Removed Unused Patches", wxOK | wxICON_INFORMATION); return true; }
void ArchiveOperations::removeUnusedTextures(Archive* archive) { // Check archive was given if (!archive) return; // --- Build list of used textures --- TexUsedMap used_textures; int total_maps = 0; // Get all SIDEDEFS entries Archive::SearchOptions opt; opt.match_type = EntryType::fromId("map_sidedefs"); vector<ArchiveEntry*> sidedefs = archive->findAll(opt); total_maps += sidedefs.size(); // Go through and add used textures to list doomside_t sdef; string tex_lower, tex_middle, tex_upper; for (unsigned a = 0; a < sidedefs.size(); a++) { int nsides = sidedefs[a]->getSize() / 30; sidedefs[a]->seek(0, SEEK_SET); for (int s = 0; s < nsides; s++) { // Read side data sidedefs[a]->read(&sdef, 30); // Get textures tex_lower = wxString::FromAscii(sdef.tex_lower, 8); tex_middle = wxString::FromAscii(sdef.tex_middle, 8); tex_upper = wxString::FromAscii(sdef.tex_upper, 8); // Add to used textures list used_textures[tex_lower].used = true; used_textures[tex_middle].used = true; used_textures[tex_upper].used = true; } } // Get all TEXTMAP entries opt.match_name = "TEXTMAP"; opt.match_type = EntryType::fromId("udmf_textmap"); vector<ArchiveEntry*> udmfmaps = archive->findAll(opt); total_maps += udmfmaps.size(); // Go through and add used textures to list Tokenizer tz; tz.setSpecialCharacters("{};="); for (unsigned a = 0; a < udmfmaps.size(); a++) { // Open in tokenizer tz.openMem(udmfmaps[a]->getMCData(), "UDMF TEXTMAP"); // Go through text tokens string token = tz.getToken(); while (!token.IsEmpty()) { // Check for sidedef definition if (token == "sidedef") { tz.getToken(); // Skip { token = tz.getToken(); while (token != "}") { // Check for texture property if (token == "texturetop" || token == "texturemiddle" || token == "texturebottom") { tz.getToken(); // Skip = used_textures[tz.getToken()].used = true; } token = tz.getToken(); } } // Next token token = tz.getToken(); } } // Check if any maps were found if (total_maps == 0) return; // Find all TEXTUREx entries opt.match_name = ""; opt.match_type = EntryType::fromId("texturex"); vector<ArchiveEntry*> tx_entries = archive->findAll(opt); // Go through texture lists PatchTable ptable; // Dummy patch table, patch info not needed here wxArrayString unused_tex; for (unsigned a = 0; a < tx_entries.size(); a++) { TextureXList txlist; txlist.readTEXTUREXData(tx_entries[a], ptable); // Go through textures bool anim = false; for (unsigned t = 1; t < txlist.nTextures(); t++) { string texname = txlist.getTexture(t)->getName(); // Check for animation start for (int b = 0; b < n_tex_anim; b++) { if (texname == tex_anim_start[b]) { anim = true; break; } } // Check for animation end bool thisend = false; for (int b = 0; b < n_tex_anim; b++) { if (texname == tex_anim_end[b]) { anim = false; thisend = true; break; } } // Mark if unused and not part of an animation if (!used_textures[texname].used && !anim && !thisend) unused_tex.Add(txlist.getTexture(t)->getName()); } } // Pop up a dialog with a checkbox list of unused textures wxMultiChoiceDialog dialog( theMainWindow, "The following textures are not used in any map,\nselect which textures to delete", "Delete Unused Textures", unused_tex); // Get base resource textures (if any) Archive* base_resource = App::archiveManager().baseResourceArchive(); vector<ArchiveEntry*> base_tx_entries; if (base_resource) base_tx_entries = base_resource->findAll(opt); PatchTable pt_temp; TextureXList tx; for (unsigned a = 0; a < base_tx_entries.size(); a++) tx.readTEXTUREXData(base_tx_entries[a], pt_temp, true); vector<string> base_resource_textures; for (unsigned a = 0; a < tx.nTextures(); a++) base_resource_textures.push_back(tx.getTexture(a)->getName()); // Determine which textures to check initially wxArrayInt selection; for (unsigned a = 0; a < unused_tex.size(); a++) { bool swtex = false; // Check for switch texture if (unused_tex[a].StartsWith("SW1")) { // Get counterpart switch name string swname = unused_tex[a]; swname.Replace("SW1", "SW2", false); // Check if its counterpart is used if (used_textures[swname].used) swtex = true; } else if (unused_tex[a].StartsWith("SW2")) { // Get counterpart switch name string swname = unused_tex[a]; swname.Replace("SW2", "SW1", false); // Check if its counterpart is used if (used_textures[swname].used) swtex = true; } // Check for base resource texture bool br_tex = false; for (unsigned b = 0; b < base_resource_textures.size(); b++) { if (base_resource_textures[b].CmpNoCase(unused_tex[a]) == 0) { LOG_MESSAGE(3, "Texture " + base_resource_textures[b] + " is in base resource"); br_tex = true; break; } } if (!swtex && !br_tex) selection.Add(a); } dialog.SetSelections(selection); int n_removed = 0; if (dialog.ShowModal() == wxID_OK) { // Get selected textures selection = dialog.GetSelections(); // Go through texture lists for (unsigned a = 0; a < tx_entries.size(); a++) { TextureXList txlist; txlist.readTEXTUREXData(tx_entries[a], ptable); // Go through selected textures to delete for (unsigned t = 0; t < selection.size(); t++) { // Get texture index int index = txlist.textureIndex(unused_tex[selection[t]]); // Delete it from the list (if found) if (index >= 0) { txlist.removeTexture(index); n_removed++; } } // Write texture list data back to entry txlist.writeTEXTUREXData(tx_entries[a], ptable); } } wxMessageBox(S_FMT("Removed %d unused textures", n_removed)); }