Decompiler improvement, and converter improvement

This commit is contained in:
Michael Wain 2024-12-31 05:14:29 +03:00
parent c9fe0153e4
commit a06423a6bc
14 changed files with 264 additions and 65 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,4 +12,6 @@ public class ActionInitObject extends ByteCodeAction {
public ActionField getType() {
return ActionField.InitObject;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String, Object> val;
@Override
public ActionPushType getType() {
return ActionPushType.INIT_OBJECT;
}
@Override
public Object getValue() {
return this.val;
}
}

View File

@ -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<String> pool;
private final List<String> pool;
private final List<ByteCodeAction> actions;
private final Stack<ByteCodeItem> stack; // ByteCodeAction
private final Map<String, FlashVar> vars;
public Map<String, FlashVar> convert(DoAction doAction) {
public Flash2Java(DoAction doAction) {
this.pool = doAction.getActionPool();
List<ByteCodeAction> actions = doAction.getActions();
this.stack = new Stack<>();
this.actions = doAction.getActions();
this.vars = new HashMap<>();
}
// convert stack from List<ByteCodeAction> to List<ByteCodeItem> and rewrite all code accordingly.
List<ByteCodeAction> stack = new ArrayList<>();
Map<String, FlashVar> vars = new HashMap<>();
public Map<String, FlashVar> 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<ByteCodeAction> stack) {
private void initObject() {
Integer numberOfEntries = (Integer) this.stack.pop().getValue();
HashMap<String, Object> 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<Integer> si = new ArrayList<>();
List<ByteCodeItem> 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<String, FlashVar> vars, List<ByteCodeAction> 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<Integer, String> m = varVal.getVal() != null ? ((Map<Integer, String>) 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<String, Object> g = (HashMap<String, Object>) 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<Integer, Map<String, Object>> m = varVal.getVal() != null ? ((Map<Integer, Map<String, Object>>) varVal.getVal()) : new HashMap<>();
Map<String, Object> 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<String, Object> m = (Map<String, Object>) 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<String, FlashVar> vars, List<ByteCodeAction> 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<String, FlashVar> vars, List<ByteCodeAction> 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) {

View File

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

View File

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