package com.jpexs.decompiler.flash.iggy; import com.jpexs.decompiler.flash.iggy.streams.DataStreamInterface; import com.jpexs.decompiler.flash.iggy.streams.IggyIndexBuilder; import com.jpexs.decompiler.flash.iggy.streams.RandomAccessFileDataStream; import com.jpexs.decompiler.flash.iggy.streams.ReadDataStreamInterface; import com.jpexs.decompiler.flash.iggy.streams.SeekMode; import com.jpexs.decompiler.flash.iggy.streams.StructureInterface; import com.jpexs.decompiler.flash.iggy.streams.TemporaryDataStream; import com.jpexs.decompiler.flash.iggy.streams.WriteDataStreamInterface; import java.io.File; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * * @author JPEXS * * Based of works of somebody called eternity. * */ public class IggyFile implements StructureInterface { final static Logger LOGGER = Logger.getLogger(IggyFile.class.getName()); private File originalFile; private IggyHeader header; private List<IggySubFileEntry> subFileEntries = new ArrayList<>(); private List<byte[]> subFileEntriesData = new ArrayList<>(); private IggySwf iggySwf; public static final int FIRST_TAG_POSITION = 3; public IggySwf getSwf() { return iggySwf; } public IggyFile(String filePath) throws IOException { this(new File(filePath)); } public IggyFile(File file) throws IOException { this.originalFile = file; try (ReadDataStreamInterface stream = new RandomAccessFileDataStream(file)) { readFromDataStream(stream); } } public File getOriginalFile() { return originalFile; } public IggyHeader getHeader() { return header; } public IggySubFileEntry getSubFileEntry(int entryIndex) { if (entryIndex < 0 || entryIndex >= subFileEntries.size()) { throw new ArrayIndexOutOfBoundsException("No entry with index " + entryIndex + " exists"); } return subFileEntries.get(entryIndex); } public int getNumEntries() { return subFileEntries.size(); } public byte[] getEntryData(int entryIndex) { if (entryIndex < 0 || entryIndex >= subFileEntries.size()) { throw new ArrayIndexOutOfBoundsException("No entry with index " + entryIndex + " exists"); } return subFileEntriesData.get(entryIndex); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("[IggyFile:").append("\r\n"); sb.append(header).append("\r\n"); sb.append("Entries:").append("\r\n"); for (IggySubFileEntry entry : subFileEntries) { sb.append(entry).append("\r\n"); } sb.append("]"); return sb.toString(); } public static void extractIggyFile(File iggyFile, File extractDir) throws IOException { final String FILENAME_FORMAT = "index%d_type%d.bin"; IggyFile ir = new IggyFile(iggyFile); for (int i = 0; i < ir.getNumEntries(); i++) { IggySubFileEntry entry = ir.getSubFileEntry(i); try (FileOutputStream fos = new FileOutputStream(new File(extractDir, String.format(FILENAME_FORMAT, i, entry.type)))) { fos.write(ir.getEntryData(i)); } } } public String getSwfName() { return iggySwf.getName(); } public long getSwfXMin() { return iggySwf.getHdr().getXMin(); } public long getSwfYMin() { return iggySwf.getHdr().getYMin(); } public long getSwfXMax() { return iggySwf.getHdr().getXMax(); } public long getSwfYMax() { return iggySwf.getHdr().getYMax(); } public float getSwfFrameRate() { return iggySwf.getHdr().getFrameRate(); } /** * Removes entries of type INDEX.There can be more than one INDEX, * continuous. This removes all ot them. */ public void removeIndexEntries() { long offsetsChange = 0; final int ENTRY_SIZE = 16; for (int i = 0; i < subFileEntries.size(); i++) { IggySubFileEntry entry = subFileEntries.get(i); entry.offset += offsetsChange; if (entry.type == IggySubFileEntry.TYPE_INDEX) { offsetsChange = offsetsChange - entry.size - ENTRY_SIZE; subFileEntriesData.remove(i); subFileEntries.remove(i); i--; } } } public boolean updateFlashEntry() throws IOException { byte replacementData[]; byte replacementIndexData[]; IggyIndexBuilder ib = new IggyIndexBuilder(); try (DataStreamInterface stream = new TemporaryDataStream()) { stream.setIndexing(ib); iggySwf.writeToDataStream(stream); replacementData = stream.getAllBytes(); replacementIndexData = ib.getIndexBytes(); } catch (IOException ex) { Logger.getLogger(IggyFile.class.getName()).log(Level.SEVERE, "Error during updating SWF", ex); return false; } long offsetsChange = 0; for (int i = 0; i < subFileEntries.size(); i++) { IggySubFileEntry entry = subFileEntries.get(i); entry.offset += offsetsChange; if (entry.type == IggySubFileEntry.TYPE_FLASH) { long oldSize = entry.size; long newSize = replacementData.length; entry.size = newSize; entry.size2 = newSize; offsetsChange = offsetsChange + (newSize - oldSize); //entries after this one will have modified offsets subFileEntriesData.set(i, replacementData); } } removeIndexEntries(); IggySubFileEntry indexEntry = new IggySubFileEntry(IggySubFileEntry.TYPE_INDEX, replacementIndexData.length, replacementIndexData.length, 0 /*offset will be set automatically*/); subFileEntries.add(indexEntry); subFileEntriesData.add(replacementIndexData); return true; } private void parseEntries() throws IOException { for (int i = 0; i < subFileEntries.size(); i++) { IggySubFileEntry entry = subFileEntries.get(i); if (entry.type == IggySubFileEntry.TYPE_FLASH) { iggySwf = new IggySwf(new TemporaryDataStream(getEntryData(i))); break; } /*if (entry.type == IggySubFileEntry.TYPE_INDEX) { IggyIndexParser.parseIndex(true, new TemporaryDataStream(getEntryData(i)), new ArrayList<>(), new ArrayList<>()); }*/ } } @Override public void readFromDataStream(ReadDataStreamInterface stream) throws IOException { header = new IggyHeader(stream); if (!header.is64()) { throw new IOException("32 bit iggy files are not (yet) supported, sorry"); } for (int i = 0; i < header.getNumSubfiles(); i++) { subFileEntries.add(new IggySubFileEntry(stream)); } for (IggySubFileEntry entry : subFileEntries) { stream.seek(entry.offset, SeekMode.SET); byte[] entryData = stream.readBytes((int) entry.size); subFileEntriesData.add(entryData); } parseEntries(); } public void saveChanges() throws IOException { updateFlashEntry(); try (RandomAccessFileDataStream raf = new RandomAccessFileDataStream(originalFile)) { writeToDataStream(raf); } } @Override public void writeToDataStream(WriteDataStreamInterface stream) throws IOException { header.writeToDataStream(stream); long startOffset = IggyHeader.STRUCT_SIZE + IggySubFileEntry.STRUCTURE_SIZE * subFileEntries.size(); long currentOffset = startOffset; for (int i = 0; i < subFileEntries.size(); i++) { IggySubFileEntry entry = subFileEntries.get(i); entry.offset = currentOffset; currentOffset += entry.size; entry.writeToDataStream(stream); } for (int i = 0; i < subFileEntries.size(); i++) { byte[] entryData = subFileEntriesData.get(i); stream.writeBytes(entryData); } } }