移动端游戏经常被一些玩家破解成白包,但是为了安全性,开发者还是需要使用算法对文本文件加密,加密的算法非常多,比如通常使用的是MD5算法,OBFS算法,SHA512算法等。由于MD5算法经常使用,网上也有现成的代码本节就直接掠过,直接讲OBFS,SHA512加密算法,为了便于大家理解,先把加密算法代码奉献上,加密函数代码如下所示:

//OBFS加密算法
    private static string OBFS(string str)
    {
        int length = str.Length;
        var array = new char[length];
        for (int i = 0; i < array.Length; i++)
        {
            char c = str[i];
            var b = (byte)(c ^ length - i);
            var b2 = (byte)((c >> 8) ^ i);
            array[i] = (char)(b2 << 8 | b);
        }
        return new string(array);
    }

    //SHA512加密算法
    public static string GetSHA512Password(string password)  
    {  
        byte[] bytes = Encoding.UTF7.GetBytes(password);  
        byte[] result;  
        SHA512 shaM = new SHA512Managed();  
        result = shaM.ComputeHash(bytes);  
        StringBuilder sb = new StringBuilder();  
        foreach (byte num in result)  
        {  
            sb.AppendFormat("{0:x2}", num);  
        }  
        return sb.ToString();  
    }

以上两个算法实现了文本文件的密码,函数的参数是开发者自己定义的字符串,然后在该字符串的基础上通过算法加密生成新的字符串用于压缩文件加密。下面列出配置文件,并且调用OBFS函数或者是SHA512函数对文件进行加密,返回的是经过加密字符串,同时调用函数SaveConfigXMLTOZip对文件进行压缩加密。对应的函数语句如下所示:

private const string configurationFile = "config.txt";
    private const string localizationFile = "translations.txt";
    private /*const*/ string configurationZipPwd = OBFS("~Ũɦ;о_ٷݫ࠵ळੰୠ_");
private /*const*/ string configurationZipPwd = GetSHA512Password("ٷ※▊ぷ┩▓ㄘЖ╔╕Ψ≮≯ゆǘйξζ");
#if UNITY_EDITOR
    protected void SaveConfigXMLToZip()
    {
        using (ZipFile zipFile = new ZipFile(Encoding.UTF8))
        {
            zipFile.Password = configurationZipPwd;
            zipFile.AddEntry(configurationFile, configuration.bytes);
            zipFile.AddEntry(localizationFile, localization.bytes);
            string zipPath = Path.Combine(Application.persistentDataPath, configurationZipFile);
            LogTool.Log("Saving configuration in \"" + zipPath + "\"");
            zipFile.Save(zipPath);
        }
    }
#endif

文件的压缩是在编辑模式下,程序运行时会将文本文件压缩同时把加密的密码赋值给它,在程序启动时,先从资源服务器加载文件的版本号,通过与本地的版本号对比决定下载需要的文本文件。函数代码如下所示:

#region Coroutines
    IEnumerator DownloadVersionFile()
    {
        Asserts.Assert(!downloadingVersionFile);
        downloadingVersionFile = true;

        WWW versionLoader = new WWW(configurationZipURL + versionFile + "?nocache=" + Environment.TickCount);

        while (!versionLoader.isDone)
        {
            yield return new WaitForEndOfFrame();
        }

        if (versionLoader.isDone && string.IsNullOrEmpty(versionLoader.error))
        {
            versionString = versionLoader.text;
        }
        else
            versionString = version.text;

        versionLoader.Dispose();

        LogTool.Log("VERSION NUMBER: " + versionString);

            downloadingVersionFile = false;

        PlayerPrefs.SetInt("last_vn", lastVersionNumber);
    }

它的下载方式是通过WWW下载的,先从资源服务器下载版本的文本文件,接下来下载文件的压缩包,函数代码如下所示:

IEnumerator DownloadZip()
    {
        Asserts.Assert(!downloadingZip);
        downloadingZip = true;
        WWW zipLoader = new WWW(configurationZipURL + configurationZipFile + "?nocache=" + Environment.TickCount);

        while (!zipLoader.isDone)
        {
            if (stopDownloading)
            {
                downloadingZip = false;
                stopDownloading = false;

                LogTool.Log("Download configuration STOPPED!");
            }

            yield return new WaitForEndOfFrame();
        }

        if (zipLoader.isDone && string.IsNullOrEmpty(zipLoader.error))
        {
            LogTool.Log("**** PELLE: DOWNLOADING ZIP COMPLETED! Duration: " + (Time.realtimeSinceStartup - startTime));
            using (FileStream fs = new FileStream(Path.Combine(Application.persistentDataPath, configurationZipFile), FileMode.Create))
            {
                fs.Seek(0, SeekOrigin.Begin);
                fs.Write(zipLoader.bytes, 0, zipLoader.bytes.Length);
                fs.Flush();
            }

            zipLoader.Dispose();

            if (!downloadingZip)
            {
                LogTool.Log("Download configuration OK!");
                yield break;
            }
            else
                LogTool.Log("Download configuration OK, configurations will be loaded from new zip!");
        }
        else
        {
            zipLoader.Dispose();
            downloadingZip = false;
            stopDownloading = false;

            yield break;
        }

        if (!dataLoaded && !stopDownloading)
            this.TryLoadingXMLsFromZip();

        downloadingZip = false;
        stopDownloading = false;
    }

最后一步就是解压缩文件并且解释文本文件,函数代码如下所示:

protected void TryLoadingXMLsFromZip()
    {
        string zipPath = Path.Combine(Application.persistentDataPath, configurationZipFile);
        if (!File.Exists(zipPath))
        {
            LogTool.Log("Configuration not found!");
            this.ParseConfigXML(configuration.text, false);
            this.ParseLocalizationXML(localization.text, false);
            return;
        }

        using (ZipFile zipFile = new ZipFile(zipPath, Encoding.UTF8))
        {
            zipFile.Password = configurationZipPwd;

            ZipEntry xmlConfEntry   = zipFile[configurationFile],
                     xmlLocaleEntry = zipFile[localizationFile];
            if (null == xmlConfEntry || null == xmlLocaleEntry)
            {
                LogTool.Log("Downloaded configuration INVALID!");
                this.ParseConfigXML(configuration.text, false);
                this.ParseLocalizationXML(localization.text, false);
                return;
            }

            using (MemoryStream ms = new MemoryStream())
            {
                xmlConfEntry.Extract(ms);

                string xmlText = Encoding.UTF8.GetString(ms.GetBuffer(), 0, ms.GetBuffer().Length);
                this.ParseConfigXML(xmlText, true);

                ms.Seek(0, SeekOrigin.Begin);
                xmlLocaleEntry.Extract(ms);

                xmlText = Encoding.UTF8.GetString(ms.GetBuffer(), 0, ms.GetBuffer().Length);
                this.ParseLocalizationXML(xmlText, true);
            }
        }
    }

这样整个加密流程就给读者介绍完了,后面将脚本挂接到对象上,在程序运行时做初始化操作处理就可以了,详情可以查看笔者已出版的著作:《Unity3D实战核心技术详解》一书。