Unity打包的APK中Asset文件分析

前言

    用Unity开发的游戏,打包成Android平台的APK文件之后,如果解开这个APK,会在assets\bin\Data目录下看到大量的文件名很长而且无意义的文件。像下图这样。本文要分析的就是这些文件。

Unity打包的APK中Asset文件分析_第1张图片


    首先要感谢UnityStudio的作者的辛勤工作,我也正是从他的代码中才学到这些知识的。在此整理出来供大家一起交流,同时也作为我自己学习历程的记录。


    这里分析的是Unity 4.6.X版本的Unity生成的Asset文件格式,5.X以后的版本在格式上会有一些调整,但是不会差别太大。确实需要的,就自己查阅UnityStudio的源代码吧。


Unity资源文件概述

在Unity中,有两种访问资源的方式,一种是使用AssetBundle,这是Unity提供的一种资源打包格式;另一种就是直接用Resources.Load()方法。而本文的目标文件是第二种方式可以读取的文件。当Unity工程在编辑器模式下运行的时候,他会直接访问放在工程的Resources目录下的资源,而当打包成APK发布的时候,Unity把这些资源全都打散了,然后把资源的文件名都变成了上图中显示的文件Hash值。这么做的原因,我个人猜测,一方面是为了保护游戏开发者的资源,另一方面也可以加快资源访问的速度。


这些文件其实都是以Asset文件的格式组织的。在这一堆文件中,应该还有一个文件名不奇怪,且体积相对比较大的文件,这个文件就是所有这些文件的组织者,或者叫领导。这个文件一般应该叫mainData,或者Unity.assets。从这个文件入手,就能把所有的这些文件都串起来。

Asset文件结构

和很多标准格式一样,Asset文件格式也是由文件头,若干数据描述块,和若干真实数据构成。若用C#语言描述,其结构为:

struct AssetFileHeader {
    Int32  tableSize;   // 文件头及段描述信息表总大小
    Int32 fileSize;// 总文件大小
       Int32 fileVersion; // 文件版本号,Unity 3.5 ~ 4.6.x为9,Unity 5.X开始是14和15
    Int32 dataOffset;// 资源数据区的偏移地址
    Int32 reserved1;// 4字节保留区
    String UnityVersion;// Unity版本号字符串,以0结束
    Int32 platformId;// 平台标示,常见的有:6 - Web; 9 - IOS; 13 - Android
}


然后接下来跟着的是N块数据区。结果定义如下:

struct BaseData {
    Int32 baseCount;  //Base结构块数量
    BaseStruct [] baseStructList; // 
    Int32 reserved;// 4字节保留区
}


上面的BaseStruct数组,当baseCount为0时,结构也是不存在的,不占用空间。其结构如下:

struct BastStruct {
    Int32 classId;
    Int32 baseType;
    String baseName;
    byte reserved[20]; 
    Int32 memberCount;
    MemberStruct [] memberList;
}
struct MemberStruct {
    string memberType;
    string memberName;
    Int32 memberSize;
    Int32 memberIndex;
    Int32 isArray;
    Int32 memberNum;
    Int16 memberNum1;
    Int16 memberNum2;
    Int32 ChildrenCount;
    ChildStruct [] childrenList;
}


TODO :至于这个结构里存储的是什么数据,目前还没遇到带有这个结构的资源文件,碰到的时候再补上。


接下来就是我们最关心的Asset结构了

struct AssetData {
    Int32 assetsCount;// 资源块数量
    AssetStruct [] assetsList;// assetsCount块资源描述数据
}
sturct AssetStruct {
    Int32 pathId;// 资源路径ID
    Int32 dataOffset;// 资源数据在数据块内的偏移量。该值加上Header中的dataOffset就是资源数据在文件中的偏移量
    Int32 assetSize;// 资源数据大小
    Int32 assetType1;// 资源类型,详细类型可以在Unity Class ID 文档中查看
    Int16 assetType2;// 资源类型,遇到的都是和Type1的值一样,还不知道具体什么意思
    Int16 reserved;// 保留2字节
    Int32 sharedFileCount;// 共享的文件数量
    ShareFileStruct []sharedFileList;
}
struct ShareFileStruct {
    string fileName;// 共享文件名
    byte reserved[20];
    string relativePathName;// 共享文件相对路径名
}


再后面跟着的就是Asset的真实数据了。原则上根据不同的Asset类型,正确的反序列化出来,就能得到能使用的资源了。


再说那些用hash值做名字的文件,其实这些文件都会在这个mainData文件中有引用,他们的格式也是上面列出的Asset文件格式。因此,从mainData文件开始,递归的处理好所有依赖的Asset文件,就能访问到这个目录下所有的文件。


最后要声明,我不鼓励任何破解他人的游戏来窃取他人劳动成果并用于非法用途的行为。但是根据本文的知识,我们看到提取他人未保护的资源并不难。所以要想更好的保护自己的劳动成果,应该考虑其他资源的加密方法。这方面的知识,可以参考赵四同学的博客,后续我也会在我的博客里写一些我自己的学习成果(但是只会有相关的技术探讨,而不会有针对特定应用的破解教程)。




你可能感兴趣的:(逆向工程)