├── Images ├── logo.png ├── window-1.png ├── window-2.png ├── ubh-apple-icon.png ├── ubh-game-icon.png ├── ubh-ping-icon.png ├── ubh-web-icon.png ├── ubh-client-icon.png ├── ubh-google-icon.png ├── ubh-huawei-icon.png ├── logo.png.meta ├── window-1.png.meta ├── window-2.png.meta ├── ubh-game-icon.png.meta ├── ubh-ping-icon.png.meta ├── ubh-web-icon.png.meta ├── ubh-apple-icon.png.meta ├── ubh-client-icon.png.meta ├── ubh-google-icon.png.meta └── ubh-huawei-icon.png.meta ├── Samples~ └── Demo │ └── Demo.cs ├── LICENSE.meta ├── README.md.meta ├── package.json.meta ├── Editor.meta ├── External.meta ├── Images.meta ├── Runtime.meta ├── Runtime ├── UBH.asmdef.meta ├── DefineSymbols.cs.meta ├── UBH.asmdef └── DefineSymbols.cs ├── Editor ├── UBH.Editor.asmdef.meta ├── UBHPrefs.cs.meta ├── GradleFixer.cs.meta ├── CommonGradleWorker.cs.meta ├── GoogleGradleWorker.cs.meta ├── HuaweiGradleWorker.cs.meta ├── PlatformValidator.cs.meta ├── PostBuildWorker.cs.meta ├── UnityBuilderHelper.cs.meta ├── PBXProjectExtensions.cs.meta ├── PreBuildManifestWorker.cs.meta ├── UBH.Editor.asmdef ├── CommonGradleWorker.cs ├── GradleFixer.cs ├── GoogleGradleWorker.cs ├── HuaweiGradleWorker.cs ├── UBHPrefs.cs ├── PlatformValidator.cs ├── PreBuildManifestWorker.cs ├── PostBuildWorker.cs ├── PBXProjectExtensions.cs └── UnityBuilderHelper.cs ├── External ├── UBH.External.asmdef.meta ├── NiceJson.cs.meta ├── UBH.External.asmdef └── NiceJson.cs ├── package.json ├── LICENSE ├── .gitignore └── README.md /Images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mopsicus/ubh/HEAD/Images/logo.png -------------------------------------------------------------------------------- /Samples~/Demo/Demo.cs: -------------------------------------------------------------------------------- 1 | // full demo project https://github.com/mopsicus/ubh-demo -------------------------------------------------------------------------------- /Images/window-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mopsicus/ubh/HEAD/Images/window-1.png -------------------------------------------------------------------------------- /Images/window-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mopsicus/ubh/HEAD/Images/window-2.png -------------------------------------------------------------------------------- /Images/ubh-apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mopsicus/ubh/HEAD/Images/ubh-apple-icon.png -------------------------------------------------------------------------------- /Images/ubh-game-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mopsicus/ubh/HEAD/Images/ubh-game-icon.png -------------------------------------------------------------------------------- /Images/ubh-ping-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mopsicus/ubh/HEAD/Images/ubh-ping-icon.png -------------------------------------------------------------------------------- /Images/ubh-web-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mopsicus/ubh/HEAD/Images/ubh-web-icon.png -------------------------------------------------------------------------------- /Images/ubh-client-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mopsicus/ubh/HEAD/Images/ubh-client-icon.png -------------------------------------------------------------------------------- /Images/ubh-google-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mopsicus/ubh/HEAD/Images/ubh-google-icon.png -------------------------------------------------------------------------------- /Images/ubh-huawei-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mopsicus/ubh/HEAD/Images/ubh-huawei-icon.png -------------------------------------------------------------------------------- /LICENSE.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ac55d66919cf64386a8a0aba377e3ae3 3 | DefaultImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /README.md.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f5657618f6e5e4fdfae5c5bd7beabf40 3 | TextScriptImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /package.json.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 815e1a1a26a7143a9b6a3fd8ae84ff4d 3 | PackageManifestImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: d232b520c3f934cd1849c75cc2dd36e9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /External.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5ce57af94fa50437c81f645126ad1fb0 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Images.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a3b7a0f62b58c4c13aa55db5c1e1c382 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2c8c8f6bb876842d58c9f2230efc7cd9 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Runtime/UBH.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eec7969a0d6af4c8db68e434d5690723 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/UBH.Editor.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9034956bcabb740b6bcb0b3c6dc68195 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /External/UBH.External.asmdef.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: eed29f14b3d2b4f4cbbc2a96074951e9 3 | AssemblyDefinitionImporter: 4 | externalObjects: {} 5 | userData: 6 | assetBundleName: 7 | assetBundleVariant: 8 | -------------------------------------------------------------------------------- /Editor/UBHPrefs.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a5bc27f1a9165457a84643aec9cd2467 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/GradleFixer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f8e33c4cb982749b4bae5e67738d881e 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /External/NiceJson.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2539183dcb0874ab7947764a125888fe 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Runtime/DefineSymbols.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 9cbe370341beb43bbafb134f4578ea2a 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/CommonGradleWorker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 370c1e46599824b99a9d1de1ddce3275 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/GoogleGradleWorker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 01bc0bf18d0a04309baada5f43221c15 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/HuaweiGradleWorker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ace7a4040513941838595430b6ed322c 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PlatformValidator.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 02a5adf620c024532bb06e680208b7c2 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PostBuildWorker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 3b53028ee4c52478b956b110753811a8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/UnityBuilderHelper.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 91ce56d69c6e140988803cc04dfb7564 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PBXProjectExtensions.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: bb1c96ef7d97c4560bed4fedc327a6e8 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /Editor/PreBuildManifestWorker.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b7ee11d7ad84c44df9bb0ec8be0c0c83 3 | MonoImporter: 4 | externalObjects: {} 5 | serializedVersion: 2 6 | defaultReferences: [] 7 | executionOrder: 0 8 | icon: {instanceID: 0} 9 | userData: 10 | assetBundleName: 11 | assetBundleVariant: 12 | -------------------------------------------------------------------------------- /External/UBH.External.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UBH.External", 3 | "rootNamespace": "", 4 | "references": [], 5 | "includePlatforms": [ 6 | "Android", 7 | "Editor", 8 | "iOS", 9 | "WebGL" 10 | ], 11 | "excludePlatforms": [], 12 | "allowUnsafeCode": false, 13 | "overrideReferences": false, 14 | "precompiledReferences": [], 15 | "autoReferenced": true, 16 | "defineConstraints": [], 17 | "versionDefines": [], 18 | "noEngineReferences": false 19 | } -------------------------------------------------------------------------------- /Editor/UBH.Editor.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UBH.Editor", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:eec7969a0d6af4c8db68e434d5690723", 6 | "GUID:eed29f14b3d2b4f4cbbc2a96074951e9" 7 | ], 8 | "includePlatforms": [ 9 | "Editor" 10 | ], 11 | "excludePlatforms": [], 12 | "allowUnsafeCode": false, 13 | "overrideReferences": false, 14 | "precompiledReferences": [], 15 | "autoReferenced": true, 16 | "defineConstraints": [], 17 | "versionDefines": [], 18 | "noEngineReferences": false 19 | } -------------------------------------------------------------------------------- /Runtime/UBH.asmdef: -------------------------------------------------------------------------------- 1 | { 2 | "name": "UBH", 3 | "rootNamespace": "", 4 | "references": [ 5 | "GUID:eed29f14b3d2b4f4cbbc2a96074951e9" 6 | ], 7 | "includePlatforms": [ 8 | "Android", 9 | "Editor", 10 | "iOS", 11 | "WebGL" 12 | ], 13 | "excludePlatforms": [], 14 | "allowUnsafeCode": false, 15 | "overrideReferences": false, 16 | "precompiledReferences": [], 17 | "autoReferenced": true, 18 | "defineConstraints": [], 19 | "versionDefines": [], 20 | "noEngineReferences": false 21 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.mopsicus.ubh", 3 | "version": "1.0.0", 4 | "displayName": "Unity Builder Helper", 5 | "description": "Run local and remote builds, validate platform options", 6 | "keywords": [ 7 | "telegram", 8 | "unity", 9 | "build", 10 | "xcode", 11 | "android", 12 | "apk", 13 | "ipa", 14 | "ci/cd" 15 | ], 16 | "repository": { 17 | "type": "git", 18 | "url": "https://github.com/mopsicus/unity-builder-helper.git" 19 | }, 20 | "samples": [ 21 | { 22 | "displayName": "Demo", 23 | "description": "A demo project with example support files", 24 | "path": "Samples~/Demo" 25 | } 26 | ], 27 | "author": { 28 | "name": "Mopsicus", 29 | "email": "mail@mopsicus.ru", 30 | "url": "https://mopsicus.ru" 31 | } 32 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Igor 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uilds/ 10 | /[Ll]ogs/ 11 | /[Mm]emoryCaptures/ 12 | 13 | # Asset meta data should only be ignored when the corresponding asset is also ignored 14 | !/[Aa]ssets/**/*.meta 15 | 16 | # Uncomment this line if you wish to ignore the asset store tools plugin 17 | # /[Aa]ssets/AssetStoreTools* 18 | 19 | # Autogenerated Jetbrains Rider plugin 20 | [Aa]ssets/Plugins/Editor/JetBrains* 21 | 22 | # Visual Studio cache directory 23 | .vs/ 24 | 25 | # Gradle cache directory 26 | .gradle/ 27 | 28 | # Autogenerated VS/MD/Consulo solution and project files 29 | ExportedObj/ 30 | .consulo/ 31 | *.csproj 32 | *.unityproj 33 | *.sln 34 | *.suo 35 | *.tmp 36 | *.user 37 | *.userprefs 38 | *.pidb 39 | *.booproj 40 | *.svd 41 | *.pdb 42 | *.mdb 43 | *.opendb 44 | *.VC.db 45 | 46 | # Unity3D generated meta files 47 | *.pidb.meta 48 | *.pdb.meta 49 | *.mdb.meta 50 | 51 | # Unity3D generated file on crash reports 52 | sysinfo.txt 53 | 54 | # Builds 55 | *.apk 56 | *.unitypackage 57 | 58 | # Crashlytics generated file 59 | crashlytics-build.properties 60 | 61 | .DS_Store 62 | -------------------------------------------------------------------------------- /Editor/CommonGradleWorker.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using UnityEditor; 3 | using UnityEditor.Build; 4 | using UnityEditor.Build.Reporting; 5 | using UnityEngine; 6 | 7 | namespace Mopsicus.UBH { 8 | 9 | public class CommonGradleWorker : IPreprocessBuildWithReport { 10 | 11 | /// 12 | /// Path supports folder 13 | /// 14 | private string _supportsPath = null; 15 | 16 | /// 17 | /// Callback for CLI 18 | /// 19 | public int callbackOrder => 0; 20 | 21 | /// 22 | /// Create main gradle file 23 | /// With task to copy files 24 | /// 25 | /// Folder with project support resources 26 | /// Output folder 27 | private void CreateMainGradleFile(string from, string to) { 28 | using (StreamWriter file = File.CreateText(Path.Combine(_supportsPath, GradleFixer.COMMON_GRADLE))) { 29 | file.Write("task copyFiles(type: Copy) {\n"); 30 | file.Write(string.Format("\tfrom '{0}'\n", from)); 31 | file.Write(string.Format("\tinto '{0}'\n", to)); 32 | file.Write("}\n\n"); 33 | file.Write("preBuild.dependsOn(copyFiles)\n"); 34 | } 35 | } 36 | 37 | /// 38 | /// Action before build 39 | /// 40 | public void OnPreprocessBuild(BuildReport report) { 41 | Application.logMessageReceived += OnBuildError; 42 | _supportsPath = Path.Combine(Directory.GetParent(Application.dataPath).FullName, GradleFixer.SUPPORT_TEMP_FOLDER); 43 | if (!Directory.Exists(_supportsPath)) { 44 | Directory.CreateDirectory(_supportsPath); 45 | } 46 | string from = Path.Combine(Directory.GetParent(Application.dataPath).FullName, GradleFixer.SUPPORT_ANDROID_FOLDER); 47 | string to = Path.Combine(Directory.GetParent(Application.dataPath).FullName, GradleFixer.OUTPUT_RESOURCES_FOLDER); 48 | CreateMainGradleFile(from, to); 49 | AssetDatabase.Refresh(); 50 | } 51 | 52 | /// 53 | /// Callback if error occurs 54 | /// 55 | private void OnBuildError(string condition, string stackTrace, LogType type) { 56 | if (type == LogType.Error) { 57 | Application.logMessageReceived -= OnBuildError; 58 | } 59 | } 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /Images/logo.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 48bbea84ee0d74819a36ac4cf6fd944e 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 1 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 0 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 1 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 8 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 5e97eb03825dee720800000000000000 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Images/window-1.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: b9b1a0454927c430ca09e622fdf169c8 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 1 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 0 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 1 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 8 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 5e97eb03825dee720800000000000000 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Images/window-2.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 4cf669469e5ea4e7a8cbf939be9a05b2 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 1 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 0 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 1 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 8 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 5e97eb03825dee720800000000000000 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Images/ubh-game-icon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: f21d6825de2914ef59d7b6c4e4cddcf5 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 1 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 0 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 1 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 8 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 5e97eb03825dee720800000000000000 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Images/ubh-ping-icon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: ec027af8419724cf79f36e4d6ad0d712 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 1 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 0 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 1 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 8 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 5e97eb03825dee720800000000000000 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Images/ubh-web-icon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 96f265b9265074791bca7b78d761cbbd 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 1 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 0 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 1 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 8 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 5e97eb03825dee720800000000000000 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Images/ubh-apple-icon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a8c9eb8cd1cac4a23a3f54eabef7bfca 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 1 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 0 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 1 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 8 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 5e97eb03825dee720800000000000000 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Images/ubh-client-icon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 5d76f20a611234f3690f8ba2620b7fbd 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 1 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 0 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 1 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 8 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 5e97eb03825dee720800000000000000 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Images/ubh-google-icon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: fb052901e496d4e18a2ff8df3851c82f 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 1 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 0 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 1 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 8 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 5e97eb03825dee720800000000000000 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Images/ubh-huawei-icon.png.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a9ac0273fccb34778b62c5f97ab3672c 3 | TextureImporter: 4 | internalIDToNameTable: [] 5 | externalObjects: {} 6 | serializedVersion: 11 7 | mipmaps: 8 | mipMapMode: 0 9 | enableMipMap: 0 10 | sRGBTexture: 1 11 | linearTexture: 0 12 | fadeOut: 0 13 | borderMipMap: 0 14 | mipMapsPreserveCoverage: 0 15 | alphaTestReferenceValue: 0.5 16 | mipMapFadeDistanceStart: 1 17 | mipMapFadeDistanceEnd: 3 18 | bumpmap: 19 | convertToNormalMap: 0 20 | externalNormalMap: 0 21 | heightScale: 0.25 22 | normalMapFilter: 0 23 | isReadable: 0 24 | streamingMipmaps: 0 25 | streamingMipmapsPriority: 0 26 | vTOnly: 0 27 | grayScaleToAlpha: 0 28 | generateCubemap: 6 29 | cubemapConvolution: 0 30 | seamlessCubemap: 0 31 | textureFormat: 1 32 | maxTextureSize: 2048 33 | textureSettings: 34 | serializedVersion: 2 35 | filterMode: 1 36 | aniso: 1 37 | mipBias: 0 38 | wrapU: 1 39 | wrapV: 1 40 | wrapW: 1 41 | nPOTScale: 0 42 | lightmap: 0 43 | compressionQuality: 50 44 | spriteMode: 1 45 | spriteExtrude: 1 46 | spriteMeshType: 1 47 | alignment: 0 48 | spritePivot: {x: 0.5, y: 0.5} 49 | spritePixelsToUnits: 100 50 | spriteBorder: {x: 0, y: 0, z: 0, w: 0} 51 | spriteGenerateFallbackPhysicsShape: 1 52 | alphaUsage: 1 53 | alphaIsTransparency: 1 54 | spriteTessellationDetail: -1 55 | textureType: 8 56 | textureShape: 1 57 | singleChannelComponent: 0 58 | flipbookRows: 1 59 | flipbookColumns: 1 60 | maxTextureSizeSet: 0 61 | compressionQualitySet: 0 62 | textureFormatSet: 0 63 | ignorePngGamma: 0 64 | applyGammaDecoding: 0 65 | platformSettings: 66 | - serializedVersion: 3 67 | buildTarget: DefaultTexturePlatform 68 | maxTextureSize: 2048 69 | resizeAlgorithm: 0 70 | textureFormat: -1 71 | textureCompression: 1 72 | compressionQuality: 50 73 | crunchedCompression: 0 74 | allowsAlphaSplitting: 0 75 | overridden: 0 76 | androidETC2FallbackOverride: 0 77 | forceMaximumCompressionQuality_BC6H_BC7: 0 78 | spriteSheet: 79 | serializedVersion: 2 80 | sprites: [] 81 | outline: [] 82 | physicsShape: [] 83 | bones: [] 84 | spriteID: 5e97eb03825dee720800000000000000 85 | internalID: 0 86 | vertices: [] 87 | indices: 88 | edges: [] 89 | weights: [] 90 | secondaryTextures: [] 91 | spritePackingTag: 92 | pSDRemoveMatte: 0 93 | pSDShowRemoveMatteOption: 0 94 | userData: 95 | assetBundleName: 96 | assetBundleVariant: 97 | -------------------------------------------------------------------------------- /Runtime/DefineSymbols.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using UnityEditor; 7 | using UnityEngine; 8 | 9 | namespace Mopsicus.UBH { 10 | 11 | public static class DefineSymbols { 12 | 13 | /// 14 | /// Delimiter for store symbols 15 | /// 16 | private const char DEFINE_SEPARATOR = ';'; 17 | 18 | /// 19 | /// List for all defines 20 | /// 21 | private static readonly List _list = new List(32); 22 | 23 | /// 24 | /// Add defines to editor settings 25 | /// 26 | /// Array of defines 27 | /// Target group 28 | public static void Add(string[] defines, BuildTargetGroup group = BuildTargetGroup.Unknown) { 29 | if (group == BuildTargetGroup.Unknown) { 30 | group = EditorUserBuildSettings.selectedBuildTargetGroup; 31 | } 32 | _list.Clear(); 33 | _list.AddRange(GetDefines(group)); 34 | _list.AddRange(defines.Except(_list)); 35 | UpdateDefines(_list, group); 36 | } 37 | 38 | /// 39 | /// Remove from editor settings 40 | /// 41 | /// Array of defines 42 | /// Target group 43 | public static void Remove(string[] defines, BuildTargetGroup group = BuildTargetGroup.Unknown) { 44 | if (group == BuildTargetGroup.Unknown) { 45 | group = EditorUserBuildSettings.selectedBuildTargetGroup; 46 | } 47 | _list.Clear(); 48 | _list.AddRange(GetDefines(group).Except(defines)); 49 | UpdateDefines(_list, group); 50 | } 51 | 52 | /// 53 | /// Clear all defines 54 | /// 55 | /// Target group 56 | public static void Clear(BuildTargetGroup group = BuildTargetGroup.Unknown) { 57 | if (group == BuildTargetGroup.Unknown) { 58 | group = EditorUserBuildSettings.selectedBuildTargetGroup; 59 | } 60 | _list.Clear(); 61 | UpdateDefines(_list, group); 62 | } 63 | 64 | /// 65 | /// Get defines list 66 | /// 67 | /// Target group 68 | private static IEnumerable GetDefines(BuildTargetGroup group) { 69 | return PlayerSettings.GetScriptingDefineSymbolsForGroup(group).Split(DEFINE_SEPARATOR).ToList(); 70 | } 71 | 72 | /// 73 | /// Update defines list 74 | /// 75 | /// List to update 76 | /// Target group 77 | private static void UpdateDefines(List list, BuildTargetGroup group) { 78 | string defines = string.Join(DEFINE_SEPARATOR.ToString(), list.ToArray()); 79 | Debug.LogFormat("Update defines: {0}", defines); 80 | try { 81 | PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines); 82 | } catch (Exception e) { 83 | Debug.LogErrorFormat("Can't update define symbols for {0}. Error: {1}", group, e.Message); 84 | } 85 | } 86 | } 87 | 88 | } 89 | 90 | #endif -------------------------------------------------------------------------------- /Editor/GradleFixer.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using UnityEditor; 3 | using UnityEditor.Android; 4 | using UnityEngine; 5 | 6 | namespace Mopsicus.UBH { 7 | 8 | public class GradleFixer : IPostGenerateGradleAndroidProject { 9 | 10 | /// 11 | /// Folder to copy resources 12 | /// 13 | public static string OUTPUT_RESOURCES_FOLDER = "Temp/gradleOut/unityLibrary/src/main/res"; 14 | 15 | /// 16 | /// Folder with resources 17 | /// 18 | public static string SUPPORT_ANDROID_FOLDER = "SupportFiles/Android"; 19 | 20 | /// 21 | /// Temp folder for gradle files 22 | /// 23 | public static string SUPPORT_TEMP_FOLDER = "SupportFiles/Temp"; 24 | 25 | /// 26 | /// Common gradle 27 | /// 28 | public static string COMMON_GRADLE = "CommonMainTemplate.gradle"; 29 | 30 | /// 31 | /// Main gradle 32 | /// 33 | public static string MAIN_GRADLE = "MainTemplate.gradle"; 34 | 35 | /// 36 | /// Launcher gradle 37 | /// 38 | public static string LAUNCHER_GRADLE = "LauncherTemplate.gradle"; 39 | 40 | /// 41 | /// Base project gradle 42 | /// 43 | public static string BASE_GRADLE = "BaseProjectTemplate.gradle"; 44 | 45 | /// 46 | /// Callback for CLI 47 | /// 48 | public int callbackOrder => 1; 49 | 50 | /// 51 | /// Fix gradle 52 | /// 53 | public void OnPostGenerateGradleAndroidProject(string path) { 54 | #if UNITY_ANDROID 55 | #if GOOGLE 56 | string file = "google-services.json"; 57 | #elif HUAWEI 58 | string file = "agconnect-services.json"; 59 | #else 60 | string file = ""; 61 | #endif 62 | string supportsPath = Path.Combine(Directory.GetParent(Application.dataPath).FullName, SUPPORT_TEMP_FOLDER); 63 | string filePath = Path.Combine(Application.streamingAssetsPath, file); 64 | string destPath = Path.Combine(Directory.GetParent(path).FullName + Path.DirectorySeparatorChar + "launcher", file); 65 | string mainTemplatePath = Path.Combine(supportsPath, MAIN_GRADLE); 66 | FileUtil.CopyFileOrDirectory(mainTemplatePath, Path.GetFullPath(path) + @"/" + MAIN_GRADLE); 67 | using (StreamWriter writer = File.AppendText(Path.GetFullPath(path) + "/build.gradle")) { 68 | writer.WriteLine(string.Format("\napply from: '{0}'", MAIN_GRADLE)); 69 | } 70 | string launcherTemplatePath = Path.Combine(supportsPath, LAUNCHER_GRADLE); 71 | FileUtil.CopyFileOrDirectory(launcherTemplatePath, Directory.GetParent(path).FullName + @"/launcher/" + LAUNCHER_GRADLE); 72 | using (StreamWriter writer = File.AppendText(Directory.GetParent(path).FullName + "/launcher/build.gradle")) { 73 | writer.WriteLine(string.Format("\napply from: '{0}'", LAUNCHER_GRADLE)); 74 | } 75 | string baseProjectTemplatePath = Path.Combine(supportsPath, BASE_GRADLE); 76 | FileUtil.CopyFileOrDirectory(baseProjectTemplatePath, Directory.GetParent(path).FullName + @"/" + BASE_GRADLE); 77 | using (StreamWriter writer = File.AppendText(Directory.GetParent(path).FullName + "/build.gradle")) { 78 | writer.WriteLine(string.Format("\napply from: '{0}'", BASE_GRADLE)); 79 | } 80 | string commonTemplatePath = Path.Combine(supportsPath, COMMON_GRADLE); 81 | FileUtil.CopyFileOrDirectory(commonTemplatePath, Path.GetFullPath(path) + @"/" + COMMON_GRADLE); 82 | using (StreamWriter writer = File.AppendText(Path.GetFullPath(path) + "/build.gradle")) { 83 | writer.WriteLine(string.Format("\napply from: '{0}'", COMMON_GRADLE)); 84 | } 85 | if (File.Exists(destPath)) { 86 | FileUtil.DeleteFileOrDirectory(destPath); 87 | } 88 | #if GOOGLE 89 | bool isGoogleServices = UBHPrefs.GetBool(UnityBuilderHelper.GOOGLE_SERVICES_KEY, false); 90 | if (!isGoogleServices) { 91 | return; 92 | } 93 | #elif HUAWEI 94 | bool isHuaweiServices = UBHPrefs.GetBool(UnityBuilderHelper.HUAWEI_SERVICES_KEY, false); 95 | if (!isHuaweiServices) { 96 | return; 97 | } 98 | #endif 99 | File.Copy(filePath, destPath); 100 | #endif 101 | } 102 | } 103 | 104 | } -------------------------------------------------------------------------------- /Editor/GoogleGradleWorker.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using UnityEditor; 3 | using UnityEditor.Build; 4 | using UnityEditor.Build.Reporting; 5 | using UnityEngine; 6 | 7 | namespace Mopsicus.UBH { 8 | 9 | public class GoogleGradleWorker : IPreprocessBuildWithReport { 10 | 11 | /// 12 | /// Path supports folder 13 | /// 14 | private string _supportsPath = null; 15 | 16 | /// 17 | /// Callback for CLI 18 | /// 19 | public int callbackOrder => 0; 20 | 21 | /// 22 | /// Create/update gradle files 23 | /// 24 | /// Array of configs 25 | private void CreateGradleFiles(string[] configs) { 26 | CreateMainGradleFile(configs); 27 | CreateLauncherGradleFile(configs); 28 | CreateBaseProjectGradleFile(); 29 | AssetDatabase.Refresh(); 30 | } 31 | 32 | /// 33 | /// Create main gradle file and add dependencies 34 | /// 35 | /// Array of configs 36 | private void CreateMainGradleFile(string[] configs) { 37 | using (StreamWriter file = File.CreateText(Path.Combine(_supportsPath, GradleFixer.MAIN_GRADLE))) { 38 | file.Write("dependencies {\n"); 39 | for (int i = 0; i < configs.Length; i++) { 40 | file.Write(AddDependency(configs[i])); 41 | } 42 | file.Write("}\n"); 43 | } 44 | } 45 | 46 | /// 47 | /// Create launcher gradle file and add dependencies 48 | /// 49 | /// Array of configs 50 | private void CreateLauncherGradleFile(string[] configs) { 51 | using (StreamWriter file = File.CreateText(Path.Combine(_supportsPath, GradleFixer.LAUNCHER_GRADLE))) { 52 | bool isGoogleServices = UBHPrefs.GetBool(UnityBuilderHelper.GOOGLE_SERVICES_KEY, false); 53 | if (isGoogleServices) { 54 | file.Write("apply plugin: 'com.google.gms.google-services'\n\n"); 55 | } 56 | file.Write("dependencies {\n"); 57 | for (int i = 0; i < configs.Length; i++) { 58 | file.Write(AddDependency(configs[i])); 59 | } 60 | file.Write("}\n"); 61 | } 62 | } 63 | 64 | /// 65 | /// Create base gradle file 66 | /// 67 | private void CreateBaseProjectGradleFile() { 68 | using (StreamWriter file = File.CreateText(Path.Combine(_supportsPath, GradleFixer.BASE_GRADLE))) { 69 | bool isGoogleServices = UBHPrefs.GetBool(UnityBuilderHelper.GOOGLE_SERVICES_KEY, false); 70 | file.Write("allprojects {\n"); 71 | file.Write("\tbuildscript {\n"); 72 | file.Write("\t\tdependencies {\n"); 73 | if (isGoogleServices) { 74 | file.Write(AddClasspath("com.google.gms:google-services:4.3.10")); 75 | } 76 | file.Write("\t\t}\n\t}\n}"); 77 | } 78 | } 79 | 80 | /// 81 | /// Add dependency class to implementation section 82 | /// 83 | /// Class name 84 | private string AddDependency(string name) { 85 | return string.Format("\timplementation '{0}'\n", name); 86 | } 87 | 88 | /// 89 | /// Add classpath to gradle 90 | /// 91 | /// Class name 92 | private string AddClasspath(string name) { 93 | return string.Format("\t\t\tclasspath '{0}'\n", name); 94 | } 95 | 96 | /// 97 | /// Action before build 98 | /// Only for Google 99 | /// 100 | public void OnPreprocessBuild(BuildReport report) { 101 | #if GOOGLE 102 | Application.logMessageReceived += OnBuildError; 103 | _supportsPath = Path.Combine(Directory.GetParent(Application.dataPath).FullName, GradleFixer.SUPPORT_TEMP_FOLDER); 104 | if (!Directory.Exists(_supportsPath)) { 105 | Directory.CreateDirectory(_supportsPath); 106 | } 107 | string[] list = new string[0]; 108 | CreateGradleFiles(list); 109 | #endif 110 | } 111 | 112 | /// 113 | /// Callback if error occurs 114 | /// 115 | private void OnBuildError(string condition, string stackTrace, LogType type) { 116 | if (type == LogType.Error) { 117 | Application.logMessageReceived -= OnBuildError; 118 | } 119 | } 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /Editor/HuaweiGradleWorker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using UnityEditor; 4 | using UnityEditor.Build; 5 | using UnityEditor.Build.Reporting; 6 | using UnityEngine; 7 | 8 | namespace Mopsicus.UBH { 9 | 10 | public class HuaweiGradleWorker : IPreprocessBuildWithReport { 11 | 12 | /// 13 | /// Path supports folder 14 | /// 15 | private string _supportsPath = null; 16 | 17 | /// 18 | /// Callback for CLI 19 | /// 20 | public int callbackOrder => 0; 21 | 22 | /// 23 | /// Create/update gradle files 24 | /// 25 | /// Array of configs 26 | private void CreateGradleFiles(string[] configs) { 27 | CreateMainGradleFile(configs); 28 | CreateLauncherGradleFile(configs); 29 | CreateBaseProjectGradleFile(); 30 | AssetDatabase.Refresh(); 31 | } 32 | 33 | /// 34 | /// Create main gradle file and add dependencies 35 | /// 36 | /// Array of configs 37 | private void CreateMainGradleFile(string[] configs) { 38 | using (StreamWriter file = File.CreateText(Path.Combine(_supportsPath, GradleFixer.MAIN_GRADLE))) { 39 | file.Write("dependencies {\n"); 40 | for (int i = 0; i < configs.Length; i++) { 41 | file.Write(AddDependency(configs[i])); 42 | } 43 | file.Write("}\n"); 44 | } 45 | } 46 | 47 | /// 48 | /// Create launcher gradle file and add dependencies 49 | /// 50 | /// Array of configs 51 | private void CreateLauncherGradleFile(string[] configs) { 52 | using (StreamWriter file = File.CreateText(Path.Combine(_supportsPath, GradleFixer.LAUNCHER_GRADLE))) { 53 | bool isHuaweiServices = UBHPrefs.GetBool(UnityBuilderHelper.HUAWEI_SERVICES_KEY, false); 54 | if (isHuaweiServices) { 55 | file.Write("apply plugin: 'com.huawei.agconnect'\n\n"); 56 | } 57 | file.Write("dependencies {\n"); 58 | for (int i = 0; i < configs.Length; i++) { 59 | file.Write(AddDependency(configs[i])); 60 | } 61 | file.Write("}\n"); 62 | } 63 | } 64 | 65 | /// 66 | /// Create base gradle file 67 | /// 68 | private void CreateBaseProjectGradleFile() { 69 | using (StreamWriter file = File.CreateText(Path.Combine(_supportsPath, GradleFixer.BASE_GRADLE))) { 70 | bool isHuaweiServices = UBHPrefs.GetBool(UnityBuilderHelper.HUAWEI_SERVICES_KEY, false); 71 | file.Write("allprojects {\n"); 72 | file.Write("\tbuildscript {\n"); 73 | file.Write("\t\trepositories {\n"); 74 | file.Write("\t\t\tmaven { url 'https://developer.huawei.com/repo/' }\n\t\t}\n\n"); 75 | file.Write("\t\tdependencies {\n"); 76 | if (isHuaweiServices) { 77 | file.Write(AddClasspath("com.huawei.agconnect:agcp:1.6.3.300")); 78 | } 79 | file.Write("\t\t}\n\t}\n\n"); 80 | file.Write("\trepositories {\n"); 81 | file.Write("\t\tmaven { url 'https://developer.huawei.com/repo/' }\n\t}\n}\n\n"); 82 | } 83 | } 84 | 85 | /// 86 | /// Add dependency class to implementation section 87 | /// 88 | /// Class name 89 | private string AddDependency(string name) { 90 | return string.Format("\timplementation '{0}'\n", name); 91 | } 92 | 93 | /// 94 | /// Add classpath to gradle 95 | /// 96 | /// Class name 97 | private string AddClasspath(string name) { 98 | return string.Format("\t\t\tclasspath '{0}'\n", name); 99 | } 100 | 101 | /// 102 | /// Action before build 103 | /// Only for Huawei 104 | /// 105 | public void OnPreprocessBuild(BuildReport report) { 106 | #if HUAWEI 107 | Application.logMessageReceived += OnBuildError; 108 | _supportsPath = Path.Combine(Directory.GetParent(Application.dataPath).FullName, GradleFixer.SUPPORT_TEMP_FOLDER); 109 | if (!Directory.Exists(_supportsPath)) { 110 | Directory.CreateDirectory(_supportsPath); 111 | } 112 | string data = UBHPrefs.GetString(UnityBuilderHelper.HUAWEI_DEPS_KEY); 113 | if (!string.IsNullOrEmpty(data)) { 114 | string[] list = data.Split(','); 115 | CreateGradleFiles(list); 116 | } 117 | // string[] list = new string[9] { "com.huawei.hms:base:6.3.0.303", "com.huawei.hms:hwid:6.4.0.300", "com.huawei.agconnect:agconnect-auth:1.6.4.300", "com.huawei.agconnect:agconnect-auth-huawei:1.6.4.300", "com.huawei.hms:iap:6.3.0.300", "com.huawei.hms:push:6.3.0.302", "com.huawei.hms:ads:3.4.52.302", "com.huawei.hms:ads-identifier:3.4.39.302", "com.huawei.hms:game:5.0.4.303" }; 118 | #endif 119 | } 120 | 121 | /// 122 | /// Callback if error occurs 123 | /// 124 | private void OnBuildError(string condition, string stackTrace, LogType type) { 125 | if (type == LogType.Error) { 126 | Application.logMessageReceived -= OnBuildError; 127 | } 128 | } 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /Editor/UBHPrefs.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | 3 | using System; 4 | using System.Globalization; 5 | using System.IO; 6 | using NiceJson; 7 | using UnityEngine; 8 | 9 | namespace Mopsicus.UBH { 10 | 11 | /// 12 | /// UBH editor prefs manager 13 | /// 14 | public static class UBHPrefs { 15 | 16 | /// 17 | /// Path mask to save prefs file 18 | /// 19 | const string STORAGE_PATH = "{0}/../ProjectSettings/UBHPrefs.txt"; 20 | 21 | /// 22 | /// Path to storage 23 | /// 24 | static string _storageFile = null; 25 | 26 | /// 27 | /// Data cache 28 | /// 29 | static JsonObject _data = null; 30 | 31 | /// 32 | /// Constructor 33 | /// 34 | static UBHPrefs() { } 35 | 36 | /// 37 | /// Load data 38 | /// 39 | static void Load() { 40 | if (string.IsNullOrEmpty(_storageFile)) { 41 | _storageFile = string.Format(STORAGE_PATH, Application.dataPath); 42 | } 43 | if (_data != null) { 44 | return; 45 | } 46 | try { 47 | string content = File.ReadAllText(_storageFile); 48 | _data = (JsonObject)JsonNode.ParseJsonString(content); 49 | } catch { 50 | _data = new JsonObject(); 51 | } 52 | } 53 | 54 | /// 55 | /// Save data 56 | /// 57 | static void Save() { 58 | try { 59 | File.WriteAllText(_storageFile, _data.ToJsonString()); 60 | } catch (Exception e) { 61 | Debug.LogErrorFormat("UBHPrefs save: {0}", e); 62 | } 63 | } 64 | 65 | /// 66 | /// Is store contains key 67 | /// 68 | /// Key 69 | public static bool Has(string key) { 70 | if (string.IsNullOrEmpty(key)) { 71 | throw new UnityException("Key is missing"); 72 | } 73 | Load(); 74 | return _data.ContainsKey(key); 75 | } 76 | 77 | /// 78 | /// Clear all 79 | /// 80 | public static void Clear() { 81 | if (string.IsNullOrEmpty(_storageFile)) { 82 | _storageFile = string.Format(STORAGE_PATH, Application.dataPath); 83 | } 84 | _data.Clear(); 85 | Save(); 86 | } 87 | 88 | /// 89 | /// Delete key 90 | /// 91 | /// Key 92 | public static void Delete(string key) { 93 | Load(); 94 | if (Has(key)) { 95 | _data.Remove(key); 96 | } 97 | Save(); 98 | } 99 | 100 | /// 101 | /// Set string value 102 | /// 103 | /// Key 104 | /// Data 105 | public static void SetString(string key, string data) { 106 | Has(key); 107 | _data[key] = data; 108 | Save(); 109 | } 110 | 111 | /// 112 | /// Get string value 113 | /// 114 | /// Key 115 | /// Default value 116 | public static string GetString(string key, string defaultValue = "") { 117 | return Has(key) ? (string)_data[key] : defaultValue; 118 | } 119 | 120 | /// 121 | /// Set float value 122 | /// 123 | /// Key 124 | /// Data 125 | public static void SetFloat(string key, float data) { 126 | SetString(key, data.ToString(NumberFormatInfo.InvariantInfo)); 127 | } 128 | 129 | /// 130 | /// Get float value 131 | /// 132 | /// Key 133 | /// Default value 134 | public static float GetFloat(string key, float defaultValue = 0f) { 135 | if (Has(key)) { 136 | float value = 0f; 137 | if (float.TryParse(_data[key], NumberStyles.Float, NumberFormatInfo.InvariantInfo, out value)) { 138 | return value; 139 | } 140 | } 141 | return defaultValue; 142 | } 143 | 144 | /// 145 | /// Set int value 146 | /// 147 | /// Key 148 | /// Data 149 | public static void SetInt(string key, int data) { 150 | SetString(key, data.ToString(NumberFormatInfo.InvariantInfo)); 151 | } 152 | 153 | /// 154 | /// Get int value 155 | /// 156 | /// Key 157 | /// Default value 158 | public static int GetInt(string key, int defaultValue = 0) { 159 | if (Has(key)) { 160 | int value = 0; 161 | if (int.TryParse(_data[key], out value)) { 162 | return value; 163 | } 164 | } 165 | return defaultValue; 166 | } 167 | 168 | /// 169 | /// Get bool value 170 | /// 171 | /// Key 172 | /// Default value 173 | public static bool GetBool(string key, bool defaultValue = false) { 174 | return GetInt(key, defaultValue ? 1 : 0) != 0; 175 | } 176 | 177 | /// 178 | /// Set bool value 179 | /// 180 | /// Key 181 | /// Data 182 | public static void SetBool(string key, bool data) { 183 | SetInt(key, data ? 1 : 0); 184 | } 185 | } 186 | } 187 | 188 | #endif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | Logo 16 | 17 | 18 |

