Decompiler improvement, compiler improvement

This commit is contained in:
Michael Wain 2025-01-04 17:23:09 +03:00
parent d9bfbf2bc5
commit 68c221eb32
15 changed files with 387 additions and 32 deletions

12
pom.xml
View File

@ -54,4 +54,16 @@
<version>2.17.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>14</source>
<target>14</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -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);
}
}

View File

@ -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<String> pool;
@Override
public ActionField getType() {
return ActionField.ConstantPool;
}
}

View File

@ -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 {

View File

@ -0,0 +1,8 @@
package com.alterdekim.flash.decompiler.compiler;
import java.util.BitSet;
import java.util.List;
public interface CompileObject {
List<Byte> compile();
}

View File

@ -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<Byte> data = new ArrayList<>();
data.addAll(List.of((byte) 0x46, (byte) 0x57, (byte) 0x53));
data.add((byte) 0x08); // swf version (8)
List<Byte> 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<CompileObject> 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);
}
}

View File

@ -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;

View File

@ -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<String> actionPool;
private List<ByteCodeAction> actions;
@ -21,4 +29,61 @@ public class DoAction extends ShockwaveTag {
public TagType getType() {
return TagType.DoAction;
}
@Override
public List<Byte> compile() {
List<Byte> bytes = new ArrayList<>();
this.actions.forEach(action -> {
bytes.add(action.getType().id);
switch(action.getType()) {
case Push:
ActionPush push = (ActionPush) action;
List<Byte> 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<Byte> fromString(String s) {
List<Byte> l = new ArrayList<>();
for( char c : s.toCharArray() ) {
l.add((byte) c);
}
l.add((byte) 0x0);
return l;
}
}

View File

@ -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<Byte> compile() {
return ByteUtil.fromPrimitive(new byte[] {0, 0});
}
}

View File

@ -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<Byte> compile() {
int val = getType().getId() << 6;
val |= 3;
return Stream.of(ByteUtil.intToBytes(val), ByteUtil.fromPrimitive(backgroundColor)).flatMap(Collection::stream).collect(Collectors.toList());
}
}

View File

@ -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<Byte> compile() {
int val = this.getType().getId() << 6;
return ByteUtil.intToBytes(val);
}
}

View File

@ -1,5 +1,8 @@
package com.alterdekim.flash.decompiler.tag;
import lombok.Getter;
@Getter
public enum TagType {
SetBackgroundColor(9),
DoAction(12),

View File

@ -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<String, Object> m = recursiveGet((ArrayDeque<String>) 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<String, Object> recursiveGet(Deque<String> 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) {

View File

@ -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<String> pool;
public List<ByteCodeAction> convert(Map<String, Object> map) {
this.pool = new HashSet<>(65535);
extractAllStrings(map);
log.info("poolSet: {}", this.pool);
return new ArrayList<>();
private List<String> pool;
private List<ByteCodeAction> actions;
public Java2Flash convert(Map<String, Object> 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<String, Object> map) {
private void setMembers(Map<String, Object> map, String key) {
HashMap<String, Object> m = (HashMap<String, Object>) 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<String, Object> s = (HashMap<String, Object>) 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<String, Object> 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<String, Object> s = (HashMap<String, Object>) 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<String, Object> map, String key) {
List<ByteCodeItem> 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<String> extractAllStrings(Set<String> pool, Map<String, Object> 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<String, Object>) o);
break;
case "String":
pool.add((String) o);
break;
switch (o.getClass().getSimpleName()) {
case "HashMap" -> extractAllStrings(pool, (HashMap<String, Object>) o);
case "String" -> pool.add((String) o);
}
});
return pool;
}
}
}

View File

@ -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<Byte> intToBytes(int i) {
return fromPrimitive(int2bytes(i, 2));
}
public static List<Byte> intToBytes8(int i) {
return fromPrimitive(int2bytes(i, 1));
}
public static List<Byte> intToBytes32(int i) {
return fromPrimitive(int2bytes(i, 4));
}
public static List<Byte> fromPrimitive(byte[] b) {
return IntStream.range(0, b.length).boxed().map(i -> b[i]).collect(Collectors.toList());
}
public static byte[] toPrimitive(List<Byte> 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<Byte> 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);
}
}