diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/AbstractDataStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/AbstractDataStream.java new file mode 100644 index 000000000..b5a7fa818 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/AbstractDataStream.java @@ -0,0 +1,106 @@ +package com.jpexs.decompiler.flash.iggy; + +import java.io.EOFException; +import java.io.IOException; + +/** + * + * @author JPEXS + */ +public abstract class AbstractDataStream { + + /** + * Available bytes + * + * @return null if unknown, long value otherwise + */ + public abstract Long available(); + + protected long readUI64() throws IOException { + try { + return (readUI32() + (readUI32() << 32)) & 0xffffffffffffffffL; + } catch (EOFException ex) { + return -1; + } + } + + protected boolean writeUI64(long val) throws IOException { + write((int) (val & 0xff)); + write((int) ((val >> 8) & 0xff)); + write((int) ((val >> 16) & 0xff)); + write((int) ((val >> 24) & 0xff)); + + write((int) ((val >> 32) & 0xff)); + write((int) ((val >> 40) & 0xff)); + write((int) ((val >> 48) & 0xff)); + write((int) ((val >> 56) & 0xff)); + return true; + } + + protected long readUI32() throws IOException { + try { + return (readUI8() + (readUI8() << 8) + (readUI8() << 16) + (readUI8() << 24)) & 0xffffffff; + } catch (EOFException ex) { + return -1; + } + } + + protected boolean writeUI32(long val) throws IOException { + write((int) (val & 0xff)); + write((int) ((val >> 8) & 0xff)); + write((int) ((val >> 16) & 0xff)); + write((int) ((val >> 24) & 0xff)); + return true; + } + + protected int readUI16() throws IOException { + try { + return (readUI8() + (readUI8() << 8)) & 0xffff; + } catch (EOFException ex) { + return -1; + } + } + + protected boolean writeUI16(int val) throws IOException { + write(val & 0xff); + write((val >> 8) & 0xff); + return true; + } + + protected int readUI8() throws IOException { + try { + return read() & 0xff; + } catch (EOFException ex) { + return -1; + } + } + + protected boolean writeUI8(int val) throws IOException { + write(val); + return true; + } + + protected float readFloat() throws IOException { + return Float.intBitsToFloat((int) readUI32()); + } + + protected boolean writeFloat(float val) throws IOException { + return writeUI32(Float.floatToIntBits(val)); + } + + protected byte[] readBytes(int numBytes) throws IOException { + byte[] ret = new byte[numBytes]; + for (int i = 0; i < numBytes; i++) { + ret[i] = (byte) read(); + } + return ret; + } + + protected abstract int read() throws IOException; + + protected abstract void seek(long pos, SeekMode mode) throws IOException; + + protected void write(int val) throws IOException { + //nothing + } +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/ByteArrayDataStream.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/ByteArrayDataStream.java new file mode 100644 index 000000000..7045e7e0f --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/ByteArrayDataStream.java @@ -0,0 +1,70 @@ +package com.jpexs.decompiler.flash.iggy; + +import java.io.EOFException; +import java.io.IOException; +import java.util.Arrays; + +/** + * + * @author JPEXS + */ +public class ByteArrayDataStream extends AbstractDataStream { + + private byte[] data; + private int pos; + + public ByteArrayDataStream(int initialSize) { + this(new byte[initialSize]); + } + + public ByteArrayDataStream(byte data[]) { + this.data = data; + pos = 0; + } + + @Override + protected int read() throws IOException { + if (pos >= data.length) { + throw new EOFException("End of stream reached"); + } + return data[pos++] & 0xff; + } + + public void resize(int newsize) { + data = Arrays.copyOf(data, newsize); + if (pos > data.length) { + pos = data.length; + } + } + + @Override + protected void write(int val) throws IOException { + if (pos >= data.length) { + throw new EOFException("End of stream reached"); + } + data[pos++] = (byte) val; + } + + @Override + protected void seek(long pos, SeekMode mode) throws IOException { + long newpos = pos; + if (mode == SeekMode.CUR) { + newpos = this.pos + pos; + } else if (mode == SeekMode.END) { + newpos = data.length - pos; + } + if (newpos > data.length) { + throw new ArrayIndexOutOfBoundsException("Position outside bounds accessed: " + pos + ". Size: " + data.length); + } else if (newpos < 0) { + throw new ArrayIndexOutOfBoundsException("Negative position accessed: " + pos); + } else { + this.pos = (int) newpos; + } + } + + @Override + public Long available() { + return (long) (data.length - pos); + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/DataType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/DataType.java new file mode 100644 index 000000000..a0c145075 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/DataType.java @@ -0,0 +1,8 @@ +package com.jpexs.decompiler.flash.iggy; + +/** + * @author JPEXS + */ +public enum DataType { + uint8_t, uint16_t, uint32_t, uint64_t, unknown +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyExtractor.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyExtractor.java new file mode 100644 index 000000000..25d842ed9 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyExtractor.java @@ -0,0 +1,647 @@ +package com.jpexs.decompiler.flash.iggy; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +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.io.RandomAccessFile; +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 IggyExtractor extends AbstractDataStream implements AutoCloseable { + + final static Logger LOGGER = Logger.getLogger(IggyExtractor.class.getName()); + + private RandomAccessFile raf; + private IggyHeader header; + private List subFileEntries = new ArrayList<>(); + + public IggyExtractor(File file) throws IOException { + raf = new RandomAccessFile(file, "r"); + header = new IggyHeader(this); + for (int i = 0; i < header.num_subfiles; i++) { + subFileEntries.add(new IggySubFileEntry(this)); + } + + List indexStreams = new ArrayList<>(); + List flashStreams = new ArrayList<>(); + + for (int i = 0; i < subFileEntries.size(); i++) { + IggySubFileEntry entry = subFileEntries.get(i); + ByteArrayDataStream entryStream = getEntryDataStream(i); + if (entry.type == IggySubFileEntry.TYPE_INDEX) { + indexStreams.add(entryStream); + } else if (entry.type == IggySubFileEntry.TYPE_FLASH) { + flashStreams.add(entryStream); + } + } + + for (ByteArrayDataStream fs : flashStreams) { + if (is64()) { + IggyFlashHeader64 fh = new IggyFlashHeader64(fs); + System.out.println("" + fh); + } else { + IggyFlashHeader32 fh = new IggyFlashHeader32(fs); + } + } + } + + private boolean is64() { + return header.is64(); + } + + 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) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream is = getEntryInputStream(entryIndex); + byte buf[] = new byte[1024]; + int cnt; + while ((cnt = is.read(buf)) > 0) { + baos.write(buf, 0, cnt); + } + return baos.toByteArray(); + } + + public ByteArrayDataStream getEntryDataStream(int entryIndex) throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream is = getEntryInputStream(entryIndex); + byte buf[] = new byte[1024]; + int cnt; + while ((cnt = is.read(buf)) > 0) { + baos.write(buf, 0, cnt); + } + byte data[] = baos.toByteArray(); + return new ByteArrayDataStream(data); + } + + public InputStream getEntryInputStream(int entryIndex) { + IggySubFileEntry entry = getSubFileEntry(entryIndex); + + return new InputStream() { + long offset = entry.offset; + long maxOffset = entry.offset + entry.size; + + @Override + public synchronized int read() throws IOException { + if (offset < maxOffset) { + raf.seek(offset); + offset++; + return raf.read(); + } + return -1; + } + }; + } + + protected int read() throws IOException { + int val = raf.read(); + if (val == -1) { + throw new EOFException(); + } + return val; + } + + @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"; + try (IggyExtractor ir = new IggyExtractor(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)); + } + } + } + } + + private static void processFile(File f) { + if (f.isDirectory()) { + System.out.println("Processing directory " + f + ":"); + File iggyFiles[] = f.listFiles(new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".iggy"); + } + }); + for (File sf : iggyFiles) { + processFile(sf); + } + return; + } + System.out.print("Processing file " + f + "..."); + try { + File dir = f.getParentFile(); + File extractDir = new File(dir, "extracted_" + (f.getName()).replace(".iggy", "")); + extractDir.mkdir(); + extractIggyFile(f, extractDir); + System.out.println("OK"); + } catch (Exception ex) { + System.err.println("FAIL"); + System.exit(1); + } + } + + public static void main(String[] args) throws IOException { + if (args.length == 0) { + System.err.println("No file specified"); + System.exit(1); + } + + for (String s : args) { + File f = new File(s); + if (!f.exists()) { + System.err.println("File " + f + " does not exists"); + System.exit(1); + } + processFile(f); + + } + System.exit(0); + } + + private static void copyStream(InputStream is, OutputStream os) { + try { + final int bufSize = 4096; + byte[] buf = new byte[bufSize]; + int cnt; + while ((cnt = is.read(buf)) > 0) { + os.write(buf, 0, cnt); + } + } catch (IOException ex) { + // ignore + } + } + + @Override + public void close() { + try { + raf.close(); + } catch (IOException ex) { + //ignore + } + } + + @Override + protected void seek(long pos, SeekMode mode) throws IOException { + long newpos = pos; + if (mode == SeekMode.CUR) { + newpos = raf.getFilePointer() + pos; + } else if (mode == SeekMode.END) { + newpos = raf.length() - pos; + } + if (newpos > raf.length()) { + throw new ArrayIndexOutOfBoundsException("Position outside bounds accessed: " + pos + ". Size: " + raf.length()); + } else if (newpos < 0) { + throw new ArrayIndexOutOfBoundsException("Negative position accessed: " + pos); + } else { + raf.seek(newpos); + } + } + + private static boolean updateIndex(long item_offset /*uint32_t*/, boolean is_64, byte index_bytes[], long item_size_change /*int32_t*/) throws IOException { + ByteArrayDataStream stream = new ByteArrayDataStream(index_bytes); + + /* + index_table: + n = UI8 + for i=0..n-1 + table[i] = UI8 + cnt = UI8 + cnt * UI16 + + */ + int index_table_size = stream.readUI8(); + int index_table[] = new int[index_table_size]; + + for (int i = 0; i < index_table_size; i++) { + index_table[i] = stream.readUI8(); + int num = stream.readUI8(); + stream.seek(num * 2, SeekMode.CUR); + //num * UI16 + } + + int state = 0; + long offset = 0;//uint32_t + int code; //uint8_t + + while ((code = stream.readUI8()) >= 0) { + if (state == 1) { + if (code != 0xFD) { + LOGGER.log(Level.WARNING, "We were expecting code 0xFD in state 1."); + return false; + } + } else if (state == 2) { + if (code != 0xFF) { + LOGGER.log(Level.WARNING, "We were expecting code 0xFF in state 2."); + return false; + } + } + + if (code < 0x80) // 0-0x7F + { + // code is directly an index to the index_table + if (code >= index_table_size) { + LOGGER.log(Level.WARNING, String.format("< 0x80: index is greater than index_table_size. %x > %x", code, index_table_size)); + return false; + } + + offset += index_table[code]; + } else if (code < 0xC0) // 0x80-BF + { + int index; //uint8_t + + if ((index = stream.readUI8()) < 0) { + LOGGER.log(Level.WARNING, "< 0xC0: Cannot read index."); + return false; + } + + if (index >= index_table_size) { + LOGGER.log(Level.WARNING, String.format("< 0xC0: index is greater than index_table_size. %x > %x", index, index_table_size)); + return false; + } + + int n = code - 0x7F; + offset += index_table[index] * n; + } else if (code < 0xD0) // 0xC0-0xCF + { + offset += ((code * 2) - 0x17E); + } else if (code < 0xE0) // 0xD0-0xDF + { + // Code here depends on plattform[0], we are assuming it is 1, as we checked in load function + int i = code & 0xF; //uint8_t + int n8; //uint8_t + int n; + + if ((n8 = stream.readUI8()) < 0) { + LOGGER.log(Level.WARNING, "< 0xE0: Cannot read n."); + return false; + } + + n = n8 + 1; + + if (is_64) { + if (i <= 2) { + offset += 8 * n; // Ptr type + } else if (i <= 4) { + offset += 2 * n; + } else if (i == 5) { + offset += 4 * n; + } else if (i == 6) { + offset += 8 * n; // 64 bits type + } else { + LOGGER.log(Level.WARNING, String.format("< 0xE0: Invalid value for i (%x %x)", i, code)); + } + } else { + switch (i) { + case 2: + offset += 4 * n; // Ptr type + break; + case 4: + offset += 2 * n; + break; + case 5: + offset += 4 * n; // 32 bits type + break; + case 6: + offset += 8 * n; + break; + default: + LOGGER.log(Level.WARNING, String.format("< 0xE0: invalid value for i (%x %x)", i, code)); + } + } + } else if (code == 0xFC) { + stream.seek(1, SeekMode.CUR); + } else if (code == 0xFD) { + int n, m; //uint8_t + + if ((n = stream.readUI8()) < 0) { + LOGGER.log(Level.WARNING, "0xFD: Cannot read n."); + return false; + } + + if (state == 1) { + if (is_64) { + if (n != 0xF) { + LOGGER.log(Level.WARNING, String.format("We were expecting an offset of 0xF in state 1.")); + return false; + } + } else if (n != 0xB) { + LOGGER.log(Level.WARNING, "We were expecting an offset of 0xB in state 1."); + return false; + } + + state = 2; + } + + if ((m = stream.readUI8()) < 0) { + LOGGER.log(Level.WARNING, "0xFD: Cannot read m."); + return false; + } + + offset += n; + stream.seek(m * 2, SeekMode.CUR); + } else if (code == 0xFE) { + int n8; //uint8_t + int n; + + if ((n8 = stream.readUI8()) < 0) { + LOGGER.log(Level.WARNING, "0xFE: Cannot read n."); + return false; + } + + n = n8 + 1; + offset += n; + } else if (code == 0xFF) { + long n; //uint32_t + + if ((n = stream.readUI32()) < 0) { + LOGGER.log(Level.WARNING, "0xFF: Cannot read n."); + return false; + } + + if (state == 2) { + n += item_size_change; + stream.seek(-4, SeekMode.CUR); + return stream.writeUI32(n); + } + + offset += n; + } else { + LOGGER.log(Level.WARNING, String.format("Unrecognized code: %x", code)); + } + + if (state == 0 && offset == item_offset) { + state = 1; + } + } + return false; + } + + /** + * Gets length of an item. + * + * @param item_offset + * @param is_64 + * @param index_bytes + * @return null when item not exists, item length otherwise + * @throws IOException + */ + private static Long getItemLength(long item_offset /*uint32_t*/, boolean is_64, byte index_bytes[]) throws IOException { + return itemLength(item_offset, is_64, index_bytes, null); + } + + /** + * Sets new length of an item. + * + * @param item_offset + * @param is_64 + * @param index_bytes + * @param newLength + * @return null when item not exists, old item length otherwise + * @throws IOException + */ + private static Long setItemLength(long item_offset /*uint32_t*/, boolean is_64, byte index_bytes[], long newLength) throws IOException { + return itemLength(item_offset, is_64, index_bytes, newLength); + } + + /** + * Sets/Gets length of an item + * + * @param item_offset + * @param is_64 + * @param index_bytes + * @param newValue New value to set. If null then no change. + * @return null when item does not exists, old item length otherwise + * @throws IOException + */ + private static Long itemLength(long item_offset /*uint32_t*/, boolean is_64, byte index_bytes[], Long newValue) throws IOException { + ByteArrayDataStream stream = new ByteArrayDataStream(index_bytes); + + /* + index_table: + n = UI8 + for i=0..n-1 + table[i] = UI8 + cnt = UI8 + cnt * UI16 + + */ + int index_table_size = stream.readUI8(); + int index_table[] = new int[index_table_size]; + + for (int i = 0; i < index_table_size; i++) { + index_table[i] = stream.readUI8(); + int num = stream.readUI8(); + stream.seek(num * 2, SeekMode.CUR); + //num * UI16 + } + + int state = 0; + long offset = 0;//uint32_t + int code; //uint8_t + + while ((code = stream.readUI8()) >= 0) { + if (state == 1) { + if (code != 0xFD) { + LOGGER.log(Level.WARNING, "We were expecting code 0xFD in state 1."); + return null; + } + } else if (state == 2) { + if (code != 0xFF) { + LOGGER.log(Level.WARNING, "We were expecting code 0xFF in state 2."); + return null; + } + } + + if (code < 0x80) // 0-0x7F + { + // code is directly an index to the index_table + if (code >= index_table_size) { + LOGGER.log(Level.WARNING, String.format("< 0x80: index is greater than index_table_size. %x > %x", code, index_table_size)); + return null; + } + + offset += index_table[code]; + } else if (code < 0xC0) // 0x80-BF + { + int index; //uint8_t + + if ((index = stream.readUI8()) < 0) { + LOGGER.log(Level.WARNING, "< 0xC0: Cannot read index."); + return null; + } + + if (index >= index_table_size) { + LOGGER.log(Level.WARNING, String.format("< 0xC0: index is greater than index_table_size. %x > %x", index, index_table_size)); + return null; + } + + int n = code - 0x7F; + offset += index_table[index] * n; + } else if (code < 0xD0) // 0xC0-0xCF + { + offset += ((code * 2) - 0x17E); + } else if (code < 0xE0) // 0xD0-0xDF + { + // Code here depends on plattform[0], we are assuming it is 1, as we checked in load function + int i = code & 0xF; //uint8_t + int n8; //uint8_t + int n; + + if ((n8 = stream.readUI8()) < 0) { + LOGGER.log(Level.WARNING, "< 0xE0: Cannot read n."); + return null; + } + + n = n8 + 1; + + if (is_64) { + if (i <= 2) { + offset += 8 * n; // Ptr type + } else if (i <= 4) { + offset += 2 * n; + } else if (i == 5) { + offset += 4 * n; + } else if (i == 6) { + offset += 8 * n; // 64 bits type + } else { + LOGGER.log(Level.WARNING, String.format("< 0xE0: Invalid value for i (%x %x)", i, code)); + } + } else { + switch (i) { + case 2: + offset += 4 * n; // Ptr type + break; + case 4: + offset += 2 * n; + break; + case 5: + offset += 4 * n; // 32 bits type + break; + case 6: + offset += 8 * n; + break; + default: + LOGGER.log(Level.WARNING, String.format("< 0xE0: invalid value for i (%x %x)", i, code)); + } + } + } else if (code == 0xFC) { + stream.seek(1, SeekMode.CUR); + } else if (code == 0xFD) { + int n, m; //uint8_t + + if ((n = stream.readUI8()) < 0) { + LOGGER.log(Level.WARNING, "0xFD: Cannot read n."); + return null; + } + + if (state == 1) { + if (is_64) { + if (n != 0xF) { + LOGGER.log(Level.WARNING, String.format("We were expecting an offset of 0xF in state 1.")); + return null; + } + } else if (n != 0xB) { + LOGGER.log(Level.WARNING, "We were expecting an offset of 0xB in state 1."); + return null; + } + + state = 2; + } + + if ((m = stream.readUI8()) < 0) { + LOGGER.log(Level.WARNING, "0xFD: Cannot read m."); + return null; + } + + offset += n; + stream.seek(m * 2, SeekMode.CUR); + } else if (code == 0xFE) { + int n8; //uint8_t + int n; + + if ((n8 = stream.readUI8()) < 0) { + LOGGER.log(Level.WARNING, "0xFE: Cannot read n."); + return null; + } + + n = n8 + 1; + offset += n; + } else if (code == 0xFF) { + long n; //uint32_t + + if ((n = stream.readUI32()) < 0) { + LOGGER.log(Level.WARNING, "0xFF: Cannot read n."); + return null; + } + + if (state == 2) { + if (newValue != null) { + stream.seek(-4, SeekMode.CUR); + stream.writeUI32(newValue); + } + return n; + } + + offset += n; + } else { + LOGGER.log(Level.WARNING, String.format("Unrecognized code: %x", code)); + } + + if (state == 0 && offset == item_offset) { + state = 1; + } + } + return null; + } + + @Override + public Long available() { + try { + return raf.length() - raf.getFilePointer(); + } catch (IOException ex) { + return null; + } + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyFlashHeader32.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyFlashHeader32.java new file mode 100644 index 000000000..458d159fb --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyFlashHeader32.java @@ -0,0 +1,170 @@ +package com.jpexs.decompiler.flash.iggy; + +import com.jpexs.decompiler.flash.iggy.annotations.IggyFieldType; +import java.io.IOException; + +/** + * + * @author JPEXS + * + * Based of works of somebody called eternity. + * + * All relative offsets are relative from that specific field position All + * relative offsets can get value "1" to indicate "nothing" + */ +public class IggyFlashHeader32 implements StructureInterface { + + @IggyFieldType(DataType.uint32_t) + long main_offset; // 0 Relative offset to first section (matches sizeof header) + @IggyFieldType(DataType.uint32_t) + long as3_section_offset; // 4 Relative offset to as3 file names table... + @IggyFieldType(DataType.uint32_t) + long unk_offset; // 8 relative offset to something + @IggyFieldType(DataType.uint32_t) + long unk_offset2; // 0xC relative offset to something + @IggyFieldType(DataType.uint32_t) + long unk_offset3; // 0x10 relative offset to something + @IggyFieldType(DataType.uint32_t) + long unk_offset4; // 0x14 relative offset to something + @IggyFieldType(DataType.uint32_t) + long xmin; //0x18 in pixels + @IggyFieldType(DataType.uint32_t) + long ymin; //0x0C in pixels + @IggyFieldType(DataType.uint32_t) + long xmax; // 0x20 in pixels + @IggyFieldType(DataType.uint32_t) + long ymax; // 0x24 in pixels + @IggyFieldType(DataType.uint32_t) + long unk_28; // probably numer of blocks/objects after header + @IggyFieldType(DataType.uint32_t) + long unk_2C; + @IggyFieldType(DataType.uint32_t) + long unk_30; + @IggyFieldType(DataType.uint32_t) + long unk_34; + @IggyFieldType(DataType.uint32_t) + long unk_38; + @IggyFieldType(DataType.uint32_t) + long unk_3C; + float unk_40; + @IggyFieldType(DataType.uint32_t) + long unk_44; + @IggyFieldType(DataType.uint32_t) + long unk_48; + @IggyFieldType(DataType.uint32_t) + long unk_4C; + @IggyFieldType(DataType.uint32_t) + long names_offset; // 0x50 relative offset to the names/import section of the file + @IggyFieldType(DataType.uint32_t) + long unk_offset5; // 0x54 relative offset to something + @IggyFieldType(DataType.uint64_t) + long unk_58; // Maybe number of imports/names pointed by names_offset + @IggyFieldType(DataType.uint32_t) + long last_section_offset; // 0x60 relative offset, points to the small last section of the file + @IggyFieldType(DataType.uint32_t) + long unk_offset6; // 0x64 relative offset to something + @IggyFieldType(DataType.uint32_t) + long as3_code_offset; // 0x68 relative offset to as3 code (8 bytes header + abc blob) + @IggyFieldType(DataType.uint32_t) + long as3_names_offset; // 0x6C relative offset to as3 file names table (or classes names or whatever) + @IggyFieldType(DataType.uint32_t) + long unk_70; + @IggyFieldType(DataType.uint32_t) + long unk_74; + @IggyFieldType(DataType.uint32_t) + long unk_78; // Maybe number of classes / as3 names + @IggyFieldType(DataType.uint32_t) + long unk_7C; + + // Offset 0x80 (outside header): there are *unk_28* relative offsets that point to flash objects. + // The flash objects are in a format different to swf but there is probably a way to convert between them. + // After the offsets, the bodies of objects pointed above, which apparently have a code like 0xFFXX to identify the type of object, followed by a (unique?) identifier + // for the object. + // A DefineEditText-like object can be easily spotted and apparently uses type code 0x06 (or 0xFF06) but as stated above, + // it is written in a different way. + public IggyFlashHeader32(AbstractDataStream stream) throws IOException { + readFromDataStream(stream); + } + + @Override + public void readFromDataStream(AbstractDataStream stream) throws IOException { + main_offset = stream.readUI32(); + as3_section_offset = stream.readUI32(); + unk_offset = stream.readUI32(); + unk_offset2 = stream.readUI32(); + unk_offset3 = stream.readUI32(); + unk_offset4 = stream.readUI32(); + xmin = stream.readUI32(); + ymin = stream.readUI32(); + xmax = stream.readUI32(); + ymax = stream.readUI32(); + unk_28 = stream.readUI32(); + unk_2C = stream.readUI32(); + unk_30 = stream.readUI32(); + unk_34 = stream.readUI32(); + unk_38 = stream.readUI32(); + unk_3C = stream.readUI32(); + unk_40 = stream.readFloat(); + unk_44 = stream.readUI32(); + unk_48 = stream.readUI32(); + unk_4C = stream.readUI32(); + unk_3C = stream.readUI32(); + names_offset = stream.readUI32(); + unk_offset5 = stream.readUI32(); + unk_58 = stream.readUI64(); + last_section_offset = stream.readUI32(); + unk_offset6 = stream.readUI32(); + as3_code_offset = stream.readUI32(); + as3_names_offset = stream.readUI32(); + unk_70 = stream.readUI32(); + unk_74 = stream.readUI32(); + unk_78 = stream.readUI32(); + unk_7C = stream.readUI32(); + } + + @Override + public void writeToDataStream(AbstractDataStream stream) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + sb.append("[\r\n"); + sb.append("main_offset ").append(main_offset).append("\r\n"); + sb.append("as3_section_offset ").append(as3_section_offset).append("\r\n"); + sb.append("unk_offset ").append(unk_offset).append("\r\n"); + sb.append("unk_offset2 ").append(unk_offset2).append("\r\n"); + sb.append("unk_offset3 ").append(unk_offset3).append("\r\n"); + sb.append("unk_offset4 ").append(unk_offset4).append("\r\n"); + sb.append("xmin ").append(xmin).append("\r\n"); + sb.append("ymin ").append(ymin).append("\r\n"); + sb.append("xmax ").append(xmax).append("\r\n"); + sb.append("ymax ").append(ymax).append("\r\n"); + sb.append("unk_28 ").append(unk_28).append("\r\n"); + sb.append("unk_2C ").append(unk_2C).append("\r\n"); + sb.append("unk_30 ").append(unk_30).append("\r\n"); + sb.append("unk_34 ").append(unk_34).append("\r\n"); + sb.append("unk_38 ").append(unk_38).append("\r\n"); + sb.append("unk_3C ").append(unk_3C).append("\r\n"); + sb.append("unk_40 ").append(unk_40).append("\r\n"); + sb.append("unk_44 ").append(unk_44).append("\r\n"); + sb.append("unk_48 ").append(unk_48).append("\r\n"); + sb.append("unk_4C ").append(unk_4C).append("\r\n"); + sb.append("names_offset ").append(names_offset).append("\r\n"); + sb.append("unk_offset5 ").append(unk_offset5).append("\r\n"); + sb.append("unk_58 ").append(unk_58).append("\r\n"); + sb.append("last_section_offset ").append(last_section_offset).append("\r\n"); + sb.append("unk_offset6 ").append(unk_offset6).append("\r\n"); + sb.append("as3_code_offset ").append(as3_code_offset).append("\r\n"); + sb.append("as3_names_offset ").append(as3_names_offset).append("\r\n"); + sb.append("unk_70 ").append(unk_70).append("\r\n"); + sb.append("unk_74 ").append(unk_74).append("\r\n"); + sb.append("unk_78 ").append(unk_78).append("\r\n"); + sb.append("unk_7C ").append(unk_7C).append("\r\n"); + sb.append("]"); + return sb.toString(); + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyFlashHeader64.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyFlashHeader64.java new file mode 100644 index 000000000..1328e1c43 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyFlashHeader64.java @@ -0,0 +1,167 @@ +package com.jpexs.decompiler.flash.iggy; + +import com.jpexs.decompiler.flash.iggy.annotations.IggyFieldType; +import java.io.IOException; + +/** + * + * @author Jindra + * + * Based of works of somebody called eternity. + */ +public class IggyFlashHeader64 implements StructureInterface { + + @IggyFieldType(DataType.uint64_t) + long main_offset; // 0 Relative offset to first section (matches sizeof header); + @IggyFieldType(DataType.uint64_t) + long as3_section_offset; // 8 Relative offset to as3 file names table... + @IggyFieldType(DataType.uint64_t) + long unk_offset; // 0x10 relative offset to something + @IggyFieldType(DataType.uint64_t) + long unk_offset2; // 0x18 relative offset to something + @IggyFieldType(DataType.uint64_t) + long unk_offset3; // 0x20 relative offset to something + @IggyFieldType(DataType.uint64_t) + long unk_offset4; // 0x28 names_offset; 0x50 relative pointer to the names/import section of the file + @IggyFieldType(DataType.uint32_t) + long xmin; // 0x30 in pixels + @IggyFieldType(DataType.uint32_t) + long ymin; // 0x34 in pixels + @IggyFieldType(DataType.uint32_t) + long xmax; // 0x38 in pixels + @IggyFieldType(DataType.uint32_t) + long ymax; // 0x3C in pixels + @IggyFieldType(DataType.uint32_t) + long unk_40; // probably numer of blocks/objects after header + @IggyFieldType(DataType.uint32_t) + long unk_44; + @IggyFieldType(DataType.uint32_t) + long unk_48; + @IggyFieldType(DataType.uint32_t) + long unk_4C; + @IggyFieldType(DataType.uint32_t) + long unk_50; + @IggyFieldType(DataType.uint32_t) + long unk_54; + float unk_58; + @IggyFieldType(DataType.uint32_t) + long unk_5C; + @IggyFieldType(DataType.uint64_t) + long unk_60; + @IggyFieldType(DataType.uint64_t) + long unk_68; + @IggyFieldType(DataType.uint64_t) + long names_offset; // 0x70 relative offset to the names/import section of the file + @IggyFieldType(DataType.uint64_t) + long unk_offset5; // 0x78 relative offset to something + @IggyFieldType(DataType.uint64_t) + long unk_80; // Maybe number of imports/names pointed by names_offset + @IggyFieldType(DataType.uint64_t) + long last_section_offset; // 0x88 relative offset, points to the small last section of the file + @IggyFieldType(DataType.uint64_t) + long unk_offset6; // 0x90 relative offset to something + @IggyFieldType(DataType.uint64_t) + long as3_code_offset; // 0x98 relative offset to as3 code (16 bytes header + abc blob) + @IggyFieldType(DataType.uint64_t) + long as3_names_offset; // 0xA0 relative offset to as3 file names table (or classes names or whatever) + @IggyFieldType(DataType.uint32_t) + long unk_A8; + @IggyFieldType(DataType.uint32_t) + long unk_AC; + @IggyFieldType(DataType.uint32_t) + long unk_B0; // Maybe number of classes / as3 names + @IggyFieldType(DataType.uint32_t) + long unk_B4; + + // Offset 0xB8 (outside header): there are *unk_40* relative offsets that point to flash objects. + // The flash objects are in a format different to swf but there is probably a way to convert between them. + // After the offsets, the bodies of objects pointed above, which apparently have a code like 0xFFXX to identify the type of object, followed by a (unique?) identifier + // for the object. + // A DefineEditText-like object can be easily spotted and apparently uses type code 0x06 (or 0xFF06) but as stated above, + // it is written in a different way. + public IggyFlashHeader64(AbstractDataStream stream) throws IOException { + readFromDataStream(stream); + } + + @Override + public void readFromDataStream(AbstractDataStream stream) throws IOException { + main_offset = stream.readUI64(); + as3_section_offset = stream.readUI64(); + unk_offset = stream.readUI64(); + unk_offset2 = stream.readUI64(); + unk_offset3 = stream.readUI64(); + unk_offset4 = stream.readUI64(); + xmin = stream.readUI32(); + ymin = stream.readUI32(); + xmax = stream.readUI32(); + ymax = stream.readUI32(); + unk_40 = stream.readUI32(); + unk_44 = stream.readUI32(); + unk_48 = stream.readUI32(); + unk_4C = stream.readUI32(); + unk_50 = stream.readUI32(); + unk_54 = stream.readUI32(); + unk_58 = stream.readFloat(); + unk_5C = stream.readUI32(); + unk_60 = stream.readUI64(); + unk_68 = stream.readUI64(); + names_offset = stream.readUI64(); + unk_offset5 = stream.readUI64(); + unk_80 = stream.readUI64(); + last_section_offset = stream.readUI64(); + unk_offset6 = stream.readUI64(); + as3_code_offset = stream.readUI64(); + as3_names_offset = stream.readUI64(); + unk_A8 = stream.readUI32(); + unk_AC = stream.readUI32(); + unk_B0 = stream.readUI32(); + unk_B4 = stream.readUI32(); + + } + + @Override + public void writeToDataStream(AbstractDataStream stream) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("[\r\n"); + sb.append("main_offset ").append(main_offset).append("\r\n"); + sb.append("as3_section_offset ").append(as3_section_offset).append("\r\n"); + sb.append("unk_offset ").append(unk_offset).append("\r\n"); + sb.append("unk_offset2 ").append(unk_offset2).append("\r\n"); + sb.append("unk_offset3 ").append(unk_offset3).append("\r\n"); + sb.append("unk_offset4 ").append(unk_offset4).append("\r\n"); + sb.append("xmin ").append(xmin).append("\r\n"); + sb.append("ymin ").append(ymin).append("\r\n"); + sb.append("xmax ").append(ymax).append("\r\n"); + sb.append("ymax ").append(ymax).append("\r\n"); + sb.append("unk_40 ").append(unk_40).append("\r\n"); + sb.append("unk_44 ").append(unk_44).append("\r\n"); + sb.append("unk_48 ").append(unk_48).append("\r\n"); + sb.append("unk_4C ").append(unk_4C).append("\r\n"); + sb.append("unk_50 ").append(unk_50).append("\r\n"); + sb.append("unk_54 ").append(unk_54).append("\r\n"); + sb.append("unk_58 ").append(unk_58).append("\r\n"); + sb.append("unk_5C ").append(unk_5C).append("\r\n"); + sb.append("unk_60 ").append(unk_60).append("\r\n"); + sb.append("unk_68 ").append(unk_68).append("\r\n"); + sb.append("names_offset ").append(names_offset).append("\r\n"); + sb.append("unk_offset5 ").append(unk_offset5).append("\r\n"); + sb.append("unk_80 ").append(unk_80).append("\r\n"); + sb.append("last_section_offset ").append(last_section_offset).append("\r\n"); + sb.append("unk_offset6 ").append(unk_offset6).append("\r\n"); + sb.append("as3_code_offset ").append(as3_code_offset).append("\r\n"); + sb.append("as3_names_offset ").append(as3_names_offset).append("\r\n"); + sb.append("unk_A8 ").append(unk_A8).append("\r\n"); + sb.append("unk_AC ").append(unk_AC).append("\r\n"); + sb.append("unk_B0 ").append(unk_B0).append("\r\n"); + sb.append("unk_B4 ").append(unk_B4).append("\r\n"); + sb.append("]"); + return sb.toString(); + + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyHeader.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyHeader.java new file mode 100644 index 000000000..1db08b956 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggyHeader.java @@ -0,0 +1,107 @@ +package com.jpexs.decompiler.flash.iggy; + +import com.jpexs.decompiler.flash.iggy.annotations.IggyArrayFieldType; +import com.jpexs.decompiler.flash.iggy.annotations.IggyFieldType; +import java.io.IOException; +import java.math.BigInteger; + +/** + * + * @author JPEXS + * + * little endian all + * + * Based of works of somebody called eternity. + */ +public class IggyHeader implements StructureInterface { + + public static long MAGIC = 0xED0A6749; + + //Must be 0xED0A6749 + @IggyFieldType(DataType.uint32_t) + public long magic = MAGIC; + + //Assume 0x900 + @IggyFieldType(DataType.uint32_t) + public long version; + + //Assuming: 1 + @IggyFieldType(value = DataType.uint8_t) + public int platform1; + + //32/64 + @IggyFieldType(value = DataType.uint8_t) + public int platform2_bittness; + + //Assuming: 1 + @IggyFieldType(value = DataType.uint8_t) + public int platform3; + + //Usually: 3 + @IggyFieldType(value = DataType.uint8_t) + public int platform4; + + //flags for platform 64? + @IggyFieldType(DataType.uint32_t) + public long unk_0C; + + @IggyArrayFieldType(value = DataType.uint8_t, count = 12) + public byte[] reserved; + + @IggyFieldType(value = DataType.uint32_t) + public long num_subfiles; + + public IggyHeader(AbstractDataStream stream) throws IOException { + readFromDataStream(stream); + } + + public IggyHeader(long version, int platform1, int platform2_bittness, int platform3, int platform4, long unk_0C, byte[] reserved, long num_subfiles) { + this.version = version; + this.platform1 = platform1; + this.platform2_bittness = platform2_bittness; + this.platform3 = platform3; + this.platform4 = platform4; + this.unk_0C = unk_0C; + this.reserved = reserved; + this.num_subfiles = num_subfiles; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append("version: ").append(version).append(", "); + sb.append("platform: ").append(platform1).append(" ").append(platform2_bittness).append(" ").append(platform3).append(" ").append(platform4).append(", "); + sb.append("unk_0C: ").append(String.format("%08X", unk_0C)).append(", "); + sb.append("reserved: 12 bytes").append(", "); + sb.append("num_subfiles: ").append(num_subfiles); + sb.append("]"); + return sb.toString(); + } + + @Override + public void readFromDataStream(AbstractDataStream stream) throws IOException { + magic = stream.readUI32(); + if (magic != IggyHeader.MAGIC) { + throw new IOException("Invalid Iggy file"); + } + version = stream.readUI32(); + platform1 = stream.readUI8(); + platform2_bittness = stream.readUI8(); + platform3 = stream.readUI8(); + platform4 = stream.readUI8(); + unk_0C = stream.readUI32(); + reserved = stream.readBytes(12); + num_subfiles = stream.readUI32(); + } + + @Override + public void writeToDataStream(AbstractDataStream stream) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); + } + + public boolean is64() { + return platform2_bittness == 64; + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggySubFileEntry.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggySubFileEntry.java new file mode 100644 index 000000000..892422cfe --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/IggySubFileEntry.java @@ -0,0 +1,68 @@ +package com.jpexs.decompiler.flash.iggy; + +import com.jpexs.decompiler.flash.types.BasicType; +import com.jpexs.decompiler.flash.types.annotations.SWFType; +import java.io.IOException; + +/** + * + * @author JPEXS + * + * Based of works of somebody called eternity. + */ +public class IggySubFileEntry implements StructureInterface { + + public static final int TYPE_INDEX = 0; + public static final int TYPE_FLASH = 1; + + @SWFType(BasicType.UI32) + long type; + + @SWFType(BasicType.UI32) + long size; + + //apparently same as size, maybe (un)compressed (?) + @SWFType(BasicType.UI32) + long size2; + + //absolute offset + @SWFType(BasicType.UI32) + long offset; + + public IggySubFileEntry(AbstractDataStream stream) throws IOException { + readFromDataStream(stream); + } + + public IggySubFileEntry(long type, long size, long size2, long offset) { + this.type = type; + this.size = size; + this.size2 = size2; + this.offset = offset; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + sb.append("id: ").append(type).append(", "); + sb.append("size: ").append(size).append(", "); + sb.append("size2: ").append(size2).append(", "); + sb.append("offset: ").append(offset); + sb.append("]"); + return sb.toString(); + } + + @Override + public void readFromDataStream(AbstractDataStream stream) throws IOException { + type = stream.readUI32(); + size = stream.readUI32(); + size2 = stream.readUI32(); + offset = stream.readUI32(); + } + + @Override + public void writeToDataStream(AbstractDataStream stream) throws IOException { + throw new UnsupportedOperationException("Not supported yet."); + } + +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/SeekMode.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/SeekMode.java new file mode 100644 index 000000000..9ae2ffbfe --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/SeekMode.java @@ -0,0 +1,9 @@ +package com.jpexs.decompiler.flash.iggy; + +/** + * + * @author Jindra + */ +public enum SeekMode { + SET, CUR, END +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/StructureInterface.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/StructureInterface.java new file mode 100644 index 000000000..84aaff3eb --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/StructureInterface.java @@ -0,0 +1,14 @@ +package com.jpexs.decompiler.flash.iggy; + +import java.io.IOException; + +/** + * + * @author JPEXS + */ +public interface StructureInterface { + + public void readFromDataStream(AbstractDataStream stream) throws IOException; + + public void writeToDataStream(AbstractDataStream stream) throws IOException; +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/annotations/IggyArrayFieldType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/annotations/IggyArrayFieldType.java new file mode 100644 index 000000000..19e39b675 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/annotations/IggyArrayFieldType.java @@ -0,0 +1,26 @@ +package com.jpexs.decompiler.flash.iggy.annotations; + +import com.jpexs.decompiler.flash.iggy.DataType; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * @author JPEXS + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface IggyArrayFieldType { + + /// Type of value + DataType value() default DataType.unknown; + + int count() default -1; + + /// Field name on which Count depends + String countField() default ""; + + //Count to add to countField + int countAdd() default 0; +} diff --git a/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/annotations/IggyFieldType.java b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/annotations/IggyFieldType.java new file mode 100644 index 000000000..d8c2463a1 --- /dev/null +++ b/libsrc/ffdec_lib/src/com/jpexs/decompiler/flash/iggy/annotations/IggyFieldType.java @@ -0,0 +1,34 @@ +package com.jpexs.decompiler.flash.iggy.annotations; + +import com.jpexs.decompiler.flash.iggy.DataType; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author JPEXS + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface IggyFieldType { + + /// Type of value + DataType value() default DataType.unknown; + + /// Alternate type when condition is met + DataType alternateValue() default DataType.unknown; + + /// Condition for alternate type + String alternateCondition() default ""; + + /// Count - used primarily for bit fields UB,SB,FB to specify number of bits + int count() default -1; + + /// Field name on which Count depends + String countField() default ""; + + //Count to add to countField + int countAdd() default 0; +}