commit 132f3c9d1643bf65b4869234b6bdaecc8331f4ca Author: alterdekim Date: Sun Dec 29 04:31:01 2024 +0300 Push it to gitea! diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc3f89c --- /dev/null +++ b/.gitignore @@ -0,0 +1,39 @@ +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### IntelliJ IDEA ### +.idea +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..1ef6c95 --- /dev/null +++ b/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + com.alterdekim.game + actionScriptDecompiler + 1.0-SNAPSHOT + jar + + + 11 + 11 + UTF-8 + com.alterdekim.flash.decompiler.Main + + + + + com.github.jponge + lzma-java + 1.2 + + + org.apache.commons + commons-compress + 1.26.1 + + + org.projectlombok + lombok + 1.18.30 + provided + + + org.slf4j + slf4j-api + 2.0.9 + + + org.slf4j + slf4j-simple + 2.0.9 + + + org.javatuples + javatuples + 1.2 + + + com.fasterxml.jackson.core + jackson-databind + 2.17.2 + + + \ No newline at end of file diff --git a/src/main/java/com/alterdekim/flash/decompiler/FlashDecompiler.java b/src/main/java/com/alterdekim/flash/decompiler/FlashDecompiler.java new file mode 100644 index 0000000..69fe30a --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/FlashDecompiler.java @@ -0,0 +1,210 @@ +package com.alterdekim.flash.decompiler; + +import com.alterdekim.flash.decompiler.action.*; +import com.alterdekim.flash.decompiler.item.*; +import com.alterdekim.flash.decompiler.tag.DoAction; +import com.alterdekim.flash.decompiler.tag.ShockwaveTag; +import com.alterdekim.flash.decompiler.tag.TagType; +import com.alterdekim.flash.decompiler.util.ActionField; +import com.alterdekim.flash.decompiler.util.Bits; +import com.alterdekim.flash.decompiler.util.PayloadCompression; +import com.alterdekim.flash.decompiler.util.StringUtils; +import lombok.extern.slf4j.Slf4j; +import org.javatuples.Triplet; + +import java.io.*; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.BitSet; +import java.util.List; + +import static com.alterdekim.flash.decompiler.util.ByteUtil.bytesToInt; +import static com.alterdekim.flash.decompiler.util.ByteUtil.bytesToIntRev; +import static com.alterdekim.flash.decompiler.util.Compression.decompressDEFLATE; +import static com.alterdekim.flash.decompiler.util.Compression.decompressLZMA; + +@Slf4j +public class FlashDecompiler { + + private PayloadCompression compression; + + public ShockwaveFile loadFromFile( File flashFile) throws IOException { + return processSWF(Files.readAllBytes(flashFile.toPath())); + } + + private ShockwaveFile processSWF(byte[] swfBytes) throws IOException { + compression = PayloadCompression.getByTag(swfBytes[0]); + byte[] data = Arrays.copyOfRange(swfBytes, 8, swfBytes.length); + switch (compression) { + case DEFLATE: + data = decompressDEFLATE(data); + break; + case LZMA: + data = decompressLZMA(data); + break; + } + return processPayload(data); + } + + private ShockwaveFile processPayload(byte[] payload) { + BitSet bitSet = BitSet.valueOf(payload); + int c = (payload.length * 8); + int i = 0; + int cnt = Bits.convert(bitSet.get(i, i+7), 7, false); // Rect x=[5] [x] [x] [x] [x] + i += 7; + i += cnt * 4; + i = Bits.ceilToByte(i); + float frameRate = Bits.convertP8(bitSet.get(i, i+16)); + i += 16; + int frameCount = Bits.convert(bitSet.get(i, i+16), 16, true); + i += 16; + // 16-bit + 16-bit = 32 bits for each header tag. + i = Bits.ceilToByte(i); + List shockwaveTags = new ArrayList<>(); + while( i < c ) { + BitSet tag = bitSet.get(i, i+16); + int v = bytesToInt(new byte[] {(byte) Bits.convert(tag.get(8, 16), 8, true), + (byte) Bits.convert(tag.get(0, 8), 8, true)}); + i += 16; + int type = (v & 65472) >> 6; // type + int len = v & 63; // length in bytes + if( len == 63 ) { + len = Bits.convert(bitSet.get(i, i+32), 32, true); + i += 32; + } + TagType tagType = TagType.fromId(type); + if( tagType == TagType.DoAction ) { + int g = i / 8; + shockwaveTags.add(processDoActionTag(Arrays.copyOfRange(payload, g, g+len))); + } else { + log.error("Unknown tag type: {}", type); + } + i += len * 8; + } + return new ShockwaveFile(frameRate, frameCount, shockwaveTags, compression); + } + + private DoAction processDoActionTag(byte[] data) { + int i = 0; + DoAction tag = new DoAction(); + tag.setActions(new ArrayList<>()); + while( i < data.length ) { + switch(ActionField.fromId(data[i])) { + case ConstantPool: + i++; + i = parseActionPool(data, i, tag); + break; + case Push: + i = parseActionPush(data, i, tag); + break; + case InitObject: + tag.getActions().add(new ActionInitObject()); + i++; + break; + case DefineLocal: + i++; + tag.getActions().add(new ActionDefineLocal()); + break; + case SetMember: + i++; + tag.getActions().add(new ActionSetMember()); + break; + case GetVariable: + i++; + tag.getActions().add(new ActionGetVariable()); + break; + default: + log.error("Unknown action field type: {}", StringUtils.bytesToHex(new byte[] {data[i]})); + i++; + break; + } + } + return tag; + } + + private Triplet findNull(byte[] data, int i, int u) { + int s = i; + while( data[i] != 0x00 ) { + i++; + u++; + } + return Triplet.with(Arrays.copyOfRange(data, s, i), i, u); + } + + private int parseActionPush(byte[] data, int i, DoAction tag) { + i++; + int len_in_bytes = bytesToIntRev(Arrays.copyOfRange(data, i, i+2)); + int u = 0; + i += 2; + int index; + ActionPush push = new ActionPush(); + push.setItems(new ArrayList<>()); + while( u < len_in_bytes ) { + ActionPushType type = ActionPushType.fromType(data[i]); + switch(type) { + case STRING: + i++; + var result = findNull(data, i, u); + push.getItems().add(new StringItem(new String(result.getValue0()))); + i = result.getValue1(); + u = result.getValue2(); + break; + case POOL_INDEX_BIG: + i++; + index = bytesToIntRev(Arrays.copyOfRange(data, i, i+2)); + push.getItems().add(new PoolIndexItem(index)); + i += 2; + u += 3; + continue; + case POOL_INDEX: + i++; + index = bytesToIntRev(new byte[] {data[i]}); + push.getItems().add(new PoolIndexItem(index)); + i++; + u+=2; + continue; + case INTEGER: + i++; + push.getItems().add(new IntegerItem(bytesToIntRev(Arrays.copyOfRange(data, i, i+4)))); + i += 4; + u += 5; + continue; + case NULL: + push.getItems().add(new NullItem()); + i++; + u++; + continue; + default: + i++; + u++; + log.info("parseActionPush(): Unknown action field: {}", data[i]); + } + } + tag.getActions().add(push); + return i; + } + + private int parseActionPool(byte[] data, int i, DoAction tag) { + List actionPool = new ArrayList<>(); + int bytes_len = bytesToIntRev(Arrays.copyOfRange(data, i, i+2)); + i += 2; + int count = bytesToIntRev(Arrays.copyOfRange(data, i, i+2)); + i += 2; + int u = 1; + StringBuilder s = new StringBuilder(); + while( u <= count ) { + if( data[i] == 0x00 ) { + actionPool.add(s.toString()); + s = new StringBuilder(); + u++; + i++; + continue; + } + s.append((char) data[i]); + i++; + } + tag.setActionPool(actionPool); + return i; + } +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/flash/decompiler/Main.java b/src/main/java/com/alterdekim/flash/decompiler/Main.java new file mode 100644 index 0000000..05900dc --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/Main.java @@ -0,0 +1,31 @@ +package com.alterdekim.flash.decompiler; + +import com.alterdekim.flash.decompiler.tag.DoAction; +import com.alterdekim.flash.decompiler.tag.TagType; +import com.alterdekim.flash.decompiler.translator.Flash2Java; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; + +@Slf4j +public class Main { + public static void main(String[] args) throws Exception { + FlashDecompiler decompiler = new FlashDecompiler(); + ShockwaveFile file = decompiler.loadFromFile(new File("catalogs.swf")); + + DoAction da = (DoAction) file.getTags() + .stream() + .filter(p -> p.getType() == TagType.DoAction) + .findFirst() + .get(); + + Flash2Java converter = new Flash2Java(); + + ObjectMapper objectMapper = new ObjectMapper(); + File out = new File("dec.json"); + if( out.exists() ) out.delete(); + objectMapper.writeValue(out, converter.convert(da)); + + } +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/flash/decompiler/ShockwaveFile.java b/src/main/java/com/alterdekim/flash/decompiler/ShockwaveFile.java new file mode 100644 index 0000000..ddbbc2f --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/ShockwaveFile.java @@ -0,0 +1,19 @@ +package com.alterdekim.flash.decompiler; + +import com.alterdekim.flash.decompiler.tag.ShockwaveTag; +import com.alterdekim.flash.decompiler.util.PayloadCompression; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.ToString; + +import java.util.List; + +@ToString +@Getter +@AllArgsConstructor +public class ShockwaveFile { + private Float frameRate; + private Integer frameCount; + private List tags; + private PayloadCompression compression; +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionDefineLocal.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionDefineLocal.java new file mode 100644 index 0000000..5368499 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionDefineLocal.java @@ -0,0 +1,16 @@ +package com.alterdekim.flash.decompiler.action; + +import com.alterdekim.flash.decompiler.util.ActionField; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@ToString +@NoArgsConstructor +public class ActionDefineLocal extends ByteCodeAction { + + @Override + public ActionField getType() { + return ActionField.DefineLocal; + } +} + diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionGetVariable.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionGetVariable.java new file mode 100644 index 0000000..7e35a41 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionGetVariable.java @@ -0,0 +1,15 @@ +package com.alterdekim.flash.decompiler.action; + +import com.alterdekim.flash.decompiler.util.ActionField; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@ToString +@NoArgsConstructor +public class ActionGetVariable extends ByteCodeAction { + + @Override + public ActionField getType() { + return ActionField.GetVariable; + } +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionInitObject.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionInitObject.java new file mode 100644 index 0000000..8a9754a --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionInitObject.java @@ -0,0 +1,15 @@ +package com.alterdekim.flash.decompiler.action; + +import com.alterdekim.flash.decompiler.util.ActionField; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@ToString +@NoArgsConstructor +public class ActionInitObject extends ByteCodeAction { + + @Override + public ActionField getType() { + return ActionField.InitObject; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionPush.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionPush.java new file mode 100644 index 0000000..37a7b7f --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionPush.java @@ -0,0 +1,24 @@ +package com.alterdekim.flash.decompiler.action; + +import com.alterdekim.flash.decompiler.util.ActionField; +import com.alterdekim.flash.decompiler.item.ByteCodeItem; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@Getter +@Setter +@NoArgsConstructor +@ToString +public class ActionPush extends ByteCodeAction { + + private List items; + + @Override + public ActionField getType() { + return ActionField.Push; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionSetMember.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionSetMember.java new file mode 100644 index 0000000..ceb84c2 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionSetMember.java @@ -0,0 +1,15 @@ +package com.alterdekim.flash.decompiler.action; + +import com.alterdekim.flash.decompiler.util.ActionField; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@ToString +@NoArgsConstructor +public class ActionSetMember extends ByteCodeAction { + + @Override + public ActionField getType() { + return ActionField.SetMember; + } +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ByteCodeAction.java b/src/main/java/com/alterdekim/flash/decompiler/action/ByteCodeAction.java new file mode 100644 index 0000000..29b81bc --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ByteCodeAction.java @@ -0,0 +1,11 @@ +package com.alterdekim.flash.decompiler.action; + +import com.alterdekim.flash.decompiler.util.ActionField; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@ToString +@NoArgsConstructor +public abstract class ByteCodeAction { + public abstract ActionField getType(); +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/ActionPushType.java b/src/main/java/com/alterdekim/flash/decompiler/item/ActionPushType.java new file mode 100644 index 0000000..1ebfd10 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/item/ActionPushType.java @@ -0,0 +1,27 @@ +package com.alterdekim.flash.decompiler.item; + +public enum ActionPushType { + STRING(0), + FLOAT(1), + NULL(2), + UNDEFINED(3), + REGISTER(4), + BOOLEAN(5), + DOUBLE(6), + INTEGER(7), + POOL_INDEX(8), + POOL_INDEX_BIG(9); + + public final int type; + + ActionPushType(int type) { + this.type = type; + } + + public static ActionPushType fromType(int type) { + for( ActionPushType pushType : ActionPushType.values()) { + if( pushType.type == type ) return pushType; + } + return NULL; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/ByteCodeItem.java b/src/main/java/com/alterdekim/flash/decompiler/item/ByteCodeItem.java new file mode 100644 index 0000000..a4d71e8 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/item/ByteCodeItem.java @@ -0,0 +1,11 @@ +package com.alterdekim.flash.decompiler.item; + +import lombok.NoArgsConstructor; +import lombok.ToString; + +@NoArgsConstructor +@ToString +public abstract class ByteCodeItem { + public abstract ActionPushType getType(); + public abstract Object getValue(); +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/IntegerItem.java b/src/main/java/com/alterdekim/flash/decompiler/item/IntegerItem.java new file mode 100644 index 0000000..706d3c7 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/item/IntegerItem.java @@ -0,0 +1,20 @@ +package com.alterdekim.flash.decompiler.item; + +import lombok.AllArgsConstructor; +import lombok.ToString; + +@ToString +@AllArgsConstructor +public class IntegerItem extends ByteCodeItem { + private Integer val; + + @Override + public ActionPushType getType() { + return ActionPushType.INTEGER; + } + + @Override + public Object getValue() { + return val; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/NullItem.java b/src/main/java/com/alterdekim/flash/decompiler/item/NullItem.java new file mode 100644 index 0000000..25583b0 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/item/NullItem.java @@ -0,0 +1,18 @@ +package com.alterdekim.flash.decompiler.item; + +import lombok.NoArgsConstructor; +import lombok.ToString; + +@ToString +@NoArgsConstructor +public class NullItem extends ByteCodeItem { + @Override + public ActionPushType getType() { + return ActionPushType.NULL; + } + + @Override + public Object getValue() { + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/PoolIndexItem.java b/src/main/java/com/alterdekim/flash/decompiler/item/PoolIndexItem.java new file mode 100644 index 0000000..22d41bd --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/item/PoolIndexItem.java @@ -0,0 +1,21 @@ +package com.alterdekim.flash.decompiler.item; + +import lombok.AllArgsConstructor; +import lombok.ToString; + +@ToString +@AllArgsConstructor +public class PoolIndexItem extends ByteCodeItem { + + private Integer val; + + @Override + public ActionPushType getType() { + return ActionPushType.POOL_INDEX; + } + + @Override + public Object getValue() { + return val; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/StringItem.java b/src/main/java/com/alterdekim/flash/decompiler/item/StringItem.java new file mode 100644 index 0000000..e3e57e7 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/item/StringItem.java @@ -0,0 +1,21 @@ +package com.alterdekim.flash.decompiler.item; + +import lombok.AllArgsConstructor; +import lombok.ToString; + +@ToString +@AllArgsConstructor +public class StringItem extends ByteCodeItem { + private String val; + + @Override + public ActionPushType getType() { + return ActionPushType.STRING; + } + + @Override + public Object getValue() { + return val; + } +} + diff --git a/src/main/java/com/alterdekim/flash/decompiler/tag/DoAction.java b/src/main/java/com/alterdekim/flash/decompiler/tag/DoAction.java new file mode 100644 index 0000000..9a1541a --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/tag/DoAction.java @@ -0,0 +1,24 @@ +package com.alterdekim.flash.decompiler.tag; + +import com.alterdekim.flash.decompiler.action.ByteCodeAction; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +import java.util.List; + +@NoArgsConstructor +@ToString +@Getter +@Setter +public class DoAction extends ShockwaveTag { + + private List actionPool; + private List actions; + + @Override + public TagType getType() { + return TagType.DoAction; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/tag/ShockwaveTag.java b/src/main/java/com/alterdekim/flash/decompiler/tag/ShockwaveTag.java new file mode 100644 index 0000000..f39ae54 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/tag/ShockwaveTag.java @@ -0,0 +1,10 @@ +package com.alterdekim.flash.decompiler.tag; + +import lombok.NoArgsConstructor; +import lombok.ToString; + +@ToString +@NoArgsConstructor +public abstract class ShockwaveTag { + public abstract TagType getType(); +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/tag/TagType.java b/src/main/java/com/alterdekim/flash/decompiler/tag/TagType.java new file mode 100644 index 0000000..9a7f93e --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/tag/TagType.java @@ -0,0 +1,22 @@ +package com.alterdekim.flash.decompiler.tag; + +public enum TagType { + SetBackgroundColor(9), + DoAction(12), + ShowFrame(1), + End(0), + Unknown(-1); + + private final int id; + + TagType(int id) { + this.id = id; + } + + public static TagType fromId(int id) { + for( TagType type : TagType.values()) { + if( type.id == id ) return type; + } + return Unknown; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/translator/Flash2Java.java b/src/main/java/com/alterdekim/flash/decompiler/translator/Flash2Java.java new file mode 100644 index 0000000..87855cb --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/translator/Flash2Java.java @@ -0,0 +1,104 @@ +package com.alterdekim.flash.decompiler.translator; + +import com.alterdekim.flash.decompiler.action.ActionPush; +import com.alterdekim.flash.decompiler.item.ActionPushType; +import com.alterdekim.flash.decompiler.action.ByteCodeAction; +import com.alterdekim.flash.decompiler.item.ByteCodeItem; +import com.alterdekim.flash.decompiler.item.PoolIndexItem; +import com.alterdekim.flash.decompiler.tag.DoAction; +import com.alterdekim.flash.decompiler.util.ActionField; +import lombok.extern.slf4j.Slf4j; + +import java.util.*; + +@Slf4j +public class Flash2Java { + + private List pool; + + public Map convert(DoAction doAction) { + this.pool = doAction.getActionPool(); + List actions = doAction.getActions(); + + List stack = new ArrayList<>(); + Map vars = new HashMap<>(); + for (ByteCodeAction action : actions) { + try { + switch (action.getType()) { + case DefineLocal: + defineLocal(vars, stack); + stack.clear(); + break; + case GetVariable: + getVariable(vars, stack); + stack.clear(); + break; + case SetMember: + setMember(vars, stack); + stack.clear(); + break; + default: + stack.add(action); + } + } catch (Exception e) { + log.error(e.getMessage()); + } + } + return vars; + } + + private void setMember(Map vars, List stack) { + String varKey = vars.keySet().stream().filter(k -> vars.get(k).isActive()).findFirst().get(); + FlashVar varVal = vars.get(varKey); + ActionPush push = (ActionPush) stack.get(0); + if( stack.size() == 1 ) { + Map m = varVal.getVal() != null ? ((Map) varVal.getVal()) : new HashMap<>(); + m.put((Integer) push.getItems().get(0).getValue(), fromPool((Integer) push.getItems().get(1).getValue())); + varVal.setVal(m); + vars.put(varKey, varVal); + return; + } + Map> m = varVal.getVal() != null ? ((Map>) varVal.getVal()) : new HashMap<>(); + Map v = new HashMap<>(); + for( int i = 1; i < push.getItems().size()-1; i+=2 ) { + v.put(fromPool((Integer) push.getItems().get(i).getValue()), valConverter(push.getItems().get(i+1))); + } + m.put((Integer) push.getItems().get(0).getValue(), v); + varVal.setVal(m); + vars.put(varKey, varVal); + } + + private Object valConverter(ByteCodeItem item) { + if ( item.getType() == ActionPushType.POOL_INDEX ) { + return fromPool( (Integer) item.getValue() ); + } + return item.getValue(); + } + + private void getVariable(Map vars, List stack) throws Exception { + if (stack.size() != 1 ) { throw new Exception("Bad getVariable format"); } + ActionPush push = (ActionPush) stack.get(0); + PoolIndexItem pi = (PoolIndexItem) push.getItems().get(0); + vars.keySet().forEach(k -> { + FlashVar fv = vars.get(k); + fv.setActive(false); + vars.put(k, fv); + }); + String key = fromPool((int) pi.getValue()); + FlashVar v = vars.get(key); + v.setActive(true); + vars.put(key, v); + } + + private void defineLocal(Map vars, List stack) throws Exception { + if (stack.size() != 2 || stack.get(1).getType() != ActionField.InitObject) { throw new Exception("Bad defineLocal format"); } + ActionPush push = (ActionPush) stack.get(0); + PoolIndexItem pi = (PoolIndexItem) push.getItems().get(0); + // todo: implement assignment ability + vars.put(fromPool((Integer) pi.getValue()), new FlashVar()); + } + + private String fromPool(int index) { + return pool.get(index); + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/translator/FlashVar.java b/src/main/java/com/alterdekim/flash/decompiler/translator/FlashVar.java new file mode 100644 index 0000000..8a5141a --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/translator/FlashVar.java @@ -0,0 +1,15 @@ +package com.alterdekim.flash.decompiler.translator; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Setter +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class FlashVar { + private boolean isActive = false; + private Object val; +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/util/ActionField.java b/src/main/java/com/alterdekim/flash/decompiler/util/ActionField.java new file mode 100644 index 0000000..7eba522 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/util/ActionField.java @@ -0,0 +1,24 @@ +package com.alterdekim.flash.decompiler.util; + +public enum ActionField { + ConstantPool((byte) 0x88), + Push((byte) 0x96), + InitObject((byte) 0x43), + DefineLocal((byte) 0x3C), + SetMember((byte)0x4F), + GetVariable((byte) 0x1C), + Unknown((byte) 0x00); + + public final byte id; + + ActionField(byte id) { + this.id = id; + } + + public static ActionField fromId(int id) { + for( ActionField type : ActionField.values()) { + if( type.id == id ) return type; + } + return Unknown; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/util/Bits.java b/src/main/java/com/alterdekim/flash/decompiler/util/Bits.java new file mode 100644 index 0000000..2ae01d2 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/util/Bits.java @@ -0,0 +1,29 @@ +package com.alterdekim.flash.decompiler.util; + +import lombok.extern.slf4j.Slf4j; + +import java.util.BitSet; + +@Slf4j +public class Bits { + + public static int convert(BitSet bits, int len, boolean swapEndian) { + int n = 0; + for(int i = 0; i < len; i++ ) { + n += bits.get(i) ? 1 << (swapEndian ? i : (len-i-1)) : 0; + } + return n; + } + + public static float convertP8(BitSet bits) { + float n = convert(bits.get(8, 16), 8, true); + for(int i = 0; i < 8; i++) { + n += bits.get(i) ? 1f / (float) (1 << i) : 0f; + } + return n; + } + + public static int ceilToByte(int n) { + return (n % 8 == 0) ? n : n + (8 - (n % 8)); + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/util/ByteUtil.java b/src/main/java/com/alterdekim/flash/decompiler/util/ByteUtil.java new file mode 100644 index 0000000..54dd718 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/util/ByteUtil.java @@ -0,0 +1,19 @@ +package com.alterdekim.flash.decompiler.util; + +public class ByteUtil { + public static int bytesToInt(byte[] bytes) { + int value = 0; + for (byte b : bytes) { + value = (value << 8) + (b & 0xFF); + } + return value; + } + + public static int bytesToIntRev(byte[] bytes) { + int value = 0; + for(int i = bytes.length-1; i >= 0; i-- ) { + value = (value << 8) + (bytes[i] & 0xFF); + } + return value; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/util/Compression.java b/src/main/java/com/alterdekim/flash/decompiler/util/Compression.java new file mode 100644 index 0000000..e34f0ad --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/util/Compression.java @@ -0,0 +1,24 @@ +package com.alterdekim.flash.decompiler.util; + +import lzma.sdk.lzma.Decoder; +import lzma.streams.LzmaInputStream; +import org.apache.commons.io.IOUtils; + +import java.io.BufferedInputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.zip.InflaterInputStream; + +public class Compression { + public static byte[] decompressDEFLATE(byte[] data) throws IOException { + return IOUtils.toByteArray(new InflaterInputStream(new ByteArrayInputStream(data))); + } + + public static byte[] decompressLZMA(byte[] data) throws IOException { + try (LzmaInputStream inputStream = new LzmaInputStream( + new BufferedInputStream(new ByteArrayInputStream(data)), + new Decoder())) { + return IOUtils.toByteArray(inputStream); + } + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/util/PayloadCompression.java b/src/main/java/com/alterdekim/flash/decompiler/util/PayloadCompression.java new file mode 100644 index 0000000..cdc8f25 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/util/PayloadCompression.java @@ -0,0 +1,19 @@ +package com.alterdekim.flash.decompiler.util; + +public enum PayloadCompression { + NONE((byte) 0x46), + DEFLATE((byte) 0x43), + LZMA((byte) 0x5a); + + public final byte tag; + + PayloadCompression(byte tag) { + this.tag = tag; + } + + public static PayloadCompression getByTag(byte tag) { + for( PayloadCompression compression : PayloadCompression.values() ) + if( compression.tag == tag ) return compression; + return PayloadCompression.NONE; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/util/StringUtils.java b/src/main/java/com/alterdekim/flash/decompiler/util/StringUtils.java new file mode 100644 index 0000000..a86e96d --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/util/StringUtils.java @@ -0,0 +1,15 @@ +package com.alterdekim.flash.decompiler.util; + +public class StringUtils { + private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray(); + + public static String bytesToHex(byte[] bytes) { + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars); + } +}