public static IEnumerator BuildResAsync(IList <string> assets, IEditorWorkProgressShower winprog, IList <IResBuilderEx> runOnceExBuilder)
        {
            bool isDefaultBuild = assets == null;
            var  logger         = new EditorWorkProgressLogger()
            {
                Shower = winprog
            };
            bool shouldCreateBuildingParams = BuildingParams == null;

            BuildingParams = BuildingParams ?? ResBuilderParams.Create();
            var    timetoken = BuildingParams.timetoken;
            var    makezip   = BuildingParams.makezip;
            string outputDir = "Latest";

            if (!isDefaultBuild)
            {
                outputDir = timetoken + "/build";
            }
            outputDir = "EditorOutput/Build/" + outputDir;

            System.IO.StreamWriter swlog = null;
            try
            {
                System.IO.Directory.CreateDirectory(outputDir + "/log/");
                swlog = new System.IO.StreamWriter(outputDir + "/log/ResBuildLog.txt", false, System.Text.Encoding.UTF8);
            }
            catch (Exception e)
            {
                Debug.Log(e);
            }

            EditorApplication.LockReloadAssemblies();
            List <IResBuilderEx> allExBuilders = new List <IResBuilderEx>(ResBuilderEx);

            if (runOnceExBuilder != null)
            {
                allExBuilders.AddRange(runOnceExBuilder);
            }
            Application.LogCallback LogToFile = (message, stack, logtype) =>
            {
                swlog.WriteLine(message);
                swlog.Flush();
            };
            if (swlog != null)
            {
                Application.logMessageReceived += LogToFile;
            }
            for (int i = 0; i < allExBuilders.Count; ++i)
            {
                allExBuilders[i].Prepare(outputDir);
            }
            bool   cleanupDone    = false;
            Action BuilderCleanup = () =>
            {
                if (!cleanupDone)
                {
                    logger.Log("(Phase) Build Res Cleaup.");
                    cleanupDone = true;
                    for (int i = 0; i < allExBuilders.Count; ++i)
                    {
                        allExBuilders[i].Cleanup();
                    }
                    logger.Log("(Done) Build Res Cleaup.");
                    if (swlog != null)
                    {
                        Application.logMessageReceived -= LogToFile;
                        swlog.Flush();
                        swlog.Dispose();

                        if (isDefaultBuild)
                        {
                            var logdir = "EditorOutput/Build/" + timetoken + "/log/";
                            System.IO.Directory.CreateDirectory(logdir);
                            System.IO.File.Copy(outputDir + "/log/ResBuildLog.txt", logdir + "ResBuildLog.txt", true);
                        }
                    }
                    if (shouldCreateBuildingParams)
                    {
                        BuildingParams = null;
                    }
                    EditorApplication.UnlockReloadAssemblies();
                }
            };

            if (winprog != null)
            {
                winprog.OnQuit += BuilderCleanup;
            }

            try
            {
                logger.Log("(Start) Build Res.");
                if (winprog != null && AsyncWorkTimer.Check())
                {
                    yield return(null);
                }

                //logger.Log("(Phase) Ex Full Build System.");
                //for (int i = 0; i < allExBuilders.Count; ++i)
                //{
                //    IEnumerator exwork = allExBuilders[i].CustomBuild();
                //    if (exwork != null)
                //    {
                //        while (exwork.MoveNext())
                //        {
                //            if (winprog != null)
                //            {
                //                yield return exwork.Current;
                //            }
                //        }
                //    }
                //    if (winprog != null && AsyncWorkTimer.Check()) yield return null;
                //}

                // Generate Build Work
                Dictionary <string, CapsResBuildWork> works = new Dictionary <string, CapsResBuildWork>();
                var work = GenerateBuildWorkAsync(works, assets, winprog, runOnceExBuilder);
                while (work.MoveNext())
                {
                    if (winprog != null)
                    {
                        yield return(work.Current);
                    }
                }

                logger.Log("(Phase) Write Manifest.");
                var managermod = CapsEditorUtils.__MOD__;
                var manidir    = "Assets/Mods/" + managermod + "/Build/";
                System.IO.Directory.CreateDirectory(manidir);
                List <AssetBundleBuild> listManiBuilds = new List <AssetBundleBuild>();
                HashSet <string>        maniFileNames  = new HashSet <string>();
                foreach (var kvp in works)
                {
                    foreach (var mani in kvp.Value.Manifests)
                    {
                        var mod  = mani.MFlag;
                        var dist = mani.DFlag;
                        if (winprog != null && AsyncWorkTimer.Check())
                        {
                            yield return(null);
                        }
                        logger.Log("Mod " + mod + "; Dist " + dist);

                        var dmani    = CapsResManifest.Save(mani);
                        var filename = "m-" + mod + "-d-" + dist;
                        var manipath = manidir + filename + ".m.asset";
                        AssetDatabase.CreateAsset(dmani, manipath);

                        maniFileNames.Add(filename.ToLower());
                        listManiBuilds.Add(new AssetBundleBuild()
                        {
                            assetBundleName = filename + ".m.ab", assetNames = new[] { manipath }
                        });
                    }
                }

                logger.Log("(Phase) Build Manifest.");
                if (winprog != null && AsyncWorkTimer.Check())
                {
                    yield return(null);
                }
                var         buildopt   = BuildAssetBundleOptions.DeterministicAssetBundle | BuildAssetBundleOptions.ChunkBasedCompression;
                BuildTarget buildtar   = EditorUserBuildSettings.activeBuildTarget;
                var         outmanidir = outputDir + "/res/mani";
                System.IO.Directory.CreateDirectory(outmanidir);
                BuildPipeline.BuildAssetBundles(outmanidir, listManiBuilds.ToArray(), buildopt, buildtar);

                logger.Log("(Phase) Delete Unused Manifest.");
                if (winprog != null && AsyncWorkTimer.Check())
                {
                    yield return(null);
                }
                var manifiles = PlatDependant.GetAllFiles(outmanidir);
                for (int i = 0; i < manifiles.Length; ++i)
                {
                    var file = manifiles[i];
                    if (file.EndsWith(".m.ab"))
                    {
                        var filename = file.Substring(outmanidir.Length + 1, file.Length - outmanidir.Length - 1 - ".m.ab".Length);
                        if (!maniFileNames.Contains(filename))
                        {
                            PlatDependant.DeleteFile(file);
                            PlatDependant.DeleteFile(file + ".manifest");
                        }
                    }
                }

                logger.Log("(Phase) Real Build.");
                foreach (var kvp in works)
                {
                    var mod = kvp.Key;
                    var abs = kvp.Value.ABs;
                    logger.Log("Mod " + mod);
                    if (winprog != null && AsyncWorkTimer.Check())
                    {
                        yield return(null);
                    }

                    var dest = outputDir + "/res";
                    if (!string.IsNullOrEmpty(mod))
                    {
                        dest += "/mod/" + mod;
                    }

                    System.IO.Directory.CreateDirectory(dest);
                    // delete the unused ab
                    HashSet <string> buildFiles = new HashSet <string>();
                    for (int i = 0; i < abs.Length; ++i)
                    {
                        if (!kvp.Value.ForceRefreshABs.Contains(i))
                        {
                            if (string.IsNullOrEmpty(abs[i].assetBundleVariant))
                            {
                                buildFiles.Add(abs[i].assetBundleName.ToLower());
                            }
                            else
                            {
                                buildFiles.Add(abs[i].assetBundleName.ToLower() + "." + abs[i].assetBundleVariant.ToLower());
                            }
                        }
                    }
                    var files = System.IO.Directory.GetFiles(dest);
                    for (int i = 0; i < files.Length; ++i)
                    {
                        var file = files[i];
                        if (!file.EndsWith(".ab"))
                        {
                            var sub   = System.IO.Path.GetFileName(file);
                            var split = sub.LastIndexOf(".ab.");
                            if (split < 0)
                            {
                                continue;
                            }
                            var ext = sub.Substring(split + ".ab.".Length);
                            if (ext.Contains("."))
                            {
                                continue;
                            }
                            if (ext == "manifest")
                            {
                                continue;
                            }
                        }
                        {
                            var fileName = System.IO.Path.GetFileName(file);
                            if (!buildFiles.Contains(fileName))
                            {
                                PlatDependant.DeleteFile(file);
                                PlatDependant.DeleteFile(file + ".manifest");
                            }
                        }
                    }

                    BuildPipeline.BuildAssetBundles(dest, abs, buildopt, buildtar);
                }

                logger.Log("(Phase) Delete Mod Folder Not Built.");
                var outmoddir = outputDir + "/res/mod/";
                if (System.IO.Directory.Exists(outmoddir))
                {
                    var builtMods           = new HashSet <string>(works.Keys);
                    var allModFolders       = System.IO.Directory.GetDirectories(outmoddir);
                    int deletedModFolderCnt = 0;
                    for (int i = 0; i < allModFolders.Length; ++i)
                    {
                        if (winprog != null && AsyncWorkTimer.Check())
                        {
                            yield return(null);
                        }
                        var modfolder = allModFolders[i];
                        logger.Log(modfolder);
                        var mod = modfolder.Substring(outmoddir.Length);
                        if (!builtMods.Contains(mod))
                        {
                            System.IO.Directory.Delete(modfolder, true);
                            ++deletedModFolderCnt;
                        }
                    }
                    if (deletedModFolderCnt == allModFolders.Length)
                    {
                        System.IO.Directory.Delete(outmoddir, true);
                    }
                }

                if (isDefaultBuild)
                {
                    logger.Log("(Phase) Write Version.");
                    var outverdir = outputDir + "/res/version.txt";
                    int version   = GetResVersion();
                    System.IO.File.WriteAllText(outverdir, version.ToString());
                }

                logger.Log("(Phase) Copy.");
                var outresdir     = outputDir + "/res/";
                var allbuildfiles = PlatDependant.GetAllFiles(outresdir);
                if (System.IO.Directory.Exists("Assets/StreamingAssets/res/"))
                {
                    logger.Log("Delete old.");
                    var allexistfiles = PlatDependant.GetAllFiles("Assets/StreamingAssets/res/");
                    for (int i = 0; i < allexistfiles.Length; ++i)
                    {
                        if (winprog != null && AsyncWorkTimer.Check())
                        {
                            yield return(null);
                        }
                        PlatDependant.DeleteFile(allexistfiles[i]);
                    }
                }
                for (int i = 0; i < allbuildfiles.Length; ++i)
                {
                    if (winprog != null && AsyncWorkTimer.Check())
                    {
                        yield return(null);
                    }
                    var srcfile = allbuildfiles[i];
                    if (srcfile.EndsWith(".manifest"))
                    {
                        continue;
                    }
                    var part = srcfile.Substring(outresdir.Length);
                    if (part == "mani/mani")
                    {
                        continue;
                    }
                    logger.Log(part);
                    var destfile = "Assets/StreamingAssets/res/" + part;
                    PlatDependant.CreateFolder(System.IO.Path.GetDirectoryName(destfile));
                    System.IO.File.Copy(srcfile, destfile);
                }

                if (System.IO.Directory.Exists("Assets/StreamingAssets/res/mod/"))
                {
                    logger.Log("(Phase) Delete StreamingAssets Mod Folder Not Built.");
                    var builtMods           = new HashSet <string>(works.Keys);
                    var allModFolders       = System.IO.Directory.GetDirectories("Assets/StreamingAssets/res/mod/");
                    int deletedModFolderCnt = 0;
                    for (int i = 0; i < allModFolders.Length; ++i)
                    {
                        if (winprog != null && AsyncWorkTimer.Check())
                        {
                            yield return(null);
                        }
                        var modfolder = allModFolders[i];
                        logger.Log(modfolder);
                        var mod = modfolder.Substring("Assets/StreamingAssets/res/mod/".Length);
                        if (!builtMods.Contains(mod))
                        {
                            System.IO.Directory.Delete(modfolder, true);
                            ++deletedModFolderCnt;
                        }
                    }
                    if (deletedModFolderCnt == allModFolders.Length)
                    {
                        System.IO.Directory.Delete("Assets/StreamingAssets/res/mod/", true);
                    }
                }

                if (isDefaultBuild && makezip)
                {
                    logger.Log("(Phase) Zip.");
                    List <Pack <string, string, IList <string> > > zips = new List <Pack <string, string, IList <string> > >();
                    var outzipdir = "EditorOutput/Build/" + timetoken + "/whole/res/";
                    System.IO.Directory.CreateDirectory(outzipdir);
                    foreach (var kvp in works)
                    {
                        var mod   = kvp.Key;
                        var manis = kvp.Value.Manifests;
                        for (int i = 0; i < manis.Length; ++i)
                        {
                            var opmod = manis[i].MFlag;
                            var dist  = manis[i].DFlag;
                            if (winprog != null && AsyncWorkTimer.Check())
                            {
                                yield return(null);
                            }
                            logger.Log("Mod " + opmod + "; Dist " + dist);

                            List <string> entries = new List <string>();
                            // abs
                            var abdir = outputDir + "/res";
                            if (!string.IsNullOrEmpty(mod))
                            {
                                abdir += "/mod/" + mod;
                            }

                            if (System.IO.Directory.Exists(abdir))
                            {
                                try
                                {
                                    var files = System.IO.Directory.GetFiles(abdir);
                                    for (int j = 0; j < files.Length; ++j)
                                    {
                                        var file = files[j];
                                        if (!file.EndsWith(".ab"))
                                        {
                                            var sub   = System.IO.Path.GetFileName(file);
                                            var split = sub.LastIndexOf(".ab.");
                                            if (split < 0)
                                            {
                                                continue;
                                            }
                                            var ext = sub.Substring(split + ".ab.".Length);
                                            if (ext.Contains("."))
                                            {
                                                continue;
                                            }
                                            if (ext == "manifest")
                                            {
                                                continue;
                                            }
                                        }
                                        {
                                            var bundle = file.Substring(abdir.Length + 1);
                                            if (IsBundleInModAndDist(bundle, opmod, dist))
                                            {
                                                var entry = file.Substring(outputDir.Length + 1);
                                                entries.Add(entry);
                                                entries.Add(entry + ".manifest");
                                            }
                                        }
                                    }
                                }
                                catch (Exception e)
                                {
                                    logger.Log("(Error)(Not Critical)");
                                    logger.Log(e.ToString());
                                }
                            }
                            if (entries.Count > 0)
                            {
                                // unity build mani
                                var umani = abdir + "/" + (string.IsNullOrEmpty(mod) ? "res" : mod);
                                umani = umani.Substring(outputDir.Length + 1);
                                entries.Add(umani);
                                entries.Add(umani + ".manifest");
                                // mani
                                var mani = "m-" + opmod.ToLower() + "-d-" + dist.ToLower() + ".m.ab";
                                mani = "res/mani/" + mani;
                                entries.Add(mani);
                                entries.Add(mani + ".manifest");
                                entries.Add("res/mani/mani");
                                entries.Add("res/mani/mani.manifest");
                                // version
                                entries.Add("res/version.txt");

                                var zipfile = outzipdir + "m-" + opmod + "-d-" + dist + ".zip";
                                zips.Add(new Pack <string, string, IList <string> >(zipfile, outputDir, entries));
                                //var workz = MakeZipAsync(zipfile, outputDir, entries, winprog);
                                //while (workz.MoveNext())
                                //{
                                //    if (winprog != null)
                                //    {
                                //        yield return workz.Current;
                                //    }
                                //}
                            }
                        }
                    }
                    if (zips.Count > 0)
                    {
                        var workz = CapsResBuilder.MakeZipsBackground(zips, winprog);
                        while (workz.MoveNext())
                        {
                            if (winprog != null)
                            {
                                yield return(workz.Current);
                            }
                        }
                    }
                }

                for (int i = 0; i < allExBuilders.Count; ++i)
                {
                    allExBuilders[i].OnSuccess();
                }
            }
            finally
            {
                BuilderCleanup();
                logger.Log("(Done) Build Res.");
            }
        }