using System.Collections; using System.Collections.Generic; using UnityEngine; using System; using UnityEditor; using System.IO; using System.Text.RegularExpressions; using System.Text; using Beebyte.Obfuscator; public class ClientPackage { public static readonly string versionsFilePath = Application.dataPath + Path.DirectorySeparatorChar + "Editor/VersionConfigs/Versions.txt"; public static readonly string[] baseLevels = new string[] { "Assets/Resources/Scenes/Launch.unity", "Assets/Resources/Scenes/Empty.unity" }; public static string auditOutTime = string.Empty; public static string SDK_PLUGIN_PROJECT { get { return LocalSave.GetString("SDK_PROJECT_PATH"); } set { LocalSave.SetString("SDK_PROJECT_PATH", value); } } public static bool obfuscatorEnabled { get { return LocalSave.GetBool("obfuscatorEnabled", false); } set { LocalSave.SetBool("obfuscatorEnabled", value); } } public static bool includeConfig { get { return LocalSave.GetBool("client_pg_includeConfig", false); } set { LocalSave.SetBool("client_pg_includeConfig", value); } } public static bool includeUI { get { return LocalSave.GetBool("client_pg_includeUI", false); } set { LocalSave.SetBool("client_pg_includeUI", value); } } public static int AssetPrior { get { return LocalSave.GetInt("HalfAssetPrior", 1); } set { LocalSave.SetInt("HalfAssetPrior", value); } } static string THE_2TH_WORLD_SDK_PATH = Application.dataPath + "/Plugins/Android/bin"; static string MANIFEST_PATH = Application.dataPath + "/Plugins/Android/AndroidManifest.xml"; static string iOS_PLUGIN_PATH = Application.dataPath + "/Plugins/iOS/Channel"; /// /// 打包 /// /// 整合sdk路径 /// _assetBundle的资源路径,默认在项目目录下的 AssetBundles/android /// 打包完成后的输出路径 /// 渠道id,可打多个包,用|分割 /// 打包序序号,用于标识名称 /// 是否为开发版 /// ios打包导出xcode工程模式,Append和Replace public static void BuildPublishers(string _sdkPath, string _assetBundlePath, string _output, string _publisherString, int _buildIndex, bool _development, bool _replace) { var publisherIds = new List(); var tempStrings = _publisherString.Split(StringUtility.splitSeparator, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < tempStrings.Length; i++) { var temp = tempStrings[i]; var matches = Regex.Matches(temp, "\\d+"); switch (matches.Count) { case 1: var id = int.Parse(matches[0].Value); publisherIds.Add(id.ToString()); break; case 2: var min = int.Parse(matches[0].Value); var max = int.Parse(matches[1].Value); for (int j = min; j <= max; j++) { publisherIds.Add(j.ToString()); } break; } } var smallPackages = new List(); var halfPackages = new List(); var bigPackages = new List(); for (int i = 0; i < publisherIds.Count; i++) { try { var publisher = publisherIds[i]; var versionName = string.Empty; var versionConfig = GetVersionConfig(publisher, out versionName); switch (versionConfig.assetAccess) { case InstalledAsset.NullAsset: smallPackages.Add(publisher); break; case InstalledAsset.HalfAsset: halfPackages.Add(publisher); break; case InstalledAsset.FullAsset: case InstalledAsset.IngoreDownLoad: bigPackages.Add(publisher); break; } } catch (Exception ex) { Debug.Log(ex); } } if (Directory.Exists(ResourcesPath.Instance.StreamingAssetPath)) { Directory.Delete(ResourcesPath.Instance.StreamingAssetPath, true); } if (smallPackages.Count > 0) { var files = new List(); var builtInFiles = new List(); FileExtersion.GetAllDirectoryFileInfos(_assetBundlePath, files); foreach (var file in files) { var fileName = Path.GetFileName(file.FullName); if (file.FullName.Contains("builtin")) { builtInFiles.Add(file); continue; } if (ConfigInitiator.builtinConfig.Contains(fileName)) { builtInFiles.Add(file); continue; } // File.Delete(file.FullName); } var totalCount = builtInFiles.Count; var completeCount = 0; foreach (var item in builtInFiles) { var extension = Path.GetExtension(item.FullName); if (extension == ".meta") { continue; } var relativePath = FileExtersion.GetFileRelativePath(_assetBundlePath, item.FullName); var to = StringUtility.Contact(ResourcesPath.Instance.StreamingAssetPath, relativePath); var directory = Path.GetDirectoryName(to); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } File.Copy(item.FullName, to, true); completeCount++; } EditorUtility.ClearProgressBar(); for (int i = 0; i < smallPackages.Count; i++) { #if UNITY_ANDROID BuildApk(_sdkPath, _output, smallPackages[i], _buildIndex, _development); #elif UNITY_IOS BuildIpa(_sdkPath, smallPackages[i], _buildIndex, _replace); #endif } } if (halfPackages.Count > 0) { PriorBundleConfig.Init(true); var fromFiles = new List(); FileExtersion.GetAllDirectoryFileInfos(_assetBundlePath, fromFiles); var excludeFileFullNames = new List(); var tempFiles = new List(); FileExtersion.GetAllDirectoryFileInfos(StringUtility.Contact(_assetBundlePath, "/gmodels"), tempFiles); foreach (var file in tempFiles) { var extersion = Path.GetExtension(file.FullName); var fileName = Path.GetFileNameWithoutExtension(file.FullName); var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.Mob, fileName); if (prior > AssetPrior) { excludeFileFullNames.Add(file.FullName); } } tempFiles.Clear(); FileExtersion.GetAllDirectoryFileInfos(StringUtility.Contact(_assetBundlePath, "/maps"), tempFiles); foreach (var file in tempFiles) { var extersion = Path.GetExtension(file.FullName); var fileName = Path.GetFileNameWithoutExtension(file.FullName); var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.Scene, fileName); if (prior > AssetPrior) { excludeFileFullNames.Add(file.FullName); } } tempFiles.Clear(); FileExtersion.GetAllDirectoryFileInfos(StringUtility.Contact(_assetBundlePath, "/audio"), tempFiles); foreach (var file in tempFiles) { var extersion = Path.GetExtension(file.FullName); var fileName = Path.GetFileNameWithoutExtension(file.FullName); var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.Audio, fileName); if (prior > AssetPrior) { excludeFileFullNames.Add(file.FullName); } } tempFiles.Clear(); FileExtersion.GetAllDirectoryFileInfos(StringUtility.Contact(_assetBundlePath, "/effect"), tempFiles); foreach (var file in tempFiles) { var extersion = Path.GetExtension(file.FullName); var fileName = Path.GetFileNameWithoutExtension(file.FullName); var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.Effect, fileName); if (prior > AssetPrior) { excludeFileFullNames.Add(file.FullName); } } //剔除视频资源 tempFiles.Clear(); var videoPath = StringUtility.Contact(_assetBundlePath, "/video"); if (File.Exists(videoPath)) { FileExtersion.GetAllDirectoryFileInfos(StringUtility.Contact(_assetBundlePath, "/video"), tempFiles); foreach (var file in tempFiles) { var extersion = Path.GetExtension(file.FullName); var fileName = Path.GetFileNameWithoutExtension(file.FullName); var prior = PriorBundleConfig.GetAssetPrior(AssetVersion.AssetCategory.Video, fileName); if (prior > AssetPrior) { excludeFileFullNames.Add(file.FullName); } } } //剔除表资源 if (!includeConfig) { tempFiles.Clear(); FileExtersion.GetAllDirectoryFileInfos(StringUtility.Contact(_assetBundlePath, "/config"), tempFiles); foreach (var file in tempFiles) { if (!ConfigInitiator.builtinConfig.Contains(file.Name)) excludeFileFullNames.Add(file.FullName); } } //剔除UI资源 tempFiles.Clear(); FileExtersion.GetAllDirectoryFileInfos(StringUtility.Contact(_assetBundlePath, "/ui/sprite"), tempFiles); foreach (var file in tempFiles) { var fileName = Path.GetFileName(file.FullName); if (fileName == "loadingbg") { excludeFileFullNames.Add(file.FullName); } } if (!includeUI) { tempFiles.Clear(); FileExtersion.GetAllDirectoryFileInfos(StringUtility.Contact(_assetBundlePath, "/ui"), tempFiles); foreach (var file in tempFiles) { excludeFileFullNames.Add(file.FullName); } } for (int i = fromFiles.Count - 1; i >= 0; i--) { var item = fromFiles[i]; if (excludeFileFullNames.Contains(item.FullName)) { fromFiles.RemoveAt(i); } } var totalCount = fromFiles.Count; var completeCount = 0; foreach (var item in fromFiles) { var relativePath = FileExtersion.GetFileRelativePath(_assetBundlePath, item.FullName); var to = StringUtility.Contact(ResourcesPath.Instance.StreamingAssetPath, relativePath); if (relativePath.StartsWith("patch"))//不包含meta文件和补丁 continue; var directory = Path.GetDirectoryName(to); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); var extension = Path.GetExtension(item.FullName); if (extension == ".meta") continue; File.Copy(item.FullName, to, true); completeCount++; } EditorUtility.ClearProgressBar(); for (int i = 0; i < halfPackages.Count; i++) { #if UNITY_ANDROID BuildApk(_sdkPath, _output, halfPackages[i], _buildIndex, _development); #elif UNITY_IOS BuildIpa(_sdkPath, halfPackages[i], _buildIndex, _replace); #endif } } if (bigPackages.Count > 0) { var fromFiles = new List(); FileExtersion.GetAllDirectoryFileInfos(_assetBundlePath, fromFiles); var totalCount = fromFiles.Count; var completeCount = 0; foreach (var item in fromFiles) { var relativePath = FileExtersion.GetFileRelativePath(_assetBundlePath, item.FullName); var to = StringUtility.Contact(ResourcesPath.Instance.StreamingAssetPath, relativePath); if (relativePath.StartsWith("patch"))//不包含meta文件和补丁 continue; var directory = Path.GetDirectoryName(to); if (!Directory.Exists(directory)) Directory.CreateDirectory(directory); var extension = Path.GetExtension(item.FullName); if (extension == ".meta") continue; File.Copy(item.FullName, to, true); completeCount++; } for (int i = 0; i < bigPackages.Count; i++) { #if UNITY_ANDROID BuildApk(_sdkPath, _output, bigPackages[i], _buildIndex, _development); #elif UNITY_IOS BuildIpa(_sdkPath, bigPackages[i], _buildIndex, _replace); #endif } } } public static void BuildApk(string _sdkPath, string _output, string _publisher, int _buildIndex, bool _development) { PreBuild(_publisher, _buildIndex); var auditTimeFile = StringUtility.Contact(Application.dataPath, Path.DirectorySeparatorChar, "Resources/Config/AuditTime.txt"); if (File.Exists(auditTimeFile)) { File.Delete(auditTimeFile); } var versionName = string.Empty; var versionConfig = GetVersionConfig(_publisher, out versionName); var versionConfigCSpath = Application.dataPath + "/Scripts/System/ClientVersion/VersionConfig.cs"; var text = File.ReadAllText(versionConfigCSpath); if (text.Contains("VERSION_ALTERNATIVE")) { var pattern = "VERSION_ALTERNATIVE = \".*\""; text = Regex.Replace(text, pattern, StringUtility.Contact("VERSION_ALTERNATIVE = ", "\"", versionConfig.m_Version, "\"")); bool encoderShouldEmitUTF8Identifier = true; bool throwOnInvalidBytes = false; UTF8Encoding encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier, throwOnInvalidBytes); bool append = false; StreamWriter streamWriter = new StreamWriter(versionConfigCSpath, append, encoding); streamWriter.Write(text); streamWriter.Close(); AssetDatabase.ImportAsset(versionConfigCSpath); } // 针对某一些打包的特殊性, 这里特指依赖的第三方库含有资源的问题 // 这里对有这种特型的渠道的sdk在打包的时候差异化gradle文件 // 更名原有文件 // string _gradleTemplateUrl = EditorApplication.applicationContentsPath + "/PlaybackEngines/AndroidPlayer/Tools/GradleTemplates/"; // string _gradleFileUrl = _gradleTemplateUrl + "mainTemplate.gradle"; // string _diffGradleFileUrl = StringUtility.Contact(_sdkPath, "/GradleDiff/", versionConfig.appId, "/mainTemplate.gradle"); // FileInfo _fileInfo = new FileInfo(_diffGradleFileUrl); // if (!_fileInfo.Exists) // { // EditorUtility.DisplayDialog("警告", "Gradle模式需要配置差异文件,但是没有找到该文件: " + _diffGradleFileUrl, "@Hale"); // return; // } // _fileInfo.CopyTo(_gradleFileUrl, true); // 移动配置文件至打包工作目录中 var keystorePath = Application.dataPath + "/Editor/Keystore/" + versionConfig.keystoreFileName + ".keystore"; if (!File.Exists(keystorePath)) { Debug.LogError("签名文件不存在"); return; } PlayerSettings.Android.keystoreName = keystorePath; PlayerSettings.Android.keystorePass = versionConfig.keystorePassword; PlayerSettings.Android.keyaliasName = versionConfig.keystoreAlias; PlayerSettings.Android.keyaliasPass = versionConfig.keystoreAliasPassword; // PlayerSettings.Android.targetSdkVersion = AndroidSdkVersions.AndroidApiLevel22; PlayerSettings.Android.bundleVersionCode = VersionConfig.GetVersionNumber(versionConfig.version); PlayerSettings.enableInternalProfiler = _development; //删除原sdk文件 if (Directory.Exists(THE_2TH_WORLD_SDK_PATH)) Directory.Delete(THE_2TH_WORLD_SDK_PATH, true); //删除原清单文件 // if (File.Exists(MANIFEST_PATH)) // File.Delete(MANIFEST_PATH); var variant = _development ? "debug" : "release"; // var manifestpath = StringUtility.Contact(_sdkPath, "/Channel/Android/", versionConfig.appId, "/AndroidManifest.xml"); var channelSdkPath = StringUtility.Contact(_sdkPath, "/Channel/Android/", versionConfig.appId, "/", variant); var manifeastPath = StringUtility.Contact(_sdkPath, "/Channel/Android/", versionConfig.appId, "/AndroidManifest.xml"); // if (File.Exists(manifestpath)) // { // File.Copy(manifestpath, MANIFEST_PATH); // DebugEx.LogFormat("执行Android 清单文件拷贝,from:{0};to:{1}", manifestpath, MANIFEST_PATH); // } // else // { // DebugEx.LogFormat("Android 清单文件不存在,{0}", manifestpath); // // return; // } if (Directory.Exists(channelSdkPath)) { FileExtersion.DirectoryCopy(channelSdkPath, THE_2TH_WORLD_SDK_PATH); DebugEx.LogFormat("执行Android 渠道SDK 文件拷贝,from:{0};to:{1}", channelSdkPath, THE_2TH_WORLD_SDK_PATH); } else { DebugEx.LogFormat("Android 渠道SDK文件不存在,{0}", channelSdkPath); return; } if (File.Exists(manifeastPath)) { if (File.Exists(MANIFEST_PATH)) File.Delete(MANIFEST_PATH); File.Copy(manifeastPath, MANIFEST_PATH); DebugEx.LogFormat("Android 清单文件拷贝成功,{0}", manifeastPath); } var option = AssetDatabase.LoadAssetAtPath("Assets/Editor/Beebyte/Obfuscator/ObfuscatorOptions.asset"); if (option != null) { option.enabled = obfuscatorEnabled; EditorUtility.SetDirty(option); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); IFix.Editor.IFixEditor.InjectAllAssemblys();//对injectAssemblys里的程序集进行注入,然后备份 if (_development) { var apkName = StringUtility.Contact(_output, "/", versionName, "_", versionConfig.clientPackageFlag, "_v", versionConfig.m_Version, "_", _buildIndex, "_development.apk"); BuildPipeline.BuildPlayer(baseLevels, apkName, BuildTarget.Android, BuildOptions.Development | BuildOptions.ConnectWithProfiler | BuildOptions.AllowDebugging); } else { var apkName = StringUtility.Contact(_output, "/", versionName, "_", versionConfig.clientPackageFlag, "_v", versionConfig.m_Version, "_", _buildIndex, ".apk"); BuildPipeline.BuildPlayer(baseLevels, apkName, BuildTarget.Android, BuildOptions.None); } } public static void BuildIpa(string _sdkPath, string _publisher, int _buildIndex, bool _replace) { PreBuild(_publisher, _buildIndex); var versionName = string.Empty; var versionConfig = GetVersionConfig(_publisher, out versionName); if (Directory.Exists(iOS_PLUGIN_PATH)) { Directory.Delete(iOS_PLUGIN_PATH, true); } // if (!versionConfig.appId.Equals("test")) // { FileExtersion.DirectoryCopy(StringUtility.Contact(_sdkPath, "/Channel/iOS/", versionConfig.appId, "/Plugins"), iOS_PLUGIN_PATH); // } DateTime outTime; var correct = DateTime.TryParse(auditOutTime, out outTime); var directory = Application.dataPath + Path.DirectorySeparatorChar + "Resources/Config"; if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } File.WriteAllText(Application.dataPath + Path.DirectorySeparatorChar + "Resources/Config/AuditTime.txt", correct ? auditOutTime : ""); var option = AssetDatabase.LoadAssetAtPath("Assets/Editor/Beebyte/Obfuscator/ObfuscatorOptions.asset"); if (option != null) { option.enabled = obfuscatorEnabled; EditorUtility.SetDirty(option); } AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); PlayerSettings.iOS.buildNumber = VersionConfig.Get().buildIndex.ToString(); PlayerSettings.iOS.appleDeveloperTeamID = VersionConfig.Get().appleDeveloperTeamID; IFix.Editor.IFixEditor.InjectAllAssemblys();//对injectAssemblys里的程序集进行注入,然后备份 #if UNITY_IOS if (_replace) { XCodeProjectMod.BuildIOSProject_Replace(); } else { XCodeProjectMod.BuildIOSProject_Append(); } #endif } private static void PreBuild(string _publisher, int _buildIndex) { var newVersionConfigPath = StringUtility.Contact("Assets/Resources/VersionConfig", ".asset"); var versionName = string.Empty; var fromVersionConfig = GetVersionConfig(_publisher, out versionName); var newVersionConfig = ScriptableObject.CreateInstance(); if (File.Exists(newVersionConfigPath)) { AssetDatabase.DeleteAsset(newVersionConfigPath); } VersionConfig.Copy(fromVersionConfig, newVersionConfig); newVersionConfig.buildTime = DateTime.Now.ToString("yy/MM/dd--HH:mm"); newVersionConfig.buildIndex = _buildIndex; if (File.Exists(Application.dataPath + "/Resources/Game.txt")) { File.Delete(Application.dataPath + "/Resources/Game.txt"); } File.WriteAllText(Application.dataPath + "/Resources/Game.txt", GetGameId(_publisher)); AssetDatabase.CreateAsset(newVersionConfig, newVersionConfigPath); EditorUtility.SetDirty(newVersionConfig); SetIconAndSplashImage(versionName); //SetCreateRoleAnimation(); PlayerSettings.companyName = "TheSecondWorld"; PlayerSettings.productName = newVersionConfig.productName; PlayerSettings.applicationIdentifier = newVersionConfig.bundleIdentifier; PlayerSettings.defaultInterfaceOrientation = UIOrientation.AutoRotation; PlayerSettings.allowedAutorotateToLandscapeLeft = true; PlayerSettings.allowedAutorotateToLandscapeRight = true; PlayerSettings.allowedAutorotateToPortrait = false; PlayerSettings.allowedAutorotateToPortraitUpsideDown = false; PlayerSettings.bundleVersion = VersionConfig.GetVersionNumber(newVersionConfig.version).ToString(); //生成ILruntiemd CLR 绑定代码 ILRuntimeCLRBinding.GenerateCLRBinding(); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); } static VersionConfig GetVersionConfig(string _publisher, out string _versionName) { var lines = File.ReadAllLines(versionsFilePath); for (int i = 2; i < lines.Length; i++) { var line = lines[i]; var lineStrings = line.Split('\t'); if (lineStrings[0] == _publisher) { var config = new VersionConfig(); config.Read(line); _versionName = lineStrings[1]; return config; } } _versionName = string.Empty; return null; } static string GetGameId(string _publisher) { var lines = File.ReadAllLines(versionsFilePath); for (int i = 2; i < lines.Length; i++) { var line = lines[i]; var lineStrings = line.Split('\t'); if (lineStrings[0] == _publisher) { return lineStrings[7]; } } return "xbqy"; } static void SetIconAndSplashImage(string _versionName) { var buildTarget = GetBuildTarget(); var texture = AssetDatabase.LoadAssetAtPath(StringUtility.Contact("Assets/Editor/Logo/", _versionName, "/Icon.png")); var iconSizes = PlayerSettings.GetIconSizesForTargetGroup(buildTarget); var icons = new Texture2D[iconSizes.Length]; for (int i = 0; i < iconSizes.Length; i++) { icons[i] = texture; } PlayerSettings.SetIconsForTargetGroup(buildTarget, icons); PlayerSettings.SplashScreen.show = false; PlayerSettings.SplashScreen.showUnityLogo = false; PlayerSettings.SplashScreen.unityLogoStyle = PlayerSettings.SplashScreen.UnityLogoStyle.LightOnDark; var splashImage = AssetDatabase.LoadAssetAtPath(StringUtility.Contact("Assets/Editor/Logo/", _versionName, "/SplashImage.png")); var splashScreenLogo = PlayerSettings.SplashScreenLogo.Create(3, splashImage); PlayerSettings.SplashScreen.logos = new PlayerSettings.SplashScreenLogo[] { splashScreenLogo }; if (buildTarget == BuildTargetGroup.Android) { var projectSettings = new SerializedObject(AssetDatabase.LoadAllAssetsAtPath("ProjectSettings/ProjectSettings.asset")[0]); var splashImageSetting = projectSettings.FindProperty("androidSplashScreen"); splashImageSetting.objectReferenceValue = splashImage.texture; projectSettings.ApplyModifiedProperties(); PlayerSettings.Android.splashScreenScale = AndroidSplashScreenScale.ScaleToFill; } } public static void SetCreateRoleAnimation() { var from = "Assets/Editor/Video/CreateRoleMovie.mp4"; var to = "Assets/StreamingAssets/CreateRoleMovie.mp4"; if (!Directory.Exists(Application.dataPath + "StreamingAssets")) { Directory.CreateDirectory(Application.dataPath + "StreamingAssets"); } if (AssetDatabase.LoadAssetAtPath(to) == null) { AssetDatabase.CopyAsset(from, to); } } static BuildTargetGroup GetBuildTarget() { #if UNITY_ANDROID return BuildTargetGroup.Android; #elif UNITY_IOS return BuildTargetGroup.iOS; #else return BuildTargetGroup.Standalone; #endif } }