Unity Builder Helper

19 | 20 |

21 | Unity editor tool to manage and build projects 22 |
23 | Report Bug 24 | · 25 | Request Feature 26 |

27 |
28 | 29 | 30 |
31 | Table of Contents 32 |
    33 |
  1. 34 | About The Project 35 | 40 |
  2. 41 |
  3. 42 | Getting Started 43 | 48 |
  4. 49 |
  5. 50 | Usage 51 | 54 |
  6. 55 |
  7. Roadmap
  8. 56 |
  9. Contributing
  10. 57 |
  11. License
  12. 58 |
  13. Contact
  14. 59 |
60 |
61 | 62 | 63 | 64 | 65 | ## About The Project 66 | 67 | Unity Builder Helper provide tools for build projects, control and fast switching build targets and defines. UBH use Unity Builder Bot (UBB) for remote builds. 68 | 69 | ### Screenshots 70 | ![UBH](/Images/window-1.png "UBH") 71 | ![Settings](/Images/window-2.png "Settings") 72 | 73 | ### Features 74 | 75 | * Change build target 76 | * Set defines 77 | * Set build directory for target 78 | * Change version and build 79 | * Validate and set keystore 80 | * Add locales and `*.lproj` files to iOS builds 81 | * Add external files and frameworks to iOS builds 82 | * Add `google-services.json` and `agconnect-services.json` 83 | * Patch `AndroidManifest.xml` 84 | * Patch gradle files 85 | * Add dependencies to Huawei build 86 | * Add external files to Android build 87 | * Build project 88 | 89 | ### Features with Unity Builder Bot 90 | * Update repository before build 91 | * Build Unity project 92 | * Build Xcode project 93 | * Compile, archive and export to IPA with configurated manifest 94 | * Generate HTML with install links 95 | * Upload all objects to remote server via sshpass 96 | * Get build logs for project 97 | * Clear project's logs and builds 98 | 99 |

