Files
AIC-Project/Client/Assets/Editor/Packager.cs
T
2026-07-10 10:24:29 +08:00

1922 lines
72 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEditor;
using UnityEngine;
using System;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Diagnostics;
//using XGame;
using UnityEditor.Build.Player;
using HybridCLR.Editor;
using Debug = UnityEngine.Debug;
#if !UNITY_WEBGL
using HybridCLR;
using UnityEditor.Build.Content;
using UnityEditor.Build.Reporting;
#if UNITY_EDITOR
using HybridCLR.Editor.Commands;
#endif
#endif
public class Packager {
//#if UNITY_STANDALONE_WIN
static public BuildTarget buildTaget = BuildTarget.StandaloneWindows;
//其他平台。。。。。。
//#endif
public static string OutputName = "LocalTest";
public static string platform = string.Empty;
static Dictionary<string, string> m_mapResNameDic = new Dictionary<string, string>();
static List<AssetBundleBuild> maps = new List<AssetBundleBuild>();
static List<string> paths = new List<string>();
static List<string> files = new List<string>();
static long UpdateDT = 0;
static string sPlatform = "pc";
static Dictionary<string, string> m_dicFilelist = new Dictionary<string, string>();
static string m_strDllFileMD5;
static string m_strResFileMD5;
public static string ProjectDir => Directory.GetParent(Application.dataPath).ToString();
public static string HybridCLRDataDir { get; } = $"{ProjectDir}/HybridCLRData";
public static string LocalIl2CppDir => $"{HybridCLRDataDir}/LocalIl2CppData/il2cpp";
public static string AssembliesPostIl2CppStripDir => $"{HybridCLRDataDir}/AssembliesPostIl2CppStrip";
public static string GetAssembliesPostIl2CppStripDir(BuildTarget target)
{
return $"{AssembliesPostIl2CppStripDir}/{target}";
}
//需要导出的aot文件,运行的时候会全部更新
public static List<string> AOTMetaAssemblyNames { get; } = new List<string>()
{
"mscorlib.dll",
"System.dll",
"System.Core.dll", // 如果使用了Linq,需要这个
};
static Dictionary<string , int> DicAssetsMap = new Dictionary<string, int>();
public static string[] HotfixDir = { "res/scene", "res/character","res/effect", "res/ui", "arts"};//shader单独打
public static string GetUpdatePath()
{
#if UNITY_WEBGL
return $"{XGame.AppConst.WebGL}/StreamingAssets/";
#else
return $"{XGame.AppConst.UpdatePath}{sPlatform}\\";//$"d:/HTTP/update/{sPlatform}/";//
#endif
}
public static string DataPath
{
get
{
return Application.dataPath + "/StreamAssets/";
}
}
public static string md5(string source)
{
MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
byte[] data = System.Text.Encoding.UTF8.GetBytes(source);
byte[] md5Data = md5.ComputeHash(data, 0, data.Length);
md5.Clear();
string destString = "";
for (int i = 0; i < md5Data.Length; i++)
{
destString += System.Convert.ToString(md5Data[i], 16).PadLeft(2, '0');
}
destString = destString.PadLeft(32, '0');
return destString;
}
public static string md5file(string file)
{
long size = 0;
return md5file(file, out size);
}
public static string md5file(string file, out long size)
{
try
{
FileStream fs = new FileStream(file, FileMode.Open);
System.Security.Cryptography.MD5 md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
byte[] retVal = md5.ComputeHash(fs);
size = fs.Length;
fs.Close();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < retVal.Length; i++)
{
sb.Append(retVal[i].ToString("x2"));
}
return sb.ToString();
}
catch (Exception ex)
{
throw new Exception("md5file() fail, error:" + ex.Message);
}
}
public static void CopyLuaBytesFiles(string sourceDir, string destDir, bool appendext = true, string searchPattern = "*.lua", SearchOption option = SearchOption.AllDirectories)
{
if (!Directory.Exists(sourceDir))
{
return;
}
string[] files = Directory.GetFiles(sourceDir, searchPattern, option);
int len = sourceDir.Length;
if (sourceDir[len - 1] == '/' || sourceDir[len - 1] == '\\')
{
--len;
}
for (int i = 0; i < files.Length; i++)
{
string str = files[i].Remove(0, len);
string dest = destDir + "/" + str;
if (appendext) dest += ".bytes";
string dir = Path.GetDirectoryName(dest);
Directory.CreateDirectory(dir);
File.Copy(files[i], dest, true);
}
}
///-----------------------------------------------------------
static string[] exts = { ".txt", ".xml", ".lua", ".assetbundle", ".json" };
static bool CanCopy(string ext) { //能不能复制
foreach (string e in exts) {
if (ext.Equals(e)) return true;
}
return false;
}
/// <summary>
/// 载入素材
/// </summary>
static UnityEngine.Object LoadAsset(string file) {
if (file.EndsWith(".lua")) file += ".txt";
return AssetDatabase.LoadMainAssetAtPath("Assets/" + file);
}
public static void BuildDLL()
{
UnityEngine.Debug.LogFormat("编译DLL");
}
public static void BuildBasePack(string OutputPath, BuildTargetGroup targetGroup, BuildTarget target)
{
//生成最终底包
string[] SCENES = FindEnabledEditorScenes();
if (EditorUserBuildSettings.activeBuildTarget != target)
EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroup, target);
CSObjectWrapEditor.Generator.ClearAll();
CSObjectWrapEditor.Generator.GenAll();
HybridCLR.Editor.Commands.LinkGeneratorCommand.GenerateLinkXml();
PlayerSettings.Android.keystoreName = "Android/android.keystore";
PlayerSettings.Android.keyaliasName = "xw";
PlayerSettings.keystorePass = "abcd12";
PlayerSettings.keyaliasPass = "abcd12";
PlayerSettings.Android.androidIsGame = true;
PlayerSettings.SetIncrementalIl2CppBuild(BuildTargetGroup.Android, true);
EditorUserBuildSettings.buildScriptsOnly = false;
EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
//Release
EditorUserBuildSettings.development = false;
EditorUserBuildSettings.allowDebugging = false;
EditorUserBuildSettings.androidBuildType = AndroidBuildType.Release;
AssetDatabase.Refresh();
//保证这个打出来的使用设置好的路径
//PlayerSettings.SetScriptingDefineSymbolsForGroup(targetGroup, "LOCAL");
BuildPipeline.BuildPlayer(SCENES, OutputPath, target, BuildOptions.None);
//CSObjectWrapEditor.Generator.ClearAll();
UnityEngine.Debug.Log("Build OK!");
}
public static bool BuildAPK(string apkBuildName = "", bool bHotfix = true, bool bRelease = true)
{
//HybridCLR.BuildConfig.Setup(); //设置il2cpp的环境变量
string[] args = Environment.GetCommandLineArgs();
Debug.LogFormat("CommandLineArgs:{0}", string.Join(",", args));
if (bHotfix)
{
HybridCLR.Editor.SettingsUtil.Enable = true;
}
else
{
HybridCLR.Editor.SettingsUtil.Enable = false;
}
//切换到目标平台
if (EditorUserBuildSettings.activeBuildTarget != BuildTarget.Android)
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
CSObjectWrapEditor.Generator.ClearAll();
CSObjectWrapEditor.Generator.GenAll();
PlayerSettings.allowUnsafeCode = true;
PlayerSettings.SetManagedStrippingLevel(BuildTargetGroup.Android, ManagedStrippingLevel.Low);
PlayerSettings.SplashScreen.show = false;
PlayerSettings.SplashScreen.showUnityLogo = false;
PlayerSettings.Android.keystoreName = "Assets/60s.keystore";
PlayerSettings.Android.keyaliasName = "60s";
PlayerSettings.keystorePass = "abcd12";
PlayerSettings.keyaliasPass = "abcd12";
PlayerSettings.Android.preferredInstallLocation = AndroidPreferredInstallLocation.Auto;
// s = GetCommandLineValue(args, "-svn_version");
// PlayerSettings.Android.bundleVersionCode = string.IsNullOrEmpty(s) ? 1 : int.Parse(s); // 上传符号表文件时需要该值在一次打包中不改变
//PlayerSettings.Android.bundleVersionCode = int.Parse(BuildSettings.VersionCode.Replace(".", ""));
PlayerSettings.Android.startInFullscreen = true;
PlayerSettings.Android.androidIsGame = true;
PlayerSettings.SetIncrementalIl2CppBuild(BuildTargetGroup.Android, true);
EditorUserBuildSettings.buildScriptsOnly = false;
EditorUserBuildSettings.androidBuildSystem = AndroidBuildSystem.Gradle;
BuildOptions buildOptions = BuildOptions.None;
if (bRelease)
{
EditorUserBuildSettings.development = false;
EditorUserBuildSettings.allowDebugging = false;
EditorUserBuildSettings.androidBuildType = AndroidBuildType.Release;
//IL2CPP C++ 编译配置默认是 Debug(带 -g 调试符号),对 HybridCLR 生成的超大 lump 做 codegen 时
//易触发 NDK clang 段错误(clang frontend command failed due to signal)。发布包统一用 Release 去掉 -g。
PlayerSettings.SetIl2CppCompilerConfiguration(BuildTargetGroup.Android, Il2CppCompilerConfiguration.Release);
Debug.Log("开始打包安卓发布版本");
}
else
{
EditorUserBuildSettings.development = true;
EditorUserBuildSettings.allowDebugging = true;
EditorUserBuildSettings.androidBuildType = AndroidBuildType.Debug;
PlayerSettings.SetIl2CppCompilerConfiguration(BuildTargetGroup.Android, Il2CppCompilerConfiguration.Debug);
buildOptions = BuildOptions.Development;
Debug.Log("开始打包安卓测试版本");
}
// 获取当前的宏定义
string currentDefines = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android);
{
PlayerSettings.stripEngineCode = true;
}
AssetDatabase.Refresh();
//打包前自检:底包里随包发布的加密 AB 必须是"已加密"状态(运行时 LoadDll 会无条件 AES 解密)。
//若 a.unity3d 等是原始未加密 AB(头部为 UnityFS / 长度非16倍数),真机会在 StartGame 解密时
//抛 "Offset and length were out of bounds" 并 "Decrypt Failed"。这里提前拦截,避免出坏包。
if (!VerifyEncryptedBaseBundles())
{
Debug.LogError("APK 打包中断:底包加密 AB 自检未通过(见上条日志)。请用『打包/生成底包/Android最小包(或完整包)』重新生成(内部会加密 a.unity3d),且不要在其后再点『更新dll(Android)』(那会删掉 a.unity3d)。");
return false;
}
try
{
string[] LauncherSceneNames;
//build一下link.xml
HybridCLR.Editor.Commands.LinkGeneratorCommand.GenerateLinkXml();
LauncherSceneNames = FindEnabledEditorScenes();
BuildReport report =
BuildPipeline.BuildPlayer(LauncherSceneNames, apkBuildName, BuildTarget.Android, buildOptions);
//CSObjectWrapEditor.Generator.ClearAll();//如果不清 可能源码参数修改导致导致编译错误 打完包默认清理一下
if (report.summary.result == BuildResult.Succeeded)
{
Debug.Log("安卓打包成功!");
return true;
}
else
{
Debug.LogFormat("安卓打包失败,totalErrors:{0}", report.summary.totalErrors);
return false;
}
}
catch (Exception e)
{
Debug.Log(e.ToString());
EditorApplication.Exit(1);
}
return false;
}
//校验随底包发布的加密 AB 是否真的处于"已加密"状态。
//判定:已加密数据不应以 AssetBundle 魔数(UnityFS/Unity)开头,且 AES-CBC 产物长度必为16倍数。
//a.unity3d 为必需(缺失或未加密都拦截)h.unity3d / bs.unity3d 仅在存在时校验。
static bool VerifyEncryptedBaseBundles()
{
bool ok = true;
// name -> 是否必需存在
var targets = new Dictionary<string, bool>
{
{ "a.unity3d", true },
{ "h.unity3d", false },
{ "bs.unity3d", false },
};
foreach (var kv in targets)
{
string file = $"{AppDataPath}/StreamingAssets/{kv.Key}";
if (!File.Exists(file))
{
if (kv.Value)
{
Debug.LogError($"加密 AB 缺失:{file} 不存在。a.unity3d(AOT元数据包)须先经『生成底包/Android…』(PackAotFile)产出并加密。");
ok = false;
}
continue;
}
byte[] head = new byte[5];
int n;
long size;
using (var fs = File.OpenRead(file))
{
size = fs.Length;
n = fs.Read(head, 0, head.Length);
}
bool isRawAB = n >= 5 && head[0] == (byte)'U' && head[1] == (byte)'n'
&& head[2] == (byte)'i' && head[3] == (byte)'t' && head[4] == (byte)'y';
bool blockAligned = (size % 16) == 0;
if (isRawAB || !blockAligned)
{
Debug.LogError($"加密 AB 自检失败:{kv.Key} 看起来未加密(头部RawAB={isRawAB}, size={size}, size%16={size % 16})。" +
"运行时会无条件 AES 解密它 → 真机必崩。请走会加密的底包菜单重新生成。");
ok = false;
}
}
return ok;
}
//[MenuItem("打包/增量编译LocalTest底包(包含C#,Config,Script,UI", false, 101)]
public static void BuildWindowsRuntime() {
//生成最终pc底包
string[] SCENES = FindEnabledEditorScenes();
//保证这个打出来的使用设置好的路径
//PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "LOCAL");
BuildPipeline.BuildPlayer(SCENES, "../" + OutputName + "/Client.exe", BuildTarget.StandaloneWindows64, BuildOptions.None);
//增量更新
string UpdateFile = AppDataPath + "/../FileTime.txt";
//获得上回时间
FileInfo fi = new FileInfo(UpdateFile);
if (fi.Exists)
{
UpdateDT = fi.LastWriteTime.ToFileTime();
}
else
{
File.Create(UpdateFile).Dispose();
UpdateDT = 0;
}
//脚本和配置
//源文件编译
string srcPath, outPath;
//Script
srcPath = AppDataPath + "/../../Scripts/";
outPath = AppDataPath + "/../../Scripts_c/";
//不转luac
CopyPath(srcPath, outPath);
//CompileScript(srcPath, outPath);
//Config
srcPath = AppDataPath + "/../../Config/Config/";
outPath = AppDataPath + "/../../Scripts_c/Base/Config/";
//配置不转luac
CopyPath(srcPath, outPath);
//CompileScript(srcPath, outPath);
UpdateDT = DateTime.Now.ToFileTime();
using (StreamWriter sw = new StreamWriter(UpdateFile))
{
sw.Write(UpdateDT);
}
CopyScript_c();//复制到目标端
//UI 资源
//BuildUI(BuildTarget.StandaloneWindows64);
UnityEngine.Debug.Log("Local Version OK!");
}
#if !UNITY_WEBGL
[MenuItem("打包/生成底包/Win64最小包", false, 101)]
public static void BuildWindowsBase()
{
string outPath = $"{AppDataPath}/StreamingAssets/";
ClearDir(outPath);
//生成最终pc底包
string[] SCENES = FindEnabledEditorScenes();
//保证这个打出来的使用设置好的路径
//PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "LOCAL");
//PrebuildCommand.GenerateAll();
//AssetDatabase.Refresh();
BuildReport buildReport = BuildPipeline.BuildPlayer(SCENES, "../" + OutputName + "/Client.exe", BuildTarget.StandaloneWindows64, BuildOptions.CompressWithLz4);
if (buildReport == null || buildReport.summary.result != BuildResult.Succeeded)
{
UnityEngine.Debug.LogError("Build Base Failed!");
}
else
{
UnityEngine.Debug.Log("Build Base OK!");
}
AssetDatabase.Refresh();
//BuildUpdate(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64);
//AssetDatabase.Refresh();
}
[MenuItem("打包/生成底包/Win64完整包", false, 101)]
public static void BuildWindowsFull()
{
string outPath = $"{AppDataPath}/StreamingAssets/";
ClearDir(outPath);
BuildUpdate(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64);
//生成最终pc底包
string[] SCENES = FindEnabledEditorScenes();
//保证这个打出来的使用设置好的路径
//PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "LOCAL");
PrebuildCommand.GenerateAll();
BuildPipeline.BuildPlayer(SCENES, "../" + OutputName + "/Client.exe", BuildTarget.StandaloneWindows64, BuildOptions.CompressWithLz4);
UnityEngine.Debug.Log("Build Full OK!");//*/
}
[MenuItem("打包/生成底包/Android最小包", false, 102)]
public static void BuildAndroidBase()
{
//ring outPath = $"{AppDataPath}/StreamingAssets/";
//earDir(outPath);
if (true == HybridCLRBuild.PackAotFile(BuildTargetGroup.Android, BuildTarget.Android, "a.unity3d", $"{Application.dataPath.ToLower()}/StreamingAssets/xaot/"))
{
BuildAPK("Client.apk", true, true);
}
//BuildBasePack("Client.apk", BuildTargetGroup.Android, BuildTarget.Android);
//HybridCLRBuild.BuildDllUpdateAndroid();
//BuildUpdate(BuildTargetGroup.Android, BuildTarget.Android);
//earDir(outPath);
}
[MenuItem("打包/生成底包/Android完整包", false, 102)]
public static void BuildAndroidFull()
{
string outPath = $"{AppDataPath}/StreamingAssets/";
HybridCLRBuild.ClearDir(outPath);
//BuildUpdate(BuildTargetGroup.Android, BuildTarget.Android);
//HybridCLRBuild.BuildDllUpdateAndroid();
HybridCLRBuild.BuildHotFix(BuildTargetGroup.Android, BuildTarget.Android, "client", false);
if (true == HybridCLRBuild.PackAotFile(BuildTargetGroup.Android, BuildTarget.Android, "a.unity3d", $"{Application.dataPath.ToLower()}/StreamingAssets/xaot/"))
{
BuildAPK("Client.apk", true, true);
}
}
[MenuItem("打包/生成基础更新Win64", false, 201)]
public static void BuildUpdateWin64()
{
//BuildUpdate(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64);
HybridCLRBuild.BuildHotFix(BuildTargetGroup.Standalone, BuildTarget.StandaloneWindows64);
}
[MenuItem("打包/生成基础更新Android", false, 202)]
public static void BuildUpdateAndroid()
{
//BuildUpdate(BuildTargetGroup.Android, BuildTarget.Android);
HybridCLRBuild.BuildDllUpdateAndroid();
}
[MenuItem("打包/生成游戏更新", false, 1030)]
public static void BuildCurGameUpdate()
{
//加载../../XDevNode/xid.txt
string xid = null;
if(File.Exists("../XDevNode/xid.txt"))
{
xid = File.ReadAllText("../XDevNode/xid.txt");
}
if(string.IsNullOrEmpty(xid))
{
Debug.LogWarning("./../XDevNode/xid.txt 不存在。自动生成");
ulong id = Generate64BitId();
byte[] bytes = BitConverter.GetBytes(id);
// 如果是大端系统需要反转数组(如网络传输场景)
if (BitConverter.IsLittleEndian == false)
{
Array.Reverse(bytes);
}
// 转换为Base64字符串
xid = Convert.ToBase64String(bytes);
File.WriteAllText("../XDevNode/xid.txt", xid);
}
if(xid.Length > 24)
{
xid = xid.Substring(0, 24);
}
//以xid为目录打包资源
string outPath = $"../XDevNode/{xid}/";
//根据平台打更新
ClearDir(outPath);
BuildGameUpdate(BuildTargetGroup.Android, BuildTarget.Android, xid);
}
public static ulong Generate64BitId()
{
RandomNumberGenerator rng = RandomNumberGenerator.Create();
DateTime Epoch1601 = new DateTime(1601, 1, 1, 0, 0, 0, DateTimeKind.Utc);
Span<byte> randomBytes = stackalloc byte[8];
rng.GetBytes(randomBytes);
var timestamp = (ulong)((DateTime.UtcNow - Epoch1601).Ticks / 10);
return ((timestamp >> 22) & 0x3FFFFFFFFFF) << 22
| (BitConverter.ToUInt64(randomBytes) & 0x3FFFFF);
}
public static void BuildGameUpdate(BuildTargetGroup targetGroup, BuildTarget target, string xid)
{
if (EditorUserBuildSettings.activeBuildTarget != target)
{
EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroup, target);
}
if (target == BuildTarget.Android)
{
platform = "android";
}
else if (target == BuildTarget.iOS)
{
platform = "ios";
}
else if (target == BuildTarget.WebGL)
{
platform = "webgl";
//bLuaPackage = true;
}
else
{
platform = "pc";
}
string outPath = $"../XDevNode/{xid}/{platform}/";
ClearDir(outPath);
if (!Directory.Exists(outPath))
Directory.CreateDirectory(outPath);
AssetDatabase.Refresh();
maps.Clear();
//lua 打包
BuildLuaAB("s.unity3d");
//渲染shader ab
//shader也跟下面的资源一起打吧
//PackAllPathFile("game_shader.unity3d", $"Assets/Game/Shader", "Shadervariants");
AddBuildMap("game_shader.unity3d", "*.*", "Assets/Game/Shader");
//资源 ab
string[] HotfixRes = { "Game/Art" };//热更资源目录, 按目录打资源
string namePre = $"Game_Art";
foreach (string respath in HotfixRes)
{
string tempPath = $"{ AppDataPath}/{respath}/";
string buildPath = $"Assets/{respath}/";//路径必须以Assets开始
string[] dirs = Directory.GetDirectories(tempPath, "*", SearchOption.AllDirectories);
for (int i = 0; i < dirs.Length; i++)
{
string path = dirs[i];
if (path.Contains(".svn"))
continue;
string pathname = dirs[i].Replace(tempPath, string.Empty);
string name = pathname.Replace('\\', '_').Replace('/', '_');//Lua ab文件使用.替换目录分隔符
name = name.ToLower() + ".unity3d";
AddBuildMap($"{namePre}_{name}", "*.*", buildPath + pathname);
//AssetDatabase.Refresh();
}
AddBuildMap($"{namePre}_{respath}.unity3d", "*.*", $"Assets/{respath}/");
//AssetDatabase.Refresh();
}
//打ab包
BuildPipeline.BuildAssetBundles(outPath, maps.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression/*| BuildAssetBundleOptions.DisableWriteTypeTree*/, target);
AssetDatabase.Refresh();
//改名资源依赖文件,加个后缀
if (File.Exists($"{outPath}{platform}"))
{
File.Move($"{outPath}{platform}", $"{outPath}abdepend.unity3d");
}
//打索引文件和版本文件
m_dicFilelist.Clear();
BuildFileIndex($"{outPath}");
string resmd5 = md5file($"{outPath}files.txt");
m_strResFileMD5 = GetFixedMd5(resmd5);
File.WriteAllText($"{outPath}/updatever.txt", $"{m_strResFileMD5}");
//加密
string[] EncryptFile = { "abdepend.unity3d", "s.unity3d" };
foreach (string f in EncryptFile)
{
string file = $"{outPath}{f}";
if (File.Exists(file))
{
byte[] srcdata = File.ReadAllBytes(file);
byte[] encryptdata = AESManager.Encrypt(srcdata);
byte[] retdata = AESManager.Decrypt(encryptdata);
File.WriteAllBytes(file, encryptdata);
}
}
//根据md5列表修改ab包名称
foreach (var file in m_dicFilelist)
{
string destFile = GetFileMd5Name($"{outPath}{file.Key}", GetFixedMd5(file.Value));
string srcFile = $"{outPath}{file.Key}";
if (!File.Exists(srcFile))
continue;
if (!File.Exists(destFile))
File.Move(srcFile, destFile);
}
//文件列表添加MD5
string newFilePath = GetFileMd5Name($"{outPath}files.txt", GetFixedMd5(m_strResFileMD5));
if (File.Exists(newFilePath))
File.Delete(newFilePath);
File.Move($"{outPath}files.txt", newFilePath);
}
#else
//[MenuItem("打包/生成WebGL端", false, 301)]
public static void BuildWebGL()
{
string outPath = $"{AppDataPath}/StreamingAssets/";
ClearDir(outPath);
BuildBasePack("Client", BuildTargetGroup.WebGL, BuildTarget.WebGL);
//BuildUpdate(BuildTargetGroup.WebGL, BuildTarget.WebGL);
//放到AppConst.WebGL
CopyPath($"{System.Environment.CurrentDirectory}/Client", XGame.AppConst.WebGL);
}
//[MenuItem("打包/生成WebGL更新(旧)", false, 302)]
public static void BuildUpdateWebGL()
{
BuildUpdate(BuildTargetGroup.WebGL, BuildTarget.WebGL);
}
#endif
public static void BuildUpdate(BuildTargetGroup targetGroup, BuildTarget target)
{
m_mapResNameDic.Clear();
if (EditorUserBuildSettings.activeBuildTarget != target)
{
EditorUserBuildSettings.SwitchActiveBuildTarget(targetGroup, target);
}
if (target == BuildTarget.Android)
{
sPlatform = "android";
}
else if (target == BuildTarget.iOS)
{
sPlatform = "ios";
}
else if (target == BuildTarget.WebGL)
{
sPlatform = "webgl";
}
else
{
sPlatform = "pc";
}
maps.Clear();
string outPath = $"{AppDataPath}/StreamingAssets/";
//if (targetGroup != BuildTargetGroup.Standalone)
//{
// outPath = $"{AppDataPath}/StreamingAssets/{sPlatform}/";
//}
ClearDir(outPath);
if (!Directory.Exists(outPath))
Directory.CreateDirectory(outPath);
#if !UNITY_WEBGL
//生成一次临时包,以便正确生成dll
//string[] SCENES = FindEnabledEditorScenes();
//保证这个打出来的使用设置好的路径
//PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "LOCAL");
//BuildPipeline.BuildPlayer(SCENES, "Client", target, BuildOptions.None);
//打入更新的资源,dll
//dll update
//BuildAssetsCommand.BuildAndCopyABAOTHotUpdateDlls();
#endif
//runtime 编译
ScriptCompilationSettings scriptCompilationSettings = default;
scriptCompilationSettings.group = targetGroup;
scriptCompilationSettings.target = target;
PlayerSettings.stripEngineCode = true;
PlayerSettings.allowUnsafeCode = true;
string BasePath = System.Environment.CurrentDirectory;
PlayerSettings.SetScriptingBackend(targetGroup, ScriptingImplementation.IL2CPP);
ScriptCompilationResult scriptCompilationResult = PlayerBuildInterface.CompilePlayerScripts(scriptCompilationSettings, $"{BasePath}/client/");
//HybridCLR dll update
BuildAssetsCommand.BuildAndCopyABAOTHotUpdateDlls();
//CopyScriptsFile($"{AppDataPath}/lua/", $"{AppDataPath}/luabytes/", "bytes");
BuildLuaAB("bs.unity3d");
//目录列表
string[] res = HotfixDir;//{ "res", "ui", "luabytes"};//必须是Assets目录下的目录
//看是否三种资源要分开特殊处理打包
//美术资源基于目录和预设相关, shader完全打包,ui需要打图集,lua脚本完全打包
//加密dll并把dll打入unity ab 包
PackDllFile("h.unity3d", $"{AppDataPath}/StreamingAssets/xhotfix/");
PackDllFile("a.unity3d", $"{AppDataPath}/StreamingAssets/xaot/");
//打包基础代码入口资源,这个为挂载GameLuncher脚本的预设
//PackDirFile($"{AppDataPath}/res/base", "res.unity3d");
AddBuildMap($"res/res.unity3d", "*.*", $"Assets/res/base/");
//打包shader到单一文件,方便开始一次加载
if (target == BuildTarget.WebGL)
PackAllPathFile("res/res_shader.unity3d", $"Assets/res/shader", "Shadervariants");
else
PackAllPathFile("res/res_shader.unity3d", $"Assets/res/shader", "Shadervariants_webgl");
foreach (string respath in res)
{
string tempPath = $"{ AppDataPath}/{respath}/";
string buildPath = $"Assets/{respath}/";//路径必须以Assets开始
string[] dirs = Directory.GetDirectories(tempPath, "*", SearchOption.AllDirectories);
for (int i = 0; i < dirs.Length; i++)
{
string path = dirs[i];
string pathname = dirs[i].Replace(tempPath, string.Empty);
string name = pathname.Replace('\\', '_').Replace('/', '_');//Lua ab文件使用.替换目录分隔符
name = name.ToLower() + ".unity3d";
AddBuildMap($"{respath}/{name}", "*.*", buildPath + pathname);
}
AddBuildMap($"{respath}/{respath}.unity3d", "*.*", $"Assets/{respath}/");
AssetDatabase.Refresh();
}
//统一打ab包
BuildPipeline.BuildAssetBundles(outPath, maps.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression, target);
AssetDatabase.Refresh();
//复制res.unity3d基础游戏开始模块到xhotfix目录,此目录为开始强制更新dll即有
string resfile = $"{outPath}xhotfix/res.unity3d";
if (File.Exists(resfile))
File.Delete(resfile);
string dir = $"{outPath}xhotfix";
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
Directory.CreateDirectory($"{outPath}xaot");
}
File.Copy($"{outPath}res/res.unity3d", resfile);
string platform = "";
//if (targetGroup != BuildTargetGroup.Standalone)
// platform = sPlatform;
//生成更新列表
m_dicFilelist.Clear();
m_strDllFileMD5 = "";
m_strResFileMD5 = "";
BuildFileIndex(platform);//StreamingAssets 里的除了\\xaot\\ 和 \\xhotfix (dll目录)外所有文件
//添加dll更新列表
BuildDllsFileIndex(platform);
SaveUpdateVer(platform);
//复制到目标地址
//先清理之前的
if (Directory.Exists(GetUpdatePath()))
{
ClearDir($"{GetUpdatePath()}xaot");
ClearDir($"{GetUpdatePath()}xhotfix");
ClearDir($"{GetUpdatePath()}arts");//原来的资源目录
ClearDir($"{GetUpdatePath()}res");
string[] files = Directory.GetFiles(GetUpdatePath(), "*.*");
for (int i = 0; i < files.Length; i++)
{
File.Delete(files[i]);
}
}
//加密
string[] EncryptFile = {"a.unity3d", "h.unity3d", "bs.unity3d" };
foreach (string f in EncryptFile)
{
byte[] srcdata = File.ReadAllBytes($"{AppDataPath}/StreamingAssets/{f}");
byte[] encryptdata = AESManager.Encrypt(srcdata);
byte[] retdata = AESManager.Decrypt(encryptdata);
File.WriteAllBytes($"{AppDataPath}/StreamingAssets/{f}", encryptdata);
}
CopyPath(outPath, GetUpdatePath());
//CopyPath($"{outPath}xaot", $"{GetUpdatePath()}xaot");
//CopyPath($"{outPath}xhotfix", $"{GetUpdatePath()}xhotfix");
//File.Copy($"{outPath}dllfiles.txt", $"{GetUpdatePath()}dllfiles.txt", true);
//File.Copy($"{outPath}updatever.txt", $"{GetUpdatePath()}updatever.txt", true);
if (target == BuildTarget.WebGL)
{//给文件加上md5后缀,以便方便webgl小程序使用UnityWebRequest自动缓存
foreach (var file in m_dicFilelist)
{
File.Move($"{GetUpdatePath()}{file.Key}", $"{GetUpdatePath()}{GetFileMd5Name(file.Key,file.Value)}");//{file.Key}_{file.Value}
}
File.Move($"{GetUpdatePath()}dllfiles.txt", $"{GetUpdatePath()}{GetFileMd5Name("dllfiles.txt", m_strDllFileMD5)}");
File.Move($"{GetUpdatePath()}files.txt", $"{GetUpdatePath()}{GetFileMd5Name("files.txt", m_strResFileMD5)}");
//File.Move($"{GetUpdatePath()}updatever.txt", $"{GetUpdatePath()}update.ver");
}
//删除临时luabytes文件
//ClearDir($"{AppDataPath}/luabytes/");
}
static string GetFileMd5Name(string filename, string md5)
{
int pos = filename.LastIndexOf('.');
if (pos != -1)
{
return $"{filename.Substring(0, pos)}_{md5}{filename.Substring(pos)}";
}
else
return $"{filename}_{md5}";
}
static void BuildLuaAB(string bundleName)
{
string luapath = $"{Application.dataPath}/l/";
ClearDir(luapath);
CopyScriptsFile($"{AppDataPath}/../../lua/", luapath, "bytes");
var files = Directory.GetFiles($"Assets/l", "*.bytes", SearchOption.AllDirectories);
for (int i = 0; i < files.Length; i++)
{
files[i] = files[i].Replace('\\', '/');
}
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = bundleName;
build.assetNames = files;
maps.Add(build);
}
public static void ClearDir(string path)
{
if (!Directory.Exists(path))
return;
if (Directory.Exists(path))
{
Directory.Delete(path, true);
if(path.EndsWith("/"))
path = path.Substring(0, path.Length - 1);
path = path + ".meta";
if (Directory.Exists(path))
File.Delete(path);
}
}
public static void CompileConfig()
{
string srcPath, outPath;
//Config
UpdateDT = 0;
srcPath = AppDataPath + "/../../Config/Config/";
outPath = AppDataPath + "/../../Scripts_c/Base/Config/";
CompileScript(srcPath, outPath);
}
//[MenuItem("打包/完整编译Script", false, 104)]
public static void CompileScript()
{
string srcPath, outPath;
//Script
UpdateDT = 0;
srcPath = AppDataPath + "/../../Scripts/";
outPath = AppDataPath + "/../../Scripts_c/";
CompileScript(srcPath, outPath);
}
//[MenuItem("打包/增量编译ScriptConfig", false, 105)]
public static void CompileConfigScript()
{
//增量更新
string UpdateFile = AppDataPath + "/../FileTime.txt";
//获得上回时间
FileInfo fi = new FileInfo(UpdateFile);
if (fi.Exists)
{
UpdateDT = fi.LastWriteTime.ToFileTime();
}
else
{
File.Create(UpdateFile).Dispose();
UpdateDT = 0;
}
string srcPath, outPath;
//Config
srcPath = AppDataPath + "/../../Config/Config/";
outPath = AppDataPath + "/../../Scripts_c/Base/Config/";
CompileScript(srcPath, outPath);
//Script
srcPath = AppDataPath + "/../../Scripts/";
outPath = AppDataPath + "/../../Scripts_c/";
CompileScript(srcPath, outPath);
UpdateDT = DateTime.Now.ToFileTime();
using (StreamWriter sw = new StreamWriter(UpdateFile))
{
sw.Write(UpdateDT);
}
}
//[MenuItem("打包/编译c#底包", false, 104)]
public static void CompileCSharp()
{
//生成最终pc底包
string[] SCENES = FindEnabledEditorScenes();
//保证这个打出来的使用设置好的路径
//PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Standalone, "LOCAL");
BuildPipeline.BuildPlayer(SCENES, "../" + OutputName + "/Client.exe", BuildTarget.StandaloneWindows64, BuildOptions.None);
}
//[MenuItem("打包/生成Windows UI 资源ab", false, 102)]
public static void CreateWindowsUI()
{
string OutputPath = AppDataPath + "/../pc/base/ui";
BuildUI(BuildTarget.StandaloneWindows64, OutputPath);
}
//[MenuItem("打包/生成Android UI 资源ab", false, 103)]
public static void CreateAndroidUI()
{
string OutputPath = AppDataPath + "/../android/base/ui";
BuildUI(BuildTarget.Android, OutputPath);
}
//public static void BuildUI(UnityEditor.BuildTarget target)
//[MenuItem("打包/生成测试UI 资源ab", false, 102)]
public static void BuildUI(UnityEditor.BuildTarget target, string OutputPath = "")
{
maps.Clear();
string resPath = AppDataPath + "/UI/";
string outPath;
if (OutputPath == null || OutputPath == "" )
{
outPath = AppDataPath + "/../../" + OutputName + "/Client_Data/UI/";
}
else
outPath = OutputPath;
if (!Directory.Exists(outPath))
{
Directory.CreateDirectory(outPath);
}
string[] dirs = Directory.GetDirectories(resPath, "*", SearchOption.AllDirectories);
for (int i = 0; i < dirs.Length; i++)
{
string path = dirs[i];
string pathname = dirs[i].Replace(resPath, string.Empty);
string name = pathname.Replace('\\', '_').Replace('/', '_');
name = name.ToLower() + ".unity3d";
AddBuildMap(name, "*.*", "Assets/UI/" + pathname);//路径必须以Assets开始
}
AssetDatabase.Refresh();
BuildPipeline.BuildAssetBundles(outPath, maps.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression, target);
AssetDatabase.Refresh();
}
static void AddBuildMap(string bundleName, string pattern, string path, bool bDirPack = true)
{
var files = Array.FindAll(Directory.GetFiles(path, pattern), x => !x.EndsWith(".meta"));
if (files.Length == 0) return;
for (int i = 0; i < files.Length; i++)
{
files[i] = files[i].Replace('\\', '/');
}
if (bDirPack)
{
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = bundleName;
build.assetNames = files;
maps.Add(build);
}
else
{
string[] singlefile;
string abname = bundleName.Substring(0, bundleName.IndexOf(".unity3d"));
abname += "_";
for (int i = 0; i < files.Length; i++)
{
AssetBundleBuild build = new AssetBundleBuild();
singlefile = new string[] { files[i] };
build.assetBundleName = abname + Path.GetFileNameWithoutExtension(files[i]).ToLower() + ".unity3d";// bundleName;
build.assetNames = singlefile;
maps.Add(build);
}
}
}
//[MenuItem("打包/生成Android Scripts资源ab", false, 106)]
public static void CreateAndroidScriptsAB()
{
string OutputPath = AppDataPath + "/../android/base/scripts";
BuildScriptsAB(BuildTarget.Android, OutputPath);
}
public static void BuildScriptsAB(UnityEditor.BuildTarget target, string OutputPath = "")
{
string fixname = "bytes";//"txt"; //"bytes";//
//Main.lua Agent.lua test.lua + Base目录下文件独一份 其他打包 Base目录下其他文件
maps.Clear();
string resPath = AppDataPath + "/../../Scripts_c/Base/";//Base/Config/
string tempPath = AppDataPath + "/lua/";//
string outPath;
if (OutputPath == null || OutputPath == "")
{
outPath = AppDataPath + "/../../" + OutputName + "/Client_Data/lua/";
}
else
outPath = OutputPath;
if (Directory.Exists(outPath))
{
DirectoryInfo dir = new DirectoryInfo(outPath);
dir.Delete(true);
}
if (!Directory.Exists(outPath))
{
Directory.CreateDirectory(outPath);
}
//复制大部分lua 文件到 tempPath
CopyScriptsFile(resPath, tempPath, fixname);
//复制最后三个文件 Main.lua Agent.lua test.lua
File.Copy(AppDataPath + "/../../Scripts_c/Main.lua", tempPath + "Main." + fixname, true);
File.Copy(AppDataPath + "/../../Scripts_c/Agent.lua", tempPath + "Agent." + fixname, true);
File.Copy(AppDataPath + "/../../Scripts_c/Test.lua", tempPath + "Test." + fixname, true);
string[] dirs = Directory.GetDirectories(tempPath, "*", SearchOption.AllDirectories);
for (int i = 0; i < dirs.Length; i++)
{
string path = dirs[i];
string pathname = dirs[i].Replace(tempPath, string.Empty);
string name = pathname.Replace('\\', '_').Replace('/', '_');//Lua ab文件使用.替换目录分隔符
name = name.ToLower() + ".unity3d";
AddBuildMap(name, "*.*", "Assets/lua/" + pathname);//路径必须以Assets开始
}
AddBuildMap("bs.unity3d", "*.*", "Assets/lua/");
AssetDatabase.Refresh();
BuildPipeline.BuildAssetBundles(outPath, maps.ToArray(), BuildAssetBundleOptions.ChunkBasedCompression, target);
AssetDatabase.Refresh();
//删除临时lua目录,此处不应删除,以免unity重新生成每个文件的一些索引,导致每次复制进来相同的文件也会产生打包变化
//DirectoryInfo di = new DirectoryInfo(tempPath);
//di.Delete(true);
}
static void CopyScriptsFile(string srcPath, string outPath, string fixname)
{
if (!Directory.Exists(outPath))
{
Directory.CreateDirectory(outPath);
}
string[] luaPaths = { srcPath };//AppDataPath + "/../../Scripts_c/" };
for (int i = 0; i < luaPaths.Length; i++)
{
paths.Clear(); files.Clear();
string luaDataPath = luaPaths[i].ToLower();
Recursive(luaDataPath);
int n = 0;
foreach (string f in files)
{
if (f.EndsWith(".meta")) continue;
string newfile = f.Replace(luaDataPath, "");
string newpath;
if (newfile.IndexOf(".lua") < 0)//转2进制文件
{
newpath = outPath + newfile + ".bytes";
}
else
{
newpath = outPath + newfile.Replace(".lua", "." + fixname);
}
string path = Path.GetDirectoryName(newpath);
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
if (File.Exists(newpath))
{
File.Delete(newpath);
}
File.Copy(f, newpath, true);
UpdateProgress(n++, files.Count, newpath);
}
}
EditorUtility.ClearProgressBar();
}
static public void CopyPath(string srcPath, string outPath)
{
srcPath = srcPath.Replace("\\", "/"); ;
UnityEngine.Debug.Log($"{srcPath} => {outPath}");
if (!Directory.Exists(outPath))
{
Directory.CreateDirectory(outPath);
}
string[] luaPaths = { srcPath };//AppDataPath + "/../../Scripts_c/" };
for (int i = 0; i < luaPaths.Length; i++)
{
paths.Clear(); files.Clear();
string luaDataPath = luaPaths[i];//.ToLower();
Recursive(luaDataPath);
int n = 0;
foreach (string f in files)
{
if (f.EndsWith(".meta") || f.EndsWith(".manifest")) continue;
string newfile = f.Replace(luaDataPath, "");
string newpath = outPath + newfile;
string path = Path.GetDirectoryName(newpath);
if (!Directory.Exists(path))
{
UnityEngine.Debug.Log($"{path}");
Directory.CreateDirectory(path);
}
if (File.Exists(newpath))
{
File.Delete(newpath);
}
File.Copy(f, newpath, true);
UpdateProgress(n++, files.Count, newpath);
}
}
EditorUtility.ClearProgressBar();
}
static void CopyScript_c()
{
string resPath = AppDataPath + "/../../" + OutputName + "/Client_Data";
string luaPath = resPath + "/Scripts_c/";
//----------复制Lua文件----------------
if (!Directory.Exists(luaPath))
{
Directory.CreateDirectory(luaPath);
}
string[] luaPaths = { AppDataPath + "/../../Scripts_c/" };
for (int i = 0; i < luaPaths.Length; i++)
{
paths.Clear(); files.Clear();
string luaDataPath = luaPaths[i].ToLower();
Recursive(luaDataPath);
int n = 0;
foreach (string f in files)
{
if (f.EndsWith(".meta")) continue;
string newfile = f.Replace(luaDataPath, "");
string newpath = luaPath + newfile;
string path = Path.GetDirectoryName(newpath);
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
if (File.Exists(newpath))
{
File.Delete(newpath);
}
//if (AppConst.LuaByteMode)
//{
// EncodeLuaFile(f, newpath);//luajit
//}
//else
//{
File.Copy(f, newpath, true);
//}
UpdateProgress(n++, files.Count, newpath);
}
}
EditorUtility.ClearProgressBar();
}
//把原始script脚本和config 转为统一的luac版
static void CompileScript(string srcPath, string outPath)
{// AppDataPath appname_Data/ Client_Data/
//string resPath = AppDataPath + "/../../" + OutputName + "/Client_Data";
//string luaPath = resPath + "/Scripts_c/";
//----------复制Lua文件----------------
if (!Directory.Exists(outPath))
{
Directory.CreateDirectory(outPath);
}
string[] luaPaths = { srcPath };//AppDataPath + "/../../Scripts_c/" };
for (int i = 0; i < luaPaths.Length; i++)
{
paths.Clear(); files.Clear();
string luaDataPath = luaPaths[i].ToLower();
Recursive(luaDataPath);
int n = 0;
foreach (string f in files)
{
if (f.EndsWith(".meta")) continue;
//时间判定
FileInfo fi = new FileInfo(f);
if (fi.Exists && fi.LastWriteTime.ToFileTime() < UpdateDT && fi.CreationTime.ToFileTime() < UpdateDT)
{
continue;
}
string newfile = f.Replace(luaDataPath, "");
string newpath = outPath + newfile;
string path = Path.GetDirectoryName(newpath);
if (!Directory.Exists(path)) Directory.CreateDirectory(path);
if (File.Exists(newpath))
{
File.Delete(newpath);
}
EncodeLuac(f, newpath);
UpdateProgress(n++, files.Count, newpath);
}
}
EditorUtility.ClearProgressBar();
}
private static string[] FindEnabledEditorScenes()
{
List<string> EditorScenes = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
if (!scene.enabled) continue;
EditorScenes.Add(scene.path);
}
return EditorScenes.ToArray();
}
static void PackDirFile(string path, string bundleName)
{
var filelist = Directory.GetFiles(path);
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = bundleName;
build.assetNames = filelist;
maps.Add(build);
}
static private List<string> GetAllDependencies(string mainAssetPath)
{
List<string> result = new List<string>();
string[] depends = AssetDatabase.GetDependencies(mainAssetPath, true);
foreach (string assetPath in depends)
{
// 注意:排除主资源对象
if (assetPath != mainAssetPath)
result.Add(assetPath);
}
return result;
}
static void PackDllFile(string bundleName, string dllpath)
{
//string resPath = $"{AppDataPath}/StreamingAssets/xhotfix/";
List<string> files = new List<string>();
files.AddRange(Directory.GetFiles(dllpath));
//files.AddRange(Directory.GetFiles($"{AppDataPath}/StreamingAssets/xaot/"));
//int nPos = resPath.IndexOf("assets/StreamingAssets/", StringComparison.OrdinalIgnoreCase);
int nPos = bundleName.IndexOf('.');
//加密
List<string> DllFiles = new List<string>();
//byte[] buffer, aesdata;
string dirname = bundleName.Substring(0, nPos);
string temppath = $"{AppDataPath}/{dirname}";
if (!Directory.Exists(temppath))
{
Directory.CreateDirectory(temppath);
}
else
{
ClearDir(temppath);
}
foreach (var file in files)
{
if (file.EndsWith(".dll"))
{
//buffer = File.ReadAllBytes(file);
//aesdata = buffer;// AESManager.Encrypt(buffer);
string name = Path.GetFileNameWithoutExtension(file);//file.Replace(".dll", ".bytes");
string filepath = $"{temppath}/{name}.bytes";
//File.WriteAllBytes(filepath, aesdata);
File.Move(file, filepath);
name = $"assets/{dirname}/{name}.bytes";
DllFiles.Add(name);
}
}
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = bundleName;
build.assetNames = DllFiles.ToArray();// filelist;
maps.Add(build);
}
static void PackAllPathFile(string bundleName, string path, string nopath = null)
{
paths.Clear(); files.Clear();
Recursive(path);
string[] filelist = files.ToArray();
List<string> total = new List<string>(filelist);
foreach (var file in filelist)
{
if (!m_mapResNameDic.ContainsKey(file))
{
m_mapResNameDic[file] = "1";
}
}
//依赖文件
foreach (var file in filelist)
{
List<string> depends = GetAllDependencies(file);
foreach (var fd in depends)
{
if (fd.EndsWith(".hlsl"))
continue;
if (!m_mapResNameDic.ContainsKey(fd))
{
m_mapResNameDic[fd] = "1";
total.Add(fd);
}
}
}
if (nopath != null)
{
for (int i = total.Count - 1; i >= 0; i--)
{
var file = total[i];
if (file.Contains(nopath))
{
string[] pathParts = file.Replace('\\','/').Split('/');
foreach (var p in pathParts)
{
if(string.Equals(p, nopath, StringComparison.OrdinalIgnoreCase))//if (p == nopath)
{
//删除不合理的
total.RemoveAt(i);
break;
}
}
}
}
}
AssetBundleBuild build = new AssetBundleBuild();
build.assetBundleName = bundleName;
build.assetNames = total.ToArray();// filelist;
maps.Add(build);
UnityEngine.Debug.Log($"ab:{bundleName} has {filelist.Length} file");
}
//生成更新文件
static void BuildDllsFileIndex(string dir = "")
{
List<string> files = new List<string>();
string resPath = $"{AppDataPath}/StreamingAssets/{dir}/";
///----------------------创建文件列表-----------------------
string newFilePath = resPath + "dllfiles.txt";
if (File.Exists(newFilePath)) File.Delete(newFilePath);
files.AddRange(Directory.GetFiles($"{resPath}xaot/"));
files.AddRange(Directory.GetFiles($"{resPath}xhotfix/"));
FileStream fs = new FileStream(newFilePath, FileMode.CreateNew);
StreamWriter sw = new StreamWriter(fs);
foreach (var dllfile in files)
{
if (!dllfile.EndsWith(".dll") && !dllfile.EndsWith(".unity3d"))
continue;
long size = 0;
string md5 = GetFixedMd5(md5file(dllfile, out size));
string value = dllfile.Replace(resPath, string.Empty);
sw.WriteLine($"{{{value},{md5},{size}}}");
m_dicFilelist[value] = md5;
}
sw.Close(); fs.Close();
}
static string GetFixedMd5(string md5)
{
return md5.Substring(0, 8);//只取8位
}
static void BuildFileIndex(string dir = "") {
string resPath;
if (dir != "")
{
resPath = dir;
}
else
{
resPath = $"{AppDataPath}/StreamingAssets/";
}
///----------------------创建文件列表-----------------------
string newFilePath = resPath + "files.txt";
if (File.Exists(newFilePath)) File.Delete(newFilePath);
paths.Clear(); files.Clear();
Recursive(resPath);//此处去掉了dll热更相关的目录
FileStream fs = new FileStream(newFilePath, FileMode.CreateNew);
StreamWriter sw = new StreamWriter(fs);
for (int i = 0; i < files.Count; i++) {
string file = files[i];
string ext = Path.GetExtension(file);
if (file.EndsWith(".meta") || file.Contains(".DS_Store") || file.EndsWith(".manifest")) continue;
long size = 0;
string md5 = GetFixedMd5(md5file(file, out size));
string value = file.Replace(resPath, string.Empty);
sw.WriteLine($"{{{value},{md5},{size}}}");
m_dicFilelist[value] = md5;
}
sw.Close(); fs.Close();
//版本文件ver.txt
//string vermd5 = md5file(newFilePath);
//File.WriteAllText($"{resPath}/updatever.txt", vermd5);
}
static void SaveUpdateVer(string dir = "")
{
string resPath;
if (dir != "")
{
resPath = dir;
}
else
{
resPath = $"{AppDataPath}/StreamingAssets/";
}
string resfilelist = $"{resPath}files.txt";
string dllfilelist = $"{resPath}dllfiles.txt";
if (!File.Exists(dllfilelist) || !File.Exists(resfilelist))
{
UnityEngine.Debug.LogError("需要先生成更新列表文件");
return;//需要先
}
string resmd5 = md5file(resfilelist);
string dllmd5 = md5file(dllfilelist);
m_strResFileMD5 = GetFixedMd5(resmd5);
m_strDllFileMD5 = GetFixedMd5(dllmd5);
File.WriteAllText($"{resPath}/updatever.txt", $"{m_strDllFileMD5},{m_strResFileMD5}");
}
/// <summary>
/// 数据目录
/// </summary>
static string AppDataPath {
get { return Application.dataPath.ToLower(); }//Assets目录
}
/// <summary>
/// 遍历目录及其子目录
/// </summary>
static void Recursive(string path) {
if (path.Contains("\\xaot\\")|| path.Contains("\\xhotfix\\"))
{//忽略
return;
}
string[] names = Directory.GetFiles(path);
string[] dirs = Directory.GetDirectories(path);
foreach (string filename in names) {
string ext = Path.GetExtension(filename);
if (ext.Equals(".meta")) continue;
if (ext.Equals(".hlsl")) continue;
files.Add(filename.Replace('\\', '/'));
}
foreach (string dir in dirs) {
if (dir.IndexOf(".svn") >= 0)
{
continue;
}
paths.Add(dir.Replace('\\', '/'));
Recursive(dir);
}
}
static void UpdateProgress(int progress, int progressMax, string desc) {
string title = "Processing...[" + progress + " - " + progressMax + "]";
float value = (float)progress / (float)progressMax;
EditorUtility.DisplayProgressBar(title, desc, value);
}
public static void EncodeLuajit(string srcFile, string outFile) {
if (!srcFile.ToLower().EndsWith(".lua")) {
File.Copy(srcFile, outFile, true);
return;
}
bool isWin = true;
string luaexe = string.Empty;
string args = string.Empty;
string exedir = string.Empty;
string currDir = Directory.GetCurrentDirectory();
if (Application.platform == RuntimePlatform.WindowsEditor) {
isWin = true;
luaexe = "luajit.exe";
args = "-b -g " + srcFile + " " + outFile;
exedir = AppDataPath.Replace("assets", "") + "LuaEncoder/luajit/";
} else if (Application.platform == RuntimePlatform.OSXEditor) {
isWin = false;
luaexe = "./luajit";
args = "-b -g " + srcFile + " " + outFile;
exedir = AppDataPath.Replace("assets", "") + "LuaEncoder/luajit_mac/";
}
Directory.SetCurrentDirectory(exedir);
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = luaexe;
info.Arguments = args;
info.WindowStyle = ProcessWindowStyle.Hidden;
info.UseShellExecute = isWin;
info.ErrorDialog = true;
UnityEngine.Debug.Log(info.FileName + " " + info.Arguments);
Process pro = Process.Start(info);
pro.WaitForExit();
Directory.SetCurrentDirectory(currDir);
}
public static void EncodeLuac(string srcFile, string outFile)
{
if (!srcFile.ToLower().EndsWith(".lua"))
{
File.Copy(srcFile, outFile, true);
return;
}
bool isWin = true;
string luaexe = string.Empty;
string args = string.Empty;
string exedir = string.Empty;
string currDir = Directory.GetCurrentDirectory();
if (Application.platform == RuntimePlatform.WindowsEditor)
{
isWin = true;
luaexe = "luac53_64_compatible.exe";//for /r %%v in (*.lua) do luac -o %%v %%v
args = " -o " + outFile + " -s " + srcFile;
exedir = AppDataPath.Replace("assets", "") + "LuaEncoder/luac/";
}
Directory.SetCurrentDirectory(exedir);
ProcessStartInfo info = new ProcessStartInfo();
info.FileName = luaexe;
info.Arguments = args;
info.WindowStyle = ProcessWindowStyle.Hidden;
info.UseShellExecute = isWin;
info.ErrorDialog = true;
//UnityEngine.Debug.Log(info.FileName + " " + info.Arguments);
Process pro = Process.Start(info);
pro.WaitForExit();
Directory.SetCurrentDirectory(currDir);
}
/// <summary>
/// 打安卓包
/// </summary>
public static void BuildPlayer()
{
List<string> levels = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
if (!scene.enabled) continue;
levels.Add(scene.path);
}
string[] args = Environment.GetCommandLineArgs();
//File.WriteAllLines("log0.txt", args);
List<string> currentArgs = new List<string>();
bool flag = false;
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "-executeMethod")
{
flag = true;
continue;
}
else
{
if (args[i].StartsWith("-"))
{
flag = false;
}
}
if (flag)
{
currentArgs.Add(args[i]);
}
}
UnityEditor.PlayerSettings.productName = "G2Online";
UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2Online";
string timeFlag = currentArgs.Count < 2 ? DateTime.Now.ToString("YYYYMMddHHmmss") : currentArgs[1];
bool isDebugAPK = currentArgs.Count > 2 ? (int.Parse(currentArgs[2]) > 0) : false;//开发者调试模式包
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
UnityEditor.PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP);
UnityEditor.PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64;
BuildOptions options = BuildOptions.CompressWithLz4;
if (isDebugAPK)
{
options = BuildOptions.CompressWithLz4 | BuildOptions.AllowDebugging | BuildOptions.WaitForPlayerConnection | BuildOptions.Development | BuildOptions.ConnectWithProfiler;
}
UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(levels.ToArray(), string.Format("Client_{0}.apk", timeFlag), BuildTarget.Android, options);
}
public static void BuildAndroidLocal()
{
List<string> levels = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
if (!scene.enabled) continue;
levels.Add(scene.path);
}
string[] args = Environment.GetCommandLineArgs();
//File.WriteAllLines("log0.txt", args);
List<string> currentArgs = new List<string>();
bool flag = false;
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "-executeMethod")
{
flag = true;
continue;
}
else
{
if (args[i].StartsWith("-"))
{
flag = false;
}
}
if (flag)
{
currentArgs.Add(args[i]);
}
}
UnityEditor.PlayerSettings.productName = "G2Local";
UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2local";
string timeFlag = currentArgs.Count < 2 ? DateTime.Now.ToString("YYYYMMddHHmmss") : currentArgs[1];
bool isDebugAPK = currentArgs.Count > 2 ? (int.Parse(currentArgs[2]) > 0) : false;//开发者调试模式包
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
UnityEditor.PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP);
UnityEditor.PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64;
BuildOptions options = BuildOptions.CompressWithLz4;
if (isDebugAPK)
{
options = BuildOptions.CompressWithLz4 | BuildOptions.AllowDebugging | BuildOptions.WaitForPlayerConnection | BuildOptions.Development | BuildOptions.ConnectWithProfiler;
}
UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(levels.ToArray(), string.Format("Client_{0}.apk", timeFlag), BuildTarget.Android, options);
}
public static void BuildAndroidAPK()
{
List<string> levels = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
if (!scene.enabled) continue;
levels.Add(scene.path);
}
string[] args = Environment.GetCommandLineArgs();
//File.WriteAllLines("log0.txt", args);
List<string> currentArgs = new List<string>();
bool flag = false;
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "-executeMethod")
{
flag = true;
continue;
}
else
{
if (args[i].StartsWith("-"))
{
flag = false;
}
}
if (flag)
{
currentArgs.Add(args[i]);
}
}
string packFileName = currentArgs.Count < 2 ? UnityEditor.PlayerSettings.productName : currentArgs[1];
if (!string.IsNullOrEmpty(packFileName) && packFileName.ToLower().IndexOf("online")>-1)
{
UnityEditor.PlayerSettings.productName = "G2Online";
UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2Online";
}
else
{
UnityEditor.PlayerSettings.productName = "G2Local";
UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2local";
}
bool isDebugAPK = currentArgs.Count > 2 ? (int.Parse(currentArgs[2]) > 0) : false;//开发者调试模式包
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
UnityEditor.PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.IL2CPP);
UnityEditor.PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64;
BuildOptions options = BuildOptions.CompressWithLz4;
if (isDebugAPK)
{
options = BuildOptions.CompressWithLz4 | BuildOptions.AllowDebugging | BuildOptions.WaitForPlayerConnection | BuildOptions.Development | BuildOptions.ConnectWithProfiler;
}
UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(levels.ToArray(), string.Format("{0}.apk", packFileName), BuildTarget.Android, options);
}
/// <summary>
/// 打包UWA包
/// </summary>
public static void BuildAndroidUWA()
{
List<string> levels = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
if (!scene.enabled) continue;
levels.Add(scene.path);
}
string[] args = Environment.GetCommandLineArgs();
List<string> currentArgs = new List<string>();
bool flag = false;
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "-executeMethod")
{
flag = true;
continue;
}
else
{
if (args[i].StartsWith("-"))
{
flag = false;
}
}
if (flag)
{
currentArgs.Add(args[i]);
}
}
string timeFlag = currentArgs.Count < 2 ? DateTime.Now.ToString("YYYYMMddHHmmss") : currentArgs[1];
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
BuildOptions options = BuildOptions.CompressWithLz4 | BuildOptions.AllowDebugging | BuildOptions.Development;
UnityEditor.PlayerSettings.productName = "G2";
UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2";
string defs = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android);
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, "UWA;INSTANCE_OC");
UnityEditor.PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.Mono2x);
UnityEditor.PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7;
UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(levels.ToArray(), string.Format("Client_{0}.apk", timeFlag), BuildTarget.Android, options);
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defs);
}
/// <summary>
/// 打包UWA包
/// </summary>
public static void BuildAndroidUWAAUTO()
{
List<string> levels = new List<string>();
foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
{
if (!scene.enabled) continue;
levels.Add(scene.path);
}
string[] args = Environment.GetCommandLineArgs();
List<string> currentArgs = new List<string>();
bool flag = false;
for (int i = 0; i < args.Length; i++)
{
if (args[i] == "-executeMethod")
{
flag = true;
continue;
}
else
{
if (args[i].StartsWith("-"))
{
flag = false;
}
}
if (flag)
{
currentArgs.Add(args[i]);
}
}
string timeFlag = currentArgs.Count < 2 ? DateTime.Now.ToString("YYYYMMddHHmmss") : currentArgs[1];
EditorUserBuildSettings.SwitchActiveBuildTarget(BuildTargetGroup.Android, BuildTarget.Android);
BuildOptions options = BuildOptions.CompressWithLz4 | BuildOptions.AllowDebugging | BuildOptions.Development;
UnityEditor.PlayerSettings.productName = "G2";
UnityEditor.PlayerSettings.applicationIdentifier = "com.gygame.g2";
string defs = PlayerSettings.GetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android);
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, "UWA_AUTO");
UnityEditor.PlayerSettings.SetScriptingBackend(BuildTargetGroup.Android, ScriptingImplementation.Mono2x);
UnityEditor.PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARMv7;
UnityEditor.Build.Reporting.BuildReport report = BuildPipeline.BuildPlayer(levels.ToArray(), string.Format("Client_{0}.apk", timeFlag), BuildTarget.Android, options);
PlayerSettings.SetScriptingDefineSymbolsForGroup(BuildTargetGroup.Android, defs);
}
//在这里找出你当前工程所有的场景文件,假设你只想把部分的scene文件打包 那么这里可以写你的条件判断 总之返回一个字符串数组。
static string[] GetBuildScenes()
{
List<string> names = new List<string>();
foreach (EditorBuildSettingsScene e in EditorBuildSettings.scenes)
{
if (e == null)
continue;
if (e.enabled)
names.Add(e.path);
}
return names.ToArray();
}
//得到项目的名称
public static string projectName
{
get
{
//在这里分析shell传入的参数, 还记得上面我们说的哪个 project-$1 这个参数吗?
//这里遍历所有参数,找到 project开头的参数, 然后把-符号 后面的字符串返回,
//这个字符串就是 91 了。。
foreach (string arg in System.Environment.GetCommandLineArgs())
{
if (arg.StartsWith("project"))
{
return arg.Split("-"[0])[1];
}
}
return "test";
}
}
//[MenuItem("打包/生成ios工程", false, 106)]
//shell脚本直接调用这个静态方法
public static void BuildForIOS()
{
//这里就是构建xcode工程的核心方法了,
//参数1 需要打包的所有场景
//参数2 需要打包的名子, 这里取到的就是 shell传进来的字符串 91
//参数3 打包平台
BuildOptions options = BuildOptions.None;//BuildOptions.CompressWithLz4;
BuildPipeline.BuildPlayer(GetBuildScenes(), "G2", BuildTarget.iOS, options);
}
}