// Near Infinity - An Infinity Engine Browser and Editor // Copyright (C) 2001 - 2005 Jon Olav Hauglid // See LICENSE.txt for license information package org.infinity.gui; import java.awt.Color; import java.awt.Container; import java.awt.FlowLayout; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.GridLayout; import java.awt.Insets; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.io.OutputStream; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; import javax.swing.BorderFactory; import javax.swing.JButton; import javax.swing.JCheckBox; import javax.swing.JComboBox; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import org.infinity.NearInfinity; import org.infinity.icon.Icons; import org.infinity.resource.Profile; import org.infinity.resource.ResourceFactory; import org.infinity.resource.key.BIFFEntry; import org.infinity.resource.key.BIFFResourceEntry; import org.infinity.resource.key.BIFFWriter; import org.infinity.resource.key.FileResourceEntry; import org.infinity.resource.key.ResourceEntry; import org.infinity.util.io.FileManager; import org.infinity.util.io.StreamUtils; public final class BIFFEditor implements ActionListener, ListSelectionListener, Runnable { public static final int BIFF = 0; public static final int BIF = 1; public static final int BIFC = 2; private static final String[] s_bifformat = {"BIFF", "BIF", "BIFC"}; private static boolean firstrun = true; private final BIFFEditorTable biftable = new BIFFEditorTable(); private final BIFFEditorTable overridetable = new BIFFEditorTable(); private final List<BIFFResourceEntry> origbiflist = new ArrayList<BIFFResourceEntry>(); private BIFFEntry bifentry; private ChildFrame editframe; private JButton bcancel, bsave, btobif, bfrombif; private JComboBox<String> cbformat; private int format; public BIFFEditor() { if (firstrun) { JOptionPane.showMessageDialog(NearInfinity.getInstance(), "Make sure you have a backup of " + Profile.getChitinKey().toString(), "Warning", JOptionPane.WARNING_MESSAGE); } firstrun = false; new ChooseBIFFrame(this); } // --------------------- Begin Interface ActionListener --------------------- @Override public void actionPerformed(ActionEvent event) { if (event.getSource() == bcancel) { editframe.close(); } else if (event.getSource() == bsave) { editframe.close(); String s_format = (String)cbformat.getSelectedItem(); for (int i = 0; i < s_bifformat.length; i++) if (s_format.equals(s_bifformat[i])) { format = i; break; } new Thread(this).start(); } else if (event.getSource() == btobif) { Object selected[] = overridetable.getSelectedValues(); for (final Object value : selected) { if (biftable.addTableLine(value)) overridetable.removeTableLine(value); } bsave.setEnabled(!biftable.isEmpty()); } else if (event.getSource() == bfrombif) { Object selected[] = biftable.getSelectedValues(); for (final Object value : selected) { if (overridetable.addTableLine(value)) biftable.removeTableLine(value); } bsave.setEnabled(!biftable.isEmpty()); } else if (event.getSource() == cbformat) { bsave.setEnabled(!biftable.isEmpty()); } } // --------------------- End Interface ActionListener --------------------- // --------------------- Begin Interface ListSelectionListener --------------------- @Override public void valueChanged(ListSelectionEvent event) { if (!event.getValueIsAdjusting()) { bfrombif.setEnabled(biftable.getSelectedValues().length != 0); btobif.setEnabled(overridetable.getSelectedValues().length != 0); } } // --------------------- End Interface ListSelectionListener --------------------- // --------------------- Begin Interface Runnable --------------------- @Override public void run() { WindowBlocker blocker = new WindowBlocker(NearInfinity.getInstance()); BifSaveProgress progress = new BifSaveProgress(); blocker.setBlocked(true); // 1: Delete old entries from keyfile for (int i = 0; i < origbiflist.size(); i++) { ResourceFactory.getResources().removeResourceEntry(origbiflist.get(i)); } progress.setProgress(1, true); // 2: Extract files from BIF (if applicable) List<ResourceEntry> overrideBif = overridetable.getValueList(BIFFEditorTable.TYPE_BIF); for (int i = 0; i < overrideBif.size(); i++) { ResourceEntry entry = overrideBif.get(i); Path file = FileManager.query(Profile.getRootFolders(), Profile.getOverrideFolderName(), entry.toString()); try (OutputStream os = StreamUtils.getOutputStream(file, true)) { StreamUtils.writeBytes(os, entry.getResourceBuffer(true)); } catch (Exception e) { progress.setProgress(2, false); JOptionPane.showMessageDialog(editframe, "Error while extracting files from " + bifentry, "Error", JOptionPane.ERROR_MESSAGE); e.printStackTrace(); blocker.setBlocked(false); return; } FileResourceEntry fileEntry = new FileResourceEntry(file, true); ResourceFactory.getResources().addResourceEntry(fileEntry, fileEntry.getTreeFolder(), true); } progress.setProgress(2, true); // 3: Write new BIF BIFFWriter biffwriter = new BIFFWriter(bifentry, format); List<ResourceEntry> bifBif = biftable.getValueList(BIFFEditorTable.TYPE_BIF); for (int i = 0; i < bifBif.size(); i++) { biffwriter.addResource(bifBif.get(i), true); // Ignore overrides } List<ResourceEntry> tobif = biftable.getValueList(BIFFEditorTable.TYPE_NEW); tobif.addAll(biftable.getValueList(BIFFEditorTable.TYPE_UPD)); for (int i = 0; i < tobif.size(); i++) { biffwriter.addResource(tobif.get(i), false); } try { biffwriter.write(); progress.setProgress(3, true); } catch (Exception e) { progress.setProgress(3, false); JOptionPane.showMessageDialog(editframe, "Error while saving " + bifentry, "Error", JOptionPane.ERROR_MESSAGE); e.printStackTrace(); blocker.setBlocked(false); return; } // 4: Delete old files from override for (int i = 0; i < tobif.size(); i++) { Path file = FileManager.query(Profile.getRootFolders(), Profile.getOverrideFolderName(), tobif.get(i).toString()); if (file != null && Files.isRegularFile(file)) { try { Files.delete(file); } catch (IOException e) { e.printStackTrace(); } } } progress.setProgress(4, true); // 5: Add new OverrideResourceEntries (ResourceEntries deleted from BIF) origbiflist.removeAll(biftable.getValueList(BIFFEditorTable.TYPE_BIF)); origbiflist.removeAll(overridetable.getValueList(BIFFEditorTable.TYPE_BIF)); for (int i = 0; i < origbiflist.size(); i++) { Path file = FileManager.query(Profile.getRootFolders(), Profile.getOverrideFolderName(), origbiflist.get(i).toString()); FileResourceEntry fileEntry = new FileResourceEntry(file, true); ResourceFactory.getResources().addResourceEntry(fileEntry, fileEntry.getTreeFolder(), true); } progress.setProgress(5, true); // 6: Write keyfile try { ResourceFactory.getKeyfile().write(); progress.setProgress(6, true); } catch (IOException e) { progress.setProgress(6, false); JOptionPane.showMessageDialog(editframe, "Error while saving keyfile", "Error", JOptionPane.ERROR_MESSAGE); e.printStackTrace(); } ResourceFactory.getResources().sort(); blocker.setBlocked(false); } // --------------------- End Interface Runnable --------------------- public void makeEditor(BIFFEntry bifentry, int format) { this.bifentry = bifentry; this.format = format; editframe = new ChildFrame("Edit BIFF", true); editframe.setIconImage(Icons.getIcon(Icons.ICON_EDIT_16).getImage()); Container pane = editframe.getContentPane(); GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); pane.setLayout(gbl); for (final ResourceEntry entry : ResourceFactory.getResources().getResourceEntries()) { if ((entry instanceof FileResourceEntry || entry.hasOverride()) && StreamUtils.splitFileName(entry.toString())[1].length() <= 8 && ResourceFactory.getKeyfile().getExtensionType(entry.getExtension()) != -1) { overridetable.addEntry(entry, BIFFEditorTable.TYPE_NEW); } else if (bifentry.getIndex() != -1 && entry instanceof BIFFResourceEntry) { BIFFResourceEntry bentry = (BIFFResourceEntry)entry; if (bentry.getBIFFEntry() == bifentry) { biftable.addEntry(bentry, BIFFEditorTable.TYPE_BIF); origbiflist.add(bentry); if (bentry.hasOverride()) { overridetable.addEntry(entry, BIFFEditorTable.TYPE_UPD); } } } } overridetable.sortTable(); biftable.sortTable(); biftable.addListSelectionListener(this); overridetable.addListSelectionListener(this); bcancel = new JButton("Cancel", Icons.getIcon(Icons.ICON_DELETE_16)); bsave = new JButton("Save", Icons.getIcon(Icons.ICON_SAVE_16)); bcancel.setMnemonic('c'); bsave.setMnemonic('s'); btobif = new JButton(Icons.getIcon(Icons.ICON_BACK_16)); bfrombif = new JButton(Icons.getIcon(Icons.ICON_FORWARD_16)); biftable.setBorder(BorderFactory.createTitledBorder("Files in " + bifentry.toString())); overridetable.setBorder(BorderFactory.createTitledBorder("Files in override")); JPanel bpanel1 = new JPanel(new GridLayout(2, 1, 6, 6)); bpanel1.add(btobif); bpanel1.add(bfrombif); JPanel bpanel2 = new JPanel(new FlowLayout(FlowLayout.RIGHT)); bpanel2.add(bsave); bpanel2.add(bcancel); List<String> formats = new ArrayList<String>(); formats.add(s_bifformat[BIFF]); if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_BIF)) { formats.add(s_bifformat[BIF]); } if ((Boolean)Profile.getProperty(Profile.Key.IS_SUPPORTED_BIFC)) { formats.add(s_bifformat[BIFC]); } cbformat = new JComboBox<>(formats.toArray(new String[formats.size()])); cbformat.addActionListener(this); if (format != BIFF) { cbformat.setSelectedIndex(1); } else { cbformat.setSelectedIndex(0); } JPanel bpanel3 = new JPanel(new FlowLayout(FlowLayout.LEFT)); bpanel3.add(new JLabel("Format: ")); bpanel3.add(cbformat); // cbformat.setEnabled(false); // Temporary while I figure things out btobif.addActionListener(this); bfrombif.addActionListener(this); bsave.addActionListener(this); bcancel.addActionListener(this); btobif.setEnabled(false); bfrombif.setEnabled(false); bsave.setEnabled(false); gbc.weightx = 1.0; gbc.weighty = 1.0; gbc.fill = GridBagConstraints.BOTH; gbc.gridwidth = 1; gbc.insets = new Insets(6, 6, 6, 6); gbl.setConstraints(biftable, gbc); pane.add(biftable); gbc.weightx = 0.0; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints(bpanel1, gbc); pane.add(bpanel1); gbc.weightx = 1.0; gbc.fill = GridBagConstraints.BOTH; gbc.gridwidth = GridBagConstraints.REMAINDER; gbl.setConstraints(overridetable, gbc); pane.add(overridetable); gbc.gridwidth = 2; gbc.weighty = 0.0; gbc.fill = GridBagConstraints.HORIZONTAL; gbl.setConstraints(bpanel3, gbc); pane.add(bpanel3); gbc.gridwidth = GridBagConstraints.REMAINDER; gbc.anchor = GridBagConstraints.EAST; gbl.setConstraints(bpanel2, gbc); pane.add(bpanel2); editframe.setSize(550, 550); Center.center(editframe, NearInfinity.getInstance().getBounds()); editframe.setVisible(true); } // -------------------------- INNER CLASSES -------------------------- private static final class BifSaveProgress extends JFrame implements ActionListener { private final JCheckBox[] boxes = new JCheckBox[6]; private final JLabel[] labels = new JLabel[6]; private final JButton bok = new JButton("Ok"); private BifSaveProgress() { super("Progress"); labels[0] = new JLabel("Remove old entries"); labels[1] = new JLabel("Extract files"); labels[2] = new JLabel("Write new BIFF"); labels[3] = new JLabel("Remove old files"); labels[4] = new JLabel("Add new files"); labels[5] = new JLabel("Write new keyfile"); bok.addActionListener(this); bok.setEnabled(false); Container pane = getContentPane(); GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); pane.setLayout(gbl); gbc.insets = new Insets(6, 6, 6, 6); gbc.weightx = 0.0; gbc.weighty = 0.0; gbc.fill = GridBagConstraints.NONE; for (int i = 0; i < boxes.length; i++) { boxes[i] = new JCheckBox(); boxes[i].setEnabled(false); gbc.gridwidth = 1; gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints(boxes[i], gbc); pane.add(boxes[i]); gbc.anchor = GridBagConstraints.WEST; gbc.gridwidth = GridBagConstraints.REMAINDER; gbl.setConstraints(labels[i], gbc); pane.add(labels[i]); } gbc.anchor = GridBagConstraints.CENTER; gbl.setConstraints(bok, gbc); pane.add(bok); setSize(200, 280); Center.center(this, NearInfinity.getInstance().getBounds()); setVisible(true); } private void setProgress(int level, boolean ok) { if (ok) boxes[level - 1].setSelected(true); else boxes[level - 1].setForeground(Color.red); bok.setEnabled(level == boxes.length || !ok); } @Override public void actionPerformed(ActionEvent event) { if (event.getSource() == bok) setVisible(false); } } }