Unity安卓OBB手动分包

Obb分包

Android游戏在发布到GooglePlay时,有50M包大小限制,当游戏包体大小超过50M时就需要分包了,也是是将一部分资源放到Obb中供用户下载。

Unity默认分包

Unity在导出Android包可以选择Split Binary,让Unity自动分出一个Obb文件包,Unity分出的Obb会默认把StreamAssets里的所有资源放到Obb中,生成的Obb文件不能随便修改,一旦修改APK就无法加载这个OBB,而当我想要替换一下Obb中的某些文件就必须要重新使用Unity导出包,这显然太浪费时间了。

Unity导出的OBB和APK是怎么关联的?

我在尝试反编译一些Unity的class代码后,得到了一些蛛丝马迹,原来Unity在导出APK和Obb时,在APK中持有了Obb文件的hash值引用,这个引用记录在APK中的资源文件assets\bin\Data\settings.xml中.

<?xml version="1.0" encoding="UTF-8"?>
<settings>
  <integer name="splash_mode">0</integer>
  <bool name="useObb">True</bool>
  <bool name="b373fd7767dea6e3331fb9301fb5e27f">True</bool>
</settings>

这个配置文件中useObb==True则表示这个APK需要Obb资源包,而下面的b373fd7767dea6e3331fb9301fb5e27f=True则是关联的Obb文件的Hash值,既然知道了引用的持有方式,我们就去class里找hash值的校验方式,最终也成功的逆向出了Obb文件Hash值的计算方式,我这里用Java实现了一下.

注意:本文算法基于Unity5.3.7p7版本,不同Unity版本,不保证一样

package me.ywy;

import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * 
 * @author Simon.H
 * 
 * 计算Unity中AndroidObb的hash值
 * 
 */

public class UnityObbHash {
    public static String gen(String file) {
        FileInputStream fis = null;
        try {
            MessageDigest md = MessageDigest.getInstance("MD5");
            fis = new FileInputStream(file);
            byte[] buffer = new byte[63];
            int length = -1;
            long size = fis.available();
            fis.skip(size - Math.min(size, 65558L));
            while ((length = fis.read(buffer)) != -1) {
                md.update(buffer, 0, length);
            }
            BigInteger bi = new BigInteger(1, md.digest());
            return bi.toString(16);
        } catch (IOException ex) {
            ex.printStackTrace();
            return null;
        } catch (NoSuchAlgorithmException ex) {
            ex.printStackTrace();
            return null;
        } finally {
            if (null != fis) {
                try {
                    fis.close();
                } catch (IOException ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) {
        if (args.length <= 0) {
            return;
        }
        System.out.println(gen(args[0]));
        //System.out.println(gen("H:\\Google\\main.2.com.xmly.deadzombie.obb"));
        try {
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

对应的逆向smali是从Unity的classes.jar中反编译出来的.

.locals 10

const/4 v1, 0x0

const/4 v0, 0x0

:try_start_0
const-string v2, "MD5"

invoke-static {v2}, Ljava/security/MessageDigest;->getInstance(Ljava/lang/String;)Ljava/security/MessageDigest;

move-result-object v3

new-instance v4, Ljava/io/FileInputStream;

invoke-direct {v4, p0}, Ljava/io/FileInputStream;-><init>(Ljava/lang/String;)V

new-instance v2, Ljava/io/File;

invoke-direct {v2, p0}, Ljava/io/File;-><init>(Ljava/lang/String;)V

invoke-virtual {v2}, Ljava/io/File;->length()J

move-result-wide v6

const-wide/32 v8, 0x10016

invoke-static {v6, v7, v8, v9}, Ljava/lang/Math;->min(JJ)J

move-result-wide v8

sub-long/2addr v6, v8

invoke-virtual {v4, v6, v7}, Ljava/io/FileInputStream;->skip(J)J

const/16 v2, 0x400

new-array v5, v2, [B

move v2, v0

:goto_0
const/4 v6, -0x1

if-eq v2, v6, :cond_0

const/4 v6, 0x0

invoke-virtual {v3, v5, v6, v2}, Ljava/security/MessageDigest;->update([BII)V

invoke-virtual {v4, v5}, Ljava/io/FileInputStream;->read([B)I

move-result v2

goto :goto_0

:cond_0
invoke-virtual {v3}, Ljava/security/MessageDigest;->digest()[B
:try_end_0
.catch Ljava/io/FileNotFoundException; {:try_start_0 .. :try_end_0} :catch_0
.catch Ljava/io/IOException; {:try_start_0 .. :try_end_0} :catch_1
.catch Ljava/security/NoSuchAlgorithmException; {:try_start_0 .. :try_end_0} :catch_2

move-result-object v2

:goto_1
if-nez v2, :cond_1

move-object v0, v1

:goto_2
return-object v0

:catch_0
move-exception v2

move-object v2, v1

goto :goto_1

:catch_1
move-exception v2

move-object v2, v1

goto :goto_1

:catch_2
move-exception v2

move-object v2, v1

goto :goto_1

:cond_1
new-instance v1, Ljava/lang/StringBuffer;

invoke-direct {v1}, Ljava/lang/StringBuffer;-><init>()V

:goto_3
array-length v3, v2

if-ge v0, v3, :cond_2

aget-byte v3, v2, v0

and-int/lit16 v3, v3, 0xff

add-int/lit16 v3, v3, 0x100

const/16 v4, 0x10

invoke-static {v3, v4}, Ljava/lang/Integer;->toString(II)Ljava/lang/String;

move-result-object v3

const/4 v4, 0x1

invoke-virtual {v3, v4}, Ljava/lang/String;->substring(I)Ljava/lang/String;

move-result-object v3

invoke-virtual {v1, v3}, Ljava/lang/StringBuffer;->append(Ljava/lang/String;)Ljava/lang/StringBuffer;

add-int/lit8 v0, v0, 0x1

goto :goto_3

:cond_2
invoke-virtual {v1}, Ljava/lang/StringBuffer;->toString()Ljava/lang/String;

move-result-object v0

goto :goto_2 

解决问题

通过上面的过程我们已经可以直接修改Obb,然后通过代码计算出新Obb的hash值,替换原setting.xml中的hash值即可生成新的有正确关联的APK和OBB.

添加新评论