00003 不思议迷宫.0009.1:来,练个手:换肤



00003 不思议迷宫.0009.1:来,练个手:换肤

我偶尔会出去晒太阳,边晒太阳边玩游戏。问题就来了,不管是我的iPhone还是小米,在阳光较多的地方,哪怕是在阴影下,游戏中的地牢背景(掀开石板后显示的那些背景方块)总是黑漆漆的,基本上看不见;而前景方块、怪物或者掉落的物品就没有这个问题。那我们就换掉它吧。

替换图片这种修改无疑是简单的。但首先需要先找到它。

在前面我们曾经遇到过一个“/src/game/ui/form/dungeon/UIDungeonMain.luac”文件,这里面就包含了地牢的界面创建:

-- 构造函数

function UIDungeonMain:ctor(levelData, floor)

   self:setName("UIDungeonMain");

 

    local node =cc.CSLoader:createNode("layout/dungeon/DungeonMain.csb");

    ……

end

csbCocos Studio导出的场景(一般是静态界面,也可以包括动画)文件,里面包含了所用资源(含图片)的名称(含路径)。遗憾的是它是二进制的,但万幸的是,资源名称还是明文的:

那么我们就可以想办法提取出其中的全部图片文件,看看哪些是我们需要的。但这个办法不够友好,我们完全可以采用可视化的方法。

csb可以在cocos2dx中被解析和显示,并可对其中的元素增加事件响应。不幸的是,《不思议迷宫》使用的cocos2dx版本似乎是3.3,而我电脑上的是3.7.13.10,而且在cocos商店中也下不到3.3,看到有个3.4Final,结果下下来却是3.7.1,网上找到的几个3.3的下载链接也不能用。用3.10加载之后,报错了。这个方法只能先放放。

如果能找到对应的版本,并且有足够的时间,还可以研究csb的详细加载过程,然后还原成CocosStudio导出前的csd格式。

下面我就试试找到的第一个图片。

当然,我先看到的是images/ui/level.plist,是个plist而不是任何一种图片后缀。我知道一些(合图)工具会使用plist,而cocos2dx也支持这种方式,并且非常好用。我的经验告诉我,images/ui/level.plist会对应一个images/ui/level.xxx,其中xxx是图片后缀。我一找,果然找到一个images/ui/level.png。然后我兴冲冲地打开它。再然后,看图软件报错了——图片格式错误。

好吧,这不算什么。我知道Texture Packer之类的合图工具会给图片加密,并且知道在cocos2dx中使用这个加密图片时,需要设置密码。设置密码的函数为setPvrEncryptionKey。于是,我ida反编译libcocos2dlua.so查找setPvrEncryptionKey的引用,然后,就傻眼了——没有引用。

是调用点被某种形式地隐藏了吗?还是确实真的就没有引用?如果是前者,那问题就麻烦了。先确定到底是哪种情况吧。

开始跟踪cc.CSLoader:createNode("layout/dungeon/DungeonMain.csb");的调用。

这是lua中的函数,在cocos2dx-lua中,它会被绑定为C++中的lua_cocos2dx_CSLoader_createNode。在ida反编译libcocos2dlua.so中找到这个函数,发现它又调用了cocos2d::CSLoader::createNode((cocos2d::CSLoader *)&v5),然后又调用了……,下面是过程:

cocos2d::CSLoader::createNodeFromProtocolBuffers(v2,(int)v1);

cocos2d::CSLoader::nodeFromProtocolBuffersFile(v3,v2);

cocos2d::SpriteFrameCache::addSpriteFramesWithFile

cocos2d::TextureCache::addImage(v7)

cocos2d::Image::initWithImageFile(v6)

cocos2d::Image::initWithImageData(v1,v6, v7)

到这个函数中就需要注意了:

void __fastcallcocos2d::Image::initWithImageData(cocos2d::Image *this, const unsigned __int8*a2, __int32 a3)

