2013年2月26日 星期二

跨混淆後的 swf 傳遞物件與轉型

跨 swf 傳遞物件,若兩 swf 都認識共同的 ValueObject Class,但因為兩 swf 各自經過混淆後,各自對於 ValueObject Class 的認知就不一樣了,所以我寫了一個小工具來作這項 "轉型" 兼 "複製" 的工作。

這並不是 "序列化"、"反序列化" 的用途,而是希望能將 "參考" 帶給對方,適用於攜帶 "callback function、event handler function" 的情況。不適用於透過 LocalConnection 傳遞、或其他會經過序列化過程後無法取得 reference 的情況。



TestMetadata.as

package
{
 import flash.display.Sprite;
 import flash.utils.describeType;
 
 public class TestMetadata extends Sprite
 {
  public function TestMetadata()
  {
   var obj:MyObject = new MyObject("AAAAA", "XYZ");
   trace(describeType(obj).toXMLString())

   trace("================")
   
   //trace(describeType(MyObject).toXMLString())
   
   obj.myName = "ABC"
   obj.myHome = "TAIPEI";
   obj.myHabit= "WEB";
   obj.doJob = doJob;

   var obj2:MyObject = cloneObj(obj, MyObject);
   
   trace(obj2, obj2.myName, obj2.myAge, obj2.myHome, obj2.a);
   obj.doJob();
  }
  
  private function doJob():void{
   trace("doJob");
  }
  
  private function cloneObj(obj:*, cls:Class):*{
   trace("cloneObj()", obj, cls);
   
   var fields:Object = {};
   var fieldKey:String;
   var fieldType:String;
   var fieldName:String;
   
   var constructorParameters:Array = [];
   var constructorParameterIndex:int;
   var cp:Array = [];
   
   var xml:XML = describeType(obj);
   var xml2:XML;
   
   for each(xml2 in xml["variable"]){
    fieldKey = String(xml2["metadata"].(@name=="ObjectData")["arg"].(@key=="name")["@value"]);
    fieldType = String(xml2["@type"]);
    fieldName = String(xml2["@name"]);
    
    if(fieldKey!="undefined" && fieldKey!=""){
     fields[fieldKey] = {
      type:fieldType
      , name:fieldName
     };
     trace(" - read:", fieldKey, fieldType, fieldName, "'" + obj[fieldName] + "'");
    }
    
    constructorParameterIndex = int(xml2["metadata"].(@name=="ObjectData")["arg"].(@key=="constructor")["@value"]);
    if(constructorParameterIndex > 0){
     constructorParameters.push({
      index:constructorParameterIndex
      , value:obj[fieldName]
     });
    }
   }
   
   for each(xml2 in xml["accessor"].(@access=="readwrite"||@access=="readonly")){
    fieldKey = String(xml2["metadata"].(@name=="ObjectData")["arg"].(@key=="name")["@value"][0]);
    fieldType = String(xml2["@type"]);
    fieldName = String(xml2["@name"]);
    
    if(fieldKey!="undefined" && fieldKey!=""){
     fields[fieldKey] = {
      type:fieldType
      , name:fieldName
     };
     trace(" - read:", fieldKey, fieldType, fieldName, "'" + obj[fieldName] + "'");
    }
    
    constructorParameterIndex = int(xml2["metadata"].(@name=="ObjectData")["arg"].(@key=="constructor")["@value"]);
    if(constructorParameterIndex > 0){
     constructorParameters.push({
      index:constructorParameterIndex
      , value:obj[fieldName]
     });
    }
   }
   
   constructorParameters = constructorParameters.sortOn("index", Array.NUMERIC);
   for each(var param:Object in constructorParameters){
    cp.push(param["value"]);
   }
   trace(" - constructor parameters:", cp.join("|"));
   
   var obj2:Object;
   switch(cp.length){
    case 0: obj2 = new cls(); break;
    case 1: obj2 = new cls(cp[0]); break;
    case 2: obj2 = new cls(cp[0], cp[1]); break;
    case 3: obj2 = new cls(cp[0], cp[1], cp[2]); break;
    case 4: obj2 = new cls(cp[0], cp[1], cp[2], cp[3]); break;
    case 5: obj2 = new cls(cp[0], cp[1], cp[2], cp[3], cp[4]); break;
    case 6: obj2 = new cls(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5]); break;
    case 7: obj2 = new cls(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6]); break;
    case 8: obj2 = new cls(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7]); break;
    case 9: obj2 = new cls(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7], cp[8]); break;
    case 10: obj2 = new cls(cp[0], cp[1], cp[2], cp[3], cp[4], cp[5], cp[6], cp[7], cp[8], cp[9]); break;
    default: throw new Error("not support, constructor parameters:" + cp.join("|"));
   }
   xml = describeType(obj2);
   
   for each(xml2 in xml["variable"]){
    fieldKey = String(xml2["metadata"].(@name=="ObjectData")["arg"].(@key=="name")["@value"]);
    fieldType = String(xml2["@type"]);
    fieldName = String(xml2["@name"]);
    
    if(fieldKey!="undefined" && fieldKey!=""){
     if(!fields[fieldKey]){
      trace(" - write:", "[WARN] no field '" + fieldKey + "' in source object");
      continue;
      
     }else if(fieldType != fields[fieldKey]["type"]){
      trace(" - write:", "[WARN] field '" + fieldKey + "' type error (source:" + fields[fieldKey]["type"] + ", target:" + fieldType + ")");
      continue;
      
     }else{
      obj2[fieldName] = obj[fields[fieldKey]["name"]];
     }
     
     trace(" - write:", fieldKey, fieldType, fieldName, "'" + obj[fields[fieldKey]["name"]] + "'");
    }
   }
   
   for each(xml2 in xml["accessor"].((@access=="readwrite"||@access=="writeonly") && child("metadata").(@name=="ObjectData")["arg"].(@key=="name"))){
    fieldKey = String(xml2["metadata"].(@name=="ObjectData")["arg"].(@key=="name")["@value"][0]);
    fieldType = String(xml2["@type"]);
    fieldName = String(xml2["@name"]);
    
    if(fieldKey!="undefined" && fieldKey!=""){
     if(!fields[fieldKey]){
      trace(" - write:", "[WARN] no field '" + fieldKey + "' in source object");
      continue;
      
     }else if(fieldType != fields[fieldKey]["type"]){
      trace(" - write:", "[WARN] field '" + fieldKey + "' type error (source:" + fields[fieldKey]["type"] + ", target:" + fieldType + ")");
      continue;
      
     }else{
      obj2[fieldName] = obj[fields[fieldKey]["name"]];
     }
     
     trace(" - write:", fieldKey, fieldType, fieldName, "'" + obj[fields[fieldKey]["name"]] + "'");
    }
   }
   
   return obj2;
  }
  
 }
}

