AB包打包工具:二.打包工具

AB包分为Window平台,Android或者IOS包,又或者自己合成Android或者IOS包,因为业务方案当时需要,合并了Android和Ios的包,解包加载通过Head文件来找到Android或者IOS的AB数据吗,好了来展示打包工具的代码吧,其中还包括了AB包加密

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using BundleMix;
using UnityEditor;
using UnityEngine;
using Utility;

namespace Editor.PlayAssetBundle
{
    public static class BundleMenu
    {
        private static readonly string ProjectPath = Environment.CurrentDirectory;
        private static readonly string Outputs = Path.Combine(ProjectPath, "build/outputs");
        private static readonly string Bundles = Path.Combine(Outputs, "bundles");
        private static readonly string Windows = Path.Combine(Bundles, "windows/full");
        private static readonly string Android = Path.Combine(Bundles, "android/full");
        private static readonly string IOS = Path.Combine(Bundles, "iOS/full");
        private static readonly string AndroidAndIOSMix = Path.Combine(Bundles, "mix/full");
        public static readonly string WindowsSingle = Path.Combine(Bundles, "windows/single");
        public static readonly string AndroidSingle = Path.Combine(Bundles, "android/single");
        public static readonly string IOSSingle = Path.Combine(Bundles, "iOS/single");
        public static readonly string SingleMix = Path.Combine(Bundles, "mix/single");

        public static List<string> PathList = new List<string>();

        public static void ExportWindowsAssetBundle() =>
            ExportFullAssetBundle(Windows, BuildTarget.StandaloneWindows64);

        public static void ExportAndroidAssetBundle() =>
            ExportFullAssetBundle(Android, BuildTarget.Android);

        public static void ExportIOSAssetBundle() =>
            ExportFullAssetBundle(IOS, BuildTarget.iOS);
        public static void ExportIOSAndAndroidSAssetBundle() =>
            ExportFullAssetBundle(AndroidAndIOSMix);
        
        private static string GetSuffix(int total, int index)
        {
            const string fileExtension = "xmb";
            var number = index + 1;
            var suffix = $"{number}";
            return fileExtension;
            // $"{suffix}{fileExtension}";
            // var suffix = total < 100
            //     ? number < 10
            //         ? $"0{number}"
            //         : $"{number}"
            //     : number < 10
            //         ? $"00{number}"
            //         : number < 100
            //             ? $"0{number}"
            //             : $"{number}";
            // return $"{suffix}{fileExtension}";
        }

        private static void ExportFullAssetBundle(string output)
        {
            if (!Directory.Exists(AndroidAndIOSMix))
            {
                Directory.CreateDirectory(AndroidAndIOSMix);
            }
            foreach (var pInfo in AssetBundleListWindow.PairList) 
            {
                foreach (var path in
                         pInfo.Paths) // public static string AndroidAndIOSMix = Path.Combine(Bundles, "mix/full");
                {
                    MixProcessor.ExportSingleMixAssetBundle(
                        BundleMenu.AndroidAndIOSMix,
                        pInfo.Name,
                        path
                    );
                }
            }
        }
        //打包加密 1
        public static IEnumerator EncyptAssetBundle(string outPath)
        {
            yield return "开始加密...";
            Debug.Log("开始加密");
            //遍历streamingAssets目录下所有的ab包,逐个进行加密
            foreach (var f in new DirectoryInfo(outPath).GetFiles("*.xmb",  //AssetBundleUtility.streamingAssetsPath
                         SearchOption.AllDirectories))
            {
                Byte[] temp = File.ReadAllBytes(f.FullName);
                yield return "加密文件:" + f.FullName;
                FileUtility.Encypt(ref temp);
                File.WriteAllBytes(f.FullName, temp);
            }

            yield return "加密完成...";
        }
        //打包加密 2
        public static IEnumerator EncyptAssetBundleOffset(string outPath)
        {
            yield return "开始加密...";
            Debug.Log("开始加密");
            //遍历streamingAssets目录下所有的ab包,逐个进行加密
            foreach (var f in new DirectoryInfo(outPath).GetFiles("*.xmb", SearchOption.AllDirectories))
            {
                FileUtility.ABOffsetEncryption(Path.Combine(outPath, f.Name));
            }

            yield return "加密完成...";
        }