{

  ……

  if (cocos2d::ZipUtils::isCCZBuffer((cocos2d::ZipUtils *)a2, (const unsigned __int8*)a3, a3) == 1 )

  {

    v11 = v4;

    v7 = (constunsigned __int8 *)cocos2d::ZipUtils::inflateCCZBuffer(v4, v3,(__int32)&v13, v6);

  }

  else

  {

    if (cocos2d::ZipUtils::isGZipBuffer(v4, v3, v5) != 1 )

    {

      v13 =(const unsigned __int8 *)v4;

      v11 = v4;

      gotoLABEL_9;

    }

    v11 = v4;

    v7 = (constunsigned __int8 *)cocos2d::ZipUtils::inflateMemory(v4, (unsigned __int8 *)v3,(__int32)&v13, v8);

  }

  v3 = v7;

LABEL_9:

  v9 =cocos2d::Image::detectFormat(v12, v13, (__int32)v3);

  *((_DWORD *)v12+ 9) = v9;

  if ( v9 <= 7)

    JUMPOUT(__CS__,(char *)dword_4B6830 + dword_4B6830[v9]);

  v10 = (void*)cocos2d::tgaLoadBuffer((cocos2d *)v13, (unsigned __int8 *)v3, (__int32)v12);

  if ( v10&& !*(_DWORD *)v10 )

  {

    cocos2d::Image::initWithTGAData(v12,v10);

  }

  else

  {

    ……

  }

  ……

}

我瞅了瞅,又瞅了瞅,从头看到尾,又从未看到头,恁是没发现有啥疑点,哪怕是JUMPOUT中的switch

在揪断几根头发之后,突然想起看看png的初始化函数,没想到这一看就看到了秘密:

signed int__fastcall cocos2d::Image::initWithPngData(cocos2d::Image *this, const unsigned__int8 *a2, __int32 a3)

{

  ……

 

  v30 = a3;

  v3 = a2;

  v32 = this;

  v4 = a2[2];

  v5 = a2[3];

  v6 = *a2;

  v7 = a2[1];

  v41 = (((a2[7] << 8) | a2[6]) <<16) | (a2[5] << 8) | a2[4];

  *(_DWORD *)v40 = (((v5 << 8) | v4)<< 16) | (v7 << 8) | v6;

  if ( cocos2d::Image::isEncrypted(this, v40,8) == 1 )

  {

    v8 = 200;

    if ( v30 <= 200 )

      v8 = v30;

    cocos2d::Image::xorEncrypt((cocos2d::Image*)1, (void *)v3, v8);

  }

  ……

}

哇哈哈哈,来吧,写个程序加密解密吧。因为图省事,用了不是最合适的C#

        staticstringxorEncryptKey="// Dump Ref object memory leaks if(__refAllocationList.empty()) { log([memory] All Ref objects successfullycleaned up (no leaks detected).); } else { log([memory] WARNING: %d Ref objectsstill active in memory., (int)__refAllocationList.size()); for (const auto&ref : __refAllocationList) { CC_ASSERT(ref); const char* type =typeid(*ref).name(); log([memory] LEAK: Ref object %s still active withreference count %d., (type ? type : ), ref->getReferenceCount()); }}";

 

        staticvoidxorEncrypt(byte[] v, intlength)

        {

            for(inti = 0; i < length; ++i)

            {

               v[i] ^= (byte)xorEncryptKey[(i %xorEncryptKey.Length)];

            }

        }

        staticintxorEncryptingLength(byte[] v)

        {

            if(v.Length <= 200) return v.Length;

            return200;

        }

        staticboolisEncryptedPng(byte[] v)

        {

            return(v[0] != 0x89) || (v[1] != 'P') ||(v[2] != 'N') || (v[3] != 'G');

        }

        privatestaticbyte[]ToPlainPng(byte[] v)

        {

            if(isEncryptedPng(v)) xorEncrypt(v, xorEncryptingLength(v));

            returnv;

        }

        privatestaticbyte[]ToEncodedPng(byte[] v)

        {

           xorEncrypt(v, xorEncryptingLength(v));

            returnv;

    }

解密level.png之后,打开看看,发现一个灰色的图块(红框标注)很像:

这个区域对应的切片名称为“images/ui/level/black_mask.png”。别问我是如何找到这个的,猜测第一,验证第二。唉,网上有一个叫“Anti TexturePacker”的小工具,可以方便地查看plist中定义的小图。我以前也用过,还不错。但是,对这个level.plist的解析却出错了。然后,我又找了一个“Cocos查看器”,结果还是看不了level.plist。实在是让人无语了。求人不如求己,还是抽空自己写个切片工具吧。目前先用PhotoShop应付一次。

找个明亮点的颜色替换进去,保存,加密,再替换掉原来的level.png,然后别忘了更新project.manifest中的相关信息,安装,看效果,才发现搞错了……

虽然闹了个乌龙,但更换图片的方法就是上面讲述的这些。需要说明的是,png图片也可以不用加密,游戏内部会自动检测的。

背景方块在阳光充足的地方看不见的问题我一定会改,只不过由于验证起来太烦,暂时不会进行,而要等到和其他修改一起。唉,没越狱没root的手机真是不方便。
展开阅读全文

没有更多推荐了,返回首页