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.Arrays;
import java.util.BitSet; import java.util.BitSet;
import java.util.List; 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.*;
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.decompressDEFLATE;
import static com.alterdekim.flash.decompiler.util.Compression.decompressLZMA; import static com.alterdekim.flash.decompiler.util.Compression.decompressLZMA;
@ -106,6 +107,10 @@ public class FlashDecompiler {
i++; i++;
tag.getActions().add(new ActionDefineLocal()); tag.getActions().add(new ActionDefineLocal());
break; break;
case DefineLocal2:
i++;
tag.getActions().add(new ActionDefineLocal2());
break;
case SetMember: case SetMember:
i++; i++;
tag.getActions().add(new ActionSetMember()); tag.getActions().add(new ActionSetMember());
@ -114,6 +119,16 @@ public class FlashDecompiler {
i++; i++;
tag.getActions().add(new ActionGetVariable()); tag.getActions().add(new ActionGetVariable());
break; break;
case SetVariable:
i++;
tag.getActions().add(new ActionSetVariable());
break;
case GetMember:
i++;
tag.getActions().add(new ActionGetMember());
break;
case Unknown:
return tag;
default: default:
log.error("Unknown action field type: {}", StringUtils.bytesToHex(new byte[] {data[i]})); log.error("Unknown action field type: {}", StringUtils.bytesToHex(new byte[] {data[i]}));
i++; i++;
@ -133,6 +148,7 @@ public class FlashDecompiler {
} }
private int parseActionPush(byte[] data, int i, DoAction tag) { private int parseActionPush(byte[] data, int i, DoAction tag) {
//int start_action_index = i; // for debug
i++; i++;
int len_in_bytes = bytesToIntRev(Arrays.copyOfRange(data, i, i+2)); int len_in_bytes = bytesToIntRev(Arrays.copyOfRange(data, i, i+2));
int u = 0; int u = 0;
@ -143,6 +159,7 @@ public class FlashDecompiler {
while( u < len_in_bytes ) { while( u < len_in_bytes ) {
ActionPushType type = ActionPushType.fromType(data[i]); ActionPushType type = ActionPushType.fromType(data[i]);
switch(type) { switch(type) {
// implement other types (float especially)
case STRING: case STRING:
i++; i++;
var result = findNull(data, i, u); var result = findNull(data, i, u);
@ -175,6 +192,18 @@ public class FlashDecompiler {
i++; i++;
u++; u++;
continue; 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: default:
i++; i++;
u++; u++;
@ -182,6 +211,12 @@ public class FlashDecompiler {
} }
} }
tag.getActions().add(push); 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; return i;
} }

View File

@ -12,7 +12,7 @@ import java.io.File;
public class Main { public class Main {
public static void main(String[] args) throws Exception { public static void main(String[] args) throws Exception {
FlashDecompiler decompiler = new FlashDecompiler(); 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() DoAction da = (DoAction) file.getTags()
.stream() .stream()
@ -20,12 +20,10 @@ public class Main {
.findFirst() .findFirst()
.get(); .get();
Flash2Java converter = new Flash2Java();
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
File out = new File("dec.json"); File out = new File("dec.json");
if( out.exists() ) out.delete(); 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; package com.alterdekim.flash.decompiler.action;
import com.alterdekim.flash.decompiler.util.ActionField; import com.alterdekim.flash.decompiler.util.ActionField;
import lombok.ToString;
@ToString
public class ActionAdd extends ByteCodeAction { public class ActionAdd extends ByteCodeAction {
@Override @Override
public ActionField getType() { 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() { public ActionField getType() {
return ActionField.InitObject; 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), DOUBLE(6),
INTEGER(7), INTEGER(7),
POOL_INDEX(8), 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; 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; package com.alterdekim.flash.decompiler.translator;
import com.alterdekim.flash.decompiler.action.ActionPush; 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.action.ByteCodeAction;
import com.alterdekim.flash.decompiler.item.ByteCodeItem; import com.alterdekim.flash.decompiler.item.internal.InitObjectItem;
import com.alterdekim.flash.decompiler.item.PoolIndexItem;
import com.alterdekim.flash.decompiler.item.StringItem;
import com.alterdekim.flash.decompiler.tag.DoAction; import com.alterdekim.flash.decompiler.tag.DoAction;
import com.alterdekim.flash.decompiler.util.ActionField; import com.alterdekim.flash.decompiler.util.ActionField;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -15,80 +13,119 @@ import java.util.*;
@Slf4j @Slf4j
public class Flash2Java { 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(); 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. public Map<String, FlashVar> convert() {
List<ByteCodeAction> stack = new ArrayList<>(); // abolish the activeness of variable inside of an HashMap. Use stack instead.
Map<String, FlashVar> vars = new HashMap<>();
for (ByteCodeAction action : actions) { for (ByteCodeAction action : actions) {
try { // try {
//log.info("Action type: " + action.getType().toString());
switch (action.getType()) { switch (action.getType()) {
case DefineLocal: case DefineLocal:
defineLocal(vars, stack); defineLocal();
stack.clear();
break; break;
case GetVariable: case GetVariable:
getVariable(vars, stack); getVariable();
stack.clear();
break; break;
case SetMember: case SetMember:
setMember(vars, stack); setMember();
stack.clear();
break; break;
case Add: 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; break;
default: default:
stack.add(action); // ignore
}
} catch (Exception e) {
log.error(e.getMessage());
} }
//} catch (Exception e) {
//log.error(e.getMessage());
//}
} }
return vars; 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 // get last 2 elements from stack
List<Integer> si = new ArrayList<>(); List<Integer> si = new ArrayList<>();
List<ByteCodeItem> g = new ArrayList<>(); List<ByteCodeItem> g = new ArrayList<>();
for( int i = stack.size()-1; i >= 0; i-- ) { for( int i = this.stack.size()-1; i >= 0; i-- ) {
ByteCodeAction e = stack.get(i); ByteCodeAction e = this.stack.get(i);
if( e.getType() != ActionField.Push ) continue; if( e.getType() != ActionField.Push ) continue;
ActionPush push = (ActionPush) e; ActionPush push = (ActionPush) e;
if( push.getItems().size() >= 2 ) { if( push.getItems().size() >= 2 ) {
StringItem from = (StringItem) push.getItems().get(push.getItems().size()-1); StringItem from = (StringItem) push.getItems().get(push.getItems().size()-1);
StringItem to = (StringItem) push.getItems().get(push.getItems().size()-2); StringItem to = (StringItem) push.getItems().get(push.getItems().size()-2);
push.getItems().remove() /*push.getItems().remove()
((String) to.getValue()) + ((String) from.getValue()) ((String) to.getValue()) + ((String) from.getValue())*//*
} }
} }
*/
}
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 void setMember(Map<String, FlashVar> vars, List<ByteCodeAction> stack) { private boolean hasActiveVariable() {
String varKey = vars.keySet().stream().filter(k -> vars.get(k).isActive()).findFirst().get(); return this.vars.keySet().stream().anyMatch(k -> this.vars.get(k).isActive());
FlashVar varVal = vars.get(varKey); }
ActionPush push = (ActionPush) stack.get(0);
if( stack.size() == 1 ) { private String getActiveVarKey() {
Map<Integer, String> m = varVal.getVal() != null ? ((Map<Integer, String>) varVal.getVal()) : new HashMap<>(); return this.vars.keySet().stream().filter(k -> this.vars.get(k).isActive()).findFirst().get();
m.put((Integer) push.getItems().get(0).getValue(), fromPool((Integer) push.getItems().get(1).getValue())); }
varVal.setVal(m);
vars.put(varKey, varVal); private void setMember() {
ByteCodeItem val = this.stack.pop();
if( this.stack.isEmpty() && this.hasActiveVariable() ) {
String var = getActiveVarKey();
return; return;
} }
Map<Integer, Map<String, Object>> m = varVal.getVal() != null ? ((Map<Integer, Map<String, Object>>) varVal.getVal()) : new HashMap<>(); Integer index = (Integer) this.stack.pop().getValue();
Map<String, Object> v = new HashMap<>(); String var = this.vars.keySet().stream().filter(k -> this.vars.get(k).isActive()).findFirst().get();
for( int i = 1; i < push.getItems().size()-1; i+=2 ) { Map<String, Object> m = (Map<String, Object>) this.vars.get(var).getVal();
v.put(fromPool((Integer) push.getItems().get(i).getValue()), valConverter(push.getItems().get(i+1))); m.put(String.valueOf(index), val.getValue());
} this.vars.put(var, new FlashVar(false, m)); // this may cause a bug (false in FlashVar arg)
m.put((Integer) push.getItems().get(0).getValue(), v);
varVal.setVal(m);
vars.put(varKey, varVal);
} }
private Object valConverter(ByteCodeItem item) { private Object valConverter(ByteCodeItem item) {
@ -100,27 +137,31 @@ public class Flash2Java {
return item.getValue(); return item.getValue();
} }
private void getVariable(Map<String, FlashVar> vars, List<ByteCodeAction> stack) throws Exception { private void setVariable() {
if (stack.size() != 1 ) { throw new Exception("Bad getVariable format"); } Object val = this.stack.pop().getValue();
ActionPush push = (ActionPush) stack.get(0); String variableName = (String) valConverter( this.stack.pop() );
PoolIndexItem pi = (PoolIndexItem) push.getItems().get(0); vars.put(variableName, new FlashVar(false, val));
}
private void getVariable() {
String variableName = (String) valConverter( this.stack.pop() );
vars.keySet().forEach(k -> { vars.keySet().forEach(k -> {
FlashVar fv = vars.get(k); FlashVar fv = vars.get(k);
fv.setActive(false); fv.setActive(false);
vars.put(k, fv); vars.put(k, fv);
}); });
String key = fromPool((int) pi.getValue()); FlashVar v = vars.get(variableName);
FlashVar v = vars.get(key);
v.setActive(true); v.setActive(true);
vars.put(key, v); vars.put(variableName, v);
} }
private void defineLocal(Map<String, FlashVar> vars, List<ByteCodeAction> stack) throws Exception { private void defineLocal() {
if (stack.size() != 2 || stack.get(1).getType() != ActionField.InitObject) { throw new Exception("Bad defineLocal format"); } ByteCodeItem val = this.stack.pop();
ActionPush push = (ActionPush) stack.get(0); String name = (String) valConverter( this.stack.pop() );
PoolIndexItem pi = (PoolIndexItem) push.getItems().get(0); // todo: implement assignment ability (from one value to another)
// todo: implement assignment ability vars.put(name,
vars.put(fromPool((Integer) pi.getValue()), new FlashVar()); new FlashVar(false, val.getValue())
);
} }
private String fromPool(int index) { private String fromPool(int index) {

View File

@ -5,8 +5,11 @@ public enum ActionField {
Push((byte) 0x96), Push((byte) 0x96),
InitObject((byte) 0x43), InitObject((byte) 0x43),
DefineLocal((byte) 0x3C), DefineLocal((byte) 0x3C),
DefineLocal2((byte) 0x41),
SetMember((byte)0x4F), SetMember((byte)0x4F),
GetMember((byte) 0x4E),
GetVariable((byte) 0x1C), GetVariable((byte) 0x1C),
SetVariable((byte) 0x1D),
Add((byte) 0x47), Add((byte) 0x47),
Unknown((byte) 0x00); Unknown((byte) 0x00);

View File

@ -1,5 +1,8 @@
package com.alterdekim.flash.decompiler.util; package com.alterdekim.flash.decompiler.util;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
public class ByteUtil { public class ByteUtil {
public static int bytesToInt(byte[] bytes) { public static int bytesToInt(byte[] bytes) {
int value = 0; int value = 0;
@ -16,4 +19,8 @@ public class ByteUtil {
} }
return value; return value;
} }
public static double bytesToDouble(byte[] bytes) {
return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).getDouble();
}
} }