        private static void 
            ExportFullAssetBundle(string output, BuildTarget target)
        {
            PathList.Clear();//清理
            if (Directory.Exists(output))
            {
                Directory.Delete(output, true);
            }

            Directory.CreateDirectory(output);

            var all = AssetDatabase.GetAllAssetBundleNames();
            var buildList = new List<AssetBundleBuild>();
            foreach (var name in all)
            {
                if(!(name.EndsWith("hat_a")||name.EndsWith("hat_b") ||name.EndsWith("xiaoxiong")))
                    PathList.Add("/"+name);
                var paths = AssetDatabase.GetAssetPathsFromAssetBundle(name);
                for (var index = 0; index < paths.Length; index++)
                {
                    var path = paths[index];
                    var suffix = GetSuffix(paths.Length, index);
                    var build = new AssetBundleBuild
                    {
                        assetBundleName = name,
                        assetBundleVariant = suffix,
                        assetNames = new[] {path}
                    };
                    buildList.Add(build);
                }
            }
            Debug.Log("pathList:"+PathList.Count);

            BuildPipeline.BuildAssetBundles(
                output,
                buildList.ToArray(),
                BuildAssetBundleOptions.UncompressedAssetBundle,
                target
            );

            foreach (var file in Directory.GetFiles(output))
            {
                if (!file.EndsWith("xmb"))
                {
                    File.Delete(file);
                }
            }
            Debug.Log(output);
            Debug.Log("打包");
        //     var encyptAssetBundle = EncyptAssetBundle(output);  1
        //     #region 流程
        //     while (encyptAssetBundle.MoveNext())
        //     {
        //     }
        //     #endregion
        
        var encyptAssetBundleOffset = EncyptAssetBundleOffset(output);  //2
        #region 流程
        while (encyptAssetBundleOffset.MoveNext())
        {
        }
        #endregion
        }

        internal static void ExportSingleAssetBundle(string output, string name, string path, BuildTarget target)
        {
            if (!Directory.Exists(output))
            {
                Directory.CreateDirectory(output);
            }

            var suffix = DateTime.Now.ToString("HH-mm-ss");
            
            var build = new AssetBundleBuild
            {
                assetBundleName = name,
                assetBundleVariant = $"{suffix}.xmb",
                assetNames = new[] {path}
            };

            BuildPipeline.BuildAssetBundles(
                output,
                new[] {build},
                BuildAssetBundleOptions.UncompressedAssetBundle,
                target
            );
            var encyptAssetBundleOffset = EncyptAssetBundleOffset(output);//2
            #region 流程
            while (encyptAssetBundleOffset.MoveNext())
            {
            }
            #endregion

            foreach (var file in Directory.GetFiles(output))
            {
                if (!file.EndsWith("xmb"))
                {
                    File.Delete(file);
                }
            }
        }
        [MenuItem("Extras/AUTOManager/2.AssetBundleManager", false, 1)]
        public static void AssetBundleList()
        {
            EditorWindow.GetWindow<AssetBundleListWindow>().Show();
        }
    }

    public class AssetBundleListWindow : EditorWindow
    {
        private Vector2 _scrollPosition;
        public static readonly List<AssetBundleInfo> PairList = new List<AssetBundleInfo>();
        
        private void OnEnable()
        {
            RashBnt();
        }

