台灣 Adobe 第一屆部落客聚會

今天晚上台灣 Adobe 舉辦第一屆部落客聚會,一些老朋友如 Brian、火星人、阿修、Ticore、小正正、Maso、Erin、奶綠...等人,都有受邀參加。



主人有準備俗稱 "天使之鈴" 的法國小點心,那是一種外殼脆脆硬硬的,咬破後裡面很鬆軟,但又沒有小林煎餅的吊鐘燒那麼軟,仍帶有蛋糕蓬鬆的口感。





開場白由台灣區的頭頭 Will 做介紹,有位網友當場問了他一個問題:這場聚會的主要目的?未來是否有更多的聯繫(Adobe 與使用者)?不過,不知道是我實在不太專心,或是 Will 也沒回答出甚麼有具體明確的答案,我印象中這次的聚會還是純粹將大家聚一聚、哈拉一下罷了。



Adobe User Group 的老朋友們很久沒見了,也藉著這次機會可以聚聚聊聊。



行銷經理-法蘭 有帶著各部落客參觀辦公室,以下是參觀影片:

台灣Adobe第一屆部落客聚會 from Ben Chang on Vimeo.

Web 版世紀帝國:Evony

這兩天在玩 Evony (http://www.evony.com/),其實注意到這個遊戲已經很久了,因為最近在各個 Google Reader 中訂閱的 RSS 中,不斷出現他的廣告,所以我終於禁不住好奇加入玩玩看。

這是一款 Web 版的多人線上遊戲,遊戲內容類似世紀帝國的模式,打造自己的國家、採集資源、攻城掠地等等。有意思的是,你不用一值待在電腦前面,只要稍做設定 (建造、生產...),即使你關掉瀏覽器離開網站,伺服器也仍會依照實際時間來完成你所設定的工作。

像這類的遊戲,還有以下幾個:

Travian (http://www.travian.tw/)

IKARIAM (http://www.ikariam.tw/)

回到 Evony,以下有些截圖:

城鎮中,可建造多種不同功能的建築物,主要的政治、行政作業,都是在市政中心處理:


學院,可學習各種工程技術,以加速各種作業的速度:


兵營,可在此開始雇用不同種類的傭兵:


城鎮外,建立四種不同資源的採集場,每次升級都需要不同的資源數量才行:


世界地圖,可以看到玩家城市周邊的其他地形,或其他玩家的城市,可決定是否要探索或攻城掠地:


左下角有些功能面板,其中最常需要開啟瀏覽的就是任務面板,初期的目標就是仰賴不斷的完成任務來獲得不同的資源:


玩家也可以從統計表中看到自己在所在伺服器中的排名,也包括了聲望值與人口數等,不過統計表中的數值與玩家右邊的面板資料有點不太同步:


從統計表來看,共有 10 筆 x 4631 頁 = 約 46300 個玩家,而我才玩一天多一點,已經達到前 20 名了,遊戲過程中不斷會有玩家詢問:"how to play?",可見得玩到一半就放棄的人應該佔大多數吧!

TypeError: Error #1007: 嘗試個體化非建構函式。

前兩天遇到一個非常詭異的錯誤訊息:

"TypeError: Error #1007: 嘗試個體化非建構函式。"

若用 google 去查的話,可以找到一堆與 Document Class 相關的議題,不過這次我遇到的可不是那樣的問題。事實上,我遇到的問題正如他的錯誤訊息一樣的清楚,只不過難以發覺。

以下,我先準備一個我簡化過的問題案例:

Test.fla 的 Document Class 為 Test.as


package{
import flash.display.MovieClip;

import a.Vector;

public class Test extends MovieClip{

var v:Vector = new Vector();

public function Test(){

trace(123, v);

}

}

}


在這個 Test.as 中,有用到一個 package a 下的 Vector 類別:


package a{
public class Vector{
public function Vector(){
trace("Vector!!");
}
}
}


這個案例,若是發佈成 Flash Player 9 的話,一點問題也沒有,但若是發佈成 Flash Player 10 的話,就會出現錯誤訊息:

TypeError: Error #1007: 嘗試個體化非建構函式。
at Test()

我查了許久後,才發現問題出在那個 a.Vector,因為在 Flash Player 10 還沒推出時,Flash Player 還沒有內建 Vector 類別,所以當我們寫 var v:Vector = new Vector(); 時,可以由 import a.Vector (或 import a.*) 中找到這個 Vector 類別。可是到了 Flash Player 10 之後,會先找到的是全域類別物件 Vector,就像 String、Number 一樣,而不是我們自訂的 a.Vector 類別。

然而因為 Flash Player 10 新具備的 Vector 類別並不具有 "不帶參數的建構式",所以就會出現 "嘗試個體化非建構函式" 的錯誤訊息啦!

如果你們家的 Flash 程式,你或前人也有從不知道哪裡找來的 Vector 類別的話,或是任何改寫自其他程式語言 (Java) 的常用類別的話,未來都很有可能遇到這種同名的衝突問題。

解決方法一,將 Test.as 改寫成:


package{
import flash.display.MovieClip;

import a.Vector;

public class Test extends MovieClip{

var v:a.Vector = new a.Vector();

public function Test(){

trace(123, v);

}

}

}


使用自訂的 Vector 類別時,請明確指出完整路徑 var v:a.Vector = new a.Vector(); 即可。

解決方法二,將自訂的 Vector 類別重構一下,改名ㄅㄟ,反正現在的開發工具都很方便,重構時會協助你將所有已經使用到的程式碼都一起更新,除非你是使用 "字串轉類別" 的作法。

將文字隱藏在圖片中

這兩天在研究如何將文字資訊隱藏在圖片中的技術,在 google 查了一些 "secure image sharing" 的資料,但是都沒有看到實作的方法(其實是沒有看到範例程式碼),最後,我參考 Wiki 上關於 Steganography 的介紹:

http://en.wikipedia.org/wiki/Steganography

寫了以下的類別:

Steganography.as


package idv.ben.steganography
{
import flash.display.BitmapData;
import flash.geom.Point;

public class Steganography
{
static private var __instance:Steganography;
static public function getInstance():Steganography{
if(__instance==null)
__instance = new Steganography();
return __instance;
}

private var __totalWidth:int;
private var __totalHeight:int;
private var __point:Point;

public function Steganography()
{
if(__instance!=null)
throw new Error("plz use Steganography.getInstance()");
}

public function encode(oBD:BitmapData, txt:String):BitmapData{
var txtLen:int = txt.length;
var txtLenLen:int = String(txtLen).length;

if(oBD.width * oBD.height < txtLen + 10)
return null;

var newBD:BitmapData = oBD.clone();
__totalWidth = newBD.width;
__totalHeight = newBD.height;
__point = null;

var c:String;
var p:Point;
var newPixel:uint;

//txtLenLen
c = String(txtLenLen);
p = getNextPoint();
newPixel = putCharIntoPixel(c, oBD.getPixel(p.x, p.y));
newBD.setPixel(p.x, p.y, newPixel);

//txtLen
for(var i:int=0; i<txtLenLen; i++){
c = String(txtLen).substr(i, 1);
p = getNextPoint();
newPixel = putCharIntoPixel(c, oBD.getPixel(p.x, p.y));
newBD.setPixel(p.x, p.y, newPixel);
}

//txt
for(var j:int=0; j<txtLen; j++){
c = txt.substr(j, 1);
p = getNextPoint();
newPixel = putCharIntoPixel(c, oBD.getPixel(p.x, p.y));
newBD.setPixel(p.x, p.y, newPixel);
}

return newBD;
}

public function decode(bd:BitmapData):String{
__totalWidth = bd.width;
__totalHeight = bd.height;
__point = null;

var txtLenLen:int;
var txtLen:int
var txt:String = "";

var p:Point;
var c:String;

//txtLenLen
if(bd.width * bd.height <= 0)return "";
p = getNextPoint();
c = getCharFromPixel(bd.getPixel(p.x, p.y));
txtLenLen = int(c);

//txtLen
if(bd.width * bd.height <= (1 + txtLenLen))return "";
var strTxtLen:String = "";
for(var i:int=0; i<txtLenLen; i++){
p = getNextPoint();
c = getCharFromPixel(bd.getPixel(p.x, p.y));
strTxtLen += c;
}
txtLen = int(strTxtLen);

//txt
if(bd.width * bd.height <= (1 + txtLenLen + txtLen))return "";
for(var j:int=0; j<txtLen; j++){
p = getNextPoint();
c = getCharFromPixel(bd.getPixel(p.x, p.y));
txt += c;
}

return txt;
}

private function getNextPoint():Point{
if(__point==null){
__point = new Point(0, 0);
}else{
if(__point.x < __totalWidth-1){
__point.x++;
}else{
__point.x = 0;
__point.y++;
}
}
return __point;
}

private function putCharIntoPixel(char:String, oPixel:uint):uint{
var r:uint = (oPixel >> 16) & 255;
var g:uint = (oPixel >> 8) & 255;
var b:uint = (oPixel >> 0) & 255;

var charCode:String = String(char.charCodeAt(0));
while(charCode.length<3)
charCode = "0" + charCode;

var dr:uint = uint(charCode.substr(0, 1));
var dg:uint = uint(charCode.substr(1, 1));
var db:uint = uint(charCode.substr(2, 1));

var r2:uint = ((r >> 4) << 4) | dr;
var g2:uint = ((g >> 4) << 4) | dg;
var b2:uint = ((b >> 4) << 4) | db;

return r2 << 16 | g2 << 8 | b2;
}

private function getCharFromPixel(pixel:uint):String{
var dr:uint = (pixel >> 16) & 15;
var dg:uint = (pixel >> 8) & 15;
var db:uint = (pixel >> 0) & 15;

var charCode:String = String(dr) + String(dg) + String(db);
var char:String = String.fromCharCode(int(charCode));
return char;
}
}
}


使用範例如下,載入一張圖,並將 "Hello World!!" 的字串給隱藏進去,產生出第二張看似一樣的圖,最後再由第二張圖的 BitmapData 取出該字串。

SteganographyAS3 .as


package {
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Sprite;

import idv.ben.steganography.Steganography;

public class SteganographyAS3 extends Sprite
{
[Embed(source='../asset/IMG_3641.jpg', mimeType='image/jpeg')]
public var Img:Class;

public function SteganographyAS3()
{
var bmp:Bitmap = new Img() as Bitmap;
bmp.scaleX = bmp.scaleY = 0.5;
this.addChild(bmp);

var msg:String = "hello world!!";
var newBD:BitmapData = Steganography.getInstance().encode(bmp.bitmapData, msg);
var newBMP:Bitmap = new Bitmap(newBD);
newBMP.x = bmp.x + bmp.width;
newBMP.scaleX = newBMP.scaleY = 0.5;
this.addChild(newBMP);

var msg2:String = Steganography.getInstance().decode(newBMP.bitmapData);
trace("decode=>", msg2);
}
}
}


執行結果,出現兩張圖,看起來沒甚麼不同,可能要將欲編入的文字資料放多一點,或許可以看到一些像素的不同吧:



將第二張圖(右邊)的 BitmapData 中隱藏的資料解出來後,trace 在 Flex Builder 的輸出視窗中:



==============================================================

[2009-06-19-13-00]

作了一個範例,完整程式碼:


package
{
import com.adobe.images.PNGEncoder;

import fl.containers.ScrollPane;
import fl.controls.Button;
import fl.controls.TextArea;

import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.MovieClip;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.net.FileFilter;
import flash.net.FileReference;
import flash.utils.ByteArray;
import flash.utils.setTimeout;

import idv.ben.steganography.Steganography;

public class Demo2 extends MovieClip
{
public var btnBrowse:Button;
public var btnEncode:Button;
public var btnSave:Button;
public var btnLoad:Button;
public var btnDecode:Button;

public var spImg:ScrollPane;
public var spImg2:ScrollPane;

public var txtInput:TextArea;
public var txtOutput:TextArea;

private var __fileReference:FileReference = new FileReference();
private var __fileReference2:FileReference = new FileReference();

public function Demo2()
{
btnBrowse.addEventListener(MouseEvent.CLICK, onBtnBrowseClick);
btnEncode.addEventListener(MouseEvent.CLICK, onBtnEncodeClick);
btnSave.addEventListener(MouseEvent.CLICK, onBtnSaveClick);
btnLoad.addEventListener(MouseEvent.CLICK, onBtnLoadClick);
btnDecode.addEventListener(MouseEvent.CLICK, onBtnDecodeClick);

__fileReference.addEventListener(Event.SELECT, onFileReferenceSelect);
__fileReference.addEventListener(Event.COMPLETE, onFileReferenceComplete);

__fileReference2.addEventListener(Event.SELECT, onFileReference2Select);
__fileReference2.addEventListener(Event.COMPLETE, onFileReference2Complete);
}

private function onBtnBrowseClick(e:MouseEvent):void{
trace("onBtnBrowseClick()");

var typeFilter:Array = [new FileFilter("Images(*.jpg;*.gif;*.png)", "*.jpg;*.gif;*.png")];
__fileReference.browse(typeFilter);
}

private function onBtnEncodeClick(e:MouseEvent):void{
trace("onBtnEncodeClick()");

var bd:BitmapData = getEncodedBitmapData(spImg);
if(bd == null)
return;

var txt:String = txtInput.text;

if(txt == "")
return;

var newBD:BitmapData = Steganography.getInstance().encode(bd, txt);
var newBMP:Bitmap = new Bitmap(newBD);
spImg2.source = newBMP;
setTimeout(spImg2.update, 200);
}

private function onBtnSaveClick(e:MouseEvent):void{
trace("onBtnSaveClick()");

var bd:BitmapData = getEncodedBitmapData(spImg2);
if(bd == null)
return;

var data:ByteArray = PNGEncoder.encode(bd);
var fr:FileReference = new FileReference();
fr.save(data, "EncodeImage.png");
}

private function onBtnLoadClick(e:MouseEvent):void{
trace("onBtnLoadClick()");

var typeFilter:Array = [new FileFilter("Images(*.jpg;*.gif;*.png)", "*.jpg;*.gif;*.png")];
__fileReference2.browse(typeFilter);
}

private function onBtnDecodeClick(e:MouseEvent):void{
trace("onBtnDecodeClick()");

var bd:BitmapData = getEncodedBitmapData(spImg2);
if(bd == null)
return;

var txt:String = Steganography.getInstance().decode(bd);
txtOutput.text = txt;
}

private function onFileReferenceSelect(e:Event):void{
trace("onFileReferenceSelect()");

__fileReference.load();
}

private function onFileReferenceComplete(e:Event):void{
trace("onFileReferenceComplete()");

var data:ByteArray = __fileReference.data;
var loader:Loader = new Loader();
loader.loadBytes(data);
spImg.source = loader;
setTimeout(spImg.update, 200);
}

private function onFileReference2Select(e:Event):void{
trace("onFileReference2Select()");

__fileReference2.load();
}

private function onFileReference2Complete(e:Event):void{
trace("onFileReference2Complete()");

var data:ByteArray = __fileReference2.data;
var loader:Loader = new Loader();
loader.loadBytes(data);
spImg2.source = loader;
setTimeout(spImg2.update, 200);
}

private function getEncodedBitmapData(sp:ScrollPane):BitmapData{
if(sp.content == null)
return null;

var bmp:Bitmap;

if(sp.content is Loader){
if(Loader(sp.content).content == null)
return null;

bmp = Bitmap(Loader(sp.content).content);
}else if(sp.content is Bitmap){
bmp = Bitmap(sp.content);
}else{
return null;
}

return bmp.bitmapData;
}
}
}


操作步驟:

加密:
1. 按下 Browse 按鈕,載入原始圖片到左上角的 ScrollPane 中。
2. 在右上角的文字框中輸入欲藏入的文字 (此版 不接受雙位元文字)。
3. 按下 Encode 按鈕,會將處理好後的圖片放到左下角的 ScrollPane 中。
4. 可按下 Save 按鈕將圖片存檔。

解密:
5. 按下 Load 按鈕,將內含資訊的圖片載入到左下角的 ScrollPane 中。
6. 按下 Decode 按鈕,將文字資訊從圖片中解出來到右下角的文字框中。

程式試用:







或到以下網址試用:

http://ben.rb.chang.googlepages.com/20090619_SteganographyAS3.html

以 SWC 當作素材被載入

如果貴公司的 Flash 平台的開發方式也是:用 Flash IDE 製作畫面,但邏輯程式都用外部 AS Code Editor(筆記本、Flex Builder、Flash Develop...)來編輯 *.as 的話,你一定會遇到一些命名問題,在 *.as 想要使用的某些類別名稱 是來自於 *.fla 中 Library 定義(匯出)的類別,當然我們可以使用 getDefinitionByName() 或其他方式來將指定字串變成想要的類別 進而產生實體,不過只要一個不注意的話,就很容易出錯並 debug 到死~尤其若是有使用擾亂器(obfuscator,一種將 *.swf 內部的程式碼改變其命名、定義...等程式碼的工具,目的是讓反組譯出來的原始碼不易閱讀)的話,恐怕那更會是一個災難。

這裡有一篇文章:http://www.airtightinteractive.com/news/?p=327

他建議我們可以將 *.fla 的素材以匯出 *.swc 的方式,來提供給 flex builder 使用,這樣當 flex builder 的 AS Project 引用這些 *.swc 時,就可以正確的找到實際存在的類別名稱。

不知道若是有使用擾亂器的話,是否仍可具有相同的優勢,不過對於一般開發的話,這種做法確實應該會比較保險一點,讓 *.fla 與 *.as 能更同步一些!

Adobe Mobile Packaging 教學影片

以下有六段教學影片,教你如何使用 Adobe Mobile Packaging 來將你開發的 Flashlite 程式打包、佈署到 Symbian 或 Windows Mobile 平台上。

http://www.myersdesign.com/wordpress/?p=311

01 Overview
http://tv.adobe.com/#vi+f15313v1005

02 Set-up Your Computer
http://tv.adobe.com/#vi+f15313v1000

03 Making Your First Packages
http://tv.adobe.com/#vi+f15313v1007

04 Symbian Signing
http://tv.adobe.com/#vi+f15313v1003

05 Windows Mobile Signing
http://tv.adobe.com/#vi+f15313v1008

06 Icon Boot Camp
http://tv.adobe.com/#vi+f15313v1006

Samsung i8910 HD DevPack(v0.2) 可模擬 GSensor

如果你的手機是 Samsung i8910 HD 的話,Samsung 推出了i8910 HD DevPack(v0.2),這是一個給 S60 5th Edition v1.0 SDK 使用的外掛,提供了模擬 GSensor 的模擬器。



介紹文章:http://s-a-m-m-i.blogspot.com/2009/05/shake-snap-up-down-start-and-stop.html

檔案下載:http://innovator.samsungmobile.com/cms/cnts/detail.view.do?platformId=1&cateId=9&childCateId=All&childCateId2=&cntsId=2341&imgType=&parentCateId=9&cateAll=all&listReturnUrl=http%3A%2F%2Finnovator.samsungmobile.com%3A80%2Fcms%2Fcnts%2Fcategory.main.list.do%3FplatformId%3D1%26cateId%3D9%26cateAll%3Dall&searchText=&secondCateId=&previousUrl=http%3A%2F%2Finnovator.samsungmobile.com%3A80%2Fcms%2Fcnts%2Fcategory.main.list.do%3FplatformId%3D1%26cateId%3D9%26cateAll%3Dall

Nokia Web Runtime 提供數種開發工具的 plug-in 以提高生產力

正當 Adobe 仍在努力於 Flash 10 on Mobile 與 Adobe AIR on Mobile 的開發時,Nokia 的 WRT(Nokia Web Runtime) 已經越來越擴大其領地了。

某種程度上,與 Adobe AIR 有著類似的概念:只要使用 html、javascript 這些既有且容易上手的開發技術,開發出網頁,再打包成某種 package,就可以安裝到有對應 Runtime 的手機環境上執行了。

現在,Nokia WRT 也提供了數種 plug-in 給知名的開發工具,包括:
* Nokia WRT Plug-in 2.0 for Aptana Studio
* Nokia WRT Extension for Adobe Dreamweaver
* Nokia WRT Plug-in for Visual Studio

加上 Nokia 強調其手機平台的市占率,使得想要進入手機開發的內容提供商,可以認真的思考是否可以開始採用 Nokia WRT 來提供手機應用程式或服務了。

官方網站:http://www.forum.nokia.com/Tools_Docs_and_Code/Tools/Runtimes/Web_Runtime/

使用 JSFL 列出有 Linkage 的 Symbol 列表

今天被問到,如何得知 Flash IDE 中,Library 中有哪些 Symbol 有 link 到 Class。

以下,我寫了一個 JSFL 來產生這份報表。首先,先任意準備一些 Symbol,並任意設定他們的 linkage 設定:







JSFL 程式碼:


var lib = fl.getDocumentDOM().library;
var libItems = lib.items;

fl.outputPanel.clear();
fl.outputPanel.trace("symbol name\ttype\tclass\tbase class\texport in first frame");
for(var i=0; i<libItems.length; i++){
var item = libItems[i];
if(item.linkageExportForAS){
fl.outputPanel.trace(item.name
+ "\t" + item.itemType
+ "\t" + item.linkageClassName
+ "\t" + item.linkageBaseClass
+ "\t" + item.linkageExportInFirstFrame);
}
}



在 Flash IDE 執行指定的 *.jsfl 檔:



將我寫在 output 視窗的文字剪貼到 Excel 中看,比較清楚:



線上說明文件:http://livedocs.adobe.com/flash/9.0/main/wwhelp/wwhimpl/js/html/wwhelp.htm

==================================================

[2009-04-08-17-51]

其實,寫這個工具的目的是,若是我們想要將一個舊的 Flash 的視覺全部重新設計,設計好後新的 FLA 希望能將一些與程式有關的設定資料都一一仿照舊 FLA 中的結構,像是 instance name 與 linkage class 等等,因此以下我又增加了對場景物件的列表:

JSFL:

fl.outputPanel.clear();

fl.trace("parent"
+ "\t" + "timeline"
+ "\t" + "layer"
+ "\t" + "frame"
+ "\t" + "instance type"
+ "\t" + "instance name"
+ "\t" + "pos"
+ "\t" + "symbol type"
+ "\t" + "symbol name"
+ "\t" + "class"
+ "\t" + "base class"
+ "\t" + "export in first frame"
);

var dom = fl.getDocumentDOM();

traceContainer(null);

fl.trace("------------------------------------------------------------------");
fl.trace("symbol name"
+ "\t" + "type"
+ "\t" + "class"
+ "\t" + "base class"
+ "\t" + "export in first frame"
);
traceLibrary();

function traceContainer(container){
if(container!=null){
fl.getDocumentDOM().selectNone();
container.selected = true;
fl.getDocumentDOM().enterEditMode("inPlace");
}

var timeline = dom.getTimeline();
var layers, layer;
var frames, frame;
var elements, element;
var libraryItem;

var layer_locked, layer_visible;

layers = timeline.layers;

for(var idxLayer=0; idxLayer<layers.length; idxLayer++){
layer = layers[idxLayer];
frames = layer.frames;

timeline.currentLayer = idxLayer;

layer_locked = layer.locked;
layer_visible = layer.visible;
layer.locked = false;
layer.visible = true;

for(var idxFrame=0; idxFrame<frames.length; idxFrame++){
frame = frames[idxFrame];
elements = frame.elements;

timeline.currentFrame = idxFrame;

if(frame.startFrame == timeline.currentFrame){
for(var idxElement=0; idxElement<elements.length; idxElement++){
element = elements[idxElement];

traceElement(container, timeline, layer, frame, element);

if(element.elementType=="instance" && element.name!=""){
traceContainer(element);
}
}
}
}

layer.locked = layer_locked;
layer.visible = layer_visible;
}

if(container!=null){
fl.getDocumentDOM().exitEditMode();
container.selected = false;
}
}

function traceElement(container, timeline, layer, frame, element){
if(element.name=="")return;

var str = "";
switch(element.elementType){
case "shape":
case "shapeObj":
break;
case "text":
case "instance":
str += (container==null ? "root" : container.name);
str += '\t' + timeline.name;
str += '\t' + layer.name;
str += '\t' + timeline.currentFrame;
str += '\t' + element.elementType;
str += '\t' + element.name;
str += '\t' + '(' + element.x + ', ' + element.y + ')';

if(element.elementType=="instance"){
libraryItem = element.libraryItem;

str += '\t' + element.instanceType;
str += '\t' + libraryItem.name;

if(libraryItem.linkageExportForAS){
str += '\t' + libraryItem.linkageClassName;
str += '\t' + libraryItem.linkageBaseClass;
str += '\t' + libraryItem.linkageExportInFirstFrame;
}else{
str += '\t\t\t';
}
}else{
str += '\t\t\t\t\t';
}
}

if(str!=""){
fl.trace(str);
}
}

function traceLibrary(){
var library = dom.library;
var items = library.items;

for(var i=0; i<items.length; i++){
var item = items[i];
if(item.linkageExportForAS){
fl.trace(item.name
+ "\t" + item.itemType
+ "\t" + item.linkageClassName
+ "\t" + item.linkageBaseClass
+ "\t" + item.linkageExportInFirstFrame);
}
}
}



將輸出結果放到 Excel 看:

[徵才]創意引晴 - Flex/php工程師

【公司名稱】創意引晴有限公司 (資訊傳播業)

【營運方向】Web 2.0 系統網站建置 / Content Management System
Flex統計分析及管理系統 / 政府大型資訊服務專案
數位學習/數位典藏型網站系統

【歷年作品】http://funwish.net/?cat=5

【工作職缺】Flex/php工程師

【工作內容】Flex專案開發(AS3/php/js)

【徵求條件】資訊背景者佳 / 或很有興趣,肯學,不僅有創意,也認真負責

【工作地點】台北市中正區懷寧街74號12樓 (近台北車站, 二二八公園)

【工作時間】一到五, 9:30~18:30
休假方式: 週休二日 國定假日 排休

【公司福利】專案獎金,季獎金,年終獎金,教育訓練, 進修基金

【薪資範圍】面議

【薪資發放】每月5號

【備註】需對資訊業有高度興趣,能對多元化的資訊加以瞭解,
亦能與內部工作人員有效溝通
公司提供學習基金,並安排在職進修,參與研討會機會

◎公司資料:

公司名稱:創意引晴有限公司
公司統編:28862730
公司電話:02-23613359
聯 絡 人:amoswish@funwish.net / Amos Huang

HP 推出 SWFScan 協助分析 SWF 的安全問題(更容易 decompile?)

有鑑於 SWF 的安全性問題引發很多爭議,HP Web Security Research Group 推出免費的 SWFScan 軟體,可以協助你分析你們家的 Flash Application,並提出一些安全方面的建議。

https://h30406.www3.hp.com/campaigns/2009/wwcampaign/1-5TUVE/index.php?key=swf&jumpid=go/swfscan

重要的是,他不僅僅是分析與提供報表而已,過程中理所當然的會進行 decompile,而且可以讓你看到所有 ActionScript Code,雖然不如其他 3rd party 的 decompiler 可以看到所有 symbol,但是有經驗的 AS Programmer 仍可藉此分析出許多商業邏輯。

以下,我僅隨意拿一個行銷網站的簡單 SWF 試試:





這個 SWF 的做法想必簡單,想必只運用了 Timeline 上的程式,想必是用 AS1/AS2。找時間再來測測用 AS3 製作的複雜 RIA、遊戲 試試看。

==================================================

[2009-03-27-13-46]

針對比較複雜的 SWF 測試之後,發現他還會檢查一些 keyword,告訴你哪些文字可能透露出使用者帳戶的訊息,譬如 "LOGIN":



或是有哪些文字可能透露出與資料加密有關的訊息。

利用 LocalConnection 限制 Flash 應用程式同時間只能執行一個

若是基於一些原因,你不希望你的 flash 應用程式,在同一台電腦被同時執行兩個,那麼你可以透過 LocalConnection 連線到已使用的名稱時會送出錯誤訊息,來達到這個目的。

以下,我準備一個 test.fla,其 Document Class 是 Main,而 Main.as 的程式碼如下:


package{
import flash.display.MovieClip;
import flash.net.LocalConnection;
import flash.utils.getQualifiedClassName;

public class Main extends MovieClip{
private var __lcChackSingleApp:LocalConnection;
public function Main(){
var lcName:String = getQualifiedClassName(this);
__lcChackSingleApp = new LocalConnection();
try{
__lcChackSingleApp.connect(lcName);
}catch(e:Error){
throw new Error("不允許同時執行兩個 flash");
return;
}

init();
}

private function init():void{
throw new Error("init()");
}
}
}


以下,我的測試畫面是直接在自己電腦用 standalone player 來開 *.swf 的效果,第一次開啟 test.swf 時,會跳出以下的 Error 訊息,表示有順利執行到 init():



然後第一個視窗就開起完成了,然後再次按下 test.swf 嘗試開啟新的視窗,就會看到以下畫面告知錯誤訊息:



結束錯誤訊息後,也不會出現表示 init() 的訊息,證明後續的工作沒有被執行到:



實務上,當然你不會用 throw new Error() 的方式來處理,你可以使用自己想要的畫面,或是導向網頁。

這個機制,不僅可以防堵相同種類的瀏覽器,對於不同種類的瀏覽器 IE 與 FF 也一樣有效。

=====================================================

[2009-03-24-15-48]

實務上,遇到一些問題,有些人的電腦,IE 瀏覽器關閉時,雖然以為已經關閉了,但是 flash 卻好像仍未結束的樣子,通常發生在還有其他 IE 瀏覽器開啟的時候,但這種情況也不是每台電腦都可見,只有某些人的電腦會發生 IE 關不完全的情況。

這種情況,就導致了 LocalConnection 還存在,以至於新開啟的 Flash 會檢查到 LC 重複,導致無法進入。

另一種作法,當第二個 instance 開啟發現第一個 instance 的 LC 還存在的話,就用該名稱向 instance 1 溝通,要求 instance 1 進行釋放資源與結束的工作,讓 instance 2 可以進入。這種方式永遠讓後者取代前者。

免費的 Flex Builder

這對一些 Flex 的開發者來說或許是個好消息,Flex Builder 可以免費取得~

http://www.eonflex.com/?p=139
http://www.digitalartsonline.co.uk/news/index.cfm?NewsID=12218

雖然這是來自英國 Adobe 的消息,但卻不限各地的 user 去索取它。

你所要做的,就是去連絡 Shorten:
email : shorten [at] adobe.com
twitter:http://twitter.com/ashorten
blog:http://www.ashorten.com/

雖然大部分的人都已經有其他方法取得 Flex Builder 了,或是採用其他的開發工具如 Visual Studio 版的 Amethyst:http://www.sapphiresteel.com/Download-Amethyst-Adobe-Flex-IDE,不過有需要的人還是可以去詢問看看。

AS3 作 3DES 編碼,與 .NET 互通資料

使用 AS3 想要進行資料加密的工作時,可以參考 as3crypto:

http://code.google.com/p/as3crypto/

以及其範例:

http://crypto.hurlant.com/demo/


假設,我們要在 .NET 中將一段明碼編碼:



會得到:

Key(Base64): Lns8zSrn2h4VGb5y1NBBHPcNDGjTHjna
IV(Hex): 41b362c1231999fc
編碼結果:GAUNI2sJCx32yFiF1J0AC5YPbbXcizYl

然後將這些資料貼進 Demo 的網頁後:



按下 Decrypt,就可以解出原來的明碼 (我就不重複貼圖了)





另外一個情況是,反過來,當 Flash 透過 3DES 進行編碼後,要提供資料給 .NET 的話:



則會提供:

Key(Base64): Lns8zSrn2h4VGb5y1NBBHPcNDGjTHjna
IV(Base64): 0sqxgBNq7iA=
編碼結果:XcMt/RnEoozLmBLUpLNRTPVMjRykzDxR9wvrKV9Zwwo=

範例程式的 Key Format 下拉選單好像有點問題,無法將 Hex 資料轉成 Base64,懶得解 bug,所以這裡的 Key(Base64) 可以直接用剛剛由 .NET 產生的繼續使用即可。

IV(Base64) 的部分,因為原範例程式只有顯示 IV(Hex),所以這部分我有修改原範例程式,多顯示一種格式的資料,便於提供給 .NET 使用。


將資料貼進 .NET 之後,按下 Decrypt 就順利解出來囉:






.NET 的原始碼,主架構可以由 MSDN 的範例找到,
http://msdn.microsoft.com/zh-tw/library/system.security.cryptography.tripledescryptoserviceprovider(VS.80).aspx

為了能編中文,所以我將原本的 ASCIIEncoding() 改為 System.Text.Encoding.UTF8。

並且設定了 填補模式 為 PKCS7(在 AS3 中用的是 PKCS#5,放心,有通!):
TripleDESCryptoServiceProvider tDESalg = new TripleDESCryptoServiceProvider();
tDESalg.Padding = PaddingMode.PKCS7;

完整程式碼如下:


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.Security.Cryptography;
using System.IO;

namespace TripleDES
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}

private void btnEncrypt_Click(object sender, EventArgs e)
{
try
{
// Create a new TripleDESCryptoServiceProvider object
// to generate a key and initialization vector (IV).
TripleDESCryptoServiceProvider tDESalg = new TripleDESCryptoServiceProvider();

// Create a string to encrypt.
string sData = txtSource.Text;

if (txtKey.Text == "")
{
txtKey.Text = Convert.ToBase64String(tDESalg.Key);
}
else
{
tDESalg.Key = Convert.FromBase64String(txtKey.Text);
}

if (txtIV.Text == "")
{
txtIV.Text = Convert.ToBase64String(tDESalg.IV);
}
else
{
tDESalg.IV = Convert.FromBase64String(txtIV.Text);
}

String hexIV = "";
for (int i = 0; i < tDESalg.IV.Length; i++)
{
hexIV += Convert.ToString(tDESalg.IV[i], 16);
}
txtIV2.Text = hexIV;

// Encrypt the string to an in-memory buffer.
byte[] Data = EncryptTextToMemory(sData, tDESalg.Key, tDESalg.IV);

txtResult.Text = Convert.ToBase64String(Data);
}
catch (Exception exp)
{
Console.WriteLine(exp.Message);
}

}

private void btnDecrypt_Click(object sender, EventArgs e)
{
try
{

String hexIV = txtIV2.Text;
hexIV = System.Text.RegularExpressions.Regex.Replace(hexIV, "/\\s|:/gm", "");
if ((hexIV.Length & 1) == 1) hexIV = "0" + hexIV;
byte[] iv = new byte[hexIV.Length/2];
for (int i = 0; i < iv.Length; i++) {
iv[i] = Convert.ToByte(hexIV.Substring(i * 2, 2), 16);
}


txtResult2.Text = DecryptTextFromMemory(Convert.FromBase64String(txtResult.Text)
, Convert.FromBase64String(txtKey.Text)
, Convert.FromBase64String(txtIV.Text));
}
catch (Exception exp)
{
Console.WriteLine(exp.Message);
}
}

public static byte[] EncryptTextToMemory(string Data, byte[] Key, byte[] IV)
{
try
{
TripleDESCryptoServiceProvider tDESalg = new TripleDESCryptoServiceProvider();
tDESalg.Padding = PaddingMode.PKCS7;


// Create a MemoryStream.
MemoryStream mStream = new MemoryStream();

// Create a CryptoStream using the MemoryStream
// and the passed key and initialization vector (IV).
CryptoStream cStream = new CryptoStream(mStream,
tDESalg.CreateEncryptor(Key, IV),
CryptoStreamMode.Write);

// Convert the passed string to a byte array.
//byte[] toEncrypt = new ASCIIEncoding().GetBytes(Data);
byte[] toEncrypt = System.Text.Encoding.UTF8.GetBytes(Data);

// Write the byte array to the crypto stream and flush it.
cStream.Write(toEncrypt, 0, toEncrypt.Length);
cStream.FlushFinalBlock();

// Get an array of bytes from the
// MemoryStream that holds the
// encrypted data.
byte[] ret = mStream.ToArray();

// Close the streams.
cStream.Close();
mStream.Close();

// Return the encrypted buffer.
return ret;
}
catch (CryptographicException e)
{
Console.WriteLine("A Cryptographic error occurred: {0}", e.Message);
return null;
}

}

public static string DecryptTextFromMemory(byte[] Data, byte[] Key, byte[] IV)
{
try
{
TripleDESCryptoServiceProvider tDESalg = new TripleDESCryptoServiceProvider();
tDESalg.Padding = PaddingMode.PKCS7;

// Create a new MemoryStream using the passed
// array of encrypted data.
MemoryStream msDecrypt = new MemoryStream(Data);

// Create a CryptoStream using the MemoryStream
// and the passed key and initialization vector (IV).
CryptoStream csDecrypt = new CryptoStream(msDecrypt,
tDESalg.CreateDecryptor(Key, IV),
CryptoStreamMode.Read);

// Create buffer to hold the decrypted data.
byte[] fromEncrypt = new byte[Data.Length];

// Read the decrypted data out of the crypto stream
// and place it into the temporary buffer.
csDecrypt.Read(fromEncrypt, 0, fromEncrypt.Length);

//Convert the buffer into a string and return it.
return System.Text.Encoding.UTF8.GetString(fromEncrypt);
}
catch (CryptographicException e)
{
Console.WriteLine("A Cryptographic error occurred: {0}", e.Message);
return null;
}
}



}


}


至於我自己改寫的 Flex 範例,原則上只是因為在原範例中,IV 是編碼後所自動產生的,並可用於解碼,我將範例改成可以使用我們所填入文字欄位提供的 IV 值作編碼;以及上面我提過的有多顯示 IV(Base64) 的欄位。我就不再轉貼落落長的原始碼了。

將 ClassLoader 用於音效管理

有了 ClassLoader 之後,可以將它用在音效的管理。

我寫了一個 SoundManager.as,裡面會用到 ClassLoader 將外部的 *.swf 載入進來,要使用的時候只要指定 音效的類別名稱 就可以播放了。


package
{
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.media.*;
import flash.utils.getQualifiedClassName;

public class SoundManager extends EventDispatcher
{
static public var SOUND_LOADED:String = "SOUND_LOADED";

private var __sounds:Object = new Object();
private var __soundClassLoaderList:Array = new Array();

static private var __instance:SoundManager;
static public function getInstance():SoundManager{
if(__instance==null)
__instance = new SoundManager();
return __instance;
}
public function SoundManager()
{
if(__instance!=null)throw new Error("please use SoundManager.getInstance()!!");
}

/**
* 同一個存放 Sound 類別的 *.swf 只會載入一次
* @param soundClassLibUrl
*
*/
public function load(soundClassLibUrl:String):void{
if(soundClassLibUrl!=null && soundClassLibUrl.length>0){
for(var i:uint=0; i<__soundClassLoaderList.length; i++){
if(__soundClassLoaderList[i]["url"]==soundClassLibUrl){
dispatchEvent(new Event(SOUND_LOADED));
return;
}
}

var soundClassLoader:ClassLoader = new ClassLoader(soundClassLibUrl);
soundClassLoader.addEventListener(ClassLoader.CLASS_LOADED, onClassLoaded);
soundClassLoader.addEventListener(ClassLoader.LOAD_ERROR, onClassLoadError);
soundClassLoader.load();

__soundClassLoaderList.push({url: soundClassLibUrl, clsLoader: soundClassLoader});

}else{
dispatchEvent(new Event(SOUND_LOADED));
}
}

/**
* 載入 *.swf 完成
* @param e
*
*/
private function onClassLoaded(e:Event):void{
dispatchEvent(new Event(SOUND_LOADED));
}

/**
* 載入 *.swf 失敗
* @param e
*
*/
private function onClassLoadError(e:Event):void{
trace(flash.utils.getQualifiedClassName(this), "onClassLoadError()", e.toString());
}

/**
* 去各個 Class Loader 找一遍,找出指定的 Sound Class
* @param id
* @return
*
*/
private function getSound(id:String):Sound{
var clsLoader:ClassLoader;
var runtimeClassRef:Class;

for(var i:uint=0; i<__soundClassLoaderList.length; i++){
clsLoader = __soundClassLoaderList[i]["clsLoader"] as ClassLoader;
runtimeClassRef = clsLoader.getClass(id);

if(runtimeClassRef!=null){
return new runtimeClassRef() as Sound;
}
}

return null;
}

/**
* 取得指定的 Sound Class 的物件,並存在 cache 中
* @param id
* @return
*
*/
private function getSoundObj(id:String):Object{
if(__sounds[id]!=null){
return __sounds[id];
}else{
var snd:Sound = getSound(id);
if(snd!=null){
var obj:Object = {};
obj["sound"] = snd;
obj["channel"] = null;
obj["transform"] = new SoundTransform();
obj["position"] = 0;

__sounds[id] = obj;
return __sounds[id];
}

return null;
}
}

/**
* 播放指定 Sound Class 的音效
* @param id
* @param isLoop
*
*/
public function play(id:String, isLoop:Boolean=false):void{
//log.debug("play sound", id);
var objSound:Object = getSoundObj(id);
if(objSound!=null){
var loops:int = isLoop ? int.MAX_VALUE : 0;
try{
objSound["channel"] = (objSound["sound"] as Sound).play(objSound["position"], loops, objSound["transform"]);
(objSound["channel"] as SoundChannel).addEventListener(Event.SOUND_COMPLETE, soundComplete);
}catch(ae:ArgumentError){
trace(flash.utils.getQualifiedClassName(this), "play()", id, isLoop, ae.toString());
}
}
}

private function soundComplete(e:Event):void{
dispatchEvent(e);
}

/**
* 停止指定 Sound Class 的音效
* @param id
* @param isPause
*
*/
public function stop(id:String, isPause:Boolean=false):void{
//log.debug("stop sound", id);
var objSound:Object = getSoundObj(id);
if(objSound!=null){
if(objSound["channel"]!=null){
var channel:SoundChannel = objSound["channel"] as SoundChannel;
objSound["position"] = isPause ? channel.position : 0; //紀錄停止的地方
channel.stop();
}
}
}

//===================================================================================

private var __soundLists:Array = new Array();

/**
* 將多個 Sound Class 組成為一個串列音效,可以一個接一個播放
* @param ids
*
*/
public function playList(...ids):void{
var sndAry:Array = new Array();
var snd:Sound;

for(var i=0; i<ids.length; i++){
snd = getSound(ids[i]);
if(snd!=null){
sndAry.push(snd);
}
}

//存放一個 sound channel 與多個 Sound 組成的陣列
var soundList:Object = {channel: null, list: sndAry};
__soundLists.push(soundList);
playListObject(soundList);
}

/**
* 播放指定的串列音效
* @param soundList
*
*/
private function playListObject(soundList:Object):void{
var sndAry:Array = soundList["list"];
if(sndAry.length>0){
//播放串列中的下一個音效
var snd:Sound = sndAry.splice(0, 1)[0];
soundList["channel"] = snd.play();
(soundList["channel"] as SoundChannel).addEventListener(Event.SOUND_COMPLETE, onPlayListSoundComplete);
}else{
//這個串列的音效都播完了
for(var i:uint=0; i<__soundLists.length; i++){
if(__soundLists[i]==soundList){
__soundLists.splice(i, 1);
dispatchEvent(new Event(Event.SOUND_COMPLETE)); //串列音效播放完畢
return;
}
}
}
}

/**
* 串列音效中的某一個音效播完時,準備播下一個
* @param e
*
*/
private function onPlayListSoundComplete(e:Event):void{
for(var i:uint=0; i<__soundLists.length; i++){
if(__soundLists[i]["channel"]==(e.currentTarget as SoundChannel)){
playListObject(__soundLists[i]); //播放串列中的下一個
return;
}
}
}

}
}


測試方法,首先先準備一個 sound.swf,裡面在 Library 中準備兩個 *.mp3,並且匯出在第一影格,類別名稱取名 "S1" 與 "S2"。在場景畫面上不用放置任何內容。

再來,在 main.swf 的 frame 1 上寫:


var soundMgr:SoundManager = SoundManager.getInstance();
soundMgr.addEventListener(SoundManager.SOUND_LOADED, onSoundLoaded);
soundMgr.load("sound.swf");

function onSoundLoaded(e:Event):void{
soundMgr.play("S2");
}


開始測試時,就會聽到 main.swf 播放在 sound.swf 中的 "S2" 類別的音效。




若是 main.swf 的 frame 1 改寫成:


var soundMgr:SoundManager = SoundManager.getInstance();
soundMgr.addEventListener(SoundManager.SOUND_LOADED, onSoundLoaded);
soundMgr.load("sound.swf");

function onSoundLoaded(e:Event):void{
soundMgr.playList("S2", "S1");
}


則測試時,就會先播放 "S2" 類別的音效後,再接著播放 "S1" 類別的音效。

使用 ClassLoader 載入外部 swf 中的 class

官方 HELP 中有段範例:

http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/system/ApplicationDomain.html#includeExamplesSummary

是談到當 main.swf 載入外部的 asset.swf 後,要使用 asset.swf 中的 Class 來動態產生物件時,要注意 ApplicationDomain 的設定。

在這個範例中最有意義的是 ClassLoader,透過這個好用的類別,你可以任意載入 *.swf,然後使用 function getClass(className:String):Class 取得在 asset.swf 中的類別,並產生物件。

我將這個 ClassLoader 類別作了一點點修改,當第一次載入完成後便會將 ByteArray 存在 cache 中,若是有不小心載入到同一個 *.swf 的話,就不會再從 server 作 request,而是從 cache 中找出之前載入過的 ByteArray 即可。


package
{
import flash.display.Loader;
import flash.errors.IllegalOperationError;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.IOErrorEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.system.ApplicationDomain;
import flash.system.LoaderContext;
import flash.utils.ByteArray;

public class ClassLoader extends EventDispatcher
{
public static var CLASS_LOADED:String = "classLoaded";
public static var LOAD_ERROR:String = "loadError";
private var loader:Loader;
private var swfLib:String;
private var request:URLRequest;
private var loadedClass:Class;

static private var cache:Object = new Object();
private var lib:String;

public function ClassLoader(lib:String)
{
this.lib = lib;

loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,completeHandler);
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR,ioErrorHandler);
loader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR,securityErrorHandler);
}

