diff --git a/src/main/java/com/alterdekim/flash/decompiler/FlashDecompiler.java b/src/main/java/com/alterdekim/flash/decompiler/FlashDecompiler.java index 69fe30a..8d48225 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/FlashDecompiler.java +++ b/src/main/java/com/alterdekim/flash/decompiler/FlashDecompiler.java @@ -18,9 +18,10 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; -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.ByteUtil.*; import static com.alterdekim.flash.decompiler.util.Compression.decompressDEFLATE; import static com.alterdekim.flash.decompiler.util.Compression.decompressLZMA; @@ -106,6 +107,10 @@ public class FlashDecompiler { i++; tag.getActions().add(new ActionDefineLocal()); break; + case DefineLocal2: + i++; + tag.getActions().add(new ActionDefineLocal2()); + break; case SetMember: i++; tag.getActions().add(new ActionSetMember()); @@ -114,6 +119,16 @@ public class FlashDecompiler { i++; tag.getActions().add(new ActionGetVariable()); break; + case SetVariable: + i++; + tag.getActions().add(new ActionSetVariable()); + break; + case GetMember: + i++; + tag.getActions().add(new ActionGetMember()); + break; + case Unknown: + return tag; default: log.error("Unknown action field type: {}", StringUtils.bytesToHex(new byte[] {data[i]})); i++; @@ -133,6 +148,7 @@ public class FlashDecompiler { } private int parseActionPush(byte[] data, int i, DoAction tag) { + //int start_action_index = i; // for debug i++; int len_in_bytes = bytesToIntRev(Arrays.copyOfRange(data, i, i+2)); int u = 0; @@ -143,6 +159,7 @@ public class FlashDecompiler { while( u < len_in_bytes ) { ActionPushType type = ActionPushType.fromType(data[i]); switch(type) { + // implement other types (float especially) case STRING: i++; var result = findNull(data, i, u); @@ -175,6 +192,18 @@ public class FlashDecompiler { i++; u++; continue; + case BOOLEAN: + i++; + push.getItems().add(new BooleanItem( data[i] == 0x01 )); + i++; + u += 2; + continue; + case DOUBLE: + i++; + push.getItems().add(new DoubleItem(bytesToDouble(Arrays.copyOfRange(data, i, i+8)))); + i += 8; + u += 9; + continue; default: i++; u++; @@ -182,6 +211,12 @@ public class FlashDecompiler { } } tag.getActions().add(push); + /*byte[] bv = Arrays.copyOfRange(data, start_action_index, i); + List s = IntStream.range(0, bv.length) + .map(v -> bv[v]) + .mapToObj(v -> Integer.toHexString(v)) + .collect(Collectors.toList()); + log.info("Push data block: {}", s);*/ return i; } diff --git a/src/main/java/com/alterdekim/flash/decompiler/Main.java b/src/main/java/com/alterdekim/flash/decompiler/Main.java index c270cde..eabc70b 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/Main.java +++ b/src/main/java/com/alterdekim/flash/decompiler/Main.java @@ -12,7 +12,7 @@ import java.io.File; public class Main { public static void main(String[] args) throws Exception { FlashDecompiler decompiler = new FlashDecompiler(); - ShockwaveFile file = decompiler.loadFromFile(new File("D:\\Documents\\rtmpSpring\\static\\swf\\cache\\rus\\resources.swf")); + ShockwaveFile file = decompiler.loadFromFile(new File("D:\\Documents\\rtmpSpring\\static\\swf\\cache\\rus\\base[17].swf")); DoAction da = (DoAction) file.getTags() .stream() @@ -20,12 +20,10 @@ public class Main { .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)); + objectMapper.writeValue(out, new Flash2Java(da).convert()); } } \ No newline at end of file diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionAdd.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionAdd.java index ab7d2d6..34e4b03 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/action/ActionAdd.java +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionAdd.java @@ -1,7 +1,9 @@ package com.alterdekim.flash.decompiler.action; import com.alterdekim.flash.decompiler.util.ActionField; +import lombok.ToString; +@ToString public class ActionAdd extends ByteCodeAction { @Override public ActionField getType() { diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionDefineLocal2.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionDefineLocal2.java new file mode 100644 index 0000000..2ef8afa --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionDefineLocal2.java @@ -0,0 +1,14 @@ +package com.alterdekim.flash.decompiler.action; + +import com.alterdekim.flash.decompiler.util.ActionField; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@ToString +@NoArgsConstructor +public class ActionDefineLocal2 extends ByteCodeAction { + @Override + public ActionField getType() { + return ActionField.DefineLocal2; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionGetMember.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionGetMember.java new file mode 100644 index 0000000..35f6da2 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionGetMember.java @@ -0,0 +1,14 @@ +package com.alterdekim.flash.decompiler.action; + +import com.alterdekim.flash.decompiler.util.ActionField; +import lombok.NoArgsConstructor; +import lombok.ToString; + +@ToString +@NoArgsConstructor +public class ActionGetMember extends ByteCodeAction { + @Override + public ActionField getType() { + return ActionField.GetMember; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionInitObject.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionInitObject.java index 8a9754a..7d2424f 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/action/ActionInitObject.java +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionInitObject.java @@ -12,4 +12,6 @@ public class ActionInitObject extends ByteCodeAction { public ActionField getType() { return ActionField.InitObject; } + + } diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionSetVariable.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionSetVariable.java new file mode 100644 index 0000000..84daf4a --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionSetVariable.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 ActionSetVariable extends ByteCodeAction { + + @Override + public ActionField getType() { + return ActionField.SetVariable; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/ActionPushType.java b/src/main/java/com/alterdekim/flash/decompiler/item/ActionPushType.java index 1ebfd10..be99bad 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/item/ActionPushType.java +++ b/src/main/java/com/alterdekim/flash/decompiler/item/ActionPushType.java @@ -10,7 +10,12 @@ public enum ActionPushType { DOUBLE(6), INTEGER(7), POOL_INDEX(8), - POOL_INDEX_BIG(9); + POOL_INDEX_BIG(9), + + // these types are internal (used for decompiler's purposes) + // they are not exist + + INIT_OBJECT(-1); public final int type; diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/BooleanItem.java b/src/main/java/com/alterdekim/flash/decompiler/item/BooleanItem.java new file mode 100644 index 0000000..da4d69f --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/item/BooleanItem.java @@ -0,0 +1,19 @@ +package com.alterdekim.flash.decompiler.item; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public class BooleanItem extends ByteCodeItem { + + private Boolean val; + + @Override + public ActionPushType getType() { + return ActionPushType.BOOLEAN; + } + + @Override + public Object getValue() { + return val; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/DoubleItem.java b/src/main/java/com/alterdekim/flash/decompiler/item/DoubleItem.java new file mode 100644 index 0000000..7f559c1 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/item/DoubleItem.java @@ -0,0 +1,21 @@ +package com.alterdekim.flash.decompiler.item; + +import lombok.AllArgsConstructor; +import lombok.ToString; + +@ToString +@AllArgsConstructor +public class DoubleItem extends ByteCodeItem { + + private Double val; + + @Override + public ActionPushType getType() { + return ActionPushType.DOUBLE; + } + + @Override + public Object getValue() { + return this.val; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/internal/InitObjectItem.java b/src/main/java/com/alterdekim/flash/decompiler/item/internal/InitObjectItem.java new file mode 100644 index 0000000..a6db696 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/item/internal/InitObjectItem.java @@ -0,0 +1,23 @@ +package com.alterdekim.flash.decompiler.item.internal; + +import com.alterdekim.flash.decompiler.item.ActionPushType; +import com.alterdekim.flash.decompiler.item.ByteCodeItem; +import lombok.AllArgsConstructor; + +import java.util.HashMap; + +@AllArgsConstructor +public class InitObjectItem extends ByteCodeItem { + + private HashMap val; + + @Override + public ActionPushType getType() { + return ActionPushType.INIT_OBJECT; + } + + @Override + public Object getValue() { + return this.val; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/translator/Flash2Java.java b/src/main/java/com/alterdekim/flash/decompiler/translator/Flash2Java.java index 24a70d8..cefb015 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/translator/Flash2Java.java +++ b/src/main/java/com/alterdekim/flash/decompiler/translator/Flash2Java.java @@ -1,11 +1,9 @@ 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.item.*; 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.item.StringItem; +import com.alterdekim.flash.decompiler.item.internal.InitObjectItem; import com.alterdekim.flash.decompiler.tag.DoAction; import com.alterdekim.flash.decompiler.util.ActionField; import lombok.extern.slf4j.Slf4j; @@ -15,80 +13,119 @@ import java.util.*; @Slf4j public class Flash2Java { - private List pool; + private final List pool; + private final List actions; + private final Stack stack; // ByteCodeAction + private final Map vars; - public Map convert(DoAction doAction) { + public Flash2Java(DoAction doAction) { this.pool = doAction.getActionPool(); - List actions = doAction.getActions(); + this.stack = new Stack<>(); + this.actions = doAction.getActions(); + this.vars = new HashMap<>(); + } - // convert stack from List to List and rewrite all code accordingly. - List stack = new ArrayList<>(); - Map vars = new HashMap<>(); + public Map convert() { + // abolish the activeness of variable inside of an HashMap. Use stack instead. for (ByteCodeAction action : actions) { - try { + // try { + //log.info("Action type: " + action.getType().toString()); switch (action.getType()) { case DefineLocal: - defineLocal(vars, stack); - stack.clear(); + defineLocal(); break; case GetVariable: - getVariable(vars, stack); - stack.clear(); + getVariable(); break; case SetMember: - setMember(vars, stack); - stack.clear(); + setMember(); break; case Add: - reduceViaAdder(stack); + reduceViaAdder(); + break; + case InitObject: + initObject(); + break; + case Push: + this.stack.addAll(((ActionPush) action).getItems()); + break; + case SetVariable: + setVariable(); + break; + case GetMember: + getMember(); break; default: - stack.add(action); + // ignore } - } catch (Exception e) { - log.error(e.getMessage()); - } + //} catch (Exception e) { + //log.error(e.getMessage()); + //} } return vars; } - private void reduceViaAdder(List stack) { + private void initObject() { + Integer numberOfEntries = (Integer) this.stack.pop().getValue(); + HashMap obj = new HashMap<>(); + for( int i = 0; i < numberOfEntries; i++ ) { + Object val = valConverter( this.stack.pop() ); + String key = (String) valConverter( this.stack.pop() ); + obj.put(key, val); + } + this.stack.push(new InitObjectItem(obj)); + } + + private void reduceViaAdder() { + /* // get last 2 elements from stack List si = new ArrayList<>(); List g = new ArrayList<>(); - for( int i = stack.size()-1; i >= 0; i-- ) { - ByteCodeAction e = stack.get(i); + for( int i = this.stack.size()-1; i >= 0; i-- ) { + ByteCodeAction e = this.stack.get(i); if( e.getType() != ActionField.Push ) continue; ActionPush push = (ActionPush) e; if( push.getItems().size() >= 2 ) { StringItem from = (StringItem) push.getItems().get(push.getItems().size()-1); StringItem to = (StringItem) push.getItems().get(push.getItems().size()-2); - push.getItems().remove() - ((String) to.getValue()) + ((String) from.getValue()) + /*push.getItems().remove() + ((String) to.getValue()) + ((String) from.getValue())*//* } } - + */ } - 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); + private void getMember() { + Object val = this.stack.pop().getValue(); + String name = (String) valConverter( this.stack.pop() ); + log.info("SetMember: {}", name); + String key = this.vars.keySet().stream().filter(k -> this.vars.get(k).isActive()).findFirst().get(); + HashMap g = (HashMap) this.vars.get(key).getVal(); + g.put(name, val); + this.vars.put(key, new FlashVar(true, g)); + } + + private boolean hasActiveVariable() { + return this.vars.keySet().stream().anyMatch(k -> this.vars.get(k).isActive()); + } + + private String getActiveVarKey() { + return this.vars.keySet().stream().filter(k -> this.vars.get(k).isActive()).findFirst().get(); + } + + private void setMember() { + ByteCodeItem val = this.stack.pop(); + + if( this.stack.isEmpty() && this.hasActiveVariable() ) { + String var = getActiveVarKey(); + 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); + Integer index = (Integer) this.stack.pop().getValue(); + String var = this.vars.keySet().stream().filter(k -> this.vars.get(k).isActive()).findFirst().get(); + Map m = (Map) this.vars.get(var).getVal(); + m.put(String.valueOf(index), val.getValue()); + this.vars.put(var, new FlashVar(false, m)); // this may cause a bug (false in FlashVar arg) } private Object valConverter(ByteCodeItem item) { @@ -100,27 +137,31 @@ public class Flash2Java { 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); + private void setVariable() { + Object val = this.stack.pop().getValue(); + String variableName = (String) valConverter( this.stack.pop() ); + vars.put(variableName, new FlashVar(false, val)); + } + + private void getVariable() { + String variableName = (String) valConverter( this.stack.pop() ); 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); + FlashVar v = vars.get(variableName); v.setActive(true); - vars.put(key, v); + vars.put(variableName, 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 void defineLocal() { + ByteCodeItem val = this.stack.pop(); + String name = (String) valConverter( this.stack.pop() ); + // todo: implement assignment ability (from one value to another) + vars.put(name, + new FlashVar(false, val.getValue()) + ); } private String fromPool(int index) { diff --git a/src/main/java/com/alterdekim/flash/decompiler/util/ActionField.java b/src/main/java/com/alterdekim/flash/decompiler/util/ActionField.java index 7519ce2..6f781bc 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/util/ActionField.java +++ b/src/main/java/com/alterdekim/flash/decompiler/util/ActionField.java @@ -5,8 +5,11 @@ public enum ActionField { Push((byte) 0x96), InitObject((byte) 0x43), DefineLocal((byte) 0x3C), + DefineLocal2((byte) 0x41), SetMember((byte)0x4F), + GetMember((byte) 0x4E), GetVariable((byte) 0x1C), + SetVariable((byte) 0x1D), Add((byte) 0x47), Unknown((byte) 0x00); diff --git a/src/main/java/com/alterdekim/flash/decompiler/util/ByteUtil.java b/src/main/java/com/alterdekim/flash/decompiler/util/ByteUtil.java index 54dd718..76723d8 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/util/ByteUtil.java +++ b/src/main/java/com/alterdekim/flash/decompiler/util/ByteUtil.java @@ -1,5 +1,8 @@ package com.alterdekim.flash.decompiler.util; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + public class ByteUtil { public static int bytesToInt(byte[] bytes) { int value = 0; @@ -16,4 +19,8 @@ public class ByteUtil { } return value; } + + public static double bytesToDouble(byte[] bytes) { + return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getDouble(); + } }