        private void RashBnt()
        {
            titleContent = new GUIContent("Asset Bundle Manager");
            PairList.Clear();
            var bundleNameList = AssetDatabase.GetAllAssetBundleNames();
            foreach (var bundleName in bundleNameList)
            {
                var pair = new AssetBundleInfo(bundleName);
                PairList.Add(pair);
            }
        }

        public void OnInspectorUpdate()
        {
            Repaint();
        }

        private void OnGUI()
        {
            if(GUILayout.Button("Rash Windows",EditorStyles.miniButtonRight))
            {
                RashBnt();
            }
            _scrollPosition = EditorGUILayout.BeginScrollView(_scrollPosition);
            AssetsMixExport();
            FullExport();
            SingleExport();

            EditorGUILayout.EndScrollView();
        }
        
        
        AssetBundle AB1;
        AssetBundle AB2;
        string _text1 = "null";
        string _text2 = "null";
        private void AssetsMixExport()
        {

            GUILayout.Label($"Assets MixExport", EditorStyles.largeLabel);
            GUILayout.Space(20);
            // OpenFileName openFileName = new OpenFileName();
            // GUILayout.Button("aaaaaa",LocalDialog.GetOfn());
            
            OpenFileName openFileName = new OpenFileName();
            
            if (GUILayout.Button("AB1", EditorStyles.miniButtonRight))   //  (GUI.Button(new Rect(10, 10, 100, 50), "Open")
            {   
               
                openFileName.structSize = Marshal.SizeOf(openFileName);
                openFileName.filter = "AB文件(*.xlsx)\0*.xmb";
                openFileName.file = new string(new char[256]);
                openFileName.maxFile = openFileName.file.Length;
                openFileName.fileTitle = new string(new char[64]);
                openFileName.maxFileTitle = openFileName.fileTitle.Length;
                openFileName.initialDir = Application.streamingAssetsPath.Replace('/', '\\'); //默认路径
                openFileName.title = "AB资源1";
                openFileName.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000008;



            
                
            // GUILayout.TextField(text, EditorStyles.textField);
            if (LocalDialog.GetSaveFileName(openFileName))
            {
                Debug.Log(openFileName.file);
                Debug.Log(openFileName.fileTitle);
                _text1 = openFileName.file;
                Debug.Log(_text1);
            }
            }
            GUILayout.Label(_text1, EditorStyles.textField);
            if (GUILayout.Button("AB2", EditorStyles.miniButtonRight))   //  (GUI.Button(new Rect(10, 10, 100, 50), "Open")
            {
                openFileName.structSize = Marshal.SizeOf(openFileName);
                openFileName.filter = "AB文件(*.xlsx)\0*.xmb";
                openFileName.file = new string(new char[256]);
                openFileName.maxFile = openFileName.file.Length;
                openFileName.fileTitle = new string(new char[64]);
                openFileName.maxFileTitle = openFileName.fileTitle.Length;
                openFileName.initialDir = Application.streamingAssetsPath.Replace('/', '\\'); //默认路径
                openFileName.title = "AB资源2";
                openFileName.flags = 0x00080000 | 0x00001000 | 0x00000800 | 0x00000008;

                if (LocalDialog.GetSaveFileName(openFileName))
                {
                    Debug.Log(openFileName.file);
                    Debug.Log(openFileName.fileTitle);
                    _text2 = openFileName.file;
                    Debug.Log(_text2);
                }

            }
            GUILayout.Label(_text2, EditorStyles.textField);
            
            if (GUILayout.Button("MIXAssets", EditorStyles.miniButtonMid))
            {
                Debug.Log("不同资源相互混合—— TODO");
            }
            GUILayout.Space(25);
            GUILayout.Box("~~资源打包~~",EditorStyles.miniButtonMid);
            GUILayout.Box(Resources.Load<Texture>("15c6cbc"),EditorStyles.helpBox);
            GUILayout.Space(25);
            // for (var index = 0; index < _pairList.Count; index++)
            // {
            //     ListItem(_pairList[index], index);
            //     GUILayout.Space(5);
            // }
        }
        