public function load():void {
if(cache[lib]!=null){
loadFromCache();
}else{
var urlLoader:URLLoader = new URLLoader();
urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
urlLoader.addEventListener(Event.COMPLETE, urlLoaderComplete);
urlLoader.load(new URLRequest(lib));
}
}

private function urlLoaderComplete(e:Event):void{
cache[lib] = (e.currentTarget as URLLoader).data;
loadFromCache();
}

private function loadFromCache(){
var ba:ByteArray = cache[lib] as ByteArray;

var context:LoaderContext = new LoaderContext();
context.applicationDomain=ApplicationDomain.currentDomain;

loader.loadBytes(ba, context);
}

public function getClass(className:String):Class {
try {
return loader.contentLoaderInfo.applicationDomain.getDefinition(className) as Class;
} catch (e:Error) {
throw new IllegalOperationError(className + " definition not found in " + swfLib);
}
return null;
}

private function completeHandler(e:Event):void {
dispatchEvent(new Event(ClassLoader.CLASS_LOADED));
}

private function ioErrorHandler(e:Event):void {
dispatchEvent(new Event(ClassLoader.LOAD_ERROR));
}

private function securityErrorHandler(e:Event):void {
dispatchEvent(new Event(ClassLoader.LOAD_ERROR));
}

}
}




