diff --git a/pom.xml b/pom.xml index 1ef6c95..d7ea83a 100644 --- a/pom.xml +++ b/pom.xml @@ -54,4 +54,16 @@ 2.17.2 + + + + org.apache.maven.plugins + maven-compiler-plugin + + 14 + 14 + + + + \ 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 index 6afd01b..dd57da6 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/Main.java +++ b/src/main/java/com/alterdekim/flash/decompiler/Main.java @@ -1,5 +1,6 @@ package com.alterdekim.flash.decompiler; +import com.alterdekim.flash.decompiler.compiler.FlashCompiler; import com.alterdekim.flash.decompiler.tag.DoAction; import com.alterdekim.flash.decompiler.tag.TagType; import com.alterdekim.flash.decompiler.translator.Flash2Java; @@ -8,12 +9,15 @@ import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; @Slf4j 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\\catalogs[18].swf")); // + ShockwaveFile file = decompiler.loadFromFile(new File("catalogs.swf")); // D:\Documents\FlashProjects\Untitled-2.swf DoAction da = (DoAction) file.getTags() .stream() @@ -25,6 +29,7 @@ public class Main { File out = new File("dec.json"); if( out.exists() ) out.delete(); //objectMapper.writeValue(out, new Flash2Java(da).convert()); - new Java2Flash().convert(new Flash2Java(da).convert()); + var j2f = new Java2Flash().convert(new Flash2Java(da).convert()); + Files.write(Path.of("./export1.swf"), new FlashCompiler(j2f).compile(), StandardOpenOption.CREATE); } } \ No newline at end of file diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionConstantPool.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionConstantPool.java new file mode 100644 index 0000000..67bd565 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionConstantPool.java @@ -0,0 +1,20 @@ +package com.alterdekim.flash.decompiler.action; + +import com.alterdekim.flash.decompiler.util.ActionField; +import lombok.*; + +import java.util.List; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class ActionConstantPool extends ByteCodeAction { + + private List pool; + + @Override + public ActionField getType() { + return ActionField.ConstantPool; + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/action/ActionPush.java b/src/main/java/com/alterdekim/flash/decompiler/action/ActionPush.java index 37a7b7f..c9e0b3a 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/action/ActionPush.java +++ b/src/main/java/com/alterdekim/flash/decompiler/action/ActionPush.java @@ -2,16 +2,14 @@ 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 lombok.*; import java.util.List; @Getter @Setter @NoArgsConstructor +@AllArgsConstructor @ToString public class ActionPush extends ByteCodeAction { diff --git a/src/main/java/com/alterdekim/flash/decompiler/compiler/CompileObject.java b/src/main/java/com/alterdekim/flash/decompiler/compiler/CompileObject.java new file mode 100644 index 0000000..4e0821a --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/compiler/CompileObject.java @@ -0,0 +1,8 @@ +package com.alterdekim.flash.decompiler.compiler; + +import java.util.BitSet; +import java.util.List; + +public interface CompileObject { + List compile(); +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/compiler/FlashCompiler.java b/src/main/java/com/alterdekim/flash/decompiler/compiler/FlashCompiler.java new file mode 100644 index 0000000..c0e613e --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/compiler/FlashCompiler.java @@ -0,0 +1,40 @@ +package com.alterdekim.flash.decompiler.compiler; + +import com.alterdekim.flash.decompiler.tag.*; +import com.alterdekim.flash.decompiler.translator.Java2Flash; +import com.alterdekim.flash.decompiler.util.ByteUtil; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Queue; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Slf4j +public class FlashCompiler { + private final Java2Flash pcode; + + public byte[] compile() { + List data = new ArrayList<>(); + data.addAll(List.of((byte) 0x46, (byte) 0x57, (byte) 0x53)); + data.add((byte) 0x08); // swf version (8) + List d1 = new ArrayList<>(); + d1.addAll(List.of((byte) 0x30, (byte) 0x0A, (byte) 0x00, (byte) 0xA0)); // rect + d1.addAll(List.of((byte) 0x00, (byte) 0x19)); // frameRate + d1.addAll(ByteUtil.intToBytes(1)); // frames Count + //List tags = List.of(new SetBackgroundColor(new byte[] {0,0,0}), new DoAction(pcode.getPool(), pcode.getActions()), new ShowFrame(), new End()); + //d1.addAll(tags.stream().map(CompileObject::compile).flatMap(Collection::stream).collect(Collectors.toList())); + d1.addAll(new SetBackgroundColor(new byte[] {0,0,0}).compile()); + d1.addAll(new DoAction(pcode.getPool(), pcode.getActions()).compile()); + d1.addAll(new ShowFrame().compile()); + d1.addAll(new End().compile()); + // fileSize u32 + log.info("fileSize u32: {}", d1.size()); + data.addAll(ByteUtil.intToBytes32(d1.size())); + data.addAll(d1); + return ByteUtil.toPrimitive(data); + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/item/BooleanItem.java b/src/main/java/com/alterdekim/flash/decompiler/item/BooleanItem.java index da4d69f..72f1cdb 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/item/BooleanItem.java +++ b/src/main/java/com/alterdekim/flash/decompiler/item/BooleanItem.java @@ -1,8 +1,10 @@ package com.alterdekim.flash.decompiler.item; import lombok.AllArgsConstructor; +import lombok.ToString; @AllArgsConstructor +@ToString public class BooleanItem extends ByteCodeItem { private Boolean 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 index 9a1541a..5eb0d4e 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/tag/DoAction.java +++ b/src/main/java/com/alterdekim/flash/decompiler/tag/DoAction.java @@ -1,18 +1,26 @@ package com.alterdekim.flash.decompiler.tag; +import com.alterdekim.flash.decompiler.action.ActionConstantPool; +import com.alterdekim.flash.decompiler.action.ActionPush; import com.alterdekim.flash.decompiler.action.ByteCodeAction; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; -import lombok.ToString; +import com.alterdekim.flash.decompiler.compiler.CompileObject; +import com.alterdekim.flash.decompiler.util.ActionField; +import com.alterdekim.flash.decompiler.util.ByteUtil; +import lombok.*; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Collection; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; @NoArgsConstructor @ToString @Getter @Setter -public class DoAction extends ShockwaveTag { +@AllArgsConstructor +public class DoAction extends ShockwaveTag implements CompileObject { private List actionPool; private List actions; @@ -21,4 +29,61 @@ public class DoAction extends ShockwaveTag { public TagType getType() { return TagType.DoAction; } + + @Override + public List compile() { + List bytes = new ArrayList<>(); + this.actions.forEach(action -> { + bytes.add(action.getType().id); + switch(action.getType()) { + case Push: + ActionPush push = (ActionPush) action; + List data = new ArrayList<>(); + push.getItems().forEach(item -> { + data.add((byte) item.getType().type); + switch (item.getType()) { + case STRING: + data.addAll(fromString((String) item.getValue())); + break; + case INTEGER: + data.addAll(ByteUtil.intToBytes32((Integer) item.getValue())); + break; + case POOL_INDEX: + Integer index = (Integer) item.getValue(); + data.addAll(index < 256 ? ByteUtil.intToBytes8(index) : ByteUtil.intToBytes(index)); + break; + case DOUBLE: + data.addAll(ByteUtil.doubleToBytes((Double) item.getValue())); + break; + case BOOLEAN: + data.add((byte) ((Boolean) item.getValue() ? 0x01 : 0x00)); + break; + } + }); + bytes.addAll(ByteUtil.intToBytes(data.size())); + bytes.addAll(data); + break; + case ConstantPool: + ActionConstantPool pool = (ActionConstantPool) action; + bytes.addAll(ByteUtil.intToBytes(pool.getPool().size())); // maybe there should also be u8 size of data + pool.getPool().forEach(s -> { + bytes.addAll(fromString(s)); + }); + break; + } + }); + bytes.add((byte) 0x0); + int val = getType().getId() << 6; + val |= 63; // 265955 + return Stream.of( ByteUtil.intToBytes(val), ByteUtil.intToBytes32(bytes.size()+6), bytes ).flatMap(Collection::stream).collect(Collectors.toList()); + } + + private List fromString(String s) { + List l = new ArrayList<>(); + for( char c : s.toCharArray() ) { + l.add((byte) c); + } + l.add((byte) 0x0); + return l; + } } diff --git a/src/main/java/com/alterdekim/flash/decompiler/tag/End.java b/src/main/java/com/alterdekim/flash/decompiler/tag/End.java new file mode 100644 index 0000000..08cfdb3 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/tag/End.java @@ -0,0 +1,22 @@ +package com.alterdekim.flash.decompiler.tag; + +import com.alterdekim.flash.decompiler.compiler.CompileObject; +import com.alterdekim.flash.decompiler.util.ByteUtil; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.List; + +@ToString +@NoArgsConstructor +public class End extends ShockwaveTag implements CompileObject { + @Override + public TagType getType() { + return TagType.End; + } + + @Override + public List compile() { + return ByteUtil.fromPrimitive(new byte[] {0, 0}); + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/tag/SetBackgroundColor.java b/src/main/java/com/alterdekim/flash/decompiler/tag/SetBackgroundColor.java new file mode 100644 index 0000000..71a968f --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/tag/SetBackgroundColor.java @@ -0,0 +1,35 @@ +package com.alterdekim.flash.decompiler.tag; + +import com.alterdekim.flash.decompiler.compiler.CompileObject; +import com.alterdekim.flash.decompiler.util.ByteUtil; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.BitSet; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@ToString +@Getter +@AllArgsConstructor +public class SetBackgroundColor extends ShockwaveTag implements CompileObject { + + private byte[] backgroundColor; + + @Override + public TagType getType() { + return TagType.SetBackgroundColor; + } + + @Override + public List compile() { + int val = getType().getId() << 6; + val |= 3; + return Stream.of(ByteUtil.intToBytes(val), ByteUtil.fromPrimitive(backgroundColor)).flatMap(Collection::stream).collect(Collectors.toList()); + } +} + diff --git a/src/main/java/com/alterdekim/flash/decompiler/tag/ShowFrame.java b/src/main/java/com/alterdekim/flash/decompiler/tag/ShowFrame.java new file mode 100644 index 0000000..4379096 --- /dev/null +++ b/src/main/java/com/alterdekim/flash/decompiler/tag/ShowFrame.java @@ -0,0 +1,25 @@ +package com.alterdekim.flash.decompiler.tag; + +import com.alterdekim.flash.decompiler.compiler.CompileObject; +import com.alterdekim.flash.decompiler.util.ByteUtil; +import lombok.NoArgsConstructor; +import lombok.ToString; + +import java.util.BitSet; +import java.util.List; +import java.util.stream.Stream; + +@ToString +@NoArgsConstructor +public class ShowFrame extends ShockwaveTag implements CompileObject { + @Override + public TagType getType() { + return TagType.ShowFrame; + } + + @Override + public List compile() { + int val = this.getType().getId() << 6; + return ByteUtil.intToBytes(val); + } +} diff --git a/src/main/java/com/alterdekim/flash/decompiler/tag/TagType.java b/src/main/java/com/alterdekim/flash/decompiler/tag/TagType.java index 9a7f93e..592c60d 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/tag/TagType.java +++ b/src/main/java/com/alterdekim/flash/decompiler/tag/TagType.java @@ -1,5 +1,8 @@ package com.alterdekim.flash.decompiler.tag; +import lombok.Getter; + +@Getter public enum TagType { SetBackgroundColor(9), DoAction(12), 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 a53ba61..dd2cb0c 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/translator/Flash2Java.java +++ b/src/main/java/com/alterdekim/flash/decompiler/translator/Flash2Java.java @@ -131,7 +131,7 @@ public class Flash2Java { m.put(index, this.vars.get(valToString(val))); return; } - m.put(index, val.getValue()); + m.put(index, valConverter(val)); return; } Map m = recursiveGet((ArrayDeque) varOrMember.getValue()); @@ -139,7 +139,7 @@ public class Flash2Java { m.put(index, this.vars.get(valToString(val))); return; } - m.put(index, val.getValue()); + m.put(index, valConverter(val)); } private Map recursiveGet(Deque l) { @@ -169,7 +169,7 @@ public class Flash2Java { } private void setVariable() { - Object val = this.stack.pop().getValue(); + Object val = valConverter( this.stack.pop() ); String variableName = valToString( this.stack.pop() ); this.vars.put(variableName, val); } @@ -186,7 +186,7 @@ public class Flash2Java { this.vars.put(name, this.vars.get(valToString(val))); return; } - this.vars.put(name, val.getValue()); + this.vars.put(name, valConverter(val)); } private String fromPool(int index) { diff --git a/src/main/java/com/alterdekim/flash/decompiler/translator/Java2Flash.java b/src/main/java/com/alterdekim/flash/decompiler/translator/Java2Flash.java index 36c4296..cf41eb2 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/translator/Java2Flash.java +++ b/src/main/java/com/alterdekim/flash/decompiler/translator/Java2Flash.java @@ -1,36 +1,115 @@ package com.alterdekim.flash.decompiler.translator; -import com.alterdekim.flash.decompiler.action.ByteCodeAction; +import com.alterdekim.flash.decompiler.action.*; +import com.alterdekim.flash.decompiler.item.*; +import com.alterdekim.flash.decompiler.item.internal.InitObjectItem; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.compress.harmony.unpack200.bytecode.ByteCode; import java.util.*; @Slf4j +@Getter public class Java2Flash { - private Set pool; - public List convert(Map map) { - this.pool = new HashSet<>(65535); - extractAllStrings(map); - log.info("poolSet: {}", this.pool); - return new ArrayList<>(); + private List pool; + private List actions; + + public Java2Flash convert(Map map) { + this.actions = new ArrayList<>(); + this.pool = new ArrayList<>(); + this.pool.addAll(extractAllStrings(new HashSet<>(65535), map)); // maybe make a check if hashset size is bigger than initialCapacity + this.actions.add(new ActionConstantPool(this.pool)); + map.keySet().forEach(k -> createVariable(map, k)); + map.keySet().stream().filter(k -> map.get(k).getClass().getSimpleName().equals("HashMap")).forEach(k -> setMembers(map, k)); + return this; } - private void extractAllStrings(Map map) { + private void setMembers(Map map, String key) { + HashMap m = (HashMap) map.get(key); + m.keySet().forEach(k -> { + this.actions.add(new ActionPush(Collections.singletonList(fromString(key)))); + this.actions.add(new ActionGetVariable()); + this.actions.add(new ActionPush(switch (m.get(k).getClass().getSimpleName()) { + case "String" -> List.of(fromString(k), fromString((String) m.get(k))); + case "Integer" -> List.of(fromString(k), new IntegerItem((Integer) m.get(k))); + case "Double" -> List.of(fromString(k), new DoubleItem((Double) m.get(k))); + case "Boolean" -> List.of(fromString(k), new BooleanItem((Boolean) m.get(k))); + default -> List.of(fromString(k)); + })); + if( m.get(k).getClass().getSimpleName().equals("HashMap") ) { + HashMap s = (HashMap) m.get(k); + parseHashMapMember(s); + this.actions.add(new ActionPush(Collections.singletonList(new IntegerItem(s.keySet().size())))); + this.actions.add(new ActionInitObject()); + } + this.actions.add(new ActionSetMember()); + }); + } + + private void parseHashMapMember(Map m) { + m.keySet().forEach(key -> { + switch (m.get(key).getClass().getSimpleName()) { + case "String": + this.actions.add(new ActionPush(List.of(fromString(key), fromString((String) m.get(key))))); + break; + case "Integer": + this.actions.add(new ActionPush(List.of(fromString(key), new IntegerItem((Integer) m.get(key))))); + break; + case "Double": + this.actions.add(new ActionPush(List.of(fromString(key), new DoubleItem((Double) m.get(key))))); + break; + case "Boolean": + this.actions.add(new ActionPush(List.of(fromString(key), new BooleanItem((Boolean) m.get(key))))); + break; + case "HashMap": + HashMap s = (HashMap) m.get(key); + parseHashMapMember(s); + this.actions.add(new ActionPush(Collections.singletonList(new IntegerItem(s.keySet().size())))); + this.actions.add(new ActionInitObject()); + break; + } + }); + } + + private void createVariable(Map map, String key) { + List items = new ArrayList<>(); + items.add(fromString(key)); + log.info("Type: {}", map.get(key).getClass().getSimpleName()); + items.add(switch (map.get(key).getClass().getSimpleName()) { + case "String" -> fromString((String) map.get(key)); + case "Integer" -> new IntegerItem((Integer) map.get(key)); + case "Double" -> new DoubleItem((Double) map.get(key)); + case "Boolean" -> new BooleanItem((Boolean) map.get(key)); + default -> new IntegerItem(0); + }); + this.actions.add(new ActionPush(items)); + if( map.get(key).getClass().getSimpleName().equals("HashMap") ) { + this.actions.add(new ActionInitObject()); + } + this.actions.add(new ActionDefineLocal()); + } + + private ByteCodeItem fromString(String s) { + int i = this.pool.indexOf(s); + return i != -1 ? new PoolIndexItem(i) : new StringItem(s); + } + + private Set extractAllStrings(Set pool, Map map) { map.keySet().forEach(k -> { - if(k.matches("[^0-9].+")) { + try { + Integer.parseInt(k); + } catch (Exception e) { pool.add(k); } }); map.values().forEach(o -> { - switch( o.getClass().getSimpleName() ) { - case "HashMap": - extractAllStrings((HashMap) o); - break; - case "String": - pool.add((String) o); - break; + switch (o.getClass().getSimpleName()) { + case "HashMap" -> extractAllStrings(pool, (HashMap) o); + case "String" -> pool.add((String) o); } }); + return pool; } -} +} \ No newline at end of file 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 7d96696..b3c4e52 100644 --- a/src/main/java/com/alterdekim/flash/decompiler/util/ByteUtil.java +++ b/src/main/java/com/alterdekim/flash/decompiler/util/ByteUtil.java @@ -5,6 +5,9 @@ import lombok.extern.slf4j.Slf4j; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; @Slf4j public class ByteUtil { @@ -24,6 +27,36 @@ public class ByteUtil { return value; } + private static byte[] int2bytes(int val, int len) { + byte[] r = new byte[len]; + for( int i = 0; i < len; i++ ) { + r[i] = (byte) ( (val >> 8*i) & 0xFF ); + } + return r; + } + + public static List intToBytes(int i) { + return fromPrimitive(int2bytes(i, 2)); + } + + public static List intToBytes8(int i) { + return fromPrimitive(int2bytes(i, 1)); + } + + public static List intToBytes32(int i) { + return fromPrimitive(int2bytes(i, 4)); + } + + public static List fromPrimitive(byte[] b) { + return IntStream.range(0, b.length).boxed().map(i -> b[i]).collect(Collectors.toList()); + } + + public static byte[] toPrimitive(List b) { + byte[] a = new byte[b.size()]; + IntStream.range(0, b.size()).boxed().forEach(i -> a[i] = b.get(i)); + return a; + } + public static double bytesToDouble(byte[] bytes) { byte[] b1 = new byte[8]; for( int i = bytes.length-1, u = 0; i >= 0; i--, u++ ) { @@ -34,4 +67,12 @@ public class ByteUtil { System.arraycopy(Arrays.copyOfRange(b1, 0, 4), 0, d, 4, 4); return ByteBuffer.wrap(d).order(ByteOrder.BIG_ENDIAN).getDouble(); } + + public static List doubleToBytes(double d) { + byte[] b = ByteBuffer.allocate(8).putDouble(d).array(); + byte[] b1 = new byte[8]; + System.arraycopy(b, 0, b1, 4, 4); + System.arraycopy(b, 4, b1, 0, 4); + return fromPrimitive(b1); + } }