        public class LocalDialog
        {
            //链接指定系统函数       打开文件对话框
            [DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)]
            private static extern bool GetOpenFileName([In, Out] OpenFileName ofn);
            public static bool GetOfn([In, Out] OpenFileName ofn)
            {
                return GetOpenFileName(ofn);
            }

            //链接指定系统函数        另存为对话框
            [DllImport("Comdlg32.dll", SetLastError = true, ThrowOnUnmappableChar = true, CharSet = CharSet.Auto)]
            public static extern bool GetSaveFileName([In, Out] OpenFileName ofn);
            public static bool GetSFN([In,Out] OpenFileName ofn)
            {
                return GetSaveFileName(ofn);
            }
        }
        

        private void SingleExport()
        {
            GUILayout.Label($"Single Export", EditorStyles.largeLabel);
            for (var index = 0; index < PairList.Count; index++)
            {
                ListItem(PairList[index], index);
                GUILayout.Space(5);
            }
        }

        private static void FullExport()
        {
            GUILayout.Label($"Full Export", EditorStyles.largeLabel);
            EditorGUILayout.BeginHorizontal();
            GUILayout.Space(20);
            if (GUILayout.Button("Windows", EditorStyles.miniButtonRight))
            {
                BundleMenu.ExportWindowsAssetBundle();
            }

            GUILayout.Space(5);
            if (GUILayout.Button("Android", EditorStyles.miniButtonRight))
            {
                BundleMenu.ExportAndroidAssetBundle();
            }

            GUILayout.Space(5);
            if (GUILayout.Button("iOS", EditorStyles.miniButtonRight))
            {
                BundleMenu.ExportIOSAssetBundle();
            }
            
            if (GUILayout.Button("Mix(Android&iOS)", EditorStyles.miniButtonRight))
            {
                // if (!Directory.Exists(BundleMenu.AndroidAndIOSMix))
                // {
                //     Directory.CreateDirectory(BundleMenu.AndroidAndIOSMix);
                // }
                // foreach (var pInfo in _pairList)
                // {
                //     foreach (var path in
                //              pInfo.Paths) // public static string AndroidAndIOSMix = Path.Combine(Bundles, "mix/full");
                //     {
                //         MixProcessor.ExportSingleMixAssetBundle(
                //             BundleMenu.AndroidAndIOSMix,
                //             pInfo.Name,
                //             path
                //         );
                //     }
                // }

                BundleMenu.ExportIOSAndAndroidSAssetBundle();
            }


 

            EditorGUILayout.EndHorizontal();
        }
        

        private static void ListItem(AssetBundleInfo info, int position)
        {
            GUILayout.Label($"{position + 1}. {info.Name}", EditorStyles.largeLabel);
            foreach (var path in info.Paths)
            {
                EditorGUILayout.BeginHorizontal();
                GUILayout.Space(20);
                GUILayout.Label("Path: ");
                GUILayout.Label($"{path}", EditorStyles.linkLabel);
                GUILayout.FlexibleSpace();
                if (GUILayout.Button("Locate"))
                {
                    EditorGUIUtility.PingObject(
                        Selection.activeObject = AssetDatabase.LoadMainAssetAtPath(path)
                    );
                }

                EditorGUILayout.EndHorizontal();

                EditorGUILayout.BeginHorizontal();
                GUILayout.Space(20);
                GUILayout.Label($"Export Asset Bundle: ");

                if (GUILayout.Button("Windows", EditorStyles.miniButtonRight))
                {
                    BundleMenu.ExportSingleAssetBundle(
                        BundleMenu.WindowsSingle,
                        info.Name,
                        path,
                        BuildTarget.StandaloneWindows64
                    );
                }

                if (GUILayout.Button("Mix(Android&iOS)", EditorStyles.miniButtonRight))
                    
                    
                    
                {
                    MixProcessor.ExportSingleMixAssetBundle(
                        BundleMenu.SingleMix,
                        info.Name,
                        path
                    );
                }

                GUILayout.Space(5);
                if (GUILayout.Button("Android", EditorStyles.miniButtonRight))
                {
                    BundleMenu.ExportSingleAssetBundle(
                        BundleMenu.AndroidSingle,
                        info.Name,
                        path,
                        BuildTarget.Android
                    );
                }

                GUILayout.Space(5);
                if (GUILayout.Button("iOS", EditorStyles.miniButtonRight))
                {
                    BundleMenu.ExportSingleAssetBundle(
                        BundleMenu.IOSSingle,
                        info.Name,
                        path,
                        BuildTarget.iOS
                    );
                }

                EditorGUILayout.EndHorizontal();
            }
        }

