|
AssetStudio解包工具(gitHub上可下载):
链接:https://pan.baidu.com/s/1eZpjQaqOJHnGrP9H9PZeRQ
提取码:mbjq
方案一
在AssetBundle头部加入部分字节
[MenuItem("AssetBundle/Build Encrypt OffSet")] static void BuildEncryptOffSet() { var exportPath = Application.streamingAssetsPath; if (Directory.Exists(exportPath)) { Directory.CreateDirectory(exportPath); } var manifest = BuildPipeline.BuildAssetBundles(exportPath, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget); foreach (var abPath in manifest.GetAllAssetBundles()) { string filepath = $"{Application.streamingAssetsPath}/{abPath}"; // 偏移值可调,或者动态设置都可以 uint offset = 96; if ( offset > 0) { byte[] filedata = File.ReadAllBytes(filepath); int filelen = ((int)offset + filedata.Length); byte[] buffer = new byte[filelen]; for (int slen = 0; slen < offset; slen++) { buffer[slen] = 1; } for (int slen = 0; slen < filedata.Length; slen++) { buffer[slen + offset] = filedata[slen]; } FileStream fs = File.OpenWrite(filepath); fs.Write(buffer, 0, filelen); fs.Close(); } } }
同步加载解密
protected AssetBundle _bundle;protected void AssetBundleUnCryptoSync(string assetBundlePatchFile) { uint offSet = 96; _bundle = AssetBundle.LoadFromFile(assetBundlePatchFile, 0,offSet); }
异步加载解密
protected IEnumerator AssetBundleUnCrypto(string assetBundlePatchFile) { uint offSet = 96; AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(assetBundlePatchFile, 0,offSet); yield return request; _bundle = request.assetBundle; }
方案二
全字节加密
[MenuItem("AssetBundle/Build Encrypt")] static void BuildEncrypt() { var exportPath = Application.streamingAssetsPath; if (!Directory.Exists(exportPath)) Directory.CreateDirectory(exportPath); var manifest = BuildPipeline.BuildAssetBundles(exportPath, BuildAssetBundleOptions.ChunkBasedCompression, EditorUserBuildSettings.activeBuildTarget); foreach (var name in manifest.GetAllAssetBundles()) { var uniqueSalt = Encoding.UTF8.GetBytes(name); var data = File.ReadAllBytes($"{Application.streamingAssetsPath}/{name}"); using (var baseStream = new FileStream($"{Application.streamingAssetsPath}/{name}", FileMode.OpenOrCreate)) { var cryptor = new SeekableAesStream(baseStream, "password", uniqueSalt); cryptor.Write(data, 0, data.Length); } } }using System;using System.Collections;using System.Collections.Generic;using System.IO;using System.Security.Cryptography;using UnityEngine;public class SeekableAesStream : Stream{ private Stream baseStream; private AesManaged aes; private ICryptoTransform encryptor; public bool autoDisposeBaseStream { get; set; } = true; /// <param name="salt">//** WARNING **: MUST be unique for each stream otherwise there is NO security</param> public SeekableAesStream(Stream baseStream, string password, byte[] salt) { this.baseStream = baseStream; using (var key = new PasswordDeriveBytes(password, salt)) { aes = new AesManaged(); aes.KeySize = 128; aes.Mode = CipherMode.ECB; aes.Padding = PaddingMode.None; aes.Key = key.GetBytes(aes.KeySize / 8); aes.IV = new byte[16]; //zero buffer is adequate since we have to use new salt for each stream encryptor = aes.CreateEncryptor(aes.Key, aes.IV); } } private void cipher(byte[] buffer, int offset, int count, long streamPos) { //find block number var blockSizeInByte = aes.BlockSize / 8; var blockNumber = (streamPos / blockSizeInByte) + 1; var keyPos = streamPos % blockSizeInByte; //buffer var outBuffer = new byte[blockSizeInByte]; var nonce = new byte[blockSizeInByte]; var init = false; for (int i = offset; i < count; i++) { //encrypt the nonce to form next xor buffer (unique key) if (!init || (keyPos % blockSizeInByte) == 0) { BitConverter.GetBytes(blockNumber).CopyTo(nonce, 0); encryptor.TransformBlock(nonce, 0, nonce.Length, outBuffer, 0); if (init) keyPos = 0; init = true; blockNumber++; } buffer ^= outBuffer[keyPos]; //simple XOR with generated unique key keyPos++; } } public override bool CanRead { get { return baseStream.CanRead; } } public override bool CanSeek { get { return baseStream.CanSeek; } } public override bool CanWrite { get { return baseStream.CanWrite; } } public override long Length { get { return baseStream.Length; } } public override long Position { get { return baseStream.Position; } set { baseStream.Position = value; } } public override void Flush() { baseStream.Flush(); } public override void SetLength(long value) { baseStream.SetLength(value); } public override long Seek(long offset, SeekOrigin origin) { return baseStream.Seek(offset, origin); } public override int Read(byte[] buffer, int offset, int count) { var streamPos = Position; var ret = baseStream.Read(buffer, offset, count); cipher(buffer, offset, count, streamPos); return ret; } public override void Write(byte[] buffer, int offset, int count) { cipher(buffer, offset, count, Position); baseStream.Write(buffer, offset, count); } protected override void Dispose(bool disposing) { if (disposing) { encryptor?.Dispose(); aes?.Dispose(); if (autoDisposeBaseStream) baseStream?.Dispose(); } base.Dispose(disposing); }}
同步加载解密
protected void AssetBundleUnCryptoSync(string assetBundlePatchFile) {#if UNITY_EDITOR FileStream fileStream; fileStream = new FileStream(assetBundlePatchFile, FileMode.Open); var uniqueSalt1 = Encoding.UTF8.GetBytes(bundleName); // 打包时设置的salt名 // Stream暗号化解除 var uncryptor1 = new SeekableAesStream(fileStream, "password", uniqueSalt1); _bundle = AssetBundle.LoadFromStream(uncryptor1);#else var www = UnityWebRequest.Get(assetBundlePatchFile); www.SendWebRequest(); // 阻塞,实现同步效果 while(!www.isDone){ System.Threading.Thread.Sleep (1/30); } if (!(www.isHttpError || www.isNetworkError)) { var dataStream = new MemoryStream(); dataStream.Write(www.downloadHandler.data, 0, www.downloadHandler.data.Length); var uniqueSalt = Encoding.UTF8.GetBytes(bundleName); // 打包时设置的salt名 // Stream暗号化解除 var uncryptor = new SeekableAesStream(dataStream, AssetBundlePathResolver.BundlePassword, uniqueSalt); _bundle = AssetBundle.LoadFromStream(uncryptor); } else { LuaInterface.Debugger.Log(string.Format("{1}:[{0}]",www.error,"www.error")); } www.Dispose(); www = null;#endif this.Complete(); }
方案一,性能几乎无消耗
方案二,性能消耗比较大
如果对加密要求不是很高,推荐使用方案一。 |
|