要測試的話,可以先建立一個 asset.swf,裡面準備一個 MovieClip,放在 Library 中,設定其類別為 "MC1",並且是 "匯出在第 1 個影格",而場景或畫面上不用放置任何東西,基本上這個 asset.swf 的解析度或其他設定要設多少也都沒關係,因為我們真正會用到的就是那 第一影格會匯出 的類別們。

第二步,建一個 main.swf,在 frame 1 寫:


var clsLoader:ClassLoader = new ClassLoader("asset.swf");
clsLoader.addEventListener(ClassLoader.CLASS_LOADED, onLoaded);
clsLoader.load();

function onLoaded(e:Event):void{
var runtimeClassRef:Class = clsLoader.getClass("MC1");
var mc1:MovieClip = new runtimeClassRef() as MovieClip;
addChild(mc1);
}


測試後,應該可以發現 main 有將 asset 中的 MC1 類別產生出實體,並放在場景上。

E4 SWT Java for Flash

http://www.tekool.net/blog/2009/02/15/e4swt-java-for-flash-running-the-demos/

寫 Java Code,輸出 swf~


......
How it works

To summarize, all the code here is Java code. None of the examples use reference to Flex SDK code nor Flash Player specific code, even the one that displays Flex controls. It all uses SWT code with its specific API.

Each example use a generic SWT wrapper written in Java and exported in ActionScript that control at runtime the access to standard Flash/Flex specific code.
......


當然也要注意一下其缺點:


......
The bad news

Now the bad news is when exploring the generated SWF files for each demo, we can see that what I call the SWT ActionScript wrapper is a SWF file named org.eclipse.swt.swf that weighs 960KB, common code for the examples 160KB and other files make any of the demo launched to load something like 2MB before to launch. It excludes to use any SWT generated application on any high audience public website.
......

Flash 10 coming to most smartphones in 2010

看到這篇新聞:

Flash 10 coming to most smartphones in 2010
http://reviews.cnet.com/8301-13970_7-10164745-78.html

不知該如何說~當我還在手機公司時,聽到的消息是未來會有新的 player,重新為了移動裝置而開發的,不同於 flash lite,效能應該會更好的 player,但,初期這個新的 player 能支援的 swf 大約是 FP6 + AS1 的樣子。後來,慢慢的有謠傳關於 AIR 放在手機上的計畫,雖然我從未當面由官方聽到這樣的消息。

沒想到現在的新聞卻變成直接將 FP10 移植到手機上?我曾聽過的部分資訊大概針對手機廠商的,也就是所謂的 MMI 的應用,拿 Flash 作為手機操作介面的,而外界能看到的新聞應該大多是針對 player 吧,也就是一般 user 關心的自己的手機可以播放哪種版本的 swf。

不過,其實我想很多會關心手機上是否能播放 swf 的玩家,也常常是想要在手機上開發點什麼的開發者,不見得是手機製造商,但很有可能是內容提供者,開發者會關心的問題大多就會包含了是否可以與手機硬體進行互動,除了基本的執行手機上的應用程式之外,包含像是是否能讀取手機通訊錄或接聽來電等等硬體資訊,這些可能就不是這一則簡單的新聞可以看得出來的。

所以,看到 2010 年會有很多手機可以支援 FP10,不知道有什麼意義~尤其是若真想要用到 FP10 的新功能如 3D、IK、其他特效,甚至別提這些,光是提到現在 FlashLite 3 都還沒支援的 BitmapData,不知道有多少手機真的能跑得動、跑得順的。

在我看來,要好好玩 Flash 手機程式,除了要把基本的 Flash 玩熟之外,最好把一些 Wrapper 程式也學一學,就像是要將 Flash 用在 desktop 的應用時,最好也學學 MDM 之類的,才能發揮更多 Flash 不擅長的能力,除非你就真的只是要製作簡單的手機動畫、多媒體導覽程式...,那就不見得需要理會 FP10 的推出了,最多就是期待一下新版 Player 的效能會更好罷了。

==============================================

[2009-03-16-10-30]

看到這篇:
http://flashmobileblog.com/2009/03/15/lg-kp500-cooky-adobe-mobile-client-flash-technology/

LG KP500 用在 MMI 的 Flash Player 已經不是 Flash Lite 了,而是 Adobe Mobile Client,使用 Flash 6 + AS1/AS2 的語法,效能與反應速度應該都能比以前的 Flash Lite MMI 好上很多。

利用 ByteArray 將 SWF 重新編碼加密

一篇舊文章,教如何透過 ByteArray 將 *.swf 進行自訂的編碼,使得一般人無法透過 decompiler 將該 *.swf 解出來。

文章已不存在,google cache:http://209.85.175.132/search?q=cache:JFsWjtfvv0QJ:lab.lzyy.name/blog/archives/32+swf+bytearray&hl=zh-TW&ct=clnk&cd=1&gl=tw&client=firefox-a

步驟一,隨便建立一個 swf 當作要被加密的內容檔案,怎麼做不管,假設檔名 asset.swf。

步驟二,做一個用來進行加密工作的 flash:


var ul:URLLoader = new URLLoader();
ul.dataFormat = URLLoaderDataFormat.BINARY;
ul.addEventListener(Event.COMPLETE, onComplete);
ul.load(new URLRequest("asset.swf"));

function onComplete(e:Event):void{
var ba:ByteArray = (e.target as URLLoader).data;

ba.compress(); //這裡要代換成一個複雜的加工作業

var fr:FileReference = new FileReference();
fr.save(ba, "asset_c.swf");
}


簡單來說,就是將 swf 以 binary 的方式讀入,並對 ByteArray 做些改變,再重新存成 swf 檔。這個作業當然也可能應該是由 Server 進行,至於要對 ByteArray 做甚麼樣的資料改變,都可以。

步驟三,可以用 decompiler 軟體對剛存好的 asset_c.swf 作業看看,就可以發現沒有效果,因為本質上他已經不是 swf format 了。

步驟四,另外開發一個讀取用的 flash,也就是未來真正要將 asset_c.swf 動態載入進來播放的 main.swf:


var ul:URLLoader = new URLLoader();
ul.dataFormat = URLLoaderDataFormat.BINARY;
ul.addEventListener(Event.COMPLETE, onComplete);
ul.load(new URLRequest("asset_c.swf"));

var loader:Loader = new Loader();
addChild(loader);

function onComplete(e:Event):void{
var ba:ByteArray = (e.target as URLLoader).data;

ba.uncompress(); //這裡要代換成一個複雜的加工作業

loader.loadBytes(ba);
}


解密的部分,要參考加密的過程,反向進行。

不過,我想聰明如你也會知道,這個 main.swf 仍是明碼,仍能被 decompiler 給破解出來,以上這作法也就只是為了更加強破解的難度罷了,只好儘量將解密的程式隱藏的複雜一點囉,騙騙君子!