(back to top)

100 | 101 | 102 | ## Getting Started 103 | 104 | UBH is Unity editor tool without dependencies*. Just add package to your project, create config file and use. 105 | 106 | ### Requirements 107 | 1. Unity 2020+ 108 | 2. NiceJson (in package)* 109 | 3. Mac OS for building Xcode projects 110 | 4. The steady hands 111 | 112 | ### Installation 113 | 114 | Get it from [releases page](https://github.com/mopsicus/ubh/releases) or add the line below to `Packages/manifest.json` and this module will be installed directly from git url: 115 | ``` 116 | "com.mopsicus.ubh": "https://github.com/mopsicus/ubh.git", 117 | ``` 118 | 119 | ### Setup 120 | 121 | 1. Open UBH in Unity Editor via hotkey `cmd+g` or menu `Tools -> Unity Builder Helper` 122 | 2. Tap "Settings" button 123 | 3. Fill settings and save 124 | 125 |

(back to top)

126 | 127 | 128 | 129 | ## Usage 130 | 131 | 1. Open UBH in Unity Editor via hotkey `cmd+g` or menu `Tools -> Unity Builder Helper` 132 | 2. Select platform, loggers and press Apply 133 | 3. Run local or remote build 134 | 135 | ### Support files 136 | 137 | You should store your support files in project root folder "Support files"; 138 | 139 | ``` 140 | -Project 141 | --Support files 142 | ---Android 143 | ----drawable 144 | ----values 145 | ----... 146 | ---iOS 147 | ----Locales 148 | -----en.lproj 149 | -----ru.lproj 150 | -----es.lproj 151 | -----.... 152 | ----splash.png 153 | ----splash.storyboard 154 | ----.... 155 | ``` 156 | 157 | All files in this folder will be added to project. For example, see [demo project](https://github.com/mopsicus/ubh-demo). 158 | 159 |

(back to top)

160 | 161 | 162 | ## Roadmap 163 | 164 | - [ ] Add WebGL build 165 | 166 | See the [open issues](https://github.com/mopsicus/unity-builder-helper/issues) for a full list of proposed features (and known issues). 167 | 168 |

(back to top)

169 | 170 | 171 | 172 | 173 | ## Contributing 174 | 175 | Contributions are what make the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**. 176 | 177 | If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement". 178 | Don't forget to give the project a star! Thanks again! 179 | 180 | 1. Fork the Project 181 | 2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) 182 | 3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) 183 | 4. Push to the Branch (`git push origin feature/AmazingFeature`) 184 | 5. Open a Pull Request 185 | 186 |

(back to top)

187 | 188 | 189 | 190 | 191 | ## License 192 | 193 | Distributed under the MIT License. See `LICENSE` file for more information. 194 | 195 |

(back to top)

196 | 197 | 198 | 199 | 200 | ## Contact 201 | 202 | Mopsicus: mail@mopsicus.ru 203 | 204 | Website: https://mopsicus.ru 205 | 206 | Telegram: https://t.me/mopsicus 207 | 208 | Project Link: [https://github.com/mopsicus/unity-builder-helper](https://github.com/mopsicus/unity-builder-helper) 209 | 210 |

(back to top)