        public class AssetBundleInfo
        {
            public string Name { get; }

            public IEnumerable<string> Paths =>
                AssetDatabase.GetAssetPathsFromAssetBundle(Name);

            public AssetBundleInfo(string name)
            {
                Name = name;
            }
        }
    }
}

几种加密方式:

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;

namespace Utility
{

    public class FileUtility
    {
        const byte kMKey = 157;

        /// <summary>
        /// 加密/解密
        /// </summary>
        /// <param name="targetData">文件流</param>
        public static void Encypt(ref byte[] targetData)
        {
            //加密,与key异或,解密的时候同样如此
            int dataLength = targetData.Length;
            for (int i = 0; i < dataLength; ++i)
            {
                targetData[i] = (byte) (targetData[i] ^ kMKey);
            }
        }

        /// <summary>
        /// 加密/解密 offset 加密
        /// </summary>
        /// <param name="fileFile"></param>
        public static void ABOffsetEncryption(string fileFile)
        {
            Debug.Log("加密文件:" + fileFile);
            byte[] oldData = File.ReadAllBytes(fileFile);
            int newOldLen = 8 + oldData.Length; //这个空字节数可以自己指定,示例指定8个字节
            var newData = new byte[newOldLen];
            for (int tb = 0; tb < oldData.Length; tb++)
            {
                newData[8 + tb] = oldData[tb];
            }

            FileStream fs = File.OpenWrite(fileFile); //打开写入进去
            fs.Write(newData, 0, newOldLen);
            fs.Close();
        }
        //


        //md5
        public static string Md5Sum(string strToEncrypt)
        {
            byte[] bs = Encoding.UTF8.GetBytes(strToEncrypt);
            MD5 md5 = MD5CryptoServiceProvider.Create();
            byte[] hashBytes = md5.ComputeHash(bs);
            string hashString = "";
            for (int i = 0; i < hashBytes.Length; i++)
            {
                hashString += System.Convert.ToString(hashBytes[i], 16).PadLeft(2, '0');
            }

            return hashString.PadLeft(32, '0');
        }
        
        //16 md5    
        public static string GetMd5_16byte(string ConvertString)
        {
            string md5Pwd = string.Empty;

            //使用加密服务提供程序
            MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();

            //将指定的字节子数组的每个元素的数值转换为它的等效十六进制字符串表示形式。
            md5Pwd = BitConverter.ToString(md5.ComputeHash(UTF8Encoding.Default.GetBytes(ConvertString)), 4, 8);

            md5Pwd = md5Pwd.Replace("-", "");

            return md5Pwd;
        }

        //32 md5
        public static string GetMd5_32byte(string str)
        {
            string pwd = string.Empty;

            //实例化一个md5对像
            MD5 md5 = MD5.Create();

            // 加密后是一个字节类型的数组,这里要注意编码UTF8/Unicode等的选择 
            byte[] s = md5.ComputeHash(Encoding.UTF8.GetBytes(str));

            // 通过使用循环,将字节类型的数组转换为字符串,此字符串是常规字符格式化所得
            for (int i = 0; i < s.Length; i++)
            {
                // 将得到的字符串使用十六进制类型格式。格式后的字符是小写的字母,如果使用大写(X)则格式后的字符是大写字符 
                pwd = pwd + s[i].ToString("X");
            }

            return pwd;
        }
        /// <summary>
        /// 加密  返回加密后的结果
        /// </summary>
        /// <param name="toE">需要加密的数据内容</param>
        /// <returns></returns>
        public static string Encrypt(string toE)
        {
            byte[] keyArray = UTF8Encoding.UTF8.GetBytes("12348578902223367877723456789012");
            RijndaelManaged rDel = new RijndaelManaged();
            rDel.Key = keyArray;
            rDel.Mode = CipherMode.ECB; //ECB
            rDel.Padding = PaddingMode.ISO10126; //PKCS7
            ICryptoTransform cTransform = rDel.CreateEncryptor();

            byte[] toEncryptArray = UTF8Encoding.UTF8.GetBytes(toE);
            byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

            return Convert.ToBase64String(resultArray, 0, resultArray.Length);
        }

        /// <summary>
        /// 解密  返回解密后的结果
        /// </summary>
        /// <param name="toD">加密的数据内容</param>
        /// <returns></returns>
        public static string Decrypt(string toD)
        {
            byte[] keyArray = UTF8Encoding.UTF8.GetBytes("12348578902223367877723456789012");

            RijndaelManaged rDel = new RijndaelManaged();
            rDel.Key = keyArray;
            rDel.Mode = CipherMode.ECB;
            rDel.Padding = PaddingMode.ISO10126; //PKCS7
            ICryptoTransform cTransform = rDel.CreateDecryptor();

            byte[] toEncryptArray = Convert.FromBase64String(toD);
            byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);

            return UTF8Encoding.UTF8.GetString(resultArray);
        }
    }
}

文件head定义:

using System;

namespace BundleMix
{
    public class HeaderEntry
    {
        public string Tag { get; }
        public long Offset { get; }
        public long Length { get; }
        public byte[] MD5 { get; }

        public HeaderEntry(string tag, long offset, long length, byte[] md5)
        {
            Tag = tag;
            Offset = offset;
            Length = length;
            MD5 = md5;
        }

        public override string ToString() =>
            $"[tag: {Tag}] [offset: {Offset}] [lenght: {Length}] [md5: {BitConverter.ToString(MD5)}]";
    }
}

AB合并的变量:

namespace BundleMix
{
    public enum Target
    {
        NoTarget,
        BB10,
        StandaloneOSX,
        StandaloneOSXIntel,
        StandaloneWindows,
        iOS,
        PS3,
        XBOX360,
        Android,
        StandaloneLinux,
        StandaloneWindows64,
        WebGL,
        WSAPlayer,
        StandaloneLinux64,
        StandaloneLinuxUniversal,
        WP8Player,
        StandaloneOSXIntel64,
        BlackBerry,
        Tizen,
        PSP2,
        PS4,
        PSM,
        XboxOne,
        SamsungTV,
        N3DS,
        WiiU,
        tvOS,
        Switch,
        Lumin,
        Stadia,
        CloudRendering,
        GameCoreScarlett,
        GameCoreXboxSeries,
        GameCoreXboxOne,
        PS5,
        MixAndroid_IOS,
    }

    public static class MixConfig
    {
        public static readonly Target[] TargetArray =
        {
            Target.Android,
            Target.iOS
        };

        public const int HeaderTagSize = 8;
        public const int HeaderOffsetSize = 8;
        public const int HeaderLengthSize = 8;
        public const int HeaderMD5Size = 16;
        public const int HeaderEntrySize = HeaderTagSize + HeaderOffsetSize + HeaderLengthSize + HeaderMD5Size;
        public static readonly int HeaderTotalSize = TargetArray.Length * HeaderEntrySize;

