// Near Infinity - An Infinity Engine Browser and Editor
// Copyright (C) 2001 - 2005 Jon Olav Hauglid
// See LICENSE.txt for license information
package org.infinity.util;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Transparency;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.awt.image.RenderedImage;
import java.io.BufferedWriter;
import java.io.EOFException;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.ThreadPoolExecutor;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFileChooser;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ProgressMonitor;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import org.infinity.NearInfinity;
import org.infinity.gui.Center;
import org.infinity.gui.ChildFrame;
import org.infinity.gui.ViewerUtil;
import org.infinity.icon.Icons;
import org.infinity.resource.Profile;
import org.infinity.resource.ResourceFactory;
import org.infinity.resource.StructEntry;
import org.infinity.resource.Writeable;
import org.infinity.resource.bcs.Decompiler;
import org.infinity.resource.cre.CreResource;
import org.infinity.resource.graphics.BamDecoder;
import org.infinity.resource.graphics.BamResource;
import org.infinity.resource.graphics.ColorConvert;
import org.infinity.resource.graphics.Compressor;
import org.infinity.resource.graphics.MosDecoder;
import org.infinity.resource.graphics.MosV1Decoder;
import org.infinity.resource.graphics.PvrDecoder;
import org.infinity.resource.graphics.TisDecoder;
import org.infinity.resource.graphics.TisResource;
import org.infinity.resource.key.ResourceEntry;
import org.infinity.resource.sound.AudioFactory;
import org.infinity.resource.video.MveResource;
import org.infinity.util.io.FileManager;
import org.infinity.util.io.StreamUtils;
public final class MassExporter extends ChildFrame implements ActionListener, ListSelectionListener,
Runnable
{
private static final String FMT_PROGRESS = "Processing resource %d/%d";
private static final String TYPES[] = {"2DA", "ARE", "BAM", "BCS", "BS", "BIO", "BMP",
"CHU", "CHR", "CRE", "DLG", "EFF", "FNT", "GAM",
"GLSL", "GUI", "IDS", "INI", "ITM", "LUA", "MENU",
"MOS", "MVE", "PLT", "PNG", "PRO", "PVRZ", "SPL",
"SQL", "SRC", "STO", "TIS", "TOH", "TOT", "TTF",
"VEF", "VVC", "WAV", "WBM", "WED", "WFX", "WMP"};
private final JButton bExport = new JButton("Export", Icons.getIcon(Icons.ICON_EXPORT_16));
private final JButton bCancel = new JButton("Cancel", Icons.getIcon(Icons.ICON_DELETE_16));
private final JButton bDirectory = new JButton(Icons.getIcon(Icons.ICON_OPEN_16));
private final JCheckBox cbIncludeExtraDirs = new JCheckBox("Include extra folders", true);
private final JCheckBox cbDecompile = new JCheckBox("Decompile scripts", true);
private final JCheckBox cbDecrypt = new JCheckBox("Decrypt text files", true);
private final JCheckBox cbConvertWAV = new JCheckBox("Convert sounds", true);
private final JCheckBox cbConvertCRE = new JCheckBox("Convert CHR=>CRE", false);
private final JCheckBox cbDecompress = new JCheckBox("Decompress BAM/MOS", false);
private final JCheckBox cbConvertToPNG = new JCheckBox("Export MOS/PVRZ/TIS as PNG", false);
private final JCheckBox cbConvertTisVersion = new JCheckBox("Convert TIS to ", false);
private final JComboBox<String> cbConvertTisList = new JComboBox<>(new String[]{"Palette-based", "PVRZ-based"});
private final JCheckBox cbExtractFramesBAM = new JCheckBox("Export BAM frames as ", false);
private final JCheckBox cbExportMVEasAVI = new JCheckBox("Export MVE as AVI", false);
private final JCheckBox cbOverwrite = new JCheckBox("Overwrite existing files", false);
private final JFileChooser fc = new JFileChooser(Profile.getGameRoot().toFile());
private final JComboBox<String> cbExtractFramesBAMFormat = new JComboBox<>(new String[]{"PNG", "BMP"});
private final JList<String> listTypes = new JList<>(TYPES);
private final JTextField tfDirectory = new JTextField(20);
private Path outputPath;
private List<String> selectedTypes;
private ProgressMonitor progress;
private int progressIndex;
private List<ResourceEntry> selectedFiles;
public MassExporter()
{
super("Mass Exporter", true);
bExport.addActionListener(this);
bCancel.addActionListener(this);
bDirectory.addActionListener(this);
bExport.setEnabled(false);
tfDirectory.setEditable(false);
listTypes.addListSelectionListener(this);
fc.setDialogTitle("Mass export: Select directory");
fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
getRootPane().setDefaultButton(bExport);
bExport.setMnemonic('e');
bCancel.setMnemonic('d');
cbConvertToPNG.setToolTipText("Caution: Selecting both MOS and TIS may overwrite or skip some files!");
cbExtractFramesBAM.setToolTipText("Note: Frames of each BAM resource are exported into separate subfolders.");
cbConvertTisVersion.setToolTipText("Caution: Conversion may take a long time. Files may be renamed to conform to naming scheme for PVRZ-based TIS files.");
cbIncludeExtraDirs.setToolTipText("Include extra folders, such as \"Characters\" or \"Portraits\", except savegames.");
JPanel leftPanel = new JPanel(new BorderLayout());
leftPanel.add(new JLabel("File types to export:"), BorderLayout.NORTH);
leftPanel.add(new JScrollPane(listTypes), BorderLayout.CENTER);
JPanel topRightPanel = new JPanel(new BorderLayout());
topRightPanel.add(new JLabel("Output directory:"), BorderLayout.NORTH);
topRightPanel.add(tfDirectory, BorderLayout.CENTER);
topRightPanel.add(bDirectory, BorderLayout.EAST);
GridBagConstraints gbc = new GridBagConstraints();
JPanel bottomRightPanel = new JPanel(new GridBagLayout());
JPanel pBamFrames = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
pBamFrames.add(cbExtractFramesBAM);
pBamFrames.add(cbExtractFramesBAMFormat);
JPanel pTisConvert = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
pTisConvert.add(cbConvertTisVersion);
pTisConvert.add(cbConvertTisList);
gbc = ViewerUtil.setGBC(gbc, 0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0);
bottomRightPanel.add(new JLabel("Options:"), gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 1, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(cbIncludeExtraDirs, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 2, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(cbConvertWAV, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 3, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(cbConvertCRE, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 4, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(cbDecompile, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 5, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(cbDecrypt, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 6, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(cbDecompress, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 7, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(cbConvertToPNG, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 8, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(pTisConvert, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 9, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(pBamFrames, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 10, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(cbExportMVEasAVI, gbc);
gbc = ViewerUtil.setGBC(gbc, 0, 11, 1, 1, 1.0, 1.0, GridBagConstraints.FIRST_LINE_START,
GridBagConstraints.HORIZONTAL, new Insets(2, 0, 0, 0), 0, 0);
bottomRightPanel.add(cbOverwrite, gbc);
JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT));
bottomPanel.add(bExport);
bottomPanel.add(bCancel);
JPanel pane = (JPanel)getContentPane();
GridBagLayout gbl = new GridBagLayout();
// GridBagConstraints gbc = new GridBagConstraints();
gbc = new GridBagConstraints();
pane.setLayout(gbl);
gbc.weightx = 0.0;
gbc.weighty = 1.0;
gbc.gridheight = 2;
gbc.fill = GridBagConstraints.BOTH;
gbc.insets = new Insets(6, 6, 6, 6);
gbl.setConstraints(leftPanel, gbc);
pane.add(leftPanel);
gbc.gridheight = 1;
gbc.gridwidth = GridBagConstraints.REMAINDER;
gbc.weighty = 0.0;
gbc.weightx = 1.0;
gbl.setConstraints(topRightPanel, gbc);
pane.add(topRightPanel);
gbc.weighty = 1.0;
gbl.setConstraints(bottomRightPanel, gbc);
pane.add(bottomRightPanel);
gbc.insets = new Insets(0, 0, 0, 0);
gbc.weighty = 0.0;
gbc.weightx = 1.0;
gbl.setConstraints(bottomPanel, gbc);
pane.add(bottomPanel);
pack();
setMinimumSize(getPreferredSize());
Center.center(this, NearInfinity.getInstance().getBounds());
setVisible(true);
}
// --------------------- Begin Interface ActionListener ---------------------
@Override
public void actionPerformed(ActionEvent event)
{
if (event.getSource() == bExport) {
selectedTypes = listTypes.getSelectedValuesList();
outputPath = FileManager.resolve(tfDirectory.getText());
try {
Files.createDirectories(outputPath);
} catch (IOException e) {
JOptionPane.showMessageDialog(this, "Unable to create target directory.",
"Error", JOptionPane.ERROR_MESSAGE);
e.printStackTrace();
return;
}
setVisible(false);
new Thread(this).start();
}
else if (event.getSource() == bCancel) {
setVisible(false);
} else if (event.getSource() == bDirectory) {
if (fc.showDialog(this, "Select") == JFileChooser.APPROVE_OPTION)
tfDirectory.setText(fc.getSelectedFile().toString());
bExport.setEnabled(listTypes.getSelectedIndices().length > 0 && tfDirectory.getText().length() > 0);
}
}
// --------------------- End Interface ActionListener ---------------------
// --------------------- Begin Interface ListSelectionListener ---------------------
@Override
public void valueChanged(ListSelectionEvent event)
{
bExport.setEnabled(listTypes.getSelectedIndices().length > 0 && tfDirectory.getText().length() > 0);
}
// --------------------- End Interface ListSelectionListener ---------------------
// --------------------- Begin Interface Runnable ---------------------
@Override
public void run()
{
try {
List<Path> extraDirs = new ArrayList<>();
if (cbIncludeExtraDirs.isSelected()) {
// do not include savegame folders
extraDirs.addAll(Profile.getProperty(Profile.Key.GET_GAME_EXTRA_FOLDERS));
int idx = 0;
while (idx < extraDirs.size()) {
String s = extraDirs.get(idx).getFileName().toString().toUpperCase(Locale.ENGLISH);
if (s.contains("SAVE")) {
extraDirs.remove(idx);
} else {
idx++;
}
}
}
selectedFiles = new ArrayList<ResourceEntry>(1000);
for (final String newVar : selectedTypes) {
selectedFiles.addAll(ResourceFactory.getResources(newVar, extraDirs));
}
// executing multithreaded search
boolean isCancelled = false;
ThreadPoolExecutor executor = Misc.createThreadPool();
progress = new ProgressMonitor(NearInfinity.getInstance(), "Exporting...",
String.format(FMT_PROGRESS, getResourceCount(), getResourceCount()),
0, selectedFiles.size());
progress.setMillisToDecideToPopup(0);
progress.setMillisToPopup(0);
progress.setProgress(0);
progress.setNote(String.format(FMT_PROGRESS, 0, getResourceCount()));
Debugging.timerReset();
for (int i = 0, count = getResourceCount(); i < count; i++) {
Misc.isQueueReady(executor, true, -1);
executor.execute(new Worker(selectedFiles.get(i)));
if (progress.isCanceled()) {
isCancelled = true;
break;
}
}
// enforcing thread termination if process has been cancelled
if (isCancelled) {
executor.shutdownNow();
} else {
executor.shutdown();
}
// waiting for pending threads to terminate
while (!executor.isTerminated()) {
if (!isCancelled && progress.isCanceled()) {
executor.shutdownNow();
isCancelled = true;
}
try { Thread.sleep(1); } catch (InterruptedException e) {}
}
if (isCancelled) {
JOptionPane.showMessageDialog(NearInfinity.getInstance(), "Mass export aborted",
"Info", JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(NearInfinity.getInstance(), "Mass export completed",
"Info", JOptionPane.INFORMATION_MESSAGE);
}
} finally {
advanceProgress(true);
if (selectedFiles != null) {
selectedFiles.clear();
}
selectedFiles = null;
}
Debugging.timerShow("Mass export completed", Debugging.TimeFormat.MILLISECONDS);
}
// --------------------- End Interface Runnable ---------------------
private int getResourceCount()
{
return (selectedFiles != null) ? selectedFiles.size() : 0;
}
private synchronized void advanceProgress(boolean finished)
{
if (progress != null) {
if (finished) {
progressIndex = 0;
progress.close();
progress = null;
} else {
progressIndex++;
if (getResourceCount() < 50 || progressIndex % 10 == 0) {
progress.setNote(String.format(FMT_PROGRESS, progressIndex, getResourceCount()));
}
progress.setProgress(progressIndex);
}
}
}
private void exportText(ResourceEntry entry, Path output) throws Exception
{
ByteBuffer bb = entry.getResourceBuffer();
if (bb.limit() > 0) {
if (bb.limit() > 1 && bb.getShort(0) == -1) {
bb = Decryptor.decrypt(bb, 2);
}
// Keep trying. File may be in use by another thread.
try (OutputStream os = tryOpenOutputStream(output, 10, 100)) {
StreamUtils.writeBytes(os, bb);
}
}
}
private void exportDecompiledScript(ResourceEntry entry, Path output) throws Exception
{
output = output.getParent().resolve(StreamUtils.replaceFileExtension(output.getFileName().toString(), "BAF"));
if (Files.exists(output) && !cbOverwrite.isSelected()) {
return;
}
ByteBuffer bb = entry.getResourceBuffer();
if (bb.limit() > 0) {
if (bb.limit() > 1 && bb.getShort(0) == -1) {
bb = Decryptor.decrypt(bb, 2);
}
Decompiler decompiler = new Decompiler(StreamUtils.readString(bb, bb.limit()), false);
String script = decompiler.getSource();
// Keep trying. File may be in use by another thread.
try (BufferedWriter bw = new BufferedWriter(tryOpenOutputWriter(output, 10, 100))) {
bw.write(script.replaceAll("\r?\n", Misc.LINE_SEPARATOR));
bw.newLine();
}
}
}
private void decompressBamMos(ResourceEntry entry, Path output) throws Exception
{
ByteBuffer bb = entry.getResourceBuffer();
if (bb.limit() > 0) {
String sig = StreamUtils.readString(bb, 4);
if (sig.equals("BAMC") || sig.equals("MOSC")) {
bb = Compressor.decompress(bb);
}
// Keep trying. File may be in use by another thread.
try (OutputStream os = tryOpenOutputStream(output, 10, 100)) {
StreamUtils.writeBytes(os, bb);
}
}
}
private void decompressWav(ResourceEntry entry, Path output) throws Exception
{
ByteBuffer buffer = StreamUtils.getByteBuffer(AudioFactory.convertAudio(entry));
if (buffer != null && buffer.limit() > 0) {
// Keep trying. File may be in use by another thread.
try (OutputStream os = tryOpenOutputStream(output, 10, 100)) {
StreamUtils.writeBytes(os, buffer);
}
}
}
private void mosToPng(ResourceEntry entry, Path output) throws Exception
{
if (entry != null && entry.getExtension().equalsIgnoreCase("MOS")) {
output = outputPath.resolve(StreamUtils.replaceFileExtension(entry.toString(), "PNG"));
if (Files.exists(output) && !cbOverwrite.isSelected()) {
return;
}
MosDecoder decoder = MosDecoder.loadMos(entry);
if (decoder != null) {
if (decoder instanceof MosV1Decoder) {
((MosV1Decoder)decoder).setTransparencyEnabled(true);
}
RenderedImage image = ColorConvert.toBufferedImage(decoder.getImage(), true);
try {
ImageIO.write(image, "png", output.toFile());
} finally {
image = null;
}
} else {
throw new Exception(String.format("Error loading resource: %1$s", entry.getResourceName()));
}
}
}
private void pvrzToPng(ResourceEntry entry, Path output) throws Exception
{
if (entry != null && entry.getExtension().equalsIgnoreCase("PVRZ")) {
output = outputPath.resolve(StreamUtils.replaceFileExtension(entry.toString(), "PNG"));
if (Files.exists(output) && !cbOverwrite.isSelected()) {
return;
}
PvrDecoder decoder = PvrDecoder.loadPvr(entry);
if (decoder != null) {
RenderedImage image = decoder.decode();
try {
ImageIO.write(image, "png", output.toFile());
} finally {
image = null;
}
} else {
throw new Exception(String.format("Error loading resource: %1$s", entry.getResourceName()));
}
}
}
private void tisToPng(ResourceEntry entry, Path output) throws Exception
{
if (entry != null && entry.getExtension().equalsIgnoreCase("TIS")) {
output = outputPath.resolve(StreamUtils.replaceFileExtension(entry.toString(), "PNG"));
if (Files.exists(output) && !cbOverwrite.isSelected()) {
return;
}
TisDecoder decoder = TisDecoder.loadTis(entry);
if (decoder != null) {
int tileCount = decoder.getTileCount();
int columns = TisResource.calcTileWidth(entry, 1);
int rows = tileCount / columns;
if ((tileCount % columns) != 0) {
rows++;
}
BufferedImage tile = ColorConvert.createCompatibleImage(64, 64, Transparency.BITMASK);
BufferedImage image = ColorConvert.createCompatibleImage(64*columns, 64*rows, Transparency.BITMASK);
try {
Graphics2D g = image.createGraphics();
try {
for (int i = 0; i < tileCount; i++) {
int x = 64*(i % columns);
int y = 64*(i / columns);
decoder.getTile(i, tile);
g.drawImage(tile, x, y, null);
}
} finally {
g.dispose();
g = null;
}
ImageIO.write(image, "png", output.toFile());
} finally {
tile = null;
image = null;
}
} else {
throw new Exception(String.format("Error loading resource: %1$s", entry.getResourceName()));
}
}
}
private void extractBamFrames(ResourceEntry entry, Path output) throws Exception
{
String format = (cbExtractFramesBAMFormat.getSelectedIndex() == 0) ? "png" : "bmp";
Path filePath = output.getParent();
String fileName = output.getFileName().toString();
int extIdx = fileName.lastIndexOf('.');
String fileBase = (extIdx >= 0) ? fileName.substring(0, extIdx) : fileName;
String fileExt = "." + format;
// creating subfolder for frames
Path path = filePath.resolve(fileBase);
if (!Files.exists(path)) {
try {
Files.createDirectory(path);
} catch (IOException e) {
String msg = String.format("Error creating folder \"%s\". Skipping file \"%s\".",
fileBase, fileName);
System.err.println(msg);
JOptionPane.showMessageDialog(NearInfinity.getInstance(), msg, "Error", JOptionPane.ERROR_MESSAGE);
return;
}
} else if (!Files.isDirectory(path)) {
String msg = String.format("Folder \"%s\" can not be created. Skipping file \"%s\".",
fileBase, fileName);
System.err.println(msg);
JOptionPane.showMessageDialog(NearInfinity.getInstance(), msg, "Error", JOptionPane.ERROR_MESSAGE);
return;
}
filePath = path;
BamDecoder decoder = BamDecoder.loadBam(entry);
BamResource.exportFrames(decoder, filePath, fileBase, fileExt, format, true);
}
private void chrToCre(ResourceEntry entry, Path output) throws Exception
{
output = outputPath.resolve(StreamUtils.replaceFileExtension(entry.toString(), "CRE"));
if (Files.exists(output) && !cbOverwrite.isSelected()) {
return;
}
CreResource crefile = new CreResource(entry);
java.util.List<StructEntry> flatList = crefile.getFlatList();
while (!flatList.get(0).toString().equals("CRE ")) {
flatList.remove(0);
}
// Keep trying. File may be in use by another thread.
try (OutputStream os = tryOpenOutputStream(output, 10, 100)) {
for (int i = 0; i < flatList.size(); i++) {
((Writeable)flatList.get(i)).write(os);
}
}
}
private void exportResource(ResourceEntry entry, Path output) throws Exception
{
if (entry != null && output != null) {
try (InputStream is = entry.getResourceDataAsStream()) {
int[] info = entry.getResourceInfo();
int size = info[0];
if (info.length > 1) {
size = size*info[1] + 0x18;
}
boolean isTis = (info.length > 1);
boolean isTisV2 = isTis && (info[1] == 0x0c);
if (isTis && cbConvertTisVersion.isSelected() &&
isTisV2 == false && cbConvertTisList.getSelectedIndex() == 1) {
TisResource tis = new TisResource(entry);
tis.convertToPvrzTis(TisResource.makeTisFileNameValid(output), false);
} else if (isTis && cbConvertTisVersion.isSelected() &&
isTisV2 == true && cbConvertTisList.getSelectedIndex() == 0) {
TisResource tis = new TisResource(entry);
tis.convertToPaletteTis(output, false);
} else if (size >= 0) {
// Keep trying. File may be in use by another thread.
try (OutputStream os = tryOpenOutputStream(output, 10, 100)) {
int bytesWritten = (int)StreamUtils.writeBytes(os, is, size);
if (bytesWritten < size) {
throw new EOFException(entry.toString() + ": " + bytesWritten + " of " + size + " bytes written");
}
}
}
}
}
}
private void export(ResourceEntry entry)
{
try {
Path output = outputPath.resolve(entry.toString());
if (Files.exists(output) && !cbOverwrite.isSelected()) {
return;
}
if ((entry.getExtension().equalsIgnoreCase("IDS") ||
entry.getExtension().equalsIgnoreCase("2DA") ||
entry.getExtension().equalsIgnoreCase("BIO") ||
entry.getExtension().equalsIgnoreCase("RES") ||
entry.getExtension().equalsIgnoreCase("INI") ||
entry.getExtension().equalsIgnoreCase("SET") ||
entry.getExtension().equalsIgnoreCase("WOK") ||
entry.getExtension().equalsIgnoreCase("TXI") ||
entry.getExtension().equalsIgnoreCase("DWK") ||
entry.getExtension().equalsIgnoreCase("PWK") ||
entry.getExtension().equalsIgnoreCase("NSS") ||
entry.getExtension().equalsIgnoreCase("TXT") ||
(Profile.isEnhancedEdition() &&
(entry.getExtension().equalsIgnoreCase("GLSL") ||
entry.getExtension().equalsIgnoreCase("GUI") ||
entry.getExtension().equalsIgnoreCase("SQL"))) ||
(entry.getExtension().equalsIgnoreCase("SRC") &&
Profile.getEngine() == Profile.Engine.IWD2)) &&
cbDecrypt.isSelected()) {
exportText(entry, output);
}
else if ((entry.getExtension().equalsIgnoreCase("BCS") ||
entry.getExtension().equalsIgnoreCase("BS")) && cbDecompile.isSelected()) {
exportDecompiledScript(entry, output);
}
else if (entry.getExtension().equalsIgnoreCase("MOS") && cbConvertToPNG.isSelected()) {
mosToPng(entry, output);
}
else if (entry.getExtension().equalsIgnoreCase("PVRZ") && cbConvertToPNG.isSelected()) {
pvrzToPng(entry, output);
}
else if (entry.getExtension().equalsIgnoreCase("TIS") && cbConvertToPNG.isSelected()) {
tisToPng(entry, output);
}
else if (entry.getExtension().equalsIgnoreCase("BAM") && cbExtractFramesBAM.isSelected()) {
extractBamFrames(entry, output);
}
else if ((entry.getExtension().equalsIgnoreCase("BAM") ||
entry.getExtension().equalsIgnoreCase("MOS")) && cbDecompress.isSelected()) {
decompressBamMos(entry, output);
}
else if (entry.getExtension().equalsIgnoreCase("CHR") && cbConvertCRE.isSelected()) {
chrToCre(entry, output);
}
else if (entry.getExtension().equalsIgnoreCase("WAV") && cbConvertWAV.isSelected()) {
decompressWav(entry, output);
}
else if (entry.getExtension().equalsIgnoreCase("MVE") && cbExportMVEasAVI.isSelected()) {
output = outputPath.resolve(StreamUtils.replaceFileExtension(entry.toString(), "avi"));
if (Files.exists(output) && !cbOverwrite.isSelected()) {
return;
}
MveResource.convertAvi(entry, output, null, true);
}
else {
exportResource(entry, output);
}
} catch (Exception e) {
System.err.println("Error in resource: " + entry.toString());
e.printStackTrace();
}
}
// Attempts to open "output" as stream to the specified file "numAttempts' time with "delayAttempts" ms delay inbetween.
private OutputStream tryOpenOutputStream(Path output, int numAttempts, int delayAttempts) throws Exception
{
if (output != null) {
numAttempts = Math.max(1, numAttempts);
delayAttempts = Math.max(0, delayAttempts);
OutputStream os = null;
while (os == null) {
try {
os = StreamUtils.getOutputStream(output, true);
} catch (FileNotFoundException fnfe) {
os = null;
if (--numAttempts == 0) {
throw fnfe;
}
try { Thread.sleep(delayAttempts); } catch (InterruptedException ie) {}
}
}
return os;
}
return null;
}
// Attempts to open "output" as writer to the specified file "numAttempts' time with "delayAttempts" ms delay inbetween.
private Writer tryOpenOutputWriter(Path output, int numAttempts, int delayAttempts) throws Exception
{
if (output != null) {
numAttempts = Math.max(1, numAttempts);
delayAttempts = Math.max(0, delayAttempts);
Writer w = null;
while (w == null) {
try {
w = Files.newBufferedWriter(output);
} catch (FileNotFoundException fnfe) {
w = null;
if (--numAttempts == 0) {
throw fnfe;
}
try { Thread.sleep(delayAttempts); } catch (InterruptedException ie) {}
}
}
return w;
}
return null;
}
//-------------------------- INNER CLASSES --------------------------
private class Worker implements Runnable
{
private final ResourceEntry entry;
public Worker(ResourceEntry entry)
{
this.entry = entry;
}
@Override
public void run()
{
if (entry != null) {
export(entry);
}
advanceProgress(false);
}
}
}