測試用、任一 Object,只要有加上指定 metadata 作辨識:

MyObject.as

package
{
 public class MyObject
 {
  [ObjectData(name="myName", constructor="2")]
  public var myName:String = "";
  
  [ObjectData(name="myAge")]
  public function get myAge():int{
   return 33;
  }
  
  [ObjectData(name="myHabit")]
  public function set myHabit(v:String):void{
  }
  
  private var _myHome:String = "";
  
  [ObjectData(name="myHome")]
  public function get myHome():String{
   return _myHome;
  }
  
  [ObjectData(name="myHome")]
  public function set myHome(v:String):void{
   _myHome = v;
  }
  
  [ObjectData(name="myAAA")]
  public function get owaifow():String{
   return "999";
  }
  
  [ObjectData(name="myAAA")]
  public function set woicmwqorm(v:String):void{
   trace("woicmwqorm()", v);
  }
  
  [ObjectData(name="doJob")]
  public var doJob:Function;
  
  private var _a:String = "";
  [ObjectData(name="a", constructor="1")]
  public function get a():String{
   return _a;
  }
  
  public function MyObject(a:String, myName:String){
   _a = a;
   this.myName = myName;
  }
 }
}

編譯時,記得要保留此 metadata:

-keep-as3-metadata+=ObjectData

輸出:
================
cloneObj() [object MyObject] [class MyObject]
 - read: doJob Function doJob 'function Function() {}'
 - read: myName String myName 'ABC'
 - read: myAAA String owaifow '999'
 - read: a String a 'AAAAA'
 - read: myAge int myAge '33'
 - read: myHome String myHome 'TAIPEI'
 - constructor parameters: AAAAA|ABC
 - write: doJob Function doJob 'function Function() {}'
 - write: myName String myName 'ABC'
 - write: [WARN] no field 'myHabit' in source object
woicmwqorm() 999
 - write: myAAA String woicmwqorm '999'
 - write: myHome String myHome 'TAIPEI'
[object MyObject] ABC 33 TAIPEI AAAAA
doJob
[SWF] E:\works_test\TestMetadata\bin-debug\TestMetadata.swf - 6,588 bytes after decompression
[Unload SWF] E:\works_test\TestMetadata\bin-debug\TestMetadata.swf

沒有留言: