Reverse engineering SWF to fix limitation in SWFTools

This post will be in English as you can see. And it’s very specific, maybe it won’t interest most of the usual readers.

A couple days ago I was hitting my head against an almost impossible to solve problem as I described in my cry for help at StackOverflow: swftools and events creating internal link

The solution was far from easy because debug in Flash is a great way to pay for your sins to save purgatory time. So I decided to take the way of reverse engineering.

Getting an working example and using swfdump -D in it I figured out that the swf dump should look like it:

                [022]       227 DEFINEBUTTON2 defines id 0011
                 condition 00a1
                 (   18 bytes) action: Push int:2 int:1 String:"butMC4"
                 (    0 bytes) action: GetVariable
                 (   13 bytes) action: Push String:"gotoAndPlay"
                 (    0 bytes) action: CallMethod
                 (    0 bytes) action: Pop
                 (    7 bytes) action: Push String:"_root"
                 (    0 bytes) action: GetVariable
                 (   11 bytes) action: Push String:"canflip" bool:false
                 (    0 bytes) action: SetMember
                 (    0 bytes) action: End
                 condition 0152
                 (   18 bytes) action: Push int:11 int:1 String:"butMC4"
                 (    0 bytes) action: GetVariable
                 (   13 bytes) action: Push String:"gotoAndPlay"
                 (    0 bytes) action: CallMethod
                 (    0 bytes) action: Pop
                 (    7 bytes) action: Push String:"_root"
                 (    0 bytes) action: GetVariable
                 (   11 bytes) action: Push String:"canflip" bool:true
                 (    0 bytes) action: SetMember
                 (    0 bytes) action: End
                 condition 0008
                 (   25 bytes) action: Constantpool(3 entries) String:"_root" String:"canflip" String:"gotoPage"
                 (    2 bytes) action: Push Lookup:0 ("_root")
                 (    0 bytes) action: GetVariable
                 (    4 bytes) action: Push Lookup:1 ("canflip") bool:true
                 (    0 bytes) action: SetMember
                 (   14 bytes) action: Push bool:true int:10 int:2 Lookup:0 ("_root")
                 (    0 bytes) action: GetVariable
                 (    2 bytes) action: Push Lookup:2 ("gotoPage")
                 (    0 bytes) action: CallMethod
                 (    0 bytes) action: Pop
                 (    0 bytes) action: End

To have the events implemented.

I changed the swftools, to get the pdf2swf producing the correct code to the internal links. It was an iterative process. Change C code, compile, run, compare against correct dump.

The final form of the function

void swfoutput_linktopage(gfxdevice_t*dev, int page, gfxline_t*points)
{
    swfoutput_internal*i = (swfoutput_internal*)dev->internal;
    ActionTAG* actions = 0;
    ActionTAG* actionOvOn = 0;
    ActionTAG* actionOvOff = 0;
 
    if(i->shapeid>=0)
	endshape(dev);
    if(i->textmode)
	endtext(dev);
        /* 
            _root.canflip=true; // flipping enabled
            _root.gotoPage(4,true); // go to page
        */
	actions = action_PushString(actions, "_root"); //function name
        actions = action_GetVariable(actions);
	actions = action_PushString(actions, "canFlip"); //function name
	actions = action_PushBoolean(actions,1);
        actions = action_SetMember(actions);
	actions = action_PushBoolean(actions,0);
	actions = action_PushInt(actions, page); //parameter
	actions = action_PushInt(actions, 2); //number of parameters (2)
	actions = action_PushString(actions, "_root"); //function name
        actions = action_GetVariable(actions);
        actions = action_PushString(actions,"gotoPage");
	actions = action_CallMethod(actions);
        actions = action_Pop(actions);
	actions = action_End(actions);
 
        /* 
            _root.canflip=false; // flipping enabled
        */
        actionOvOn = action_PushString(actionOvOn, "_root"); //function name
        actionOvOn = action_GetVariable(actionOvOn);
        actionOvOn = action_PushString(actionOvOn, "canFlip"); //function name
        actionOvOn = action_PushBoolean(actionOvOn,0);
        actionOvOn = action_SetMember(actionOvOn);
	actionOvOn = action_End(actionOvOn);
 
        /* 
            _root.canflip=true; // flipping enabled
        */
        actionOvOff = action_PushString(actionOvOff, "_root"); //function name
        actionOvOff = action_GetVariable(actionOvOff);
        actionOvOff = action_PushString(actionOvOff, "canFlip"); //function name
        actionOvOff = action_PushBoolean(actionOvOff,1);
        actionOvOff = action_SetMember(actionOvOff);
	actionOvOff = action_End(actionOvOff);
 
        printf("Debug: Actions GotoPage %d\n",page);
        char name[80];
        sprintf(name, "page%d", page);
 
	drawlink3 (dev,actions,actionOvOff,actionOvOn,points,page,name);
        swf_ActionFree(actions);
	swf_ActionFree(actionOvOn);
	swf_ActionFree(actionOvOff);
}

I had to create a new function to attach 3 events to a button. The optimal solution should have a variable number of events, but I didn’t have time for that.

static void drawlink3(gfxdevice_t*dev, ActionTAG*actions1, 
                      ActionTAG*actions2, ActionTAG*actions3, 
                      gfxline_t*points, char*type, const char*url)
{
    swfoutput_internal*i = (swfoutput_internal*)dev->internal;
    RGBA rgb;
    SRECT r;
    int lsid=0;
    int fsid;
    int myshapeid;
    int myshapeid2;
    double posx = 0;
    double posy = 0;
    int buttonid = getNewID(dev);
    gfxbbox_t bbox = gfxline_getbbox(points);
 
    i->hasbuttons = 1;
 
    /* shape */
    myshapeid = getNewID(dev);
    i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
    swf_ShapeNew(&i->shape);
    rgb.r = rgb.b = rgb.a = rgb.g = 0; 
    fsid = swf_ShapeAddSolidFillStyle(i->shape,&rgb);
    swf_SetU16(i->tag, myshapeid);
    r.xmin = (int)(bbox.xmin*20);
    r.ymin = (int)(bbox.ymin*20);
    r.xmax = (int)(bbox.xmax*20);
    r.ymax = (int)(bbox.ymax*20);
    r = swf_ClipRect(i->pagebbox, r);
    swf_SetRect(i->tag,&r);
    swf_SetShapeStyles(i->tag,i->shape);
    swf_ShapeCountBits(i->shape,NULL,NULL);
    swf_SetShapeBits(i->tag,i->shape);
    swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,0,fsid,0);
    i->swflastx = i->swflasty = 0;
    drawgfxline(dev, points, 1);
    swf_ShapeSetEnd(i->tag);
    swf_ShapeFree(i->shape);
 
    /* shape2 */
    myshapeid2 = getNewID(dev);
    i->tag = swf_InsertTag(i->tag,ST_DEFINESHAPE3);
    swf_ShapeNew(&i->shape);
 
    rgb = i->config_linkcolor;
 
    fsid = swf_ShapeAddSolidFillStyle(i->shape,&rgb);
    swf_SetU16(i->tag, myshapeid2);
    r.xmin = (int)(bbox.xmin*20);
    r.ymin = (int)(bbox.ymin*20);
    r.xmax = (int)(bbox.xmax*20);
    r.ymax = (int)(bbox.ymax*20);
    r = swf_ClipRect(i->pagebbox, r);
    swf_SetRect(i->tag,&r);
    swf_SetShapeStyles(i->tag,i->shape);
    swf_ShapeCountBits(i->shape,NULL,NULL);
    swf_SetShapeBits(i->tag,i->shape);
    swf_ShapeSetAll(i->tag,i->shape,/*x*/0,/*y*/0,0,fsid,0);
    i->swflastx = i->swflasty = 0;
    drawgfxline(dev, points, 1);
    swf_ShapeSetEnd(i->tag);
    swf_ShapeFree(i->shape);
 
    // ST_DEFINEBUTTON2 support multiple conditions
    // ST_DEFINEBUTTON Don't!!!
    i->tag = swf_InsertTag(i->tag,ST_DEFINEBUTTON2); 
    swf_SetU16(i->tag,buttonid); //id
    swf_ButtonSetFlags(i->tag, 0); //menu=no
    swf_ButtonSetRecord(i->tag,0x01,myshapeid,i->depth,0,0);
    swf_ButtonSetRecord(i->tag,0x02,myshapeid2,i->depth,0,0);
    swf_ButtonSetRecord(i->tag,0x04,myshapeid2,i->depth,0,0);
    swf_ButtonSetRecord(i->tag,0x08,myshapeid,i->depth,0,0);
    swf_SetU8(i->tag,0);
 
    // on(rollOver, dragOver)
    swf_ButtonSetCondition(i->tag,0x00a1);
    swf_ActionSet(i->tag,actions3);
 
    //on(rollOut, dragOut, releaseOutside)
    swf_ButtonSetCondition(i->tag,0x0152);
    swf_ActionSet(i->tag,actions2);
 
    // on(release)
    swf_ButtonSetCondition(i->tag,0x0008);
    swf_ActionSet(i->tag,actions1);
 
    swf_SetU8(i->tag,0);
    swf_ButtonPostProcess(i->tag,3);
 
    char buf[80];
    char*buf2 = 0;
    const char* name = 0;
    if(i->config_linknameurl) {
        buf2 = malloc(strlen(type)+strlen(url)+2);
        sprintf(buf2, "%s:%s", type, url);
        name = buf2;
    } else {
        name = buf;
        sprintf(buf, "button%d", buttonid);
    }
 
    i->tag = swf_InsertTag(i->tag,ST_PLACEOBJECT2);
 
    if(posx!=0 || posy!=0) {
        SPOINT p;
        p.x = (int)(posx*20);
        p.y = (int)(posy*20);
        p = swf_TurnPoint(p, &i->page_matrix);
	MATRIX m;
        m = i->page_matrix;
        m.tx = p.x;
        m.ty = p.y;
	swf_ObjectPlace(i->tag, buttonid, 
                  getNewDepth(dev),&m,0,(U8*)name);
    } else {
	swf_ObjectPlace(i->tag, buttonid, 
                  getNewDepth(dev),&i->page_matrix,0,(U8*)name);
    }
 
    if(buf2)
	free(buf2);
}

Deu trabalho …

Adobe vai descontinuar o Flash para dispositivos móveis.

É curioso que, logo depois de um post sobre os problemas do Flash com os browsers de desktops e notebooks, eu venha aqui dar a notícia de que a Adobe vai parar de desenvolver o Flash para dispositivos móveis.

Ontem eles anunciaram que não vão mais desenvolver o plugin para browsers móveis e vão focar no desenvolvimento de soluções baseadas em HTML5.

“O HTML5 é agora universalmente suportado pelos aparelhos portáteis, e, em alguns casos, exclusivamente. Isso faz do HTML5 a melhor solução para criação e exibição de conteúdo em navegadores de plataformas móveis.”, escreveu Danny Winokur, vice-presidente e gerente-geral de desenvolvimento interativo da Adobe.“Daqui pra frente, nosso trabalho com o Flash será focado em permitir a desenvolvedores Flash criar apps nativos com Adobe AIR para as principais lojas de aplicativos.”

Para os fanáticos desenvolvedores de joguinhos em Flash para celular, isso é um balde de água fria. Para os desenvolvedores de aplicativos nativos, isso é um alento. Para os usuários, isso é a melhor coisa que aconteceu, já que mesmo nos dispositivos móveis que tinham plugin para Flash, o desempenho dele era péssimo.

Concordo plenamente com o VP da Adobe quando ele diz que precisa focar no Flash para o Adobe AIR. Isso é agora a melhor plataforma da Adobe. Fazer aplicativos que funcionem bem em PC/Mac/Linux em Adobe AIR é mil vezes mais simples do que qualquer solução em Java, Qt, GTK+ ou outro framework/linguagem multi-plataforma. Já fiz alguns programas muito legais com o AIR, que demorariam bem mais para serem feitos em Java, ou Qt, por exemplo.

Parabéns para a Adobe pela coragem de dar esse passo! Agora, para ficarem perfeitos, só precisam baixar os preços do Photoshop para eu poder parar de usar o Gimp ou o Acorn no Mac. 😀