SWF Parser

有一篇舊文章,連原始文章都不在了,只能靠 google cache 看到:

http://72.14.235.132/search?q=cache:OUVn3qZqmC4J:flashpanoramas.com/blog/2007/07/02/swf-parser-air-application/+swf+bytearray&hl=zh-TW&ct=clnk&cd=6&gl=tw&client=firefox-a

提到因為 ByteArray 的關係,所以其實我們在 Flash 中就可以讀取另一個 swf 的資訊,程式碼如下:


/*
Simple SWF parser
Written by Denis V. Chumakov
http://flashpanoramas.com/blog/
Use this code without any restrictions.
*/

// log string
var parseLog:String;

// handlers for SWF tags
// for example:
// handlers[6] = parseJPEG;
// to set your handler for DefineBitsJPEG tag.
// you can find SWF file specifications in Google.
var handlers:Array = [];

// parse SWF file
function parseSWF(data:ByteArray) {
parseLog = "";
data.endian = Endian.LITTLE_ENDIAN;
var format:String = data.readUTFBytes(3);
var compressed:Boolean = format=="CWS";
if (format=="FWS" || format=="CWS") {
parseLog += "SWF version "+data.readByte();
parseLog += ", size: "+data.readUnsignedInt();
} else {
parseLog += "Not a Flash file.";
return;
}
data.readBytes(data);
data.length -= 8;
if (compressed) {
data.uncompress();
}
data.position = 0;
var frame:Array = readBox(data);
parseLog += "\n";
parseLog += "Width: "+Math.round((frame[1]-frame[0])/20);
parseLog += ", height: "+Math.round((frame[3]-frame[2])/20);
var fps_f:uint = data.readUnsignedByte();
var fps_i:uint = data.readUnsignedByte();
parseLog += "\n";
parseLog += "FPS: "+(fps_i+fps_f/256);
var count:uint = data.readUnsignedShort();
parseLog += "\n";
parseLog += "Total frames: "+count;
parseLog += "\n";
while (data.bytesAvailable) {
readSWFTag(data);
}
trace(parseLog);
}

// read SWF tag and call handler if present
function readSWFTag(data:ByteArray) {
var tag:uint = data.readUnsignedShort();
var id:int = tag>>6;
var size:int = tag&0x3F;
if (size == 0x3F) {
size = data.readUnsignedInt();
}
parseLog += "\n";
parseLog += "Tag "+id;
if (handlers[id]!=null) {
var dump:ByteArray = new ByteArray();
if (size!=0) {
data.readBytes(dump,0,size);
}
handlers[id](tag, id, size, dump);
} else {
data.position += size;
}
parseLog += "\tsize: "+size;
}

// read compressed box format
function readBox(data:ByteArray):Array {
var c:Array = [];
var current:uint = data.readUnsignedByte();
var size:uint = current>>3;
var off:int = 3;
for (var i:int=0; i<4; i+=1) {
c[i] = current<<(32-off)>>(32-size);
off -= size;
while (off<0) {
current = data.readUnsignedByte();
c[i] |= off<-8?current<<(-off-8):current>>(-off-8);
off += 8;
}
}
return c;
}

var loader:URLLoader = new URLLoader();
loader.dataFormat = URLLoaderDataFormat.BINARY;
loader.addEventListener(Event.COMPLETE, loadComplete);
// load complete handler
function loadComplete(event:Event) {
parseSWF(loader.data);
}
loader.load(new URLRequest("asset.swf"));



最後一句的 swf 檔名你自己換~

輸出結果:


SWF version 10, size: 1404
Width: 50, height: 50
FPS: 24
Total frames: 1

Tag 69 size: 4
Tag 77 size: 1284
Tag 9 size: 3
Tag 86 size: 12
Tag 83 size: 48
Tag 26 size: 6
Tag 1 size: 0
Tag 0 size: 0


其實 swf format 早已是公開的文件,有興趣的話可以自己寫一個 swf generator 或是 flash player。