        public const int HeaderTagStartIndex = 0;
        public const int HeaderOffsetStartIndex = HeaderTagSize;
        public const int HeaderLengthStartIndex = HeaderTagSize + HeaderOffsetSize;
        public const int HeaderMD5StartIndex = HeaderTagSize + HeaderOffsetSize + HeaderLengthSize;
    }
}

混合AB包文件的提取:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEngine;

namespace BundleMix
{
    public class MixExtractor
    {
              
        /// <summary>
        /// 提取混合文件中的所有平台的资源文件到指定目录.
        /// </summary>
        /// <param name="inputFile">混合文件路径</param>
        /// <param name="outputFolder">输出提取文件的目录</param>
        public static void ExtractAllXmbFile(string inputFile, string outputFolder)
        {
            if (!Directory.Exists(outputFolder))
            {
                Directory.CreateDirectory(outputFolder);
            }

            var entryList = ParseMixFileHeader(inputFile);

            Stream ifs = null;
            try
            {
                ifs = File.OpenRead(inputFile);
                foreach (var entry in entryList)
                {
                    var fileName = entry.Tag.Substring(0, 3);
                    var outputFile = Path.Combine(outputFolder, fileName);
                    ExtractXmbInternal(entry, ifs, outputFile);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            finally
            {
                MixUtils.CloseStream(ifs);
            }
        }

        /// <summary>
        /// 提取混合文件中指定平台的资源文件.
        /// </summary>
        /// <param name="target"></param>
        /// <param name="inputFile"></param>
        /// <param name="outputFile"></param>
        /// <returns></returns>
        public static bool ExtractXmbFile(Target target, string inputFile, string outputFile)
        {
            if (File.Exists(outputFile))
            {
                File.Delete(outputFile);
            }

            if (Directory.Exists(outputFile))
            {
                Directory.Delete(outputFile, true);
            }

            var tag = target.ToString().Substring(0, 3).ToUpper() + ":";
            var entryList = ParseMixFileHeader(inputFile);
            HeaderEntry targetEntry = null;
            foreach (var entry in entryList)
            {
                if (entry.Tag.Equals(tag))
                {
                    targetEntry = entry;
                }
            }

            if (targetEntry == null)
            {
                Debug.LogWarning("extract failure: no build target matched!");
                return false;
            }


            Stream ifs = null;
            try
            {
                ifs = File.OpenRead(inputFile);
                return ExtractXmbInternal(targetEntry, ifs, outputFile);
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            finally
            {
                MixUtils.CloseStream(ifs);
            }
        }

        private static bool ExtractXmbInternal(HeaderEntry entry, Stream ifs, string outputFile)
        {
            Stream ofs = null;
            try
            {
                ofs = new FileStream(
                    outputFile,
                    FileMode.OpenOrCreate,
                    FileAccess.Write,
                    FileShare.Read,
                    8 * 1024,
                    FileOptions.RandomAccess
                );
                ifs.Seek(entry.Offset, SeekOrigin.Begin);
                var finish = false;
                var remained = entry.Length;
                var buffer = new byte[8 * 1024];
                while (!finish)
                {
                    var readSize = buffer.Length;
                    if (remained < buffer.Length)
                    {
                        readSize = (int) remained;
                    }

                    var haveReadSize = ifs.Read(buffer, 0, readSize);
                    remained -= haveReadSize;
                    ofs.Write(buffer, 0, haveReadSize);
                    if (remained <= 0) finish = true;
                }

                ofs.Flush();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            finally
            {
                MixUtils.CloseStream(ofs);
            }

            var md5 = MixUtils.FileMd5(outputFile);
            if (entry.MD5.SequenceEqual(md5)) return true;
            
            File.Delete(outputFile);
            Debug.Log($"extract failure: md5 no matched!");
            return false;
        }

        private static IEnumerable<HeaderEntry> ParseMixFileHeader(string path)
        {
            var result = new List<HeaderEntry>();
            if (!File.Exists(path)) return result;
            var headerBytes = new byte[MixConfig.HeaderTotalSize];
            FileStream stream = null;
            try
            {
                stream = File.OpenRead(path);
                if (!stream.CanRead) return result;
                if (stream.Length < MixConfig.HeaderTotalSize) return result;
                stream.Read(headerBytes, 0, MixConfig.HeaderTotalSize);


                for (var index = 0; index < MixConfig.TargetArray.Length; index++)
                {
                    var entryOffset = MixConfig.HeaderEntrySize * index;

                    // 提取TAG
                    var tagBytes = new byte[MixConfig.HeaderTagSize];
                    Array.Copy(
                        headerBytes, entryOffset + MixConfig.HeaderTagStartIndex,
                        tagBytes, 0, MixConfig.HeaderTagSize
                    );
                    var tag = new string(new[]
                    {
                        BitConverter.ToChar(tagBytes, 0),
                        BitConverter.ToChar(tagBytes, 2),
                        BitConverter.ToChar(tagBytes, 4),
                        BitConverter.ToChar(tagBytes, 6)
                    });

                    // 提取内容起始偏移
                    var offsetBytes = new byte[MixConfig.HeaderOffsetSize];
                    Array.Copy(
                        headerBytes, entryOffset + MixConfig.HeaderOffsetStartIndex,
                        offsetBytes, 0, MixConfig.HeaderOffsetSize
                    );
                    if (BitConverter.IsLittleEndian) Array.Reverse(offsetBytes);
                    var offset = BitConverter.ToInt64(offsetBytes, 0);

                    // 提取内容长度
                    var lengthBytes = new byte[MixConfig.HeaderLengthSize];
                    Array.Copy(
                        headerBytes, entryOffset + MixConfig.HeaderLengthStartIndex,
                        lengthBytes, 0, MixConfig.HeaderLengthSize
                    );
                    if (BitConverter.IsLittleEndian) Array.Reverse(lengthBytes);
                    var length = BitConverter.ToInt64(lengthBytes, 0);

                    // 提取内容MD5
                    var md5Bytes = new byte[MixConfig.HeaderMD5Size];
                    Array.Copy(
                        headerBytes, entryOffset + MixConfig.HeaderMD5StartIndex,
                        md5Bytes, 0, MixConfig.HeaderMD5Size
                    );
                    var headerEntry = new HeaderEntry(tag, offset, length, md5Bytes);
                    Debug.Log($"HeaderEntry: {headerEntry}");
                    result.Add(headerEntry);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            finally
            {
                MixUtils.CloseStream(stream);
            }

            return result;
        }
    }
}

AB包文件流读取处理:

using System;
using System.IO;
using System.Security.Cryptography;
using UnityEngine;

namespace BundleMix
{
    public static class MixUtils
    {
        public static byte[] FileMd5(string filePath)
        {
            Stream stream = null;
            try
            {
                stream = File.OpenRead(filePath);
                return new MD5CryptoServiceProvider()
                    .ComputeHash(stream);
            }
            catch (Exception e)
            {
                Debug.LogException(e);
                return new byte[16];
            }
            finally
            {
                CloseStream(stream);
            }
        }

        public static void CloseStream(Stream stream)
        {
            try
            {
                stream?.Close();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
    }
}

对加密的AB进行解密加载:

   //加密方法一
            // byte[] stream = File.ReadAllBytes(path);
            // FileUtility.Encypt(ref stream);
            // var ab = AssetBundle.LoadFromMemory(stream);
            
            //加密方法二
            AssetBundle ab = AssetBundle.LoadFromFile(bunpath,0,8);
            if (ab == null)
            {
                UnityControllerImpl.Log_M("UnityTag: ani ab is null");
                return false;
            }

猜你喜欢

转载自blog.csdn.net/qq_35385242/article/details/126741013