211 | -------------------------------------------------------------------------------- /Editor/PlatformValidator.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using NiceJson; 3 | using UnityEditor; 4 | using UnityEngine; 5 | using Debug = UnityEngine.Debug; 6 | 7 | namespace Mopsicus.UBH { 8 | 9 | /// 10 | /// Platforms type 11 | /// 12 | public enum PlatformType { 13 | UNKNOWN = -1, 14 | GOOGLE = 0, 15 | IOS = 1, 16 | HUAWEI = 2, 17 | WEB = 3 18 | } 19 | 20 | [InitializeOnLoad] 21 | public class PlatformValidator : Editor { 22 | 23 | #if UNITY_EDITOR 24 | /// 25 | /// Key for save/load last platform 26 | /// 27 | private const string KEY = "last_platform"; 28 | 29 | /// 30 | /// Config for Huawei services 31 | /// 32 | private const string HUAWEI_CONFIG = "agconnect-services.json"; 33 | 34 | /// 35 | /// Config for Google services 36 | /// 37 | private const string GOOGLE_CONFIG = "google-services.json"; 38 | 39 | /// 40 | /// Cached last validated platform 41 | /// 42 | private static PlatformType _lastValidatedPlatform = PlatformType.UNKNOWN; 43 | 44 | /// 45 | /// Callback on editor validate 46 | /// 47 | static PlatformValidator() { 48 | #if GOOGLE || HUAWEI 49 | SetKeyStore(); 50 | #endif 51 | _lastValidatedPlatform = (PlatformType)UBHPrefs.GetInt(KEY, (int)PlatformType.UNKNOWN); 52 | #if HUAWEI 53 | if (_lastValidatedPlatform != PlatformType.HUAWEI) { 54 | bool isHuaweiServices = UBHPrefs.GetBool(UnityBuilderHelper.HUAWEI_SERVICES_KEY, false); 55 | if (isHuaweiServices && !File.Exists(Path.Combine(Application.dataPath, "StreamingAssets", HUAWEI_CONFIG))) { 56 | Debug.LogErrorFormat(string.Format("Huawei config \"{0}\" not found in StreamingAssets!", HUAWEI_CONFIG)); 57 | return; 58 | } 59 | if (!SetKeyStore(true)) { 60 | return; 61 | } 62 | _lastValidatedPlatform = PlatformType.HUAWEI; 63 | UBHPrefs.SetInt(KEY, (int)_lastValidatedPlatform); 64 | Debug.Log("Platform validated for Huawei"); 65 | if (!PlayerSettings.applicationIdentifier.Contains(".huawei")) { 66 | PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, string.Format("{0}.huawei", PlayerSettings.applicationIdentifier)); 67 | Debug.LogFormat("Bundle changed to: {0}", PlayerSettings.applicationIdentifier); 68 | } 69 | } 70 | #elif GOOGLE 71 | if (_lastValidatedPlatform != PlatformType.GOOGLE) { 72 | bool isGoogleServices = UBHPrefs.GetBool(UnityBuilderHelper.GOOGLE_SERVICES_KEY, false); 73 | if (isGoogleServices && !File.Exists(Path.Combine(Application.dataPath, "StreamingAssets", GOOGLE_CONFIG))) { 74 | Debug.LogErrorFormat("Google config \"{0}\" not found in StreamingAssets!", GOOGLE_CONFIG); 75 | return; 76 | } 77 | if (!SetKeyStore(true)) { 78 | return; 79 | } 80 | _lastValidatedPlatform = PlatformType.GOOGLE; 81 | UBHPrefs.SetInt(KEY, (int)_lastValidatedPlatform); 82 | Debug.Log("Platform validated for Google"); 83 | if (PlayerSettings.applicationIdentifier.Contains(".huawei")) { 84 | PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, PlayerSettings.applicationIdentifier.Replace(".huawei", "")); 85 | Debug.LogFormat("Bundle changed to: {0}", PlayerSettings.applicationIdentifier); 86 | } 87 | } 88 | #elif UNITY_IOS 89 | if (_lastValidatedPlatform != PlatformType.IOS) { 90 | _lastValidatedPlatform = PlatformType.IOS; 91 | UBHPrefs.SetInt(KEY, (int)_lastValidatedPlatform); 92 | Debug.Log("Platform validated for iOS"); 93 | } 94 | #endif 95 | } 96 | 97 | /// 98 | /// Set keystore data to settings 99 | /// 100 | /// Refresh data 101 | public static bool SetKeyStore(bool isForceUpdate = false) { 102 | if (isForceUpdate) { 103 | PlayerSettings.Android.keystorePass = ""; 104 | } 105 | if (!string.IsNullOrEmpty(PlayerSettings.Android.keystorePass)) { 106 | return true; 107 | } 108 | #if GOOGLE 109 | bool isGoogleKeystore = UBHPrefs.GetBool(UnityBuilderHelper.GOOGLE_KEYSTORE_KEY, false); 110 | if (!isGoogleKeystore) { 111 | return false; 112 | } 113 | if (string.IsNullOrEmpty(UBHPrefs.GetString(UnityBuilderHelper.GOOGLE_PASSWORD_KEY))) { 114 | Debug.LogError("KeyStore object in Config not completed"); 115 | return false; 116 | } 117 | PlayerSettings.Android.keystoreName = UBHPrefs.GetString(UnityBuilderHelper.GOOGLE_PATH_KEY); 118 | PlayerSettings.Android.keystorePass = UBHPrefs.GetString(UnityBuilderHelper.GOOGLE_PASSWORD_KEY); 119 | PlayerSettings.Android.keyaliasName = UBHPrefs.GetString(UnityBuilderHelper.GOOGLE_ALIAS_KEY); 120 | PlayerSettings.Android.keyaliasPass = UBHPrefs.GetString(UnityBuilderHelper.GOOGLE_APASS_KEY); 121 | Debug.LogFormat("Keystore file loaded from: {0}", UBHPrefs.GetString(UnityBuilderHelper.GOOGLE_PATH_KEY)); 122 | #elif HUAWEI 123 | bool isHuaweiKeystore = UBHPrefs.GetBool(UnityBuilderHelper.HUAWEI_KEYSTORE_KEY, false); 124 | if (!isHuaweiKeystore) { 125 | return false; 126 | } 127 | if (string.IsNullOrEmpty(UBHPrefs.GetString(UnityBuilderHelper.HUAWEI_PASSWORD_KEY))) { 128 | Debug.LogError("KeyStore object in Config not completed"); 129 | return false; 130 | } 131 | PlayerSettings.Android.keystoreName = UBHPrefs.GetString(UnityBuilderHelper.HUAWEI_PATH_KEY); 132 | PlayerSettings.Android.keystorePass = UBHPrefs.GetString(UnityBuilderHelper.HUAWEI_PASSWORD_KEY); 133 | PlayerSettings.Android.keyaliasName = UBHPrefs.GetString(UnityBuilderHelper.HUAWEI_ALIAS_KEY); 134 | PlayerSettings.Android.keyaliasPass = UBHPrefs.GetString(UnityBuilderHelper.HUAWEI_APASS_KEY); 135 | Debug.LogFormat("Keystore file loaded from: {0}", UBHPrefs.GetString(UnityBuilderHelper.HUAWEI_PATH_KEY)); 136 | #endif 137 | return true; 138 | } 139 | 140 | #endif 141 | 142 | } 143 | 144 | } -------------------------------------------------------------------------------- /Editor/PreBuildManifestWorker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Xml; 5 | using NiceJson; 6 | using UnityEditor.Android; 7 | using UnityEngine; 8 | 9 | namespace Mopsicus.UBH { 10 | 11 | public class PreBuildManifestWorker : IPostGenerateGradleAndroidProject { 12 | 13 | /// 14 | /// Huawei config JSON 15 | /// 16 | const string HUAWEI_CONFIG = "agconnect-services.json"; 17 | 18 | /// 19 | /// Method runs after gradle generated, before build 20 | /// 21 | public void OnPostGenerateGradleAndroidProject(string basePath) { 22 | AndroidManifest manifest = new AndroidManifest(GetManifestPath(basePath)); 23 | manifest.SetHardwareAcceleration(); 24 | #if HUAWEI 25 | manifest.RemoveUsesPermission("bluetooth"); 26 | manifest.RemoveUsesPermission("camera"); 27 | manifest.RemoveUsesPermission("read_calendar"); 28 | manifest.RemoveUsesPermission("write_calendar"); 29 | manifest.RemoveUsesPermission("access_wifi_state"); 30 | manifest.RemoveUsesPermission("query_all_packages"); 31 | string data = File.ReadAllText(Path.Combine(Application.streamingAssetsPath, HUAWEI_CONFIG)); 32 | try { 33 | JsonObject json = (JsonObject)JsonNode.ParseJsonString(data); 34 | manifest.AddMetaData("com.huawei.hms.client.appid", string.Format("appid={0}", json["client"]["app_id"])); 35 | manifest.AddMetaData("com.huawei.hms.client.cpid", string.Format("cpid={0}", json["client"]["cp_id"])); 36 | } catch (Exception e) { 37 | Debug.LogErrorFormat("Can't get Huawei config data for manifest: {0}", e.Message); 38 | } 39 | #endif 40 | manifest.Save(); 41 | } 42 | 43 | /// 44 | /// Callback (need for CLI) 45 | /// 46 | public int callbackOrder { 47 | get { 48 | return 1; 49 | } 50 | } 51 | 52 | /// 53 | /// Cached manifest path 54 | /// 55 | private string _manifestFilePath = null; 56 | 57 | /// 58 | /// Get manifest file 59 | /// 60 | private string GetManifestPath(string basePath) { 61 | if (string.IsNullOrEmpty(_manifestFilePath)) { 62 | StringBuilder pathBuilder = new StringBuilder(basePath); 63 | pathBuilder.Append(Path.DirectorySeparatorChar).Append("src"); 64 | pathBuilder.Append(Path.DirectorySeparatorChar).Append("main"); 65 | pathBuilder.Append(Path.DirectorySeparatorChar).Append("AndroidManifest.xml"); 66 | _manifestFilePath = pathBuilder.ToString(); 67 | } 68 | return _manifestFilePath; 69 | } 70 | } 71 | 72 | internal class AndroidXmlDocument : XmlDocument { 73 | 74 | /// 75 | /// Cached path 76 | /// 77 | private string _path = null; 78 | 79 | /// 80 | /// Namespace manager 81 | /// 82 | protected XmlNamespaceManager _namespaceManager = null; 83 | 84 | /// 85 | /// XML namespace 86 | /// 87 | public readonly string AndroidXmlNamespace = "http://schemas.android.com/apk/res/android"; 88 | 89 | /// 90 | /// Tools namespace 91 | /// 92 | public readonly string ToolsXmlNamespace = "http://schemas.android.com/tools"; 93 | 94 | /// 95 | /// Constructor 96 | /// 97 | /// Path for document 98 | public AndroidXmlDocument(string path) { 99 | _path = path; 100 | using (var reader = new XmlTextReader(_path)) { 101 | reader.Read(); 102 | Load(reader); 103 | } 104 | _namespaceManager = new XmlNamespaceManager(NameTable); 105 | _namespaceManager.AddNamespace("android", AndroidXmlNamespace); 106 | } 107 | 108 | /// 109 | /// Save file 110 | /// 111 | public string Save() { 112 | return SaveAs(_path); 113 | } 114 | 115 | /// 116 | /// Save file as 117 | /// 118 | /// Path to save 119 | public string SaveAs(string path) { 120 | using (XmlTextWriter writer = new XmlTextWriter(path, new UTF8Encoding(false))) { 121 | writer.Formatting = Formatting.Indented; 122 | Save(writer); 123 | } 124 | return path; 125 | } 126 | } 127 | 128 | internal class AndroidManifest : AndroidXmlDocument { 129 | 130 | /// 131 | /// Application element 132 | /// 133 | private readonly XmlElement _application = null; 134 | 135 | /// 136 | /// Root manifest element 137 | /// 138 | private readonly XmlElement _root = null; 139 | 140 | /// 141 | /// Constructor 142 | /// 143 | /// Path to manifest 144 | public AndroidManifest(string path) : base(path) { 145 | _root = SelectSingleNode("/manifest") as XmlElement; 146 | _application = SelectSingleNode("/manifest/application") as XmlElement; 147 | } 148 | 149 | /// 150 | /// Create new attribute to manifest 151 | /// 152 | private XmlAttribute CreateAndroidAttribute(string key, string value) { 153 | XmlAttribute attr = CreateAttribute("android", key, AndroidXmlNamespace); 154 | attr.Value = value; 155 | return attr; 156 | } 157 | 158 | /// 159 | /// Get main activity 160 | /// 161 | internal XmlNode GetActivityWithLaunchIntent() { 162 | return SelectSingleNode("/manifest/application/activity[intent-filter/action/@android:name='android.intent.action.MAIN' and intent-filter/category/@android:name='android.intent.category.LAUNCHER']", _namespaceManager); 163 | } 164 | 165 | /// 166 | /// Change app theme 167 | /// 168 | internal void SetApplicationTheme(string theme) { 169 | Debug.LogFormat("App theme set to {0}", theme); 170 | _application.Attributes.Append(CreateAndroidAttribute("theme", theme)); 171 | } 172 | 173 | /// 174 | /// Set starting activity 175 | /// 176 | internal void SetStartingActivityName(string name) { 177 | Debug.LogFormat("Start activity set to {0}", name); 178 | GetActivityWithLaunchIntent().Attributes.Append(CreateAndroidAttribute("name", name)); 179 | } 180 | 181 | /// 182 | /// Enable hardware acceleration 183 | /// 184 | internal void SetHardwareAcceleration() { 185 | Debug.Log("Set hardware acceleration done"); 186 | GetActivityWithLaunchIntent().Attributes.Append(CreateAndroidAttribute("hardwareAccelerated", "true")); 187 | } 188 | 189 | /// 190 | /// Add meta data to activity 191 | /// 192 | internal void AddMetaData(string name, string value) { 193 | Debug.LogFormat("Add meta data to manifest: name = {0}, value = {1}", name, value); 194 | XmlNode node = CreateNode(XmlNodeType.Element, "meta-data", null); 195 | node.Attributes.Append(CreateAndroidAttribute("name", name)); 196 | node.Attributes.Append(CreateAndroidAttribute("value", value)); 197 | _application.AppendChild(node); 198 | } 199 | 200 | /// 201 | /// Set authorities for file provider 202 | /// 203 | /// App name 204 | internal void SetProviderAuthorities(string name) { 205 | XmlNode node = SelectSingleNode("/manifest/application/provider[meta-data/@android:name='android.support.FILE_PROVIDER_PATHS']", _namespaceManager); 206 | if (node == null) { 207 | Debug.LogError("Can't find in manifest"); 208 | } else { 209 | Debug.LogFormat("Set provider authorities: {0}", name); 210 | node.Attributes.Append(CreateAndroidAttribute("authorities", string.Format("{0}.fileprovider", name))); 211 | } 212 | } 213 | 214 | /// 215 | /// Remove permission from manifest on merge 216 | /// 217 | /// Permission name 218 | internal void RemoveUsesPermission(string name) { 219 | XmlNode node = SelectSingleNode(string.Format("/manifest[uses-permission/@android:name='android.permission.{0}']", name.ToUpperInvariant()), _namespaceManager); 220 | if (node == null) { 221 | Debug.LogFormat("Add uses-permission permission to manifest: name = {0}", name); 222 | node = CreateNode(XmlNodeType.Element, "uses-permission", null); 223 | node.Attributes.Append(CreateAndroidAttribute("name", string.Format("android.permission.{0}", name.ToUpperInvariant()))); 224 | _root.AppendChild(node); 225 | } 226 | Debug.LogFormat("Add tools remove permission action to manifest: name = {0}", name); 227 | XmlAttribute remover = CreateAttribute("tools", "node", ToolsXmlNamespace); 228 | remover.Value = "remove"; 229 | node.Attributes.Append(remover); 230 | } 231 | 232 | } 233 | 234 | } -------------------------------------------------------------------------------- /Editor/PostBuildWorker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using UnityEditor; 4 | using UnityEditor.Callbacks; 5 | using UnityEngine; 6 | using System.Collections.Generic; 7 | using NiceJson; 8 | #if UNITY_IOS 9 | using UnityEditor.iOS.Xcode; 10 | #endif 11 | 12 | namespace Mopsicus.UBH { 13 | 14 | /// 15 | /// Proccess data after build 16 | /// Folder for libs in root, Frameworks 17 | /// 18 | public class PostBuildWorker { 19 | 20 | /// 21 | /// Support files directory 22 | /// 23 | private const string SUPPORT_FILES_FOLDER = "SupportFiles/iOS"; 24 | 25 | /// 26 | /// Directory for iOS locales 27 | /// 28 | private const string LOCALES_FOLDER = "Locales"; 29 | 30 | /// 31 | /// Run after project build 32 | /// Save version.txt file for build shell script 33 | /// 34 | /// Platform 35 | /// Path to folder 36 | [PostProcessBuild] 37 | public static void PostProcess(BuildTarget buildTarget, string pathToBuiltProject) { 38 | if (buildTarget == BuildTarget.iOS) { 39 | #if UNITY_IOS 40 | FixPlist(pathToBuiltProject); 41 | AddFrameworks(pathToBuiltProject); 42 | AddSupportFiles(pathToBuiltProject); 43 | AddLanguages(pathToBuiltProject); 44 | AddLocalization(pathToBuiltProject); 45 | #endif 46 | try { 47 | int build = int.Parse(PlayerSettings.iOS.buildNumber); 48 | build++; 49 | PlayerSettings.iOS.buildNumber = build.ToString(); 50 | Debug.LogFormat("New build number: {0}", build); 51 | } catch (Exception e) { 52 | Debug.LogErrorFormat("Error on setup new build: {0}. Error: {1}", PlayerSettings.iOS.buildNumber, e.Message); 53 | } 54 | } else if (buildTarget == BuildTarget.Android) { 55 | PlayerSettings.Android.bundleVersionCode++; 56 | Debug.LogFormat("New bundle version code: {0}", PlayerSettings.Android.bundleVersionCode); 57 | } 58 | } 59 | 60 | #if UNITY_IOS 61 | /// 62 | /// Add params to Info.plist 63 | /// 64 | /// Path to folder 65 | private static void FixPlist(string path) { 66 | string plistPath = Path.Combine(path, "Info.plist"); 67 | PlistDocument plist = new PlistDocument(); 68 | plist.ReadFromFile(plistPath); 69 | PlistElementDict rootDict = plist.root; 70 | string data = UBHPrefs.GetString(UnityBuilderHelper.PLIST_KEY); 71 | if (string.IsNullOrEmpty(data)) { 72 | return; 73 | } 74 | JsonArray list = (JsonArray)JsonNode.ParseJsonString(data); 75 | foreach (JsonObject item in list) { 76 | rootDict.SetString(item["key"], item["value"]); 77 | } 78 | plist.WriteToFile(plistPath); 79 | Debug.Log("Plist fixed"); 80 | } 81 | 82 | /// 83 | /// Add external files to project 84 | /// 85 | /// Path to folder 86 | private static void AddSupportFiles(string path) { 87 | string projectPath = PBXProject.GetPBXProjectPath(path); 88 | PBXProject project = new PBXProject(); 89 | string file = File.ReadAllText(projectPath); 90 | project.ReadFromString(file); 91 | string target = project.GetUnityMainTargetGuid(); 92 | string data = UBHPrefs.GetString(UnityBuilderHelper.SUPPORT_FILES_KEY); 93 | if (string.IsNullOrEmpty(data)) { 94 | return; 95 | } 96 | string[] list = data.Split(','); 97 | foreach (string item in list) { 98 | AddFileToRoot(project, path, target, item.Trim()); 99 | } 100 | } 101 | 102 | /// 103 | /// Add file to Xcode project 104 | /// 105 | /// Project 106 | /// Project path 107 | /// Target GUID 108 | /// File to add 109 | static void AddFileToRoot(PBXProject project, string path, string target, string fileName) { 110 | CopyAndReplaceFile(SUPPORT_FILES_FOLDER, path, fileName); 111 | string name = project.AddFile(fileName, fileName, PBXSourceTree.Source); 112 | project.AddFileToBuild(target, name); 113 | Debug.LogFormat("File \"{0}\" added to project", fileName); 114 | } 115 | 116 | /// 117 | /// Copy file 118 | /// 119 | /// Dir copy from 120 | /// Dir copy to 121 | /// File to copy 122 | static void CopyAndReplaceFile(string source, string distination, string fileName) { 123 | if (File.Exists(Path.Combine(distination, fileName))) { 124 | File.Delete(Path.Combine(distination, fileName)); 125 | } 126 | File.Copy(Path.Combine(source, fileName), Path.Combine(distination, fileName)); 127 | } 128 | 129 | /// 130 | /// Add languages to project 131 | /// 132 | /// Path to folder 133 | static void AddLanguages(string path) { 134 | string projectPath = PBXProject.GetPBXProjectPath(path); 135 | PBXProject project = new PBXProject(); 136 | project.ReadFromFile(projectPath); 137 | project.ClearKnownRegions(); 138 | PlistDocument plist = new PlistDocument(); 139 | string plistPath = Path.Combine(path, "Info.plist"); 140 | plist.ReadFromFile(plistPath); 141 | PlistElementArray languages = plist.root.CreateArray("CFBundleLocalizations"); 142 | string data = UBHPrefs.GetString(UnityBuilderHelper.LOCALES_KEY); 143 | if (!string.IsNullOrEmpty(data)) { 144 | string[] list = data.Split(','); 145 | foreach (string code in list) { 146 | project.AddKnownRegion(code.Trim()); 147 | languages.AddString(code.Trim()); 148 | Debug.LogFormat("Language \"{0}\" added to project", code.Trim()); 149 | } 150 | } 151 | plist.WriteToFile(plistPath); 152 | project.WriteToFile(projectPath); 153 | } 154 | 155 | /// 156 | /// Add localization to project *.lproj 157 | /// 158 | /// Path to folder 159 | /// File to localize 160 | static void AddLocalization(string path, string infoFile = "InfoPlist.strings") { 161 | string projectPath = PBXProject.GetPBXProjectPath(path); 162 | PBXProject project = new PBXProject(); 163 | project.ReadFromFile(projectPath); 164 | string data = UBHPrefs.GetString(UnityBuilderHelper.LOCALES_KEY); 165 | if (!string.IsNullOrEmpty(data)) { 166 | string[] list = data.Split(','); 167 | foreach (string code in list) { 168 | string langDir = string.Format("{0}.lproj", code.Trim()); 169 | string directory = Path.Combine(path, langDir); 170 | Directory.CreateDirectory(directory); 171 | string filePath = Path.Combine(directory, infoFile); 172 | string relativePath = Path.Combine(langDir, infoFile); 173 | string sourcePath = Path.Combine(SUPPORT_FILES_FOLDER, LOCALES_FOLDER, langDir, infoFile); 174 | string source = File.ReadAllText(sourcePath); 175 | File.WriteAllText(filePath, source); 176 | project.AddLocaleVariantFile(infoFile, code.Trim(), relativePath); 177 | Debug.LogFormat("Localization \"{0}\" added to project", langDir); 178 | } 179 | } 180 | project.WriteToFile(projectPath); 181 | } 182 | 183 | /// 184 | /// Connect external libs 185 | /// 186 | /// Path to folder 187 | private static void AddFrameworks(string path) { 188 | string projectPath = PBXProject.GetPBXProjectPath(path); 189 | PBXProject project = new PBXProject(); 190 | string file = File.ReadAllText(projectPath); 191 | project.ReadFromString(file); 192 | string target = project.GetUnityMainTargetGuid(); 193 | string managerFile = string.Format("Unity-iPhone/{0}.entitlements", UBHPrefs.GetString(UnityBuilderHelper.GAME_TITLE_KEY)); 194 | ProjectCapabilityManager manager = new ProjectCapabilityManager(projectPath, managerFile, "Unity-iPhone", target); 195 | if (UBHPrefs.GetBool(UnityBuilderHelper.PURCHASE_KEY)) { 196 | project.AddFrameworkToProject(target, "StoreKit.framework", false); 197 | manager.AddInAppPurchase(); 198 | } 199 | if (UBHPrefs.GetBool(UnityBuilderHelper.SIGN_KEY)) { 200 | manager.AddSignInWithApple(); 201 | } 202 | if (UBHPrefs.GetBool(UnityBuilderHelper.PUSH_KEY)) { 203 | manager.AddBackgroundModes(BackgroundModesOptions.RemoteNotifications); 204 | #if CLIENT_DEBUG 205 | manager.AddPushNotifications(true); 206 | #else 207 | manager.AddPushNotifications(false); 208 | #endif 209 | } 210 | manager.WriteToFile(); 211 | project.AddFile(managerFile, managerFile); 212 | project.AddBuildProperty(target, "CODE_SIGN_ENTITLEMENTS", managerFile); 213 | string targetFramework = project.GetUnityFrameworkTargetGuid(); 214 | string data = UBHPrefs.GetString(UnityBuilderHelper.FRAMEWORKS_KEY); 215 | if (!string.IsNullOrEmpty(data)) { 216 | string[] list = data.Split(','); 217 | foreach (string framework in list) { 218 | project.AddFrameworkToProject(targetFramework, string.Format("{0}.framework", framework.Trim()), false); 219 | } 220 | } 221 | #if CLIENT_DEBUG 222 | project.SetBuildProperty(target, "ENABLE_BITCODE", "NO"); 223 | project.SetBuildProperty(targetFramework, "ENABLE_BITCODE", "NO"); 224 | #endif 225 | project.WriteToFile(projectPath); 226 | Debug.Log("Frameworks added"); 227 | } 228 | #endif 229 | 230 | } 231 | 232 | } -------------------------------------------------------------------------------- /Editor/PBXProjectExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Reflection; 4 | using UnityEditor.iOS.Xcode; 5 | 6 | namespace Mopsicus.UBH { 7 | 8 | static class PBXProjectExtensions { 9 | 10 | readonly static Type _guidList; 11 | readonly static Type _pbxBuildFileData; 12 | readonly static Type _pbxElementArray; 13 | readonly static Type _pbxElementString; 14 | readonly static Type _pbxVariantGroupData; 15 | readonly static FieldInfo _dataFileGroups; 16 | readonly static FieldInfo _dataFileRefsField; 17 | readonly static FieldInfo _knownRegionsDict; 18 | readonly static FieldInfo _groupChildren; 19 | readonly static FieldInfo _groupName; 20 | readonly static FieldInfo _groupPath; 21 | readonly static FieldInfo _pbxObjectGUID; 22 | readonly static FieldInfo _pbxElementArrayValues; 23 | readonly static FieldInfo _projectData; 24 | readonly static FieldInfo _resourceFiles; 25 | readonly static FieldInfo _variantGroupName; 26 | readonly static PropertyInfo _fileRefsPath; 27 | readonly static PropertyInfo _projectResoruces; 28 | readonly static PropertyInfo _projectSection; 29 | readonly static PropertyInfo _projectSectionObjectData; 30 | readonly static PropertyInfo _projectVariantGroups; 31 | readonly static MethodInfo _dataFileRefsFieldObjects; 32 | readonly static MethodInfo _fileRefDataCreateFromFile; 33 | readonly static MethodInfo _getPropertiesRaw; 34 | readonly static MethodInfo _groupsObjects; 35 | readonly static MethodInfo _guidListAdd; 36 | readonly static MethodInfo _guidListContains; 37 | readonly static MethodInfo _pbxBuildFileDataCreateFromFile; 38 | readonly static MethodInfo _projectBuildFilesAdd; 39 | readonly static MethodInfo _projectFileRefsAdd; 40 | readonly static MethodInfo _projectBuildFilesGetForSourceFile; 41 | readonly static MethodInfo _rawPropertiesValuesAddValue; 42 | readonly static MethodInfo _rawPropertiesValuesGetValue; 43 | readonly static MethodInfo _resorucesObjects; 44 | readonly static MethodInfo _variantGroupsAddEntry; 45 | readonly static MethodInfo _variantGroupsObjects; 46 | readonly static MethodInfo _variantGroupsSetPropertyString; 47 | 48 | static PBXProjectExtensions() { 49 | 50 | /// 51 | /// Namespace for assembly 52 | /// 53 | const string PBX_NAMESPACE = "UnityEditor.iOS.Xcode.PBX"; 54 | 55 | /// 56 | /// Assembly 57 | /// 58 | Assembly assembly = typeof(PBXProject).Assembly; 59 | 60 | /// 61 | /// Flags list for reflection 62 | /// 63 | const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic; 64 | 65 | _guidList = assembly.GetType($"{PBX_NAMESPACE}.GUIDList"); 66 | _pbxBuildFileData = assembly.GetType($"{PBX_NAMESPACE}.PBXBuildFileData"); 67 | _pbxElementArray = assembly.GetType($"{PBX_NAMESPACE}.PBXElementArray"); 68 | _pbxElementString = assembly.GetType($"{PBX_NAMESPACE}.PBXElementString"); 69 | _pbxVariantGroupData = assembly.GetType($"{PBX_NAMESPACE}.PBXVariantGroupData"); 70 | Type fileRefData = assembly.GetType($"{PBX_NAMESPACE}.PBXFileReferenceData"); 71 | Type group = assembly.GetType($"{PBX_NAMESPACE}.PBXGroupData"); 72 | Type pbxObject = assembly.GetType($"{PBX_NAMESPACE}.PBXObjectData"); 73 | Type fileGUIDListBase = assembly.GetType($"{PBX_NAMESPACE}.FileGUIDListBase"); 74 | Type pbxElementDict = assembly.GetType($"{PBX_NAMESPACE}.PBXElementDict"); 75 | Type pbxProjectSection = assembly.GetType($"{PBX_NAMESPACE}.PBXProjectSection"); 76 | _projectData = typeof(PBXProject).GetField("m_Data", flags); 77 | _dataFileRefsField = _projectData.FieldType.GetField("fileRefs", flags); 78 | _dataFileGroups = _projectData.FieldType.GetField("groups", flags); 79 | _resourceFiles = fileGUIDListBase.GetField("files"); 80 | _pbxObjectGUID = pbxObject.GetField("guid"); 81 | _pbxElementArrayValues = _pbxElementArray.GetField("values"); 82 | _groupChildren = group.GetField("children"); 83 | _groupName = group.GetField("name"); 84 | _groupPath = group.GetField("path"); 85 | _variantGroupName = _pbxVariantGroupData.GetField("name"); 86 | _groupsObjects = _dataFileGroups.FieldType.GetMethod("GetObjects"); 87 | _dataFileRefsFieldObjects = _dataFileRefsField.FieldType.GetMethod("GetObjects"); 88 | _getPropertiesRaw = pbxObject.GetMethod("GetPropertiesRaw", flags); 89 | _guidListAdd = _guidList.GetMethod("AddGUID"); 90 | _guidListContains = _guidList.GetMethod("Contains"); 91 | _fileRefDataCreateFromFile = fileRefData.GetMethod("CreateFromFile", BindingFlags.Static | BindingFlags.Public); 92 | _pbxBuildFileDataCreateFromFile = _pbxBuildFileData.GetMethod("CreateFromFile", BindingFlags.Static | BindingFlags.Public); 93 | _projectBuildFilesAdd = typeof(PBXProject).GetMethod("BuildFilesAdd", flags); 94 | _projectFileRefsAdd = typeof(PBXProject).GetMethod("FileRefsAdd", flags); 95 | _projectBuildFilesGetForSourceFile = typeof(PBXProject).GetMethod("BuildFilesGetForSourceFile", flags); 96 | _fileRefsPath = fileRefData.GetProperty("path"); 97 | _knownRegionsDict = pbxElementDict.GetField("m_PrivateValue", flags); 98 | _projectSection = typeof(PBXProject).GetProperty("project", flags); 99 | _projectSectionObjectData = _projectSection.PropertyType.GetProperty("project"); 100 | _projectResoruces = typeof(PBXProject).GetProperty("resources", flags); 101 | _projectVariantGroups = typeof(PBXProject).GetProperty("variantGroups", flags); 102 | _rawPropertiesValuesGetValue = _knownRegionsDict.FieldType.GetMethod("TryGetValue"); 103 | _rawPropertiesValuesAddValue = _knownRegionsDict.FieldType.GetMethod("Add"); 104 | _resorucesObjects = _projectResoruces.PropertyType.GetMethod("GetObjects"); 105 | _variantGroupsAddEntry = _projectVariantGroups.PropertyType.GetMethod("AddEntry"); 106 | _variantGroupsObjects = _projectVariantGroups.PropertyType.GetMethod("GetObjects"); 107 | _variantGroupsSetPropertyString = group.GetMethod("SetPropertyString", flags); 108 | } 109 | 110 | /// 111 | /// Get file reference by path 112 | /// 113 | /// PBX project 114 | /// Path to directory 115 | /// File reference 116 | static object GetFileRefDataByPath(this PBXProject project, string path) { 117 | object data = _projectData.GetValue(project); 118 | object fileRefs = _dataFileRefsField.GetValue(data); 119 | ICollection values = _dataFileRefsFieldObjects.Invoke(fileRefs, null) as ICollection; 120 | foreach (object file in values) { 121 | string fileRefPath = _fileRefsPath.GetValue(file) as string; 122 | if (fileRefPath.Equals(path)) { 123 | return file; 124 | } 125 | } 126 | return null; 127 | } 128 | 129 | /// 130 | /// Get group by custom name 131 | /// 132 | /// PBX project 133 | /// Name 134 | /// Group 135 | static object GetGroupByName(this PBXProject project, string name) { 136 | object data = _projectData.GetValue(project); 137 | object groups = _dataFileGroups.GetValue(data); 138 | ICollection groupsValues = _groupsObjects.Invoke(groups, null) as ICollection; 139 | foreach (var group in groupsValues) { 140 | string groupName = _groupName.GetValue(group) as string; 141 | if (groupName.Equals(name)) { 142 | return group; 143 | } 144 | } 145 | return null; 146 | } 147 | 148 | /// 149 | /// Get all languages exists in project 150 | /// 151 | /// PBX project 152 | /// List of exists regions 153 | static IList GetKnownRegions(this PBXProject project) { 154 | const string target = "knownRegions"; 155 | object section = _projectSection.GetValue(project); 156 | object data = _projectSectionObjectData.GetValue(section); 157 | object properties = _getPropertiesRaw.Invoke(data, null); 158 | object[] args = new[] { target, null }; 159 | object dictionary = _knownRegionsDict.GetValue(properties); 160 | bool result = (bool)_rawPropertiesValuesGetValue.Invoke(dictionary, args); 161 | if (!result) { 162 | args[1] = Activator.CreateInstance(_pbxElementArray); 163 | _rawPropertiesValuesAddValue.Invoke(dictionary, new object[] { target, args[1] }); 164 | } 165 | return _pbxElementArrayValues.GetValue(args[1]) as IList; 166 | } 167 | 168 | /// 169 | /// Add file reference to build 170 | /// 171 | /// PBX project 172 | /// Target GUID 173 | /// Group GUID 174 | /// Reference of file 175 | static string AddFileRefToBuild(this PBXProject project, string target, string guid) { 176 | object data = _pbxBuildFileDataCreateFromFile.Invoke(null, new object[] { guid, false, null }); 177 | _projectBuildFilesAdd.Invoke(project, new object[] { target, data }); 178 | return _pbxObjectGUID.GetValue(data) as string; 179 | } 180 | 181 | /// 182 | /// Add file to resources 183 | /// 184 | /// PBX project 185 | /// Build GUID 186 | /// File GUID to add 187 | static void AddFileToResourceBuildPhase(this PBXProject project, string buildPhaseGuid, string fileGuid) { 188 | object resources = _projectResoruces.GetValue(project); 189 | ICollection values = _resorucesObjects.Invoke(resources, null) as ICollection; 190 | foreach (object value in values) { 191 | string guid = _pbxObjectGUID.GetValue(value) as string; 192 | if (guid.Equals(buildPhaseGuid)) { 193 | object files = _resourceFiles.GetValue(value); 194 | _guidListAdd.Invoke(files, new object[] { fileGuid }); 195 | } 196 | } 197 | } 198 | 199 | /// 200 | /// Clear old deprecated languages 201 | /// 202 | /// PBX Project 203 | public static void ClearKnownRegions(this PBXProject project) { 204 | IList regions = project.GetKnownRegions(); 205 | regions.Clear(); 206 | } 207 | 208 | /// 209 | /// Add language to project 210 | /// 211 | /// PBX project 212 | /// Language code 213 | public static void AddKnownRegion(this PBXProject project, string code) { 214 | IList regions = project.GetKnownRegions(); 215 | object element = Activator.CreateInstance(_pbxElementString, code); 216 | regions.Add(element); 217 | } 218 | 219 | /// 220 | /// Add locale for file 221 | /// 222 | /// PBX project 223 | /// Filename to add variant 224 | /// Language code 225 | /// Relative path to *.lproj 226 | public static void AddLocaleVariantFile(this PBXProject project, string groupName, string code, string path) { 227 | path = path.Replace('\\', '/'); 228 | object variantGroups = _projectVariantGroups.GetValue(project); 229 | ICollection variantGroupValues = _variantGroupsObjects.Invoke(variantGroups, null) as ICollection; 230 | object group = null; 231 | foreach (var value in variantGroupValues) { 232 | string name = _variantGroupName.GetValue(value) as string; 233 | if (name.Equals(groupName)) { 234 | group = value; 235 | } 236 | } 237 | if (group == null) { 238 | string guid = Guid.NewGuid().ToString("N").Substring(8).ToUpper(); 239 | group = Activator.CreateInstance(_pbxVariantGroupData); 240 | _variantGroupName.SetValue(group, groupName); 241 | _groupPath.SetValue(group, groupName); 242 | _pbxObjectGUID.SetValue(group, guid); 243 | _groupChildren.SetValue(group, Activator.CreateInstance(_guidList)); 244 | _variantGroupsSetPropertyString.Invoke(group, new object[] { "isa", "PBXVariantGroup" }); 245 | _variantGroupsAddEntry.Invoke(variantGroups, new object[] { group }); 246 | } 247 | string targetGuid = project.GetUnityMainTargetGuid(); 248 | string groupGuid = _pbxObjectGUID.GetValue(group) as string; 249 | object buildFileData = _projectBuildFilesGetForSourceFile.Invoke(project, new object[] { targetGuid, groupGuid }); 250 | if (buildFileData == null) { 251 | object customData = project.GetGroupByName("CustomTemplate"); 252 | object children = _groupChildren.GetValue(customData); 253 | _guidListAdd.Invoke(children, new object[] { groupGuid }); 254 | string buildFileGuid = project.AddFileRefToBuild(project.GetUnityMainTargetGuid(), groupGuid); 255 | string buildPhaseGuid = project.GetResourcesBuildPhaseByTarget(targetGuid); 256 | project.AddFileToResourceBuildPhase(buildPhaseGuid, buildFileGuid); 257 | } 258 | object fileRef = project.GetFileRefDataByPath(path); 259 | if (fileRef == null) { 260 | fileRef = _fileRefDataCreateFromFile.Invoke(null, new object[] { path, code, PBXSourceTree.Source }); 261 | _projectFileRefsAdd.Invoke(project, new object[] { path, code, group, fileRef }); 262 | } 263 | string fileRefsGuid = _pbxObjectGUID.GetValue(fileRef) as string; 264 | object groupChildren = _groupChildren.GetValue(group); 265 | bool result = (bool)_guidListContains.Invoke(groupChildren, new object[] { fileRefsGuid }); 266 | if (!result) { 267 | _guidListAdd.Invoke(groupChildren, new[] { fileRefsGuid }); 268 | } 269 | } 270 | } 271 | 272 | } -------------------------------------------------------------------------------- /External/NiceJson.cs: -------------------------------------------------------------------------------- 1 | /* 2 | NiceJson 1.3.3 (2020-05-11) 3 | 4 | MIT License 5 | =========== 6 | 7 | Copyright (C) 2015 Ángel Quiroga Mendoza 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of 10 | this software and associated documentation files (the "Software"), to deal in 11 | the Software without restriction, including without limitation the rights to 12 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 13 | of the Software, and to permit persons to whom the Software is furnished to do 14 | so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 25 | SOFTWARE. 26 | 27 | Appreciation Contributions: 28 | Rayco Sánchez García 29 | */ 30 | 31 | using System; 32 | using System.Collections.Generic; 33 | using System.Collections; 34 | using System.Globalization; 35 | using System.Text; 36 | 37 | namespace NiceJson 38 | { 39 | [Serializable] 40 | public abstract class JsonNode 41 | { 42 | protected const char PP_IDENT_CHAR = '\t'; //Modify this to spaces or whatever char you want to be the ident one 43 | protected const int PP_IDENT_COUNT = 1; //Modify this to be the numbers of IDENT_CHAR x identation 44 | protected const bool ESCAPE_SOLIDUS = false; //If you are going to to embed this json in html, you can turn this on ref: http://andowebsit.es/blog/noteslog.com/post/the-solidus-issue/ 45 | 46 | protected const char CHAR_CURLY_OPEN = '{'; 47 | protected const char CHAR_CURLY_CLOSED = '}'; 48 | protected const char CHAR_SQUARED_OPEN = '['; 49 | protected const char CHAR_SQUARED_CLOSED = ']'; 50 | 51 | protected const char CHAR_COLON = ':'; 52 | protected const char CHAR_COMMA = ','; 53 | protected const char CHAR_QUOTE = '"'; 54 | 55 | protected const char CHAR_NULL_LITERAL = 'n'; 56 | protected const char CHAR_TRUE_LITERAL = 't'; 57 | protected const char CHAR_FALSE_LITERAL = 'f'; 58 | 59 | protected const char CHAR_SPACE = ' '; 60 | 61 | protected const char CHAR_BS = '\b'; 62 | protected const char CHAR_FF = '\f'; 63 | protected const char CHAR_RF = '\r'; 64 | protected const char CHAR_NL = '\n'; 65 | protected const char CHAR_HT = '\t'; 66 | protected const char CHAR_ESCAPE = '\\'; 67 | protected const char CHAR_SOLIDUS = '/'; 68 | protected const char CHAR_ESCAPED_QUOTE = '\"'; 69 | 70 | protected const char CHAR_N = 'n'; 71 | protected const char CHAR_R = 'r'; 72 | protected const char CHAR_B = 'b'; 73 | protected const char CHAR_T = 't'; 74 | protected const char CHAR_F = 'f'; 75 | protected const char CHAR_U = 'u'; 76 | 77 | protected const string STRING_ESCAPED_BS = "\\b"; 78 | protected const string STRING_ESCAPED_FF = "\\f"; 79 | protected const string STRING_ESCAPED_RF = "\\r"; 80 | protected const string STRING_ESCAPED_NL = "\\n"; 81 | protected const string STRING_ESCAPED_TAB = "\\t"; 82 | protected const string STRING_ESCAPED_ESCAPE = "\\\\"; 83 | protected const string STRING_ESCAPED_SOLIDUS = "\\/"; 84 | protected const string STRING_ESCAPED_ESCAPED_QUOTE = "\\\""; 85 | 86 | protected const string STRING_SPACE = " "; 87 | protected const string STRING_LITERAL_NULL = "null"; 88 | protected const string STRING_LITERAL_TRUE = "true"; 89 | protected const string STRING_LITERAL_FALSE = "false"; 90 | 91 | protected const string STRING_ESCAPED_UNICODE_INIT = "\\u00"; 92 | 93 | //Indexers and accesors 94 | public JsonNode this[string key] 95 | { 96 | get 97 | { 98 | if (this is JsonObject) 99 | { 100 | return ((JsonObject)this)[key]; 101 | } 102 | else 103 | { 104 | return null; 105 | } 106 | } 107 | 108 | set 109 | { 110 | if (this is JsonObject) 111 | { 112 | ((JsonObject)this)[key] = value; 113 | } 114 | } 115 | } 116 | 117 | public JsonNode this[int index] 118 | { 119 | get 120 | { 121 | if (this is JsonArray) 122 | { 123 | return ((JsonArray)this)[index]; 124 | } 125 | else 126 | { 127 | return null; 128 | } 129 | } 130 | 131 | set 132 | { 133 | if (this is JsonArray) 134 | { 135 | ((JsonArray)this)[index] = value; 136 | } 137 | } 138 | } 139 | 140 | public bool ContainsKey (string key) 141 | { 142 | if (this is JsonObject) 143 | { 144 | return ((JsonObject)this).ContainsKey(key); 145 | } 146 | else 147 | { 148 | return false; 149 | } 150 | } 151 | 152 | //escaping logic 153 | 154 | //Escaping/Unescaping logic 155 | protected static string EscapeString(string s) 156 | { 157 | StringBuilder result = new StringBuilder(); 158 | 159 | foreach (char c in s) 160 | { 161 | switch (c) 162 | { 163 | case CHAR_ESCAPE: 164 | { 165 | result.Append(STRING_ESCAPED_ESCAPE); 166 | } 167 | break; 168 | case CHAR_SOLIDUS: 169 | { 170 | #pragma warning disable 171 | if (ESCAPE_SOLIDUS) 172 | { 173 | result.Append(STRING_ESCAPED_SOLIDUS); 174 | } 175 | else 176 | { 177 | result.Append(c); 178 | } 179 | #pragma warning restore 180 | } 181 | break; 182 | case CHAR_ESCAPED_QUOTE: 183 | { 184 | result.Append (STRING_ESCAPED_ESCAPED_QUOTE); 185 | } 186 | break; 187 | case CHAR_NL: 188 | { 189 | result.Append (STRING_ESCAPED_NL); 190 | } 191 | break; 192 | case CHAR_RF: 193 | { 194 | result.Append (STRING_ESCAPED_RF); 195 | } 196 | break; 197 | case CHAR_HT: 198 | { 199 | result.Append (STRING_ESCAPED_TAB); 200 | } 201 | break; 202 | case CHAR_BS: 203 | { 204 | result.Append (STRING_ESCAPED_BS); 205 | } 206 | break; 207 | case CHAR_FF: 208 | { 209 | result.Append (STRING_ESCAPED_FF); 210 | } 211 | break; 212 | default: 213 | if (c < CHAR_SPACE) 214 | { 215 | result.Append (STRING_ESCAPED_UNICODE_INIT + Convert.ToByte(c).ToString("x2").ToUpper()); 216 | } 217 | else 218 | { 219 | result.Append(c); 220 | } 221 | break; 222 | } 223 | } 224 | 225 | return result.ToString(); 226 | } 227 | 228 | protected static string UnescapeString(string s) 229 | { 230 | StringBuilder result = new StringBuilder(s.Length); 231 | 232 | for (int i=0;i splittedParts = SplitJsonParts(jsonPart.Substring(1, jsonPart.Length - 2)); 404 | 405 | string[] keyValueParts = new string[2]; 406 | foreach (string keyValuePart in splittedParts) 407 | { 408 | keyValueParts = SplitKeyValuePart (keyValuePart); 409 | if (keyValueParts [0] != null) 410 | { 411 | jsonObject [JsonNode.UnescapeString (keyValueParts [0])] = ParseJsonPart (keyValueParts [1]); 412 | } 413 | } 414 | jsonPartValue = jsonObject; 415 | } 416 | break; 417 | case JsonNode.CHAR_SQUARED_OPEN: 418 | { 419 | JsonArray jsonArray = new JsonArray(); 420 | List splittedParts = SplitJsonParts(jsonPart.Substring(1, jsonPart.Length - 2)); 421 | 422 | foreach (string part in splittedParts) 423 | { 424 | if (part.Length > 0) 425 | { 426 | jsonArray.Add (ParseJsonPart (part)); 427 | } 428 | } 429 | jsonPartValue = jsonArray; 430 | } 431 | break; 432 | case JsonNode.CHAR_QUOTE: 433 | { 434 | jsonPartValue = new JsonBasic(JsonNode.UnescapeString(jsonPart.Substring(1, jsonPart.Length - 2))); 435 | } 436 | break; 437 | case JsonNode.CHAR_FALSE_LITERAL://false 438 | { 439 | jsonPartValue = new JsonBasic(false); 440 | } 441 | break; 442 | case JsonNode.CHAR_TRUE_LITERAL://true 443 | { 444 | jsonPartValue = new JsonBasic(true); 445 | } 446 | break; 447 | case JsonNode.CHAR_NULL_LITERAL://null 448 | { 449 | jsonPartValue = null; 450 | } 451 | break; 452 | default://it must be a number or it will fail 453 | { 454 | long longValue = 0; 455 | if (long.TryParse(jsonPart, NumberStyles.Any, CultureInfo.InvariantCulture, out longValue)) 456 | { 457 | if (longValue > int.MaxValue || longValue < int.MinValue) 458 | { 459 | jsonPartValue = new JsonBasic(longValue); 460 | } 461 | else 462 | { 463 | jsonPartValue = new JsonBasic((int)longValue); 464 | } 465 | } 466 | else 467 | { 468 | decimal decimalValue = 0; 469 | if (decimal.TryParse(jsonPart, NumberStyles.Any, CultureInfo.InvariantCulture, out decimalValue)) 470 | { 471 | jsonPartValue = new JsonBasic(decimalValue); 472 | } 473 | } 474 | } 475 | break; 476 | } 477 | 478 | return jsonPartValue; 479 | } 480 | 481 | private static List SplitJsonParts(string json) 482 | { 483 | List jsonParts = new List(); 484 | int identLevel = 0; 485 | int lastPartChar = 0; 486 | bool inString = false; 487 | 488 | for (int i = 0; i < json.Length; i++) 489 | { 490 | switch (json[i]) 491 | { 492 | case JsonNode.CHAR_COMMA: 493 | { 494 | if (!inString && identLevel == 0) 495 | { 496 | jsonParts.Add(json.Substring(lastPartChar, i - lastPartChar)); 497 | lastPartChar = i + 1; 498 | } 499 | } 500 | break; 501 | case JsonNode.CHAR_QUOTE: 502 | { 503 | if (i == 0 || (json[i - 1] != JsonNode.CHAR_ESCAPE)) 504 | { 505 | inString = !inString; 506 | } 507 | } 508 | break; 509 | case JsonNode.CHAR_CURLY_OPEN: 510 | case JsonNode.CHAR_SQUARED_OPEN: 511 | { 512 | if (!inString) 513 | { 514 | identLevel++; 515 | } 516 | } 517 | break; 518 | case JsonNode.CHAR_CURLY_CLOSED: 519 | case JsonNode.CHAR_SQUARED_CLOSED: 520 | { 521 | if (!inString) 522 | { 523 | identLevel--; 524 | } 525 | } 526 | break; 527 | } 528 | } 529 | 530 | jsonParts.Add(json.Substring(lastPartChar)); 531 | 532 | return jsonParts; 533 | } 534 | 535 | private static string[] SplitKeyValuePart(string json) 536 | { 537 | string[] parts = new string[2]; 538 | bool inString = false; 539 | 540 | bool found = false; 541 | int index = 0; 542 | 543 | while (index < json.Length && !found) 544 | { 545 | if (json[index] == JsonNode.CHAR_QUOTE && (index == 0 || (json[index - 1] != JsonNode.CHAR_ESCAPE))) 546 | { 547 | if (!inString) 548 | { 549 | inString = true; 550 | index++; 551 | } 552 | else 553 | { 554 | parts[0] = json.Substring(1, index - 1); 555 | parts[1] = json.Substring(index + 2);//+2 because of the : 556 | found = true; 557 | } 558 | } 559 | else 560 | { 561 | index++; 562 | } 563 | } 564 | 565 | return parts; 566 | } 567 | 568 | private static string RemoveNonTokenChars(string s) 569 | { 570 | int len = s.Length; 571 | char[] s2 = new char[len]; 572 | int currentPos = 0; 573 | bool outString = true; 574 | for (int i = 0; i < len; i++) 575 | { 576 | char c = s[i]; 577 | if (c == JsonNode.CHAR_QUOTE) 578 | { 579 | if (i == 0 || (s[i - 1] != JsonNode.CHAR_ESCAPE)) 580 | { 581 | outString = !outString; 582 | } 583 | } 584 | 585 | if (!outString || ( 586 | (c != JsonNode.CHAR_SPACE) && 587 | (c != JsonNode.CHAR_RF) && 588 | (c != JsonNode.CHAR_NL) && 589 | (c != JsonNode.CHAR_HT) && 590 | (c != JsonNode.CHAR_BS) && 591 | (c != JsonNode.CHAR_FF) 592 | )) 593 | { 594 | s2[currentPos++] = c; 595 | } 596 | } 597 | return new String(s2, 0, currentPos); 598 | } 599 | 600 | //Object logic 601 | 602 | public abstract string ToJsonString(); 603 | 604 | public string ToJsonPrettyPrintString() 605 | { 606 | string jsonString = this.ToJsonString(); 607 | 608 | string identStep = string.Empty; 609 | for (int i = 0; i < PP_IDENT_COUNT; i++) 610 | { 611 | identStep += PP_IDENT_CHAR; 612 | } 613 | 614 | bool inString = false; 615 | 616 | string currentIdent = string.Empty; 617 | for (int i = 0; i < jsonString.Length; i++) 618 | { 619 | switch (jsonString[i]) 620 | { 621 | case CHAR_COLON: 622 | { 623 | if (!inString) 624 | { 625 | jsonString = jsonString.Insert(i + 1, STRING_SPACE); 626 | } 627 | } 628 | break; 629 | case CHAR_QUOTE: 630 | { 631 | if (i == 0 || (jsonString[i - 1] != CHAR_ESCAPE)) 632 | { 633 | inString = !inString; 634 | } 635 | } 636 | break; 637 | case CHAR_COMMA: 638 | { 639 | if (!inString) 640 | { 641 | jsonString = jsonString.Insert(i + 1, CHAR_NL + currentIdent); 642 | } 643 | } 644 | break; 645 | case CHAR_CURLY_OPEN: 646 | case CHAR_SQUARED_OPEN: 647 | { 648 | if (!inString) 649 | { 650 | currentIdent += identStep; 651 | jsonString = jsonString.Insert(i + 1, CHAR_NL + currentIdent); 652 | } 653 | } 654 | break; 655 | case CHAR_CURLY_CLOSED: 656 | case CHAR_SQUARED_CLOSED: 657 | { 658 | if (!inString) 659 | { 660 | currentIdent = currentIdent.Substring(0, currentIdent.Length - identStep.Length); 661 | jsonString = jsonString.Insert(i, CHAR_NL + currentIdent); 662 | i += currentIdent.Length + 1; 663 | } 664 | } 665 | break; 666 | } 667 | } 668 | 669 | return jsonString; 670 | } 671 | } 672 | 673 | [Serializable] 674 | public class JsonBasic : JsonNode 675 | { 676 | public object ValueObject 677 | { 678 | get 679 | { 680 | return m_value; 681 | } 682 | } 683 | 684 | private object m_value; 685 | 686 | public JsonBasic(object value) 687 | { 688 | m_value = value; 689 | } 690 | 691 | public override string ToString() 692 | { 693 | return m_value.ToString(); 694 | } 695 | 696 | public override string ToJsonString () 697 | { 698 | if (m_value == null) 699 | { 700 | return STRING_LITERAL_NULL; 701 | } 702 | else if (m_value is string) 703 | { 704 | return CHAR_QUOTE + EscapeString(m_value.ToString()) + CHAR_QUOTE; 705 | } 706 | else if (m_value is bool) 707 | { 708 | if ((bool) m_value) 709 | { 710 | return STRING_LITERAL_TRUE; 711 | } 712 | else 713 | { 714 | return STRING_LITERAL_FALSE; 715 | } 716 | } 717 | else //number 718 | { 719 | if (m_value is decimal) 720 | { 721 | return ((decimal) m_value).ToString(CultureInfo.InvariantCulture); 722 | } 723 | else 724 | { 725 | return m_value.ToString(); 726 | } 727 | } 728 | } 729 | 730 | } 731 | 732 | [Serializable] 733 | public class JsonObject : JsonNode, IEnumerable 734 | { 735 | private Dictionary m_dictionary = new Dictionary(); 736 | 737 | public Dictionary.KeyCollection Keys 738 | { 739 | get 740 | { 741 | return m_dictionary.Keys; 742 | } 743 | } 744 | 745 | public Dictionary.ValueCollection Values 746 | { 747 | get 748 | { 749 | return m_dictionary.Values; 750 | } 751 | } 752 | 753 | public new JsonNode this[string key] 754 | { 755 | get 756 | { 757 | return m_dictionary[key]; 758 | } 759 | 760 | set 761 | { 762 | m_dictionary[key] = value; 763 | } 764 | } 765 | 766 | public void Add(string key, JsonNode value) 767 | { 768 | m_dictionary.Add(key, value); 769 | } 770 | 771 | public bool Remove(string key) 772 | { 773 | return m_dictionary.Remove(key); 774 | } 775 | 776 | public JsonNode GetValue(string key, JsonNode defaultValue = default(JsonNode)) 777 | { 778 | JsonNode value; 779 | return m_dictionary.TryGetValue(key, out value) ? value : defaultValue; 780 | } 781 | 782 | public new bool ContainsKey(string key) 783 | { 784 | return m_dictionary.ContainsKey(key); 785 | } 786 | 787 | public bool ContainsValue(JsonNode value) 788 | { 789 | return m_dictionary.ContainsValue(value); 790 | } 791 | 792 | public void Clear() 793 | { 794 | m_dictionary.Clear(); 795 | } 796 | 797 | public int Count 798 | { 799 | get 800 | { 801 | return m_dictionary.Count; 802 | } 803 | } 804 | 805 | public IEnumerator GetEnumerator() 806 | { 807 | foreach (KeyValuePair jsonKeyValue in m_dictionary) 808 | { 809 | yield return jsonKeyValue; 810 | } 811 | } 812 | 813 | IEnumerator IEnumerable.GetEnumerator() 814 | { 815 | return GetEnumerator(); 816 | } 817 | 818 | public override string ToJsonString () 819 | { 820 | if (m_dictionary == null) 821 | { 822 | return STRING_LITERAL_NULL; 823 | } 824 | else 825 | { 826 | StringBuilder jsonString = new StringBuilder(); 827 | jsonString.Append(CHAR_CURLY_OPEN); 828 | foreach (string key in m_dictionary.Keys) 829 | { 830 | jsonString.Append(CHAR_QUOTE); 831 | jsonString.Append(EscapeString(key)); 832 | jsonString.Append(CHAR_QUOTE); 833 | jsonString.Append(CHAR_COLON); 834 | 835 | if (m_dictionary[key] != null) 836 | { 837 | jsonString.Append(m_dictionary[key].ToJsonString()); 838 | } 839 | else 840 | { 841 | jsonString.Append(STRING_LITERAL_NULL); 842 | } 843 | jsonString.Append(CHAR_COMMA); 844 | } 845 | if (jsonString[jsonString.Length -1] == CHAR_COMMA) 846 | { 847 | jsonString.Remove(jsonString.Length -1,1); 848 | } 849 | jsonString.Append(CHAR_CURLY_CLOSED); 850 | 851 | return jsonString.ToString(); 852 | } 853 | 854 | } 855 | } 856 | 857 | [Serializable] 858 | public class JsonArray : JsonNode, IEnumerable 859 | { 860 | private List m_list = new List(); 861 | 862 | public int Count 863 | { 864 | get 865 | { 866 | return m_list.Count; 867 | } 868 | } 869 | 870 | public new JsonNode this[int index] 871 | { 872 | get 873 | { 874 | return m_list[index]; 875 | } 876 | 877 | set 878 | { 879 | m_list[index] = value; 880 | } 881 | } 882 | 883 | public IEnumerator GetEnumerator() 884 | { 885 | foreach (JsonNode value in m_list) 886 | { 887 | yield return value; 888 | } 889 | } 890 | 891 | IEnumerator IEnumerable.GetEnumerator() 892 | { 893 | return GetEnumerator(); 894 | } 895 | 896 | //expose some methods of list extends with needs 897 | 898 | public void Add(JsonNode item) 899 | { 900 | m_list.Add(item); 901 | } 902 | 903 | public void AddRange(IEnumerable collection) 904 | { 905 | m_list.AddRange(collection); 906 | } 907 | 908 | public void Insert(int index,JsonNode item) 909 | { 910 | m_list.Insert(index,item); 911 | } 912 | 913 | public void InsertRange(int index,IEnumerable collection) 914 | { 915 | m_list.InsertRange(index,collection); 916 | } 917 | 918 | public void RemoveAt(int index) 919 | { 920 | m_list.RemoveAt(index); 921 | } 922 | 923 | public bool Remove(JsonNode item) 924 | { 925 | return m_list.Remove(item); 926 | } 927 | 928 | public void Clear() 929 | { 930 | m_list.Clear(); 931 | } 932 | 933 | //end exposed methods 934 | 935 | 936 | public override string ToJsonString () 937 | { 938 | if (m_list == null) 939 | { 940 | return STRING_LITERAL_NULL; 941 | } 942 | else 943 | { 944 | StringBuilder jsonString = new StringBuilder(); 945 | jsonString.Append (CHAR_SQUARED_OPEN); 946 | foreach (JsonNode value in m_list) 947 | { 948 | if (value != null) 949 | { 950 | jsonString.Append (value.ToJsonString()); 951 | } 952 | else 953 | { 954 | jsonString.Append (STRING_LITERAL_NULL); 955 | } 956 | 957 | jsonString.Append (CHAR_COMMA); 958 | } 959 | if (jsonString[jsonString.Length-1] == CHAR_COMMA) 960 | { 961 | jsonString.Remove(jsonString.Length -1,1); 962 | } 963 | jsonString.Append (CHAR_SQUARED_CLOSED); 964 | 965 | return jsonString.ToString(); 966 | } 967 | } 968 | } 969 | } -------------------------------------------------------------------------------- /Editor/UnityBuilderHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Text; 4 | using NiceJson; 5 | using UnityEditor; 6 | using UnityEditor.Build.Reporting; 7 | using UnityEngine; 8 | using UnityEngine.Networking; 9 | using Debug = UnityEngine.Debug; 10 | 11 | namespace Mopsicus.UBH { 12 | 13 | /// 14 | /// Struct for fill PList 15 | /// 16 | public struct PListItem { 17 | public string Key; 18 | public string Value; 19 | } 20 | 21 | public class UnityBuilderHelper : EditorWindow { 22 | 23 | /// 24 | /// Types of loggers 25 | /// 26 | enum LoggerType { 27 | CLIENT = 0, 28 | GAME = 1, 29 | PING = 2 30 | } 31 | 32 | /// 33 | /// Default width 34 | /// 35 | const int WIDTH = 576; 36 | 37 | /// 38 | /// Default height 39 | /// 40 | const int HEIGHT = 600; 41 | 42 | /// 43 | /// Horizontal 44 | /// 45 | const int OFFSET = 150; 46 | 47 | /// 48 | /// Height for select buttons 49 | /// 50 | const int BUTTON_HEIGHT = 64; 51 | 52 | /// 53 | /// Height for action buttons 54 | /// 55 | const int ACTION_HEIGHT = 48; 56 | 57 | /// 58 | /// Max bundle count 59 | /// 60 | const int MAX_BUNDLE = 9999; 61 | 62 | /// 63 | /// Window title 64 | /// 65 | const string TITLE = "Unity Builder Helper"; 66 | 67 | /// 68 | /// Color for enabled button 69 | /// 70 | private Color ON_BUTTON = Color.green; 71 | 72 | /// 73 | /// Color for disabled button 74 | /// 75 | private Color OFF_BUTTON = Color.gray; 76 | 77 | /// 78 | /// Key for save/load position 79 | /// 80 | const string POSITION_KEY = "ubh_position"; 81 | 82 | /// 83 | /// Key for save/load platform 84 | /// 85 | const string PLATFORM_KEY = "ubh_platform"; 86 | 87 | /// 88 | /// Key for save/load logger 89 | /// 90 | const string LOGGER_KEY = "ubh_logger"; 91 | 92 | /// 93 | /// Key for save/load build path 94 | /// 95 | const string BUILD_KEY = "ubh_build"; 96 | 97 | /// 98 | /// Browse button width 99 | /// 100 | private const int BROWSE_WIDTH = 32; 101 | 102 | /// 103 | /// Spacing for vertical elements 104 | /// 105 | private const float VERTICAL_SPACING = 10f; 106 | 107 | /// 108 | /// Android build file mask 109 | /// 110 | private const string ANDROID_BUILD_FILE_MASK = "{0}/{1}.{2}.{3}{4}{5}.{6}"; 111 | 112 | /// 113 | /// iOS build mask 114 | /// 115 | private const string IOS_BUILD_MASK = "{0}/{1}.{2}.{3}{4}"; 116 | 117 | /// 118 | /// Bot command payload 119 | /// 120 | private const string BOT_PAYLOAD = "&parse_mode=html&reply_markup={\"inline_keyboard\": [[{\"text\": \"🛠️ Run build\", \"callback_data\": \"build\"}]]}"; 121 | 122 | /// 123 | /// Bot url mask command 124 | /// 125 | private const string BOT_COMMAND_URL = "https://api.telegram.org/bot{0}/sendMessage?chat_id={1}&text=/build {2} {3} {4} {5}{6}"; 126 | 127 | /// 128 | /// Remote key fold 129 | /// 130 | private const string REMOTE_KEY = "ubh_remote"; 131 | 132 | /// 133 | /// Android key fold 134 | /// 135 | private const string ANDROID_KEY = "ubh_android"; 136 | 137 | /// 138 | /// iOS key fold 139 | /// 140 | private const string IOS_KEY = "ubh_ios"; 141 | 142 | /// 143 | /// Token key 144 | /// 145 | public const string BOT_TOKEN_KEY = "ubh_token"; 146 | 147 | /// 148 | /// Telegram user key 149 | /// 150 | public const string USER_ID_KEY = "ubh_user"; 151 | 152 | /// 153 | /// Game title key 154 | /// 155 | public const string GAME_TITLE_KEY = "ubh_title"; 156 | 157 | /// 158 | /// Google keystore path 159 | /// 160 | public const string GOOGLE_PATH_KEY = "ubh_g_path"; 161 | 162 | /// 163 | /// Google keystore password 164 | /// 165 | public const string GOOGLE_PASSWORD_KEY = "ubh_g_pass"; 166 | 167 | /// 168 | /// Google keystore alias 169 | /// 170 | public const string GOOGLE_ALIAS_KEY = "ubh_g_alias"; 171 | 172 | /// 173 | /// Google keystore alias pass 174 | /// 175 | public const string GOOGLE_APASS_KEY = "ubh_g_apass"; 176 | 177 | /// 178 | /// Huawei keystore path 179 | /// 180 | public const string HUAWEI_PATH_KEY = "ubh_h_path"; 181 | 182 | /// 183 | /// Huawei keystore password 184 | /// 185 | public const string HUAWEI_PASSWORD_KEY = "ubh_h_pass"; 186 | 187 | /// 188 | /// Huawei keystore alias 189 | /// 190 | public const string HUAWEI_ALIAS_KEY = "ubh_h_alias"; 191 | 192 | /// 193 | /// Huawei keystore alias pass 194 | /// 195 | public const string HUAWEI_APASS_KEY = "ubh_h_apass"; 196 | 197 | /// 198 | /// Huawei dependencies 199 | /// 200 | public const string HUAWEI_DEPS_KEY = "ubh_h_deps"; 201 | 202 | /// 203 | /// iOS locales 204 | /// 205 | public const string LOCALES_KEY = "ubh_locales"; 206 | 207 | /// 208 | /// iOS frameworks 209 | /// 210 | public const string FRAMEWORKS_KEY = "ubh_frameworks"; 211 | 212 | /// 213 | /// iOS support files 214 | /// 215 | public const string SUPPORT_FILES_KEY = "ubh_support"; 216 | 217 | /// 218 | /// iOS push enabled 219 | /// 220 | public const string PUSH_KEY = "ubh_push"; 221 | 222 | /// 223 | /// iOS purchase enabled 224 | /// 225 | public const string PURCHASE_KEY = "ubh_purchase"; 226 | 227 | /// 228 | /// iOS sign in with apple enabled 229 | /// 230 | public const string SIGN_KEY = "ubh_sign"; 231 | 232 | /// 233 | /// iOS plist 234 | /// 235 | public const string PLIST_KEY = "ubh_plist"; 236 | 237 | /// 238 | /// Use google services 239 | /// 240 | public const string GOOGLE_SERVICES_KEY = "ubh_g_services"; 241 | 242 | /// 243 | /// Use huawei services 244 | /// 245 | public const string HUAWEI_SERVICES_KEY = "ubh_h_services"; 246 | 247 | /// 248 | /// Use keystore 249 | /// 250 | public const string GOOGLE_KEYSTORE_KEY = "ubh_g_keystore"; 251 | 252 | /// 253 | /// Use keystore 254 | /// 255 | public const string HUAWEI_KEYSTORE_KEY = "ubh_h_keystore"; 256 | 257 | /// 258 | /// Current platform 259 | /// 260 | private PlatformType _currentPlatform = PlatformType.IOS; 261 | 262 | /// 263 | /// Color for platform buttons 264 | /// 265 | private Color[] _platformsColors = new Color[4]; 266 | 267 | /// 268 | /// Icons for buttons 269 | /// 270 | private Texture2D[] _platformIcons = new Texture2D[4]; 271 | 272 | /// 273 | /// Paths for builds 274 | /// 275 | private string[] _platformBuilds = new string[4]; 276 | 277 | /// 278 | /// Color for loggers buttons 279 | /// 280 | private Color[] _loggersColors = new Color[3]; 281 | 282 | /// 283 | /// Icons for loggers 284 | /// 285 | private Texture2D[] _loggersIcons = new Texture2D[3]; 286 | 287 | /// 288 | /// Active loggers 289 | /// 290 | private List _loggers = new List(3); 291 | 292 | /// 293 | /// Cached last action message 294 | /// 295 | private string _actionMessage = null; 296 | 297 | /// 298 | /// Cached app version 299 | /// 300 | private string _buildVersion = ""; 301 | 302 | /// 303 | /// Cached app bundle code 304 | /// 305 | private string _buildCode = ""; 306 | 307 | /// 308 | /// Flag to apply bundle build for Android 309 | /// 310 | private bool _isBundleBuild = false; 311 | 312 | /// 313 | /// Flog to show settings 314 | /// 315 | private bool _isSettingsMode = false; 316 | 317 | /// 318 | /// Title for builds 319 | /// 320 | private string _gameTitle = null; 321 | 322 | /// 323 | /// Telegram bot token 324 | /// 325 | private string _botToken = null; 326 | 327 | /// 328 | /// Telegram user id 329 | /// 330 | private string _userID = null; 331 | 332 | /// 333 | /// Locales list, separated by comma 334 | /// 335 | private string _locales = null; 336 | 337 | /// 338 | /// Framework names list, separated by comma 339 | /// 340 | private string _frameworks = null; 341 | 342 | /// 343 | /// Support files for iOS, separated by comma 344 | /// 345 | private string _supportFiles = null; 346 | 347 | /// 348 | /// Enable push notification in xcode 349 | /// 350 | private bool _isPushes = false; 351 | 352 | /// 353 | /// Enable purchases in xcode 354 | /// 355 | private bool _isPurchases = false; 356 | 357 | /// 358 | /// Enable sign in with Apple in xcode 359 | /// 360 | private bool _isSignIn = false; 361 | 362 | /// 363 | /// Enable google services 364 | /// 365 | private bool _isGoogleServices = false; 366 | 367 | /// 368 | /// Enable huawei services 369 | /// 370 | private bool _isHuaweiServices = false; 371 | 372 | /// 373 | /// Use keystore 374 | /// 375 | private bool _isGoogleKeystore = false; 376 | 377 | /// 378 | /// Use keystore 379 | /// 380 | private bool _isHuaweiKeystore = false; 381 | 382 | /// 383 | /// Path to keystore file 384 | /// 385 | private string _googlePath = ""; 386 | 387 | /// 388 | /// Password for keystore 389 | /// 390 | private string _googlePassword = ""; 391 | 392 | /// 393 | /// Alias in keystore 394 | /// 395 | private string _googleAlias = ""; 396 | 397 | /// 398 | /// Password for alias 399 | /// 400 | private string _googleAliasPassword = ""; 401 | 402 | /// 403 | /// Path to keystore file 404 | /// 405 | private string _huaweiPath = ""; 406 | 407 | /// 408 | /// Password for keystore 409 | /// 410 | private string _huaweiPassword = ""; 411 | 412 | /// 413 | /// Alias in keystore 414 | /// 415 | private string _huaweiAlias = ""; 416 | 417 | /// 418 | /// Password for alias 419 | /// 420 | private string _huaweiAliasPassword = ""; 421 | 422 | /// 423 | /// Dependencies separated by comma 424 | /// 425 | private string _huaweiDependencies = ""; 426 | 427 | /// 428 | /// Position for scroll view 429 | /// 430 | Vector2 _scrollPosition = Vector2.zero; 431 | 432 | /// 433 | /// Visible status for remote 434 | /// 435 | private bool _remoteStatus = false; 436 | 437 | /// 438 | /// Visible status for Android 439 | /// 440 | private bool _androidStatus = false; 441 | 442 | /// 443 | /// Visible status for iOS 444 | /// 445 | private bool _iosStatus = false; 446 | 447 | /// 448 | /// List of plist items 449 | /// 450 | private List _plist = new List(); 451 | 452 | /// 453 | /// Init 454 | /// 455 | [MenuItem("Tools/Unity Builder Helper %g")] 456 | static void Init() { 457 | UnityBuilderHelper window = GetWindow(true); 458 | window.LoadPosition(); 459 | window.Load(); 460 | window.Show(); 461 | } 462 | 463 | /// 464 | /// 465 | /// 466 | void OnEnable() { 467 | titleContent.text = TITLE; 468 | } 469 | 470 | /// 471 | /// Action on close 472 | /// 473 | void OnDisable() { 474 | SavePosition(); 475 | } 476 | 477 | /// 478 | /// Load window position and size 479 | /// 480 | void LoadPosition() { 481 | UnityBuilderHelper window = GetWindow(true); 482 | string data = UBHPrefs.GetString(POSITION_KEY); 483 | if (string.IsNullOrEmpty(data)) { 484 | window.position = new Rect(WIDTH, HEIGHT / 2, WIDTH, HEIGHT); 485 | } else { 486 | JsonObject json = (JsonObject)JsonNode.ParseJsonString(data); 487 | window.position = new Rect(json["x"], json["y"], json["w"], json["h"]); 488 | } 489 | } 490 | 491 | /// 492 | /// Save window position and size 493 | /// 494 | void SavePosition() { 495 | JsonObject json = new JsonObject(); 496 | UnityBuilderHelper window = GetWindow(true); 497 | json["x"] = window.position.x; 498 | json["y"] = window.position.y; 499 | json["w"] = window.position.width; 500 | json["h"] = window.position.height; 501 | SaveData(POSITION_KEY, json.ToJsonString()); 502 | } 503 | 504 | /// 505 | /// Check data before save 506 | /// 507 | private void SaveData(string key, string value) { 508 | if (string.IsNullOrEmpty(key) || string.IsNullOrEmpty(value)) { 509 | Debug.LogErrorFormat("Error on save data: key = {0}, value = {1}", key, value); 510 | return; 511 | } 512 | UBHPrefs.SetString(key, value); 513 | } 514 | 515 | 516 | /// 517 | /// Init window 518 | /// 519 | void Load() { 520 | InitColors(); 521 | InitPlatformIcons(); 522 | InitLoggerIcons(); 523 | LoadSettings(); 524 | BuildTarget target = EditorUserBuildSettings.activeBuildTarget; 525 | PlatformType platform = (PlatformType)UBHPrefs.GetInt(PLATFORM_KEY, -1); 526 | SwitchPlatform(platform); 527 | string list = UBHPrefs.GetString(LOGGER_KEY); 528 | if (!string.IsNullOrEmpty(list)) { 529 | JsonArray json = (JsonArray)JsonNode.ParseJsonString(list); 530 | foreach (int logger in json) { 531 | SwitchLogger((LoggerType)logger); 532 | } 533 | } 534 | list = UBHPrefs.GetString(BUILD_KEY); 535 | if (!string.IsNullOrEmpty(list)) { 536 | JsonArray json = (JsonArray)JsonNode.ParseJsonString(list); 537 | for (int i = 0; i < json.Count; i++) { 538 | _platformBuilds[i] = json[i]; 539 | } 540 | } 541 | _isBundleBuild = EditorUserBuildSettings.buildAppBundle; 542 | #if UNITY_ANDROID 543 | _buildCode = PlayerSettings.Android.bundleVersionCode.ToString(); 544 | #elif UNITY_IOS 545 | _buildCode = PlayerSettings.iOS.buildNumber; 546 | #endif 547 | _buildVersion = PlayerSettings.bundleVersion; 548 | } 549 | 550 | /// 551 | /// Init platform icons 552 | /// 553 | void InitPlatformIcons() { 554 | _platformIcons[(int)PlatformType.IOS] = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets("ubh-apple-icon")[0])); 555 | _platformIcons[(int)PlatformType.GOOGLE] = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets("ubh-google-icon")[0])); 556 | _platformIcons[(int)PlatformType.HUAWEI] = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets("ubh-huawei-icon")[0])); 557 | _platformIcons[(int)PlatformType.WEB] = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets("ubh-web-icon")[0])); 558 | } 559 | 560 | /// 561 | /// Init loggers icons 562 | /// 563 | void InitLoggerIcons() { 564 | _loggersIcons[(int)LoggerType.CLIENT] = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets("ubh-client-icon")[0])); 565 | _loggersIcons[(int)LoggerType.GAME] = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets("ubh-game-icon")[0])); 566 | _loggersIcons[(int)LoggerType.PING] = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(AssetDatabase.FindAssets("ubh-ping-icon")[0])); 567 | } 568 | 569 | /// 570 | /// Show current loggers states 571 | /// 572 | /// Logger type 573 | void SwitchLogger(LoggerType logger) { 574 | if (_loggers.Contains(logger)) { 575 | _loggersColors[(int)logger] = OFF_BUTTON; 576 | _loggers.Remove(logger); 577 | ShowActionMessage(string.Format("Logger {0} disabled", logger.ToString())); 578 | } else { 579 | _loggersColors[(int)logger] = ON_BUTTON; 580 | _loggers.Add(logger); 581 | ShowActionMessage(string.Format("Logger {0} enabled", logger.ToString())); 582 | } 583 | } 584 | 585 | /// 586 | /// Show current platform state 587 | /// 588 | /// Platform type 589 | void SwitchPlatform(PlatformType platform) { 590 | for (int i = 0; i < _platformsColors.Length; i++) { 591 | _platformsColors[i] = OFF_BUTTON; 592 | } 593 | if (platform != PlatformType.UNKNOWN) { 594 | _platformsColors[(int)platform] = ON_BUTTON; 595 | _currentPlatform = platform; 596 | ShowActionMessage(string.Format("Platform {0} selected", _currentPlatform.ToString())); 597 | } else { 598 | ShowActionMessage(string.Format("Platform unknown: {0}", platform.ToString())); 599 | } 600 | } 601 | 602 | /// 603 | /// Draw window items 604 | /// 605 | void OnGUI() { 606 | if (_isSettingsMode) { 607 | ShowSettings(); 608 | } else { 609 | ShowPanel(); 610 | } 611 | } 612 | 613 | /// 614 | /// Show settings 615 | /// 616 | private void ShowSettings() { 617 | GUILayout.BeginVertical(); 618 | GUILayout.Space(VERTICAL_SPACING); 619 | GUI.backgroundColor = Color.gray; 620 | _remoteStatus = EditorGUILayout.Foldout(_remoteStatus, "Remote settings"); 621 | if (_remoteStatus) { 622 | _botToken = EditorGUILayout.TextField("Bot token:", _botToken); 623 | _userID = EditorGUILayout.TextField("User ID:", _userID); 624 | _gameTitle = EditorGUILayout.TextField("Game title:", _gameTitle); 625 | } 626 | GUILayout.Space(VERTICAL_SPACING); 627 | _androidStatus = EditorGUILayout.Foldout(_androidStatus, "Android settings"); 628 | if (_androidStatus) { 629 | _isGoogleServices = EditorGUILayout.Toggle("Use google-services.json:", _isGoogleServices); 630 | _isGoogleKeystore = EditorGUILayout.Toggle("Use KeyStore for Google", _isGoogleKeystore); 631 | if (_isGoogleKeystore) { 632 | GUILayout.Space(VERTICAL_SPACING); 633 | GUILayout.BeginHorizontal(); 634 | _googlePath = EditorGUILayout.TextField("Path:", _googlePath); 635 | if (GUILayout.Button("...", GUILayout.MaxWidth(BROWSE_WIDTH))) { 636 | string path = EditorUtility.OpenFilePanel("Select keystore", _googlePath, ""); 637 | if (!string.IsNullOrEmpty(path)) { 638 | _googlePath = path; 639 | } 640 | } 641 | GUILayout.EndHorizontal(); 642 | _googlePassword = EditorGUILayout.TextField("Password:", _googlePassword); 643 | _googleAlias = EditorGUILayout.TextField("Alias:", _googleAlias); 644 | _googleAliasPassword = EditorGUILayout.TextField("Alias password:", _googleAliasPassword); 645 | } 646 | GUILayout.Space(VERTICAL_SPACING); 647 | _isHuaweiServices = EditorGUILayout.Toggle("Use agconnect-services.json:", _isHuaweiServices); 648 | _isHuaweiKeystore = EditorGUILayout.Toggle("Use KeyStore for Huawei", _isHuaweiKeystore); 649 | if (_isHuaweiKeystore) { 650 | GUILayout.BeginHorizontal(); 651 | _huaweiPath = EditorGUILayout.TextField("Path:", _huaweiPath); 652 | if (GUILayout.Button("...", GUILayout.MaxWidth(BROWSE_WIDTH))) { 653 | string path = EditorUtility.OpenFilePanel("Select keystore", _huaweiPath, ""); 654 | if (!string.IsNullOrEmpty(path)) { 655 | _huaweiPath = path; 656 | } 657 | } 658 | GUILayout.EndHorizontal(); 659 | _huaweiPassword = EditorGUILayout.TextField("Password:", _huaweiPassword); 660 | _huaweiAlias = EditorGUILayout.TextField("Alias:", _huaweiAlias); 661 | _huaweiAliasPassword = EditorGUILayout.TextField("Alias password:", _huaweiAliasPassword); 662 | } 663 | GUILayout.Space(VERTICAL_SPACING); 664 | _huaweiDependencies = EditorGUILayout.TextField("Huawei dependencies:", _huaweiDependencies); 665 | } 666 | GUILayout.Space(VERTICAL_SPACING); 667 | _iosStatus = EditorGUILayout.Foldout(_iosStatus, "iOS settings"); 668 | if (_iosStatus) { 669 | _locales = EditorGUILayout.TextField("Locales:", _locales); 670 | _frameworks = EditorGUILayout.TextField("Frameworks:", _frameworks); 671 | _supportFiles = EditorGUILayout.TextField("Support files:", _supportFiles); 672 | _isPushes = EditorGUILayout.Toggle("Pushes:", _isPushes); 673 | _isPurchases = EditorGUILayout.Toggle("Purchases:", _isPurchases); 674 | _isSignIn = EditorGUILayout.Toggle("Sign in with Apple:", _isSignIn); 675 | GUILayout.Space(VERTICAL_SPACING); 676 | GUILayout.Label("Plist items", EditorStyles.label); 677 | GUILayout.BeginHorizontal(); 678 | GUILayout.Space(OFFSET); 679 | GUILayout.BeginVertical(); 680 | _scrollPosition = GUILayout.BeginScrollView(_scrollPosition, false, true); 681 | if (_plist.Count > 0) { 682 | for (int i = 0; i < _plist.Count; i++) { 683 | PListItem item = _plist[i]; 684 | GUILayout.BeginVertical(); 685 | item.Key = EditorGUILayout.TextField("Key:", item.Key); 686 | item.Value = EditorGUILayout.TextField("Value:", item.Value); 687 | _plist[i] = item; 688 | if (GUILayout.Button("Remove")) { 689 | _plist.Remove(item); 690 | } 691 | GUILayout.EndVertical(); 692 | } 693 | } 694 | if (GUILayout.Button("Add")) { 695 | PListItem item = new PListItem(); 696 | _plist.Add(item); 697 | } 698 | GUILayout.EndScrollView(); 699 | GUILayout.EndVertical(); 700 | GUILayout.EndHorizontal(); 701 | } 702 | GUILayout.Space(VERTICAL_SPACING); 703 | if (GUILayout.Button("Save", GUILayout.Height(ACTION_HEIGHT))) { 704 | SaveSettings(); 705 | _isSettingsMode = false; 706 | } 707 | GUILayout.EndVertical(); 708 | } 709 | 710 | /// 711 | /// Load all settings 712 | /// 713 | private void LoadSettings() { 714 | _remoteStatus = UBHPrefs.GetBool(REMOTE_KEY, true); 715 | _androidStatus = UBHPrefs.GetBool(ANDROID_KEY, false); 716 | _iosStatus = UBHPrefs.GetBool(IOS_KEY, true); 717 | _isGoogleServices = UBHPrefs.GetBool(GOOGLE_SERVICES_KEY, false); 718 | _isHuaweiServices = UBHPrefs.GetBool(HUAWEI_SERVICES_KEY, false); 719 | _isGoogleKeystore = UBHPrefs.GetBool(GOOGLE_KEYSTORE_KEY, false); 720 | _isHuaweiKeystore = UBHPrefs.GetBool(HUAWEI_KEYSTORE_KEY, false); 721 | _iosStatus = UBHPrefs.GetBool(IOS_KEY, true); 722 | _botToken = UBHPrefs.GetString(BOT_TOKEN_KEY); 723 | _userID = UBHPrefs.GetString(USER_ID_KEY); 724 | _gameTitle = UBHPrefs.GetString(GAME_TITLE_KEY); 725 | _googlePath = UBHPrefs.GetString(GOOGLE_PATH_KEY); 726 | _googlePassword = UBHPrefs.GetString(GOOGLE_PASSWORD_KEY); 727 | _googleAlias = UBHPrefs.GetString(GOOGLE_ALIAS_KEY); 728 | _googleAliasPassword = UBHPrefs.GetString(GOOGLE_APASS_KEY); 729 | _huaweiPath = UBHPrefs.GetString(HUAWEI_PATH_KEY); 730 | _huaweiPassword = UBHPrefs.GetString(HUAWEI_PASSWORD_KEY); 731 | _huaweiAlias = UBHPrefs.GetString(HUAWEI_ALIAS_KEY); 732 | _huaweiAliasPassword = UBHPrefs.GetString(HUAWEI_APASS_KEY); 733 | _huaweiDependencies = UBHPrefs.GetString(HUAWEI_DEPS_KEY, "com.huawei.hms:base:6.3.0.303, com.huawei.hms:hwid:6.4.0.300, com.huawei.agconnect:agconnect-auth:1.6.4.300, com.huawei.agconnect:agconnect-auth-huawei:1.6.4.300, com.huawei.hms:iap:6.3.0.300, com.huawei.hms:push:6.3.0.302, com.huawei.hms:ads:3.4.52.302, com.huawei.hms:ads-identifier:3.4.39.302, com.huawei.hms:game:5.0.4.303"); 734 | _locales = UBHPrefs.GetString(LOCALES_KEY, "en, ru"); 735 | _frameworks = UBHPrefs.GetString(FRAMEWORKS_KEY, "AppTrackingTransparency, UserNotifications, AuthenticationServices, StoreKit, MessageUI, Webkit"); 736 | _supportFiles = UBHPrefs.GetString(SUPPORT_FILES_KEY, "splash-iphone.png, splash-ipad.png"); 737 | _isPushes = UBHPrefs.GetBool(PUSH_KEY, true); 738 | _isPurchases = UBHPrefs.GetBool(PURCHASE_KEY, true); 739 | _isSignIn = UBHPrefs.GetBool(SIGN_KEY, true); 740 | string data = UBHPrefs.GetString(PLIST_KEY); 741 | if (!string.IsNullOrEmpty(data)) { 742 | _plist.Clear(); 743 | JsonArray list = (JsonArray)JsonNode.ParseJsonString(data); 744 | foreach (JsonObject json in list) { 745 | PListItem item = new PListItem(); 746 | item.Key = json["key"]; 747 | item.Value = json["value"]; 748 | _plist.Add(item); 749 | } 750 | } else { 751 | PListItem item = new PListItem(); 752 | item.Key = "NSUserTrackingUsageDescription"; 753 | item.Value = "$(PRODUCT_NAME) need to access the IDFA in order to deliver personalized advertising and to help us improve the game."; 754 | _plist.Add(item); 755 | } 756 | } 757 | 758 | /// 759 | /// Save all settings 760 | /// 761 | private void SaveSettings() { 762 | UBHPrefs.SetBool(REMOTE_KEY, _remoteStatus); 763 | UBHPrefs.SetBool(ANDROID_KEY, _androidStatus); 764 | UBHPrefs.SetBool(IOS_KEY, _iosStatus); 765 | UBHPrefs.SetBool(GOOGLE_SERVICES_KEY, _isGoogleServices); 766 | UBHPrefs.SetBool(HUAWEI_SERVICES_KEY, _isHuaweiServices); 767 | UBHPrefs.SetBool(GOOGLE_KEYSTORE_KEY, _isGoogleKeystore); 768 | UBHPrefs.SetBool(HUAWEI_KEYSTORE_KEY, _isHuaweiKeystore); 769 | UBHPrefs.SetString(BOT_TOKEN_KEY, !string.IsNullOrEmpty(_botToken) ? _botToken.Trim() : ""); 770 | UBHPrefs.SetString(USER_ID_KEY, !string.IsNullOrEmpty(_userID) ? _userID.Trim() : ""); 771 | UBHPrefs.SetString(GAME_TITLE_KEY, !string.IsNullOrEmpty(_gameTitle) ? _gameTitle.Trim() : ""); 772 | UBHPrefs.SetString(GOOGLE_PATH_KEY, _googlePath); 773 | UBHPrefs.SetString(GOOGLE_PASSWORD_KEY, _googlePassword); 774 | UBHPrefs.SetString(GOOGLE_ALIAS_KEY, _googleAlias); 775 | UBHPrefs.SetString(GOOGLE_APASS_KEY, _googleAliasPassword); 776 | UBHPrefs.SetString(HUAWEI_PATH_KEY, _huaweiPath); 777 | UBHPrefs.SetString(HUAWEI_PASSWORD_KEY, _huaweiPassword); 778 | UBHPrefs.SetString(HUAWEI_ALIAS_KEY, _huaweiAlias); 779 | UBHPrefs.SetString(HUAWEI_APASS_KEY, _huaweiAliasPassword); 780 | UBHPrefs.SetString(HUAWEI_DEPS_KEY, !string.IsNullOrEmpty(_huaweiDependencies) ? _huaweiDependencies.Trim() : ""); 781 | UBHPrefs.SetString(LOCALES_KEY, !string.IsNullOrEmpty(_locales) ? _locales.Trim() : ""); 782 | UBHPrefs.SetString(FRAMEWORKS_KEY, !string.IsNullOrEmpty(_frameworks) ? _frameworks.Trim() : ""); 783 | UBHPrefs.SetString(SUPPORT_FILES_KEY, !string.IsNullOrEmpty(_supportFiles) ? _supportFiles.Trim() : ""); 784 | UBHPrefs.SetBool(PUSH_KEY, _isPushes); 785 | UBHPrefs.SetBool(PURCHASE_KEY, _isPurchases); 786 | UBHPrefs.SetBool(SIGN_KEY, _isSignIn); 787 | JsonArray plist = new JsonArray(); 788 | for (int i = 0; i < _plist.Count; i++) { 789 | JsonObject item = new JsonObject(); 790 | item["key"] = _plist[i].Key; 791 | item["value"] = _plist[i].Value; 792 | plist.Add(item); 793 | } 794 | UBHPrefs.SetString(PLIST_KEY, plist.ToJsonString()); 795 | } 796 | 797 | /// 798 | /// Show UBB panel 799 | /// 800 | private void ShowPanel() { 801 | GUILayout.BeginVertical(); 802 | GUILayout.Space(VERTICAL_SPACING); 803 | if (GUILayout.Button("Settings", GUILayout.Height(ACTION_HEIGHT))) { 804 | _isSettingsMode = true; 805 | } 806 | GUILayout.Space(VERTICAL_SPACING); 807 | GUILayout.Label("Platform:", EditorStyles.boldLabel); 808 | GUILayout.BeginHorizontal(); 809 | GUI.backgroundColor = _platformsColors[(int)PlatformType.IOS]; 810 | if (GUILayout.Button(_platformIcons[(int)PlatformType.IOS], GUILayout.Height(BUTTON_HEIGHT))) { 811 | SwitchPlatform(PlatformType.IOS); 812 | } 813 | GUI.backgroundColor = _platformsColors[(int)PlatformType.GOOGLE]; 814 | if (GUILayout.Button(_platformIcons[(int)PlatformType.GOOGLE], GUILayout.Height(BUTTON_HEIGHT))) { 815 | SwitchPlatform(PlatformType.GOOGLE); 816 | } 817 | GUI.backgroundColor = _platformsColors[(int)PlatformType.HUAWEI]; 818 | if (GUILayout.Button(_platformIcons[(int)PlatformType.HUAWEI], GUILayout.Height(BUTTON_HEIGHT))) { 819 | SwitchPlatform(PlatformType.HUAWEI); 820 | } 821 | GUI.backgroundColor = _platformsColors[(int)PlatformType.WEB]; 822 | if (GUILayout.Button(_platformIcons[(int)PlatformType.WEB], GUILayout.Height(BUTTON_HEIGHT))) { 823 | SwitchPlatform(PlatformType.WEB); 824 | } 825 | GUILayout.EndHorizontal(); 826 | GUILayout.Space(VERTICAL_SPACING); 827 | GUILayout.Label("Logging:", EditorStyles.boldLabel); 828 | GUILayout.BeginHorizontal(); 829 | GUI.backgroundColor = _loggersColors[(int)LoggerType.CLIENT]; 830 | if (GUILayout.Button(_loggersIcons[(int)LoggerType.CLIENT], GUILayout.Height(BUTTON_HEIGHT))) { 831 | SwitchLogger(LoggerType.CLIENT); 832 | } 833 | GUI.backgroundColor = _loggersColors[(int)LoggerType.GAME]; 834 | if (GUILayout.Button(_loggersIcons[(int)LoggerType.GAME], GUILayout.Height(BUTTON_HEIGHT))) { 835 | SwitchLogger(LoggerType.GAME); 836 | } 837 | GUI.backgroundColor = _loggersColors[(int)LoggerType.PING]; 838 | if (GUILayout.Button(_loggersIcons[(int)LoggerType.PING], GUILayout.Height(BUTTON_HEIGHT))) { 839 | SwitchLogger(LoggerType.PING); 840 | } 841 | GUILayout.EndHorizontal(); 842 | GUILayout.Space(VERTICAL_SPACING); 843 | GUI.backgroundColor = Color.gray; 844 | GUILayout.Label("Build settings:", EditorStyles.boldLabel); 845 | _buildVersion = EditorGUILayout.TextField("Version:", _buildVersion); 846 | _buildCode = EditorGUILayout.TextField("Code:", _buildCode); 847 | GUILayout.BeginHorizontal(); 848 | _platformBuilds[(int)_currentPlatform] = EditorGUILayout.TextField("Build directory:", _platformBuilds[(int)_currentPlatform]); 849 | if (GUILayout.Button("...", GUILayout.MaxWidth(BROWSE_WIDTH))) { 850 | string path = EditorUtility.OpenFolderPanel("Select build directory", _platformBuilds[(int)_currentPlatform], ""); 851 | if (!string.IsNullOrEmpty(path)) { 852 | _platformBuilds[(int)_currentPlatform] = path; 853 | } 854 | } 855 | GUILayout.EndHorizontal(); 856 | if (_currentPlatform == PlatformType.GOOGLE || _currentPlatform == PlatformType.HUAWEI) { 857 | GUI.backgroundColor = Color.white; 858 | bool isBundleBuild = EditorGUILayout.Toggle("App bundle:", _isBundleBuild); 859 | if (_isBundleBuild != isBundleBuild) { 860 | _isBundleBuild = isBundleBuild; 861 | EditorUserBuildSettings.buildAppBundle = _isBundleBuild; 862 | } 863 | } 864 | GUILayout.Space(VERTICAL_SPACING); 865 | GUI.backgroundColor = Color.yellow; 866 | if (GUILayout.Button("Apply settings", GUILayout.Height(ACTION_HEIGHT))) { 867 | ApplyPlatform(); 868 | ApplyVersion(); 869 | ApplyLoggers(); 870 | ApplyPath(); 871 | EditorUtility.DisplayDialog(titleContent.text, "Apply settings completed", "Close"); 872 | } 873 | GUILayout.EndVertical(); 874 | GUI.backgroundColor = Color.red; 875 | if (GUILayout.Button("Local build", GUILayout.Height(ACTION_HEIGHT))) { 876 | if (string.IsNullOrEmpty(_gameTitle)) { 877 | if (EditorUtility.DisplayDialog("Error", "Game title is empty!", "Close")) { 878 | return; 879 | } 880 | } 881 | Close(); 882 | BuildResult result = Build(); 883 | string message = (result == BuildResult.Succeeded) ? "Build succeeded!" : "Build failed. See console logs :("; 884 | if (EditorUtility.DisplayDialog(titleContent.text, message, "Close")) { 885 | if (result == BuildResult.Succeeded) { 886 | EditorUtility.RevealInFinder(_platformBuilds[(int)_currentPlatform]); 887 | } 888 | } 889 | } 890 | GUI.backgroundColor = Color.gray; 891 | if (!string.IsNullOrEmpty(_actionMessage)) { 892 | EditorGUILayout.HelpBox(_actionMessage, MessageType.None, true); 893 | } 894 | GUILayout.FlexibleSpace(); 895 | GUI.backgroundColor = Color.magenta; 896 | if (GUILayout.Button("Remote build", GUILayout.Height(ACTION_HEIGHT))) { 897 | if (string.IsNullOrEmpty(_botToken)) { 898 | if (EditorUtility.DisplayDialog("Error", "Bot token is empty!", "Close")) { 899 | return; 900 | } 901 | } 902 | if (string.IsNullOrEmpty(_userID)) { 903 | if (EditorUtility.DisplayDialog("Error", "User ID is empty!", "Close")) { 904 | return; 905 | } 906 | } 907 | if (string.IsNullOrEmpty(_gameTitle)) { 908 | if (EditorUtility.DisplayDialog("Error", "Game title is empty!", "Close")) { 909 | return; 910 | } 911 | } 912 | string error = ""; 913 | string branch = Execute("git", "branch --show-current", out error); 914 | if (!string.IsNullOrEmpty(error)) { 915 | if (EditorUtility.DisplayDialog("Error", error, "Close")) { 916 | return; 917 | } 918 | } 919 | string path = Execute("git", "rev-parse --show-toplevel", out error); 920 | if (!string.IsNullOrEmpty(error)) { 921 | if (EditorUtility.DisplayDialog("Error", error, "Close")) { 922 | return; 923 | } 924 | } 925 | string project = new DirectoryInfo(path).Name; 926 | string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup); 927 | string url = string.Format(BOT_COMMAND_URL, _botToken, _userID, project, branch, _currentPlatform.ToString().ToLowerInvariant(), defines, BOT_PAYLOAD).Replace("\n", "").Replace("\r", ""); 928 | UnityWebRequest request = UnityWebRequest.Get(url); 929 | request.SendWebRequest(); 930 | } 931 | if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape) { 932 | Close(); 933 | } 934 | } 935 | 936 | /// 937 | /// Get active scene list 938 | /// 939 | static string[] GetScenes() { 940 | List scenes = new List(); 941 | for (int i = 0; i < EditorBuildSettings.scenes.Length; i++) { 942 | if (EditorBuildSettings.scenes[i].enabled) { 943 | scenes.Add(EditorBuildSettings.scenes[i].path); 944 | } 945 | } 946 | return scenes.ToArray(); 947 | } 948 | 949 | /// 950 | /// Run build process 951 | /// 952 | private BuildResult Build() { 953 | string postfix = ""; 954 | string path = ""; 955 | string[] scenes = GetScenes(); 956 | string ext = (_isBundleBuild) ? "aab" : "apk"; 957 | #if CLIENT_DEBUG 958 | EditorUserBuildSettings.development = true; 959 | postfix = "-dev"; 960 | #endif 961 | switch (_currentPlatform) { 962 | case PlatformType.GOOGLE: 963 | path = string.Format(ANDROID_BUILD_FILE_MASK, _platformBuilds[(int)_currentPlatform], _gameTitle, _buildVersion, _buildCode, "", postfix, ext); 964 | break; 965 | case PlatformType.HUAWEI: 966 | path = string.Format(ANDROID_BUILD_FILE_MASK, _platformBuilds[(int)_currentPlatform], _gameTitle, _buildVersion, _buildCode, "-huawei", postfix, ext); 967 | break; 968 | case PlatformType.IOS: 969 | case PlatformType.WEB: 970 | path = string.Format(IOS_BUILD_MASK, _platformBuilds[(int)_currentPlatform], _gameTitle, _buildVersion, _buildCode, postfix); 971 | if (!Directory.Exists(path)) { 972 | Directory.CreateDirectory(path); 973 | } 974 | break; 975 | default: 976 | return BuildResult.Unknown; 977 | } 978 | Debug.LogFormat("App path: {0}", path); 979 | BuildReport report = BuildPipeline.BuildPlayer(scenes, path, EditorUserBuildSettings.activeBuildTarget, BuildOptions.None); 980 | return report.summary.result; 981 | } 982 | 983 | /// 984 | /// Apply logger in settings 985 | /// 986 | void ApplyLoggers() { 987 | BuildTargetGroup group = BuildTargetGroup.Unknown; 988 | switch (_currentPlatform) { 989 | case PlatformType.IOS: 990 | group = BuildTargetGroup.iOS; 991 | break; 992 | case PlatformType.GOOGLE: 993 | case PlatformType.HUAWEI: 994 | group = BuildTargetGroup.Android; 995 | break; 996 | case PlatformType.WEB: 997 | group = BuildTargetGroup.WebGL; 998 | break; 999 | default: 1000 | break; 1001 | } 1002 | string[] defines = new string[3] { "CLIENT_DEBUG", "GAME_DEBUG", "SHOW_PING" }; 1003 | DefineSymbols.Remove(defines, group); 1004 | SaveData(LOGGER_KEY, "[]"); 1005 | foreach (LoggerType logger in _loggers) { 1006 | SetLoggerDefine(logger, group); 1007 | } 1008 | } 1009 | 1010 | /// 1011 | /// Switch on logger 1012 | /// 1013 | /// Logger type 1014 | /// Target group 1015 | void SetLoggerDefine(LoggerType logger, BuildTargetGroup group) { 1016 | Debug.LogFormat("Enable logger: {0}", logger.ToString()); 1017 | string[] define = new string[1]; 1018 | switch (logger) { 1019 | case LoggerType.CLIENT: 1020 | define[0] = "CLIENT_DEBUG"; 1021 | break; 1022 | case LoggerType.GAME: 1023 | define[0] = "GAME_DEBUG"; 1024 | break; 1025 | case LoggerType.PING: 1026 | define[0] = "SHOW_PING"; 1027 | break; 1028 | default: 1029 | break; 1030 | } 1031 | DefineSymbols.Add(define, group); 1032 | JsonArray list = new JsonArray(); 1033 | foreach (LoggerType log in _loggers) { 1034 | list.Add((int)log); 1035 | } 1036 | SaveData(LOGGER_KEY, list.ToJsonString()); 1037 | } 1038 | 1039 | /// 1040 | /// Apply version and build code 1041 | /// 1042 | private void ApplyVersion() { 1043 | PlayerSettings.bundleVersion = _buildVersion; 1044 | #if UNITY_ANDROID 1045 | int bundle = MAX_BUNDLE; 1046 | int.TryParse(_buildCode, out bundle); 1047 | if (bundle == MAX_BUNDLE) { 1048 | Debug.LogErrorFormat("Can't apply new bundle code. It must contains only digits: {0}", _buildCode); 1049 | } else { 1050 | PlayerSettings.Android.bundleVersionCode = bundle; 1051 | } 1052 | #elif UNITY_IOS 1053 | PlayerSettings.iOS.buildNumber = _buildCode; 1054 | #endif 1055 | } 1056 | 1057 | /// 1058 | /// Apply platform in settings 1059 | /// 1060 | void ApplyPlatform() { 1061 | switch (_currentPlatform) { 1062 | case PlatformType.IOS: 1063 | DefineSymbols.Remove(new string[] { "HUAWEI", "GOOGLE" }, BuildTargetGroup.iOS); 1064 | EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.iOS, BuildTarget.iOS); 1065 | break; 1066 | case PlatformType.GOOGLE: 1067 | DefineSymbols.Remove(new string[] { "HUAWEI" }, BuildTargetGroup.Android); 1068 | DefineSymbols.Add(new string[] { "GOOGLE" }, BuildTargetGroup.Android); 1069 | EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.Android, BuildTarget.Android); 1070 | break; 1071 | case PlatformType.HUAWEI: 1072 | DefineSymbols.Remove(new string[] { "GOOGLE" }, BuildTargetGroup.Android); 1073 | DefineSymbols.Add(new string[] { "HUAWEI" }, BuildTargetGroup.Android); 1074 | EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.Android, BuildTarget.Android); 1075 | break; 1076 | case PlatformType.WEB: 1077 | DefineSymbols.Remove(new string[] { "HUAWEI", "GOOGLE" }, BuildTargetGroup.WebGL); 1078 | EditorUserBuildSettings.SwitchActiveBuildTargetAsync(BuildTargetGroup.WebGL, BuildTarget.WebGL); 1079 | break; 1080 | default: 1081 | break; 1082 | } 1083 | Debug.LogFormat("Enable platform: {0}", _currentPlatform.ToString()); 1084 | UBHPrefs.SetInt(PLATFORM_KEY, (int)_currentPlatform); 1085 | } 1086 | 1087 | /// 1088 | /// Run external app 1089 | /// 1090 | /// Command name 1091 | /// Arguments 1092 | /// Error 1093 | private string Execute(string cmd, string args, out string error) { 1094 | System.Diagnostics.Process process = new System.Diagnostics.Process(); 1095 | process.StartInfo.FileName = cmd; 1096 | process.StartInfo.Arguments = args; 1097 | process.StartInfo.UseShellExecute = false; 1098 | process.StartInfo.RedirectStandardError = true; 1099 | process.StartInfo.RedirectStandardOutput = true; 1100 | process.Start(); 1101 | string result = process.StandardOutput.ReadToEnd(); 1102 | error = process.StandardError.ReadToEnd(); 1103 | process.WaitForExit(); 1104 | return result; 1105 | } 1106 | 1107 | /// 1108 | /// Save build paths 1109 | /// 1110 | private void ApplyPath() { 1111 | JsonArray list = new JsonArray(); 1112 | foreach (string item in _platformBuilds) { 1113 | list.Add(item); 1114 | } 1115 | SaveData(BUILD_KEY, list.ToJsonString()); 1116 | } 1117 | 1118 | /// 1119 | /// Show action message in log 1120 | /// 1121 | /// Log message 1122 | void ShowActionMessage(string message) { 1123 | _actionMessage = message; 1124 | HideMessage(); 1125 | } 1126 | 1127 | /// 1128 | /// Hide message after delay 1129 | /// 1130 | async static void HideMessage() { 1131 | UnityBuilderHelper window = GetWindow(true); 1132 | await System.Threading.Tasks.Task.Delay(1500); 1133 | window._actionMessage = null; 1134 | window.Repaint(); 1135 | } 1136 | 1137 | /// 1138 | /// Init buttons colors 1139 | /// 1140 | void InitColors() { 1141 | for (int i = 0; i < _platformsColors.Length; i++) { 1142 | _platformsColors[i] = OFF_BUTTON; 1143 | } 1144 | for (int i = 0; i < _loggersColors.Length; i++) { 1145 | _loggersColors[i] = OFF_BUTTON; 1146 | } 1147 | } 1148 | 1149 | /// 1150 | /// Assmble project by command line 1151 | /// 1152 | static void Assemble() { 1153 | string[] args = System.Environment.GetCommandLineArgs(); 1154 | Dictionary param = new Dictionary(); 1155 | for (int i = 1; i < args.Length - 1; i++) { 1156 | string key = (args[i][0].Equals('-')) ? args[i].Substring(1) : ""; 1157 | if (!string.IsNullOrEmpty(key) && !args[i + 1][0].Equals('-')) { 1158 | param.Add(key, args[i + 1]); 1159 | } 1160 | } 1161 | JsonObject info = new JsonObject(); 1162 | PlatformType platform = PlatformType.UNKNOWN; 1163 | switch (param["platform"]) { 1164 | case "ios": 1165 | platform = PlatformType.IOS; 1166 | break; 1167 | case "google": 1168 | platform = PlatformType.GOOGLE; 1169 | break; 1170 | case "huawei": 1171 | platform = PlatformType.HUAWEI; 1172 | break; 1173 | case "webgl": 1174 | platform = PlatformType.WEB; 1175 | break; 1176 | default: 1177 | break; 1178 | } 1179 | string list = UBHPrefs.GetString(LOGGER_KEY); 1180 | string defines = param["defines"]; 1181 | string path = param["output"]; 1182 | string postfix = ""; 1183 | BuildTargetGroup group = BuildTargetGroup.Unknown; 1184 | BuildTarget target = BuildTarget.iOS; 1185 | switch (platform) { 1186 | case PlatformType.IOS: 1187 | group = BuildTargetGroup.iOS; 1188 | target = BuildTarget.iOS; 1189 | info["code"] = PlayerSettings.iOS.buildNumber; 1190 | path = string.Format(IOS_BUILD_MASK, path, UBHPrefs.GetString(GAME_TITLE_KEY), PlayerSettings.bundleVersion, PlayerSettings.iOS.buildNumber, "-dev"); 1191 | if (!Directory.Exists(path)) { 1192 | Directory.CreateDirectory(path); 1193 | } 1194 | break; 1195 | case PlatformType.GOOGLE: 1196 | group = BuildTargetGroup.Android; 1197 | target = BuildTarget.Android; 1198 | info["code"] = PlayerSettings.Android.bundleVersionCode; 1199 | path = string.Format(ANDROID_BUILD_FILE_MASK, path, UBHPrefs.GetString(GAME_TITLE_KEY), PlayerSettings.bundleVersion, PlayerSettings.Android.bundleVersionCode, "", "-dev", "apk"); 1200 | break; 1201 | case PlatformType.HUAWEI: 1202 | group = BuildTargetGroup.Android; 1203 | target = BuildTarget.Android; 1204 | postfix = "-huawei"; 1205 | info["code"] = PlayerSettings.Android.bundleVersionCode; 1206 | path = string.Format(ANDROID_BUILD_FILE_MASK, path, UBHPrefs.GetString(GAME_TITLE_KEY), PlayerSettings.bundleVersion, PlayerSettings.Android.bundleVersionCode, "-huawei", "-dev", "apk"); 1207 | break; 1208 | default: 1209 | break; 1210 | } 1211 | info["name"] = UBHPrefs.GetString(GAME_TITLE_KEY); 1212 | info["bundle"] = PlayerSettings.applicationIdentifier; 1213 | info["company"] = PlayerSettings.companyName; 1214 | info["version"] = PlayerSettings.bundleVersion; 1215 | info["source"] = string.Format("{0}.{1}.{2}{3}-dev", UBHPrefs.GetString(GAME_TITLE_KEY), info["version"], info["code"], postfix); 1216 | byte[] data = Encoding.UTF8.GetBytes(info.ToJsonString()); 1217 | File.WriteAllText(Path.Combine(param["output"], string.Format("{0}.{1}.build.json", param["project"], param["platform"])), info.ToJsonString()); 1218 | PlayerSettings.SetScriptingBackend(group, ScriptingImplementation.IL2CPP); 1219 | PlayerSettings.SetScriptingDefineSymbolsForGroup(group, defines); 1220 | EditorUserBuildSettings.SwitchActiveBuildTarget(group, target); 1221 | EditorUserBuildSettings.development = true; 1222 | if (platform == PlatformType.GOOGLE) { 1223 | PlayerSettings.Android.keystoreName = UBHPrefs.GetString(GOOGLE_PATH_KEY); 1224 | PlayerSettings.Android.keystorePass = UBHPrefs.GetString(GOOGLE_PASSWORD_KEY); 1225 | PlayerSettings.Android.keyaliasName = UBHPrefs.GetString(GOOGLE_ALIAS_KEY); 1226 | PlayerSettings.Android.keyaliasPass = UBHPrefs.GetString(GOOGLE_APASS_KEY); 1227 | } else if (platform == PlatformType.HUAWEI) { 1228 | PlayerSettings.Android.keystoreName = UBHPrefs.GetString(HUAWEI_PATH_KEY); 1229 | PlayerSettings.Android.keystorePass = UBHPrefs.GetString(HUAWEI_PASSWORD_KEY); 1230 | PlayerSettings.Android.keyaliasName = UBHPrefs.GetString(HUAWEI_ALIAS_KEY); 1231 | PlayerSettings.Android.keyaliasPass = UBHPrefs.GetString(HUAWEI_APASS_KEY); 1232 | } 1233 | BuildReport report = BuildPipeline.BuildPlayer(GetScenes(), path, target, BuildOptions.None); 1234 | int code = (report.summary.result == BuildResult.Succeeded) ? 0 : 1; 1235 | EditorApplication.Exit(code); 1236 | } 1237 | 1238 | } 1239 | 1240 | } --------------------------